Skip to content

Commit

Permalink
Fix mesh scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
TokisanGames committed Feb 17, 2024
1 parent efcb0f2 commit bc02497
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 71 deletions.
6 changes: 6 additions & 0 deletions doc/api/class_terrain3d.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,12 @@ The correlated size of the terrain meshes. Lod0 has ``4*mesh_size + 2`` quads pe

The distance between vertices. Godot units are typically considered to be meters. This scales the terrain on X and Z axes.

This variable changes the global position of landscape features. A mountain peak might be at (512, 512), but with a vertex spacing of 2.0 it is now located at (1024, 1024).

All Terrain3D functions with a global_position expect an absolute global value. If you would normally use :ref:`Terrain3DStorage.import_images<class_Terrain3DStorage_method_import_images>` to import an image in the region at (-1024, -1024), with a mesh_vertex_spacing of 2, you'll need to import that image at (-2048, -2048) to place it in the same region.

To scale heights, export the height map and reimport it with a new height scale.

.. rst-class:: classref-item-separator

----
Expand Down
6 changes: 3 additions & 3 deletions doc/api/class_terrain3dstorage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ And :ref:`get_region_offset<class_Terrain3DStorage_method_get_region_offset>` wh
- void **set_region_size** **(** :ref:`RegionSize<enum_Terrain3DStorage_RegionSize>` value **)**
- :ref:`RegionSize<enum_Terrain3DStorage_RegionSize>` **get_region_size** **(** **)**

The size of each region. Limited to 1024 for now.
The number of vertices in each sculptable region, and the number of pixels for each layer in the TextureArrays that store the height, control, and color maps. Limited to 1024 for now. This does not factor in :ref:`Terrain3D.mesh_vertex_spacing<class_Terrain3D_property_mesh_vertex_spacing>`.

.. rst-class:: classref-item-separator

Expand Down Expand Up @@ -614,7 +614,7 @@ Returns the location of a terrain vertex at a certain LOD. If there is a hole at

:ref:`Vector3<class_Vector3>` **get_normal** **(** :ref:`Vector3<class_Vector3>` global_position **)**

Returns the terrain normal at the specified location.
Returns the terrain normal at the specified location. This function uses :ref:`get_height<class_Terrain3DStorage_method_get_height>`, which is not currently accurate between vertices. Therefore, this function may also be inacurate between vertices.

Returns ``Vector3(NAN, NAN, NAN)`` if the requested position is a hole or outside of defined regions.

Expand Down Expand Up @@ -722,7 +722,7 @@ Imports an Image set (Height, Control, Color) into this resource. It does NOT no

\ ``images`` - MapType.TYPE_MAX sized array of Images for Height, Control, Color. Images can be blank or null.

\ ``global_position`` - X,0,Z location on the region map. Valid range is ~ (+/-8192, +/-8192).
\ ``global_position`` - X,0,Z location on the region map. Valid range is (+/-8192, +/-8192) \* :ref:`Terrain3D.mesh_vertex_spacing<class_Terrain3D_property_mesh_vertex_spacing>`.

\ ``offset`` - Add this factor to all height values, can be negative.

