diff --git a/doc/api/class_terrain3d.rst b/doc/api/class_terrain3d.rst index 47619e6b..6749e930 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 with a new height scale. + .. rst-class:: classref-item-separator ---- diff --git a/doc/api/class_terrain3dstorage.rst b/doc/api/class_terrain3dstorage.rst index 11317127..89d5a134 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 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`. .. 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 03af6033..8cc94945 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 with a new height 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 b7b42e82..535654a9 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 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]. 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 b93f3288..e2682f29 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 d34a503a..0f444837 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 c33f90b1..31981722 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 c3b5d00a..67ddba06 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 ca81d652..457bcf32 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 67844680..1df7dd3c 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);