From ea1dc426953cf7dfabd6549b6a26446fb11d569b Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Wed, 14 Feb 2024 01:51:06 +0700 Subject: [PATCH] Fix mesh scaling --- doc/api/class_terrain3d.rst | 6 ++ doc/api/class_terrain3dstorage.rst | 10 ++-- doc/classes/Terrain3D.xml | 3 + doc/classes/Terrain3DStorage.xml | 9 +-- .../addons/terrain_3d/editor/components/ui.gd | 12 ++-- project/addons/terrain_3d/editor/editor.gd | 6 +- src/terrain_3d.cpp | 3 + src/terrain_3d_editor.cpp | 20 ++++--- src/terrain_3d_storage.cpp | 57 +++++++++++++------ src/terrain_3d_storage.h | 7 ++- 10 files changed, 88 insertions(+), 45 deletions(-) diff --git a/doc/api/class_terrain3d.rst b/doc/api/class_terrain3d.rst index 47619e6b9..22f75e37e 100644 --- a/doc/api/class_terrain3d.rst +++ b/doc/api/class_terrain3d.rst @@ -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` 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 applying scale. + .. rst-class:: classref-item-separator ---- diff --git a/doc/api/class_terrain3dstorage.rst b/doc/api/class_terrain3dstorage.rst index 113171272..a2a058ea6 100644 --- a/doc/api/class_terrain3dstorage.rst +++ b/doc/api/class_terrain3dstorage.rst @@ -417,7 +417,7 @@ And :ref:`get_region_offset` wh - void **set_region_size** **(** :ref:`RegionSize` value **)** - :ref:`RegionSize` **get_region_size** **(** **)** -The size of each region. Limited to 1024 for now. +The size of each sculptable region. This defines the size of each layer in the texture arrays that store the height, control, and color maps. Limited to 1024 for now. This does not factor in :ref:`Terrain3D.mesh_vertex_spacing` .. rst-class:: classref-item-separator @@ -544,7 +544,9 @@ Returns the associated pixel on the control map at the requested location. Calls :ref:`float` **get_height** **(** :ref:`Vector3` global_position **)** -Returns the associated pixel on the height map at the requested location. Calls :ref:`get_pixel`. +Returns the associated pixel on the height map at the requested location. This function is only accurate at vertex coordinates, and on flat areas. It does not currently interpolate heights between vertices, while mesh faces do. Locations between vertices will return the height of the vertex at the floored coordinates. See `issue 324 <"https://github.com/TokisanGames/Terrain3D/issues/324">`__. + +Calls :ref:`get_pixel`. .. rst-class:: classref-item-separator @@ -610,7 +612,7 @@ Returns the location of a terrain vertex at a certain LOD. If there is a hole at :ref:`Vector3` **get_normal** **(** :ref:`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`, which is not currently accurate between vertices. Therefore, this function may also be inacurate between vertices. .. rst-class:: classref-item-separator @@ -714,7 +716,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`. \ ``offset`` - Add this factor to all height values, can be negative. diff --git a/doc/classes/Terrain3D.xml b/doc/classes/Terrain3D.xml index 03af60333..5be607e78 100644 --- a/doc/classes/Terrain3D.xml +++ b/doc/classes/Terrain3D.xml @@ -149,6 +149,9 @@ 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 applying scale. Tells the renderer how to cast shadows from the terrain onto other objects. This sets [code skip-lint]GeometryInstance3D.ShadowCastingSetting[/code] in the engine. diff --git a/doc/classes/Terrain3DStorage.xml b/doc/classes/Terrain3DStorage.xml index b7b42e82f..a36935c1e 100644 --- a/doc/classes/Terrain3DStorage.xml +++ b/doc/classes/Terrain3DStorage.xml @@ -58,7 +58,8 @@ - Returns the associated pixel on the height map at the requested location. Calls [method get_pixel]. + Returns the associated pixel on the height map at the requested location. This function is only accurate at vertex coordinates, and on flat areas. It does not currently interpolate heights between vertices, while mesh faces do. Locations between vertices will return the height of the vertex at the floored coordinates. See [url="https://github.com/TokisanGames/Terrain3D/issues/324"]issue 324[/url]. + Calls [method get_pixel]. @@ -99,7 +100,7 @@ - Returns the terrain normal at the specified location. + Returns the terrain normal at the specified location. This function uses [method get_height], which is not currently accurate between vertices. Therefore, this function may also be inacurate between vertices. @@ -162,7 +163,7 @@ 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). @@ -298,7 +299,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). - The size of each region. Limited to 1024 for now. + The size of each sculptable region. This defines the size of each layer in the texture arrays that store the height, control, and color maps. Limited to 1024 for now. This does not factor in [member Terrain3D.mesh_vertex_spacing] 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. diff --git a/project/addons/terrain_3d/editor/components/ui.gd b/project/addons/terrain_3d/editor/components/ui.gd index b93f3288a..e2682f297 100644 --- a/project/addons/terrain_3d/editor/components/ui.gd +++ b/project/addons/terrain_3d/editor/components/ui.gd @@ -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): @@ -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 @@ -301,6 +299,7 @@ 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_timer.start() @@ -313,7 +312,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 @@ -325,7 +324,8 @@ 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. add_child(gradient_decal) gradient_decals.push_back(gradient_decal) diff --git a/project/addons/terrain_3d/editor/editor.gd b/project/addons/terrain_3d/editor/editor.gd index d34a503a6..0f444837d 100644 --- a/project/addons/terrain_3d/editor/editor.gd +++ b/project/addons/terrain_3d/editor/editor.gd @@ -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() diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index c33f90b17..319817227 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -679,6 +679,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(); } diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index c3b5d00a7..67ddba06d 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -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); @@ -147,6 +149,8 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di } Object::cast_to(_terrain->get_plugin()->get("ui"))->call("set_decal_rotation", rot); + Vector3 descaled_position = p_global_position / _terrain->get_mesh_vertex_spacing(); + AABB edited_area; edited_area.position = p_global_position - Vector3(brush_size, 0.0f, brush_size) / 2.0f; edited_area.size = Vector3(brush_size, 0.0f, brush_size); @@ -219,10 +223,11 @@ 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); + real_t step = _terrain->get_mesh_vertex_spacing(); + Vector3 left_position = brush_global_position - Vector3(step, 0, 0); + Vector3 right_position = brush_global_position + Vector3(step, 0, 0); + Vector3 down_position = brush_global_position - Vector3(0, 0, step); + Vector3 up_position = brush_global_position + Vector3(0, 0, step); real_t left = srcf, right = srcf, up = srcf, down = srcf; @@ -381,11 +386,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; } diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index ca81d6524..457bcf322 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -87,12 +87,22 @@ void Terrain3DStorage::clear_edited_area() { } void Terrain3DStorage::add_edited_area(AABB p_area) { + AABB descaled_area = p_area; + descaled_area.position /= _mesh_vertex_spacing; + descaled_area.size /= _mesh_vertex_spacing; if (_edited_area.has_surface()) { _edited_area = _edited_area.merge(p_area); } else { _edited_area = p_area; } - emit_signal("maps_edited", p_area); + emit_signal("maps_edited", get_edited_area()); +} + +AABB Terrain3DStorage::get_edited_area() const { + AABB area = _edited_area; + area.position *= _mesh_vertex_spacing; + area.size *= _mesh_vertex_spacing; + return area; } void Terrain3DStorage::set_region_size(RegionSize p_size) { @@ -114,7 +124,8 @@ void Terrain3DStorage::set_region_offsets(const TypedArray &p_offsets) /** Returns a region offset given a location */ Vector2i Terrain3DStorage::get_region_offset(Vector3 p_global_position) { - return Vector2i((Vector2(p_global_position.x, p_global_position.z) / real_t(_region_size)).floor()); + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; + return Vector2i((Vector2(descaled_position.x, descaled_position.z) / real_t(_region_size)).floor()); } int Terrain3DStorage::get_region_index(Vector3 p_global_position) { @@ -143,7 +154,7 @@ Error Terrain3DStorage::add_region(Vector3 p_global_position, const TypedArray= REGION_MAP_SIZE || region_pos.y >= REGION_MAP_SIZE || region_pos.x < 0 || region_pos.y < 0) { - LOG(ERROR, "Specified position outside of maximum region map size: +/-", REGION_MAP_SIZE / 2 * _region_size); + LOG(ERROR, "Specified position outside of maximum region map size: +/-", real_t((REGION_MAP_SIZE / 2) * _region_size) * _mesh_vertex_spacing); return FAILED; } @@ -412,12 +423,13 @@ void Terrain3DStorage::set_pixel(MapType p_map_type, Vector3 p_global_position, if (region < 0 || region >= _region_offsets.size()) { return; } - Ref map = get_map_region(p_map_type, region); Vector2i global_offset = Vector2i(get_region_offsets()[region]) * _region_size; + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i( - Vector2(p_global_position.x - global_offset.x, - p_global_position.z - global_offset.y) + Vector2(descaled_position.x - global_offset.x, + descaled_position.z - global_offset.y) .floor()); + Ref map = get_map_region(p_map_type, region); map->set_pixelv(img_pos, p_pixel); } @@ -430,12 +442,13 @@ Color Terrain3DStorage::get_pixel(MapType p_map_type, Vector3 p_global_position) if (region < 0 || region >= _region_offsets.size()) { return COLOR_ZERO; } - Ref map = get_map_region(p_map_type, region); Vector2i global_offset = Vector2i(get_region_offsets()[region]) * _region_size; + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i( - Vector2(p_global_position.x - global_offset.x, - p_global_position.z - global_offset.y) + Vector2(descaled_position.x - global_offset.x, + descaled_position.z - global_offset.y) .floor()); + Ref map = get_map_region(p_map_type, region); return map->get_pixelv(img_pos); } @@ -695,15 +708,16 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, Vector3 return; } + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; int max_dimension = _region_size * REGION_MAP_SIZE / 2; - if ((abs(p_global_position.x) > max_dimension) || (abs(p_global_position.z) > max_dimension)) { - LOG(ERROR, "Specify a position within +/-", Vector3i(max_dimension, 0, max_dimension)); + if ((abs(descaled_position.x) > max_dimension) || (abs(descaled_position.z) > max_dimension)) { + LOG(ERROR, "Specify a position within +/-", Vector3(max_dimension, 0.f, max_dimension) * _mesh_vertex_spacing); return; } - if ((p_global_position.x + img_size.x > max_dimension) || - (p_global_position.z + img_size.y > max_dimension)) { + if ((descaled_position.x + img_size.x > max_dimension) || + (descaled_position.z + img_size.y > max_dimension)) { LOG(ERROR, img_size, " image will not fit at ", p_global_position, - ". Try ", -img_size / 2, " to center"); + ". Try ", -(img_size * _mesh_vertex_spacing) / 2.f, " to center"); return; } @@ -769,8 +783,8 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, Vector3 images[i] = img_slice; } // Add the heightmap slice and only regenerate on the last one - Vector3 position = Vector3(p_global_position.x + start_coords.x, 0, p_global_position.z + start_coords.y); - add_region(position, images, (x == slices_width - 1 && y == slices_height - 1)); + Vector3 position = Vector3(descaled_position.x + start_coords.x, 0, descaled_position.z + start_coords.y); + add_region(position * _mesh_vertex_spacing, images, (x == slices_width - 1 && y == slices_height - 1)); } } // for y < slices_height, x < slices_width } @@ -956,10 +970,17 @@ Vector3 Terrain3DStorage::get_normal(Vector3 p_global_position) { real_t right = get_height(p_global_position + Vector3(1.0f, 0.0f, 0.0f)); real_t back = get_height(p_global_position + Vector3(0.f, 0.f, -1.0f)); real_t front = get_height(p_global_position + Vector3(0.f, 0.f, 1.0f)); - Vector3 horizontal = Vector3(2.0f, right - left, 0.0f); - Vector3 vertical = Vector3(0.0f, back - front, 2.0f); + Vector3 horizontal = Vector3(2.0f * _mesh_vertex_spacing, right - left, 0.0f); + Vector3 vertical = Vector3(0.0f, back - front, 2.0f * _mesh_vertex_spacing); Vector3 normal = vertical.cross(horizontal).normalized(); normal.z *= -1.0f; + + // TODO - When get_height interpolates, change this function to a 3-lookup method + //real_t height = get_height(p_global_position); + //real_t u = height - get_height(p_global_position + Vector3(1.f, 0.0f, 0.0f)); + //real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, 1.f)); + //Vector3 normal = Vector3(u, _mesh_vertex_spacing, v); + //normal.normalize(); return normal; } diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 678446808..1df7dd3c6 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -11,11 +11,15 @@ #include "terrain_3d_texture_list.h" #include "util.h" +class Terrain3D; + using namespace godot; class Terrain3DStorage : public Resource { GDCLASS(Terrain3DStorage, Resource); + friend class Terrain3D; + public: // Constants static inline const char *__class__ = "Terrain3DStorage"; @@ -73,6 +77,7 @@ class Terrain3DStorage : public Resource { bool _save_16_bit = false; RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); + real_t _mesh_vertex_spacing = 1.0f; // Set by Terrain3D for get_normal() // Stored Data Vector2 _height_range = Vector2(0, 0); @@ -117,7 +122,7 @@ class Terrain3DStorage : public Resource { void clear_edited_area(); void add_edited_area(AABB p_area); - AABB get_edited_area() const { return _edited_area; } + AABB get_edited_area() const; // Regions void set_region_size(RegionSize p_size);