Expand Down
3 changes: 3 additions & 0 deletions doc/classes/Terrain3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@
</member>
<member name="mesh_vertex_spacing" type="float" setter="set_mesh_vertex_spacing" getter="get_mesh_vertex_spacing" default="1.0">
The distance between vertices. Godot units are typically considered to be meters. This scales the terrain on X and Z axes.
This variable changes the global position of landscape features. A mountain peak might be at (512, 512), but with a vertex spacing of 2.0 it is now located at (1024, 1024).
All Terrain3D functions with a global_position expect an absolute global value. If you would normally use [method Terrain3DStorage.import_images] to import an image in the region at (-1024, -1024), with a mesh_vertex_spacing of 2, you'll need to import that image at (-2048, -2048) to place it in the same region.
To scale heights, export the height map and reimport it with a new height scale.
</member>
<member name="render_cast_shadows" type="int" setter="set_cast_shadows" getter="get_cast_shadows" enum="GeometryInstance3D.ShadowCastingSetting" default="1">
Tells the renderer how to cast shadows from the terrain onto other objects. This sets [code skip-lint]GeometryInstance3D.ShadowCastingSetting[/code] in the engine.
Expand Down
4 changes: 2 additions & 2 deletions doc/classes/Terrain3DStorage.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
<description>
Imports an Image set (Height, Control, Color) into this resource. It does NOT normalize values to 0-1. You must do that using get_min_max() and adjusting scale and offset.
[code skip-lint]images[/code] - MapType.TYPE_MAX sized array of Images for Height, Control, Color. Images can be blank or null.
[code skip-lint]global_position[/code] - X,0,Z location on the region map. Valid range is ~ (+/-8192, +/-8192).
[code skip-lint]global_position[/code] - X,0,Z location on the region map. Valid range is (+/-8192, +/-8192) * [member Terrain3D.mesh_vertex_spacing].
[code skip-lint]offset[/code] - Add this factor to all height values, can be negative.
[code skip-lint]scale[/code] - Scale all height values by this factor (applied after offset).
</description>
Expand Down Expand Up @@ -302,7 +302,7 @@
And [method get_region_offset] which converts a location in world space to a region space, which is what is stored in this array. Eg. [code skip-lint]get_region_offset(Vector3(1500, 0, 1500))[/code] would return (1, 1).
</member>
<member name="region_size" type="int" setter="set_region_size" getter="get_region_size" enum="Terrain3DStorage.RegionSize" default="1024">
The size of each region. Limited to 1024 for now.
The number of vertices in each sculptable region, and the number of pixels for each layer in the TextureArrays that store the height, control, and color maps. Limited to 1024 for now. This does not factor in [member Terrain3D.mesh_vertex_spacing].
</member>
<member name="save_16_bit" type="bool" setter="set_save_16_bit" getter="get_save_16_bit" default="false">
Heightmaps are loaded and edited in 32-bit. This option converts the file to 16-bit upon saving to reduce file size. This process is lossy.
Expand Down
14 changes: 8 additions & 6 deletions project/addons/terrain_3d/editor/components/ui.gd
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,7 @@ func update_decal() -> void:
await get_tree().create_timer(.05).timeout
decal.visible = true

decal.size = Vector3.ONE * brush_data["size"] * plugin.terrain.get_mesh_vertex_spacing()
decal.size.y = max(1000, decal.size.y)
decal.cull_mask = 1 << plugin.terrain.render_mouse_layer - 1
decal.size = Vector3.ONE * brush_data["size"]
if brush_data["align_to_view"]:
var cam: Camera3D = plugin.terrain.get_camera();
if (cam):
Expand All @@ -242,7 +240,7 @@ func update_decal() -> void:
# Set texture and color
if picking != Terrain3DEditor.TOOL_MAX:
decal.texture_albedo = picker_texture
decal.size = Vector3.ONE*10.
decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
match picking:
Terrain3DEditor.HEIGHT:
decal.modulate = COLOR_PICK_HEIGHT
Expand Down Expand Up @@ -301,7 +299,9 @@ func update_decal() -> void:
_:
decal.modulate = Color.WHITE
decal.modulate.a = max(.3, brush_data["opacity"])
decal.size.y = max(1000, decal.size.y)
decal.albedo_mix = 1.0
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
decal_timer.start()

for gradient_decal in gradient_decals:
Expand All @@ -313,7 +313,7 @@ func update_decal() -> void:
if point != Vector3.ZERO:
var point_decal: Decal = _get_gradient_decal(index)
point_decal.visible = true
point_decal.position = point * plugin.terrain.get_mesh_vertex_spacing()
point_decal.position = point
index += 1


