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 15, 2024
1 parent 5eb3f60 commit 3ce0e89
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 45 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
10 changes: 6 additions & 4 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 @@ -544,7 +544,9 @@ Returns the associated pixel on the control map at the requested location. Calls

:ref:`float<class_float>` **get_height** **(** :ref:`Vector3<class_Vector3>` global_position **)**

Returns the associated pixel on the height map at the requested location. Calls :ref:`get_pixel<class_Terrain3DStorage_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 `issue 324 <"https://github.com/TokisanGames/Terrain3D/issues/324">`__.

Calls :ref:`get_pixel<class_Terrain3DStorage_method_get_pixel>`.

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

Expand Down Expand Up @@ -610,7 +612,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.

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

Expand Down Expand Up @@ -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<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
9 changes: 5 additions & 4 deletions doc/classes/Terrain3DStorage.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
<return type="float" />
<param index="0" name="global_position" type="Vector3" />
<description>
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].
</description>
</method>
<method name="get_map_region">
Expand Down Expand Up @@ -99,7 +100,7 @@
<return type="Vector3" />
<param index="0" name="global_position" type="Vector3" />
<description>
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.
</description>
</method>
<method name="get_pixel">
Expand Down Expand Up @@ -162,7 +163,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 @@ -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).
</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
12 changes: 6 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,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()

Expand All @@ -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


Expand All @@ -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)
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
3 changes: 3 additions & 0 deletions src/terrain_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
20 changes: 12 additions & 8 deletions src/terrain_3d_editor.cpp
Original file line number Diff line number Diff line change
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 @@ -147,6 +149,8 @@ void Terrain3DEditor::_operate_map(Vector3 p_global_position, real_t p_camera_di
}
Object::cast_to<Node>(_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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down
57 changes: 39 additions & 18 deletions src/terrain_3d_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -114,7 +124,8 @@ void Terrain3DStorage::set_region_offsets(const TypedArray<Vector2i> &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) {
Expand Down Expand Up @@ -143,7 +154,7 @@ Error Terrain3DStorage::add_region(Vector3 p_global_position, const TypedArray<I

Vector2i region_pos = Vector2i(uv_offset + (REGION_MAP_VSIZE / 2));
if (region_pos.x >= 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;
}

Expand Down Expand Up @@ -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<Image> 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<Image> map = get_map_region(p_map_type, region);
map->set_pixelv(img_pos, p_pixel);
}

Expand All @@ -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<Image> 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<Image> map = get_map_region(p_map_type, region);
return map->get_pixelv(img_pos);
}

Expand Down Expand Up @@ -695,15 +708,16 @@ void Terrain3DStorage::import_images(const TypedArray<Image> &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;
}

Expand Down Expand Up @@ -769,8 +783,8 @@ void Terrain3DStorage::import_images(const TypedArray<Image> &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
}
Expand Down Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit 3ce0e89

Please sign in to comment.