diff --git a/project/addons/terrain_3d/editor/components/point_picker.gd b/project/addons/terrain_3d/editor/components/point_picker.gd index 05c743ed..dbc1280f 100644 --- a/project/addons/terrain_3d/editor/components/point_picker.gd +++ b/project/addons/terrain_3d/editor/components/point_picker.gd @@ -73,10 +73,12 @@ func add_point(p_value: Vector3) -> void: if points.has(p_value): return + # If manually selecting a point individually if picking_index != -1: points[picking_index] = p_value picking_index = -1 else: + # Else picking a sequence of points (non-drawable) for i in range(MAX_POINTS): if points[i] == Vector3.ZERO: points[i] = p_value diff --git a/project/addons/terrain_3d/editor/components/ui.gd b/project/addons/terrain_3d/editor/components/ui.gd index ce9d2149..c633715c 100644 --- a/project/addons/terrain_3d/editor/components/ui.gd +++ b/project/addons/terrain_3d/editor/components/ui.gd @@ -229,7 +229,7 @@ func update_decal() -> void: await get_tree().create_timer(.05).timeout decal.visible = true - decal.size = Vector3.ONE * brush_data["size"] + decal.size = Vector3.ONE * brush_data["size"] * plugin.terrain.get_mesh_vertex_spacing() if brush_data["align_to_view"]: var cam: Camera3D = plugin.terrain.get_camera(); if (cam): @@ -311,7 +311,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 + point_decal.position = point * plugin.terrain.get_mesh_vertex_spacing() index += 1 diff --git a/project/addons/terrain_3d/editor/editor.gd b/project/addons/terrain_3d/editor/editor.gd index 133bece1..c637ec89 100644 --- a/project/addons/terrain_3d/editor/editor.gd +++ b/project/addons/terrain_3d/editor/editor.gd @@ -130,11 +130,14 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir) mouse_global_position = (camera_pos + t * camera_dir) - # Update decal + ## Update decal ui.decal.global_position = mouse_global_position ui.decal.albedo_mix = 1.0 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() @@ -214,7 +217,7 @@ func update_region_grid() -> void: region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT region_gizmo.region_position = current_region_position - region_gizmo.region_size = terrain.get_storage().get_region_size() + region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing() region_gizmo.grid = terrain.get_storage().get_region_offsets() terrain.update_gizmos() diff --git a/src/shaders/debug_views.glsl b/src/shaders/debug_views.glsl index b8301fdc..5ee446ed 100644 --- a/src/shaders/debug_views.glsl +++ b/src/shaders/debug_views.glsl @@ -6,7 +6,7 @@ R"( //INSERT: DEBUG_CHECKERED // Show a checkered grid - vec2 __p = UV * 1.0; // scale + vec2 __p = uv * 1.0; // scale vec2 __ddx = dFdx(__p); vec2 __ddy = dFdy(__p); vec2 __w = max(abs(__ddx), abs(__ddy)) + 0.01; @@ -25,7 +25,7 @@ R"( //INSERT: DEBUG_HEIGHTMAP // Show heightmap - ALBEDO = vec3(smoothstep(-0.1, 2.0, 0.5 + get_height(UV2)/300.0)); + ALBEDO = vec3(smoothstep(-0.1, 2.0, 0.5 + get_height(uv2)/300.0)); ROUGHNESS = 0.7; SPECULAR = 0.; NORMAL_MAP = vec3(0.5, 0.5, 1.0); @@ -74,7 +74,7 @@ R"( NORMAL_MAP = vec3(0.5, 0.5, 1.0); //INSERT: DEBUG_AUTOSHADER - ivec3 __ruv = get_region_uv(floor(UV)); + ivec3 __ruv = get_region_uv(floor(uv)); uint __control = texelFetch(_control_maps, __ruv, 0).r; float __autoshader = float( bool(__control & 0x1u) || __ruv.z<0 ); ALBEDO = vec3(__autoshader); @@ -113,16 +113,16 @@ R"( float __view_distance = 300.0; // Visible distance of grid // Draw region grid __region_line = .1*sqrt(__camera_dist); - if (mod(__pixel_pos.x + __region_line*.5, _region_size) <= __region_line || - mod(__pixel_pos.z + __region_line*.5, _region_size) <= __region_line ) { + if (mod(__pixel_pos.x * _mesh_vertex_density + __region_line*.5, _region_size) <= __region_line || + mod(__pixel_pos.z * _mesh_vertex_density + __region_line*.5, _region_size) <= __region_line ) { ALBEDO = vec3(1.); } vec3 __vertex_mul = vec3(0.); vec3 __vertex_add = vec3(0.); float __distance_factor = clamp(1.-__camera_dist/__view_distance, 0., 1.); // Draw vertex grid - if ( mod(__pixel_pos.x + __grid_line*.5, __grid_step) < __grid_line || - mod(__pixel_pos.z + __grid_line*.5, __grid_step) < __grid_line ) { + if ( mod(__pixel_pos.x * _mesh_vertex_density + __grid_line*.5, __grid_step) < __grid_line || + mod(__pixel_pos.z * _mesh_vertex_density + __grid_line*.5, __grid_step) < __grid_line ) { __vertex_mul = vec3(0.5) * __distance_factor; } // Draw Vertices diff --git a/src/shaders/editor_functions.glsl b/src/shaders/editor_functions.glsl index a4ccb3b9..012b4036 100644 --- a/src/shaders/editor_functions.glsl +++ b/src/shaders/editor_functions.glsl @@ -6,7 +6,7 @@ R"( //INSERT: EDITOR_NAVIGATION // Show navigation - if(bool(texelFetch(_control_maps, get_region_uv(floor(UV)), 0).r >>1u & 0x1u)) { + if(bool(texelFetch(_control_maps, get_region_uv(floor(uv)), 0).r >>1u & 0x1u)) { ALBEDO *= vec3(.5, .0, .85); } diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl index 1abf4499..d7576d9a 100644 --- a/src/shaders/main.glsl +++ b/src/shaders/main.glsl @@ -23,6 +23,8 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic // Private uniforms uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1/1024 +uniform float _mesh_vertex_spacing = 1.0; +uniform float _mesh_vertex_density = 1.0; // = 1/_mesh_vertex_spacing uniform int _region_map_size = 16; uniform int _region_map[256]; uniform vec2 _region_offsets[256]; @@ -60,10 +62,12 @@ struct Material { float blend; }; -varying vec3 v_vertex; // World coordinate vertex location -varying vec3 v_camera_pos; -varying float v_vertex_dist; +varying flat vec3 v_vertex; // World coordinate vertex location +varying flat vec3 v_camera_pos; +varying flat float v_vertex_dist; varying flat ivec3 v_region; +varying flat vec2 v_uv_offset; +varying flat vec2 v_uv2_offset; //////////////////////// // Vertex @@ -110,7 +114,7 @@ void vertex() { v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // UV coordinates in world space. Values are 0 to _region_size within regions - UV = v_vertex.xz; + UV = round(v_vertex.xz * _mesh_vertex_density); // Discard vertices if designated as a hole or background disabled. 1 lookup. v_region = get_region_uv(UV); @@ -128,6 +132,12 @@ void vertex() { v_vertex_dist = length(v_vertex - v_camera_pos); //INSERT: DUAL_SCALING_VERTEX } + + // Transform UVs to local to avoid poor precision during varying interpolation. + v_uv_offset = MODEL_MATRIX[3].xz * _mesh_vertex_density; + UV -= v_uv_offset; + v_uv2_offset = v_uv_offset * _region_texel_size; + UV2 -= v_uv2_offset; } //////////////////////// @@ -140,8 +150,8 @@ vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) { float right = get_height(uv + vec2(_region_texel_size, 0)); float back = get_height(uv + vec2(0, -_region_texel_size)); float front = get_height(uv + vec2(0, _region_texel_size)); - vec3 horizontal = vec3(2.0, right - left, 0.0); - vec3 vertical = vec3(0.0, back - front, 2.0); + vec3 horizontal = vec3(2.0 * _mesh_vertex_spacing, right - left, 0.0); + vec3 vertical = vec3(0.0, back - front, 2.0 * _mesh_vertex_spacing); vec3 normal = normalize(cross(vertical, horizontal)); normal.z *= -1.0; tangent = cross(normal, vec3(0, 0, 1)); @@ -248,16 +258,20 @@ float blend_weights(float weight, float detail) { } void fragment() { + // Recover UVs + vec2 uv = UV + v_uv_offset; + vec2 uv2 = UV2 + v_uv2_offset; + // Calculate Terrain Normals. 4 lookups vec3 w_tangent, w_binormal; - vec3 w_normal = get_normal(UV2, w_tangent, w_binormal); + vec3 w_normal = get_normal(uv2, w_tangent, w_binormal); NORMAL = mat3(VIEW_MATRIX) * w_normal; TANGENT = mat3(VIEW_MATRIX) * w_tangent; BINORMAL = mat3(VIEW_MATRIX) * w_binormal; // Idenfity 4 vertices surrounding this pixel - vec2 texel_pos = UV; - highp vec2 texel_pos_floor = floor(UV); + vec2 texel_pos = uv; + highp vec2 texel_pos_floor = floor(uv); // Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0); @@ -278,18 +292,18 @@ void fragment() { // Get the textures for each vertex. 8-16 lookups (2-4 ea) Material mat[4]; - get_material(UV, control00, index00UV, w_normal, mat[0]); - get_material(UV, control01, index01UV, w_normal, mat[1]); - get_material(UV, control10, index10UV, w_normal, mat[2]); - get_material(UV, control11, index11UV, w_normal, mat[3]); + get_material(uv, control00, index00UV, w_normal, mat[0]); + get_material(uv, control01, index01UV, w_normal, mat[1]); + get_material(uv, control10, index10UV, w_normal, mat[2]); + get_material(uv, control11, index11UV, w_normal, mat[3]); // Calculate weight for the pixel position between the vertices - // Bilinear interpolation of difference of UV and floor(UV) + // Bilinear interpolation of difference of uv and floor(uv) vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1); weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy); vec2 weights0 = vec2(1.0) - weights1; // Adjust final weights by noise. 1 lookup - float noise3 = texture(noise_texture, UV*noise3_scale).r; + float noise3 = texture(noise_texture, uv*noise3_scale).r; vec4 weights; weights.x = blend_weights(weights0.x * weights0.y, noise3); weights.y = blend_weights(weights0.x * weights1.y, noise3); @@ -313,7 +327,7 @@ void fragment() { mat[3].nrm_rg * weights.w ); // Determine if we're in a region or not (region_uv.z>0) - vec3 region_uv = get_region_uv2(UV2); + vec3 region_uv = get_region_uv2(uv2); // Colormap. 1 lookup vec4 color_map = vec4(1., 1., 1., .5); @@ -324,8 +338,8 @@ void fragment() { } // Macro variation. 2 Lookups - float noise1 = texture(noise_texture, rotate(UV*noise1_scale*.1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r; - float noise2 = texture(noise_texture, UV*noise2_scale*.1).r; + float noise1 = texture(noise_texture, rotate(uv*noise1_scale*.1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r; + float noise2 = texture(noise_texture, uv*noise2_scale*.1).r; vec3 macrov = mix(macro_variation1, vec3(1.), clamp(noise1 + v_vertex_dist*.0002, 0., 1.)); macrov *= mix(macro_variation2, vec3(1.), clamp(noise2 + v_vertex_dist*.0002, 0., 1.)); diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 2073a68d..201588a2 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -66,6 +66,7 @@ void Terrain3D::_initialize() { // Initialize the system if (!_initialized && _is_inside_world && is_inside_tree()) { _material->initialize(_storage->get_region_size()); + _material->set_mesh_vertex_spacing(_mesh_vertex_spacing); _storage->update_regions(true); // generate map arrays _texture_list->update_list(); // generate texture arrays _build(_mesh_lods, _mesh_size); @@ -363,6 +364,7 @@ void Terrain3D::_update_collision() { // Rotated shape Y=90 for -90 rotated array index Transform3D xform = Transform3D(Basis(Vector3(0, 1.0, 0), Math_PI * .5), global_pos + Vector3(region_size, 0, region_size) * .5); + xform.scale(Vector3(_mesh_vertex_spacing, 1, _mesh_vertex_spacing)); if (!_show_debug_collision) { RID shape = PhysicsServer3D::get_singleton()->heightmap_shape_create(); @@ -494,10 +496,10 @@ void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector } } } else { - int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z); - int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z) + 1; - int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x); - int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x) + 1; + int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z / _mesh_vertex_spacing); + int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z / _mesh_vertex_spacing) + 1; + int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x / _mesh_vertex_spacing); + int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x / _mesh_vertex_spacing) + 1; for (int32_t z = z_start; z < z_end; ++z) { for (int32_t x = x_start; x < x_end; ++x) { @@ -516,10 +518,11 @@ void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVe 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); 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)); - Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)); - Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z + step)); + 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; 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); @@ -536,9 +539,9 @@ void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVe control2 = _storage->get_control(Vector3(x + step, 0.0, z)); control3 = _storage->get_control(Vector3(x + step, 0.0, z + step)); 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)); - Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z)); - Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)); + 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; 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); @@ -604,6 +607,19 @@ void Terrain3D::set_mesh_size(int p_size) { } } +void Terrain3D::set_mesh_vertex_spacing(real_t p_spacing) { + p_spacing = CLAMP(p_spacing, 0.25f, 100.0f); + if (_mesh_vertex_spacing != p_spacing) { + LOG(INFO, "Setting mesh vertex spacing: ", p_spacing); + _mesh_vertex_spacing = p_spacing; + _clear(); + _initialize(); + } + if (Engine::get_singleton()->is_editor_hint() && _plugin != nullptr) { + _plugin->call("update_region_grid"); + } +} + void Terrain3D::set_material(const Ref &p_material) { if (_storage != p_material) { LOG(INFO, "Setting material"); @@ -695,17 +711,19 @@ void Terrain3D::snap(Vector3 p_cam_pos) { p_cam_pos.y = 0; LOG(DEBUG_CONT, "Snapping terrain to: ", String(p_cam_pos)); - Transform3D t = Transform3D(Basis(), p_cam_pos.floor()); + Vector3 snapped_pos = (p_cam_pos / _mesh_vertex_spacing).floor() * _mesh_vertex_spacing; + Transform3D t = Transform3D().scaled(Vector3(_mesh_vertex_spacing, 1, _mesh_vertex_spacing)); + t.origin = snapped_pos; RS->instance_set_transform(_data.cross, t); int edge = 0; int tile = 0; for (int l = 0; l < _mesh_lods; l++) { - real_t scale = real_t(1 << l); + real_t scale = real_t(1 << l) * _mesh_vertex_spacing; Vector3 snapped_pos = (p_cam_pos / scale).floor() * scale; - Vector3 tile_size = Vector3(real_t(_mesh_size << l), 0, real_t(_mesh_size << l)); - Vector3 base = snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))); + Vector3 tile_size = Vector3(real_t(_mesh_size << l), 0, real_t(_mesh_size << l)) * _mesh_vertex_spacing; + Vector3 base = snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))) * _mesh_vertex_spacing; // Position tiles for (int x = 0; x < 4; x++) { @@ -756,7 +774,7 @@ void Terrain3D::snap(Vector3 p_cam_pos) { // Position seams { - Vector3 next_base = next_snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))); + Vector3 next_base = next_snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))) * _mesh_vertex_spacing; Transform3D t = Transform3D().scaled(Vector3(scale, 1, scale)); t.origin = next_base; RS->instance_set_transform(_data.seams[edge], t); @@ -832,7 +850,7 @@ Vector3 Terrain3D::get_intersection(Vector3 p_position, Vector3 p_direction) { if (_storage.is_valid()) { for (int i = 0; i < 3000; i++) { test_point += test_dir; - test_point.y = _storage->get_height(test_point); + test_point.y = _storage->get_height(test_point / _mesh_vertex_spacing); Vector3 test_vec = (test_point - p_position).normalized(); real_t test_dotp = p_direction.dot(test_vec); @@ -1016,6 +1034,8 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); ClassDB::bind_method(D_METHOD("get_mesh_size"), &Terrain3D::get_mesh_size); + ClassDB::bind_method(D_METHOD("set_mesh_vertex_spacing", "scale"), &Terrain3D::set_mesh_vertex_spacing); + ClassDB::bind_method(D_METHOD("get_mesh_vertex_spacing"), &Terrain3D::get_mesh_vertex_spacing); ClassDB::bind_method(D_METHOD("set_material", "material"), &Terrain3D::set_material); ClassDB::bind_method(D_METHOD("get_material"), &Terrain3D::get_material); @@ -1077,6 +1097,7 @@ void Terrain3D::_bind_methods() { ADD_GROUP("Mesh", "mesh_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_lods", PROPERTY_HINT_RANGE, "1,10,1"), "set_mesh_lods", "get_mesh_lods"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_size", PROPERTY_HINT_RANGE, "8,64,1"), "set_mesh_size", "get_mesh_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_vertex_spacing", PROPERTY_HINT_RANGE, "0.25,10.0,0.05,or_greater"), "set_mesh_vertex_spacing", "get_mesh_vertex_spacing"); ADD_GROUP("Debug", "debug_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_level", PROPERTY_HINT_ENUM, "Errors,Info,Debug,Debug Continuous"), "set_debug_level", "get_debug_level"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 93134b8a..057afb00 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -35,6 +35,7 @@ class Terrain3D : public Node3D { // Terrain settings int _mesh_size = 48; int _mesh_lods = 7; + real_t _mesh_vertex_spacing = 1.0f; Ref _storage; Ref _material; @@ -104,6 +105,8 @@ class Terrain3D : public Node3D { int get_mesh_lods() const { return _mesh_lods; } void set_mesh_size(int p_size); int get_mesh_size() const { return _mesh_size; } + void set_mesh_vertex_spacing(real_t p_spacing); + real_t get_mesh_vertex_spacing() const { return _mesh_vertex_spacing; } void set_storage(const Ref &p_storage); Ref get_storage() const { return _storage; } diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index 704081f9..06f1554e 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -508,6 +508,13 @@ Variant Terrain3DMaterial::get_shader_param(const StringName &p_name) const { return value; } +void Terrain3DMaterial::set_mesh_vertex_spacing(real_t p_spacing) { + LOG(INFO, "Setting mesh vertex spacing in material: ", p_spacing); + _mesh_vertex_spacing = p_spacing; + RS->material_set_param(_material, "_mesh_vertex_spacing", p_spacing); + RS->material_set_param(_material, "_mesh_vertex_density", 1.0f / p_spacing); +} + void Terrain3DMaterial::set_show_checkered(bool p_enabled) { LOG(INFO, "Enable set_show_checkered: ", p_enabled); _debug_view_checkered = p_enabled; diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h index 8bc98fde..43e4171b 100644 --- a/src/terrain_3d_material.h +++ b/src/terrain_3d_material.h @@ -63,6 +63,7 @@ class Terrain3DMaterial : public Resource { // Cached data from Storage int _texture_count = 0; int _region_size = 1024; + real_t _mesh_vertex_spacing = 1.0f; Vector2i _region_sizev = Vector2i(_region_size, _region_size); PackedInt32Array _region_map; GeneratedTex _generated_region_blend_map; // 512x512 blurred image of region_map @@ -108,6 +109,8 @@ class Terrain3DMaterial : public Resource { void set_shader_param(const StringName &p_name, const Variant &p_value); Variant get_shader_param(const StringName &p_name) const; + void set_mesh_vertex_spacing(real_t p_spacing); + // Editor functions / Debug views void set_show_checkered(bool p_enabled); bool get_show_checkered() const { return _debug_view_checkered; }