Expand All @@ -325,7 +325,9 @@ func _get_gradient_decal(index: int) -> Decal:
gradient_decal = Decal.new()
gradient_decal.texture_albedo = picker_texture
gradient_decal.modulate = COLOR_SLOPE
gradient_decal.size = Vector3(10, 1000, 10)
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
gradient_decal.size.y = 1000.
gradient_decal.cull_mask = decal.cull_mask
add_child(gradient_decal)

gradient_decals.push_back(gradient_decal)
Expand Down
6 changes: 2 additions & 4 deletions project/addons/terrain_3d/editor/editor.gd
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,10 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
else:
ui.decal_timer.start()

## Incorporate vertex spacing into operations
mouse_global_position.x /= terrain.get_mesh_vertex_spacing()
mouse_global_position.z /= terrain.get_mesh_vertex_spacing()
## Update region highlight
var region_size = terrain.get_storage().get_region_size()
var region_position: Vector2 = (Vector2(mouse_global_position.x, mouse_global_position.z) / region_size).floor()
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
/ (region_size * terrain.get_mesh_vertex_spacing()) ).floor()
if current_region_position != region_position:
current_region_position = region_position
update_region_grid()
Expand Down
39 changes: 23 additions & 16 deletions src/terrain_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,17 +376,17 @@ void Terrain3D::_update_collision() {
Ref<Image> cmap, cmap_x, cmap_z, cmap_xz;
map = _storage->get_map_region(Terrain3DStorage::TYPE_HEIGHT, i);
cmap = _storage->get_map_region(Terrain3DStorage::TYPE_CONTROL, i);
int region = _storage->get_region_index(Vector3(global_pos.x + region_size, 0.f, global_pos.z));
int region = _storage->get_region_index(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing);
if (region >= 0) {
map_x = _storage->get_map_region(Terrain3DStorage::TYPE_HEIGHT, region);
cmap_x = _storage->get_map_region(Terrain3DStorage::TYPE_CONTROL, region);
}
region = _storage->get_region_index(Vector3(global_pos.x, 0.f, global_pos.z + region_size));
region = _storage->get_region_index(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing);
if (region >= 0) {
map_z = _storage->get_map_region(Terrain3DStorage::TYPE_HEIGHT, region);
cmap_z = _storage->get_map_region(Terrain3DStorage::TYPE_CONTROL, region);
}
region = _storage->get_region_index(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size));
region = _storage->get_region_index(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing);
if (region >= 0) {
map_xz = _storage->get_map_region(Terrain3DStorage::TYPE_HEIGHT, region);
cmap_xz = _storage->get_map_region(Terrain3DStorage::TYPE_CONTROL, region);
Expand Down Expand Up @@ -582,14 +582,18 @@ void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector
void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, int32_t p_lod, Terrain3DStorage::HeightFilter p_filter, bool p_require_nav, int32_t x, int32_t z) const {
int32_t step = 1 << CLAMP(p_lod, 0, 8);

uint32_t control1 = _storage->get_control(Vector3(x, 0.0, z));
uint32_t control2 = _storage->get_control(Vector3(x + step, 0.0, z + step));
uint32_t control3 = _storage->get_control(Vector3(x, 0.0, z + step));
Vector3 vertex_scaler = Vector3(_mesh_vertex_spacing, 1.0, _mesh_vertex_spacing);
Vector3 xz = Vector3(x, 0.0f, z) * _mesh_vertex_spacing;
Vector3 xsz = Vector3(x + step, 0.0f, z) * _mesh_vertex_spacing;
Vector3 xzs = Vector3(x, 0.0f, z + step) * _mesh_vertex_spacing;
Vector3 xszs = Vector3(x + step, 0.0f, z + step) * _mesh_vertex_spacing;

uint32_t control1 = _storage->get_control(xz);
uint32_t control2 = _storage->get_control(xszs);
uint32_t control3 = _storage->get_control(xzs);
if (!p_require_nav || (Util::is_nav(control1) && Util::is_nav(control2) && Util::is_nav(control3))) {
Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)) * vertex_scaler;
Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)) * vertex_scaler;
Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z + step)) * vertex_scaler;
Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, xz);
Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, xszs);
Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, xzs);
if (!UtilityFunctions::is_nan(v1.y) && !UtilityFunctions::is_nan(v2.y) && !UtilityFunctions::is_nan(v3.y)) {
p_vertices.push_back(v1);
p_vertices.push_back(v2);
Expand All @@ -602,13 +606,13 @@ void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVe
}
}

control1 = _storage->get_control(Vector3(x, 0.0, z));
control2 = _storage->get_control(Vector3(x + step, 0.0, z));
control3 = _storage->get_control(Vector3(x + step, 0.0, z + step));
control1 = _storage->get_control(xz);
control2 = _storage->get_control(xsz);
control3 = _storage->get_control(xszs);
if (!p_require_nav || (Util::is_nav(control1) && Util::is_nav(control2) && Util::is_nav(control3))) {
Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)) * vertex_scaler;
Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z)) * vertex_scaler;
Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)) * vertex_scaler;
Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, xz);
Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, xsz);
Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, xszs);
if (!UtilityFunctions::is_nan(v1.y) && !UtilityFunctions::is_nan(v2.y) && !UtilityFunctions::is_nan(v3.y)) {
p_vertices.push_back(v1);
p_vertices.push_back(v2);
Expand Down Expand Up @@ -679,6 +683,9 @@ void Terrain3D::set_mesh_vertex_spacing(real_t p_spacing) {
if (_mesh_vertex_spacing != p_spacing) {
LOG(INFO, "Setting mesh vertex spacing: ", p_spacing);
_mesh_vertex_spacing = p_spacing;
if (_storage != nullptr) {
_storage->_mesh_vertex_spacing = p_spacing;
}
_clear();
_initialize();
}
Expand Down
38 changes: 20 additions & 18 deletions src/terrain_3d_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ void Terrain3DEditor::Brush::set_data(Dictionary p_data) {
}
_image = p_data["image"];
if (_image.is_valid()) {
_img_size = Vector2(_image->get_size());
_img_size = _image->get_size();
} else {
_img_size = Vector2(0, 0);
_img_size = Vector2i(0, 0);
}
_texture = p_data["texture"];

Expand Down Expand Up @@ -51,6 +51,8 @@ void Terrain3DEditor::_region_modified(Vector3 p_global_position, Vector2 p_heig
AABB edited_area;
edited_area.position = Vector3(region_offset.x * region_size, p_height_range.x, region_offset.y * region_size);
edited_area.size = Vector3(region_size, p_height_range.y - p_height_range.x, region_size);
edited_area.position *= _terrain->get_mesh_vertex_spacing();
edited_area.size *= _terrain->get_mesh_vertex_spacing();

_modified = true;
_terrain->get_storage()->add_edited_area(edited_area);
Expand Down Expand Up @@ -131,7 +133,7 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di
Ref<Image> map = storage->get_map_region(map_type, region_index);
int brush_size = _brush.get_size();
int texture_id = _brush.get_texture_index();
Vector2 img_size = _brush.get_image_size();
Vector2i img_size = _brush.get_image_size();
real_t opacity = _brush.get_opacity();
real_t height = _brush.get_height();
Color color = _brush.get_color();
Expand All @@ -151,12 +153,13 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di
edited_area.position = p_global_position - Vector3(brush_size, 0.f, brush_size) / 2.f;
edited_area.size = Vector3(brush_size, 0.f, brush_size);

for (int x = 0; x < brush_size; x++) {
for (int y = 0; y < brush_size; y++) {
Vector2i brush_offset = Vector2i(x, y) - (Vector2i(brush_size, brush_size) / 2);
Vector3 brush_global_position = Vector3(0.5f, 0.f, 0.5f) +
Vector3(p_global_position.x + real_t(brush_offset.x), p_global_position.y,
p_global_position.z + real_t(brush_offset.y));
real_t vertex_spacing = _terrain->get_mesh_vertex_spacing();
for (real_t x = 0.f; x < brush_size; x += vertex_spacing) {
for (real_t y = 0.f; y < brush_size; y += vertex_spacing) {
Vector2 brush_offset = Vector2(x, y) - (Vector2(brush_size, brush_size) / 2.f);
Vector3 brush_global_position =
Vector3(p_global_position.x + brush_offset.x + .5f, p_global_position.y,
p_global_position.z + brush_offset.y + .5f);

// If we're brushing across a region boundary, possibly add a region, and get the other map
int new_region_index = storage->get_region_index(brush_global_position);
Expand Down Expand Up @@ -185,7 +188,7 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di
Vector2 brush_uv = Vector2(x, y) / real_t(brush_size);
Vector2i brush_pixel_position = Vector2i(_rotate_uv(brush_uv, rot) * img_size);

if (!_is_in_bounds(brush_pixel_position, Vector2i(img_size))) {
if (!_is_in_bounds(brush_pixel_position, img_size)) {
continue;
}

Expand Down Expand Up @@ -219,10 +222,10 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di
destf = Math::lerp(srcf, height, brush_alpha * opacity);
break;
case AVERAGE: {
Vector3 left_position = brush_global_position - Vector3(1, 0, 0);
Vector3 right_position = brush_global_position + Vector3(1, 0, 0);
Vector3 down_position = brush_global_position - Vector3(0, 0, 1);
Vector3 up_position = brush_global_position + Vector3(0, 0, 1);
Vector3 left_position = brush_global_position - Vector3(vertex_spacing, 0.f, 0.f);
Vector3 right_position = brush_global_position + Vector3(vertex_spacing, 0.f, 0.f);
Vector3 down_position = brush_global_position - Vector3(0.f, 0.f, vertex_spacing);
Vector3 up_position = brush_global_position + Vector3(0.f, 0.f, vertex_spacing);

real_t left = srcf, right = srcf, up = srcf, down = srcf;

Expand Down Expand Up @@ -381,11 +384,10 @@ bool Terrain3DEditor::_is_in_bounds(Vector2i p_position, Vector2i p_max_position
}

Vector2 Terrain3DEditor::_get_uv_position(Vector3 p_global_position, int p_region_size) {
Vector2 global_position_2d = Vector2(p_global_position.x, p_global_position.z);
Vector2 region_position = global_position_2d / real_t(p_region_size);
Vector2 descaled_position_2d = Vector2(p_global_position.x, p_global_position.z) / _terrain->get_mesh_vertex_spacing();
Vector2 region_position = descaled_position_2d / real_t(p_region_size);
region_position = region_position.floor();
Vector2 uv_position = (global_position_2d / real_t(p_region_size)) - region_position;

Vector2 uv_position = (descaled_position_2d / real_t(p_region_size)) - region_position;
return uv_position;
}

Expand Down
4 changes: 2 additions & 2 deletions src/terrain_3d_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Terrain3DEditor : public Object {
class Brush {
private:
Ref<Image> _image;
Vector2 _img_size;
Vector2i _img_size;
Ref<ImageTexture> _texture;

int _size = 0;
Expand All @@ -89,7 +89,7 @@ class Terrain3DEditor : public Object {

Ref<ImageTexture> get_texture() const { return _texture; }
Ref<Image> get_image() const { return _image; }
Vector2 get_image_size() const { return _img_size; }
Vector2i get_image_size() const { return _img_size; }

int get_size() const { return _size; }
real_t get_opacity() const { return _opacity; }
Expand Down
Loading

0 comments on commit bc02497

Please sign in to comment.