From fac1f4c733abbfc74442899ea2662c43a5b34cfb Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 2 Mar 2026 23:29:32 +0100 Subject: [PATCH] feat: implemented smooth paths --- modules/terrain/terrain_modifier.cpp | 59 ++++++++++++++++-------- modules/terrain/terrain_modifier.h | 3 ++ project/scenes/terrain_test.tscn | 69 +++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/modules/terrain/terrain_modifier.cpp b/modules/terrain/terrain_modifier.cpp index cee3550d..fe166926 100644 --- a/modules/terrain/terrain_modifier.cpp +++ b/modules/terrain/terrain_modifier.cpp @@ -84,6 +84,9 @@ void TerrainModifierDistance::curves_changed() { if (!update_bounds()) { push_changed(get_bounds()); } + this->lock.lock_exclusive(); + this->distance_weight_curve_buffer = this->distance_weight_curve.is_valid() ? this->distance_weight_curve->duplicate(true) : nullptr; + this->lock.unlock_exclusive(); } bool TerrainModifierDistance::update_bounds() { @@ -99,9 +102,9 @@ bool TerrainModifierDistance::update_bounds() { bounds.size = { max_diameter, max_diameter }; bounds.position -= { max_radius, max_radius }; } + bool const changed{ before != bounds }; this->lock.unlock_shared(); this->lock.lock_exclusive(); - bool const changed{ before != bounds }; set_bounds(bounds); this->lock.unlock_exclusive(); return changed; @@ -132,19 +135,17 @@ float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) { float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) { this->lock.lock_shared(); - if (this->distance_weight_curve.is_null()) { + if (this->distance_weight_curve_buffer.is_null()) { this->lock.unlock_shared(); return before; } float const distance{ distance_at(world_coordinate) }; - if (distance >= this->distance_weight_curve->get_max_domain()) { + if (distance >= this->distance_weight_curve_buffer->get_max_domain()) { this->lock.unlock_shared(); return before; } - float const weight_offset{ - std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain()) - }; - float const weight{ this->distance_weight_curve->sample(weight_offset) }; + float const weight_offset{ std::clamp(distance, this->distance_weight_curve_buffer->get_min_domain(), this->distance_weight_curve_buffer->get_max_domain()) }; + float const weight{ this->distance_weight_curve_buffer->sample(weight_offset) }; float out{ weight <= 0.f ? before : Math::lerp(before, get_thread_safe_global_position().y, weight) }; this->lock.unlock_shared(); @@ -213,6 +214,10 @@ void TerrainModifierPath::curves_changed() { if (!update_bounds()) { push_changed(get_bounds()); } + this->lock.lock_exclusive(); + this->curve_left_buffer = this->curve_left.is_valid() ? this->curve_left->duplicate(true) : nullptr; + this->curve_right_buffer = this->curve_right.is_valid() ? this->curve_right->duplicate(true) : nullptr; + this->lock.unlock_exclusive(); } bool TerrainModifierPath::update_bounds() { @@ -240,7 +245,9 @@ bool TerrainModifierPath::update_bounds() { Rect2 bounds{ min, max - min }; bool const changed{ bounds != get_bounds() }; this->lock.unlock_shared(); + this->lock.lock_exclusive(); set_bounds(bounds); + this->lock.unlock_exclusive(); return changed; } @@ -284,10 +291,14 @@ float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) { this->lock.lock_shared(); - if (this->curve_left.is_null() || this->curve_right.is_null() || this->points.size() <= 1) { + if (this->curve_left_buffer.is_null() || this->points.size() <= 1) { this->lock.unlock_shared(); return before; } + Ref right_curve{ this->curve_right_buffer }; + if (right_curve.is_null()) { + right_curve = this->curve_left_buffer; + } float out_score{ 0.f }; float out_delta{ 0.f }; long const count{ this->closed ? this->points.size() : this->points.size() - 1 }; @@ -296,29 +307,30 @@ float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) { float dot, distance, percentage; bool const is_start{ !this->closed && i == 0 }, is_end{ !this->closed && i == count - 1 }; float const height{ evaluate_line(ipos, is_start, this->points[Math::wrapi(i + 1, 0, this->points.size())], is_end, world_coordinate, dot, distance, percentage) }; - float const left{ this->curve_left->sample(distance) }; - float const right{ this->curve_right->sample(distance) }; + float const left{ this->curve_left_buffer->sample(distance) }; + float const right{ right_curve->sample(distance) }; float const ndot{ dot / distance }; float separation{ ndot / 2.f + 0.5f }; - float turn{ ndot }; - Vector3 const right_direction{ (this->points[Math::wrapi(i + 1, 0, this->points.size())] - ipos).cross({ 0, 1, 0 }) }; + float turn{ 0.f }; + Vector3 const right_direction{ (this->points[Math::wrapi(i + 1, 0, this->points.size())] - ipos).normalized().cross({ 0, 1, 0 }) }; if (!is_end && percentage >= 1.f) { turn = (this->points[Math::wrapi(i + 2, 0, this->points.size())] - ipos).dot(right_direction); - separation = turn <= 0 ? 0 : 1; + separation = turn >= 0 ? 0 : 1; } else if (!is_start && percentage <= 0.f) { turn = (this->points[Math::wrapi(i - 1, 0, this->points.size())] - ipos).dot(right_direction); - separation = turn <= 0 ? 0 : 1; + separation = turn >= 0 ? 0 : 1; } else if (!is_start && !is_end) { separation = Math::round(separation); + turn = -dot; } - if (turn / turn == dot / dot) { + if ((percentage > 0.f && percentage < 1.f) || Math::abs(turn) / turn != Math::abs(dot) / dot) { float const weight{ left * (1 - separation) + right * separation }; float const blended_height{ Math::lerp(before, height, weight) }; float const delta{ blended_height - before }; - float const score{ weight }; + float const score{ Math::abs(delta) }; if (score > out_score) { out_score = score; - out_delta = delta * score; + out_delta = delta; } } } @@ -326,7 +338,7 @@ float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) { if (out_score == 0.f) { return before; } - return before + (out_delta / out_score); + return before + out_delta; } void TerrainModifierPath::path_changed() { @@ -369,6 +381,14 @@ PackedStringArray TerrainModifierPath::get_configuration_warnings() const { void TerrainModifierPath::set_curve_left(Ref curve) { this->lock.lock_exclusive(); + if (curve.is_valid() && curve == this->curve_right) { + curve = curve->duplicate(); + } else if (!curve.is_valid() && this->curve_right.is_valid()) { + curve = this->curve_right; + this->lock.unlock_exclusive(); + set_curve_right(nullptr); + this->lock.lock_exclusive(); + } if (Engine::get_singleton()->is_editor_hint()) { if (this->curve_left.is_valid()) { this->curve_left->disconnect_changed(callable_mp(this, &self_type::curves_changed)); @@ -389,6 +409,9 @@ Ref TerrainModifierPath::get_curve_left() const { void TerrainModifierPath::set_curve_right(Ref curve) { this->lock.lock_exclusive(); + if (curve.is_valid() && curve == this->curve_left) { + curve = curve->duplicate(); + } if (Engine::get_singleton()->is_editor_hint()) { if (this->curve_right.is_valid()) { this->curve_right->disconnect_changed(callable_mp(this, &self_type::curves_changed)); diff --git a/modules/terrain/terrain_modifier.h b/modules/terrain/terrain_modifier.h index b34146f9..be94ebd2 100644 --- a/modules/terrain/terrain_modifier.h +++ b/modules/terrain/terrain_modifier.h @@ -61,6 +61,7 @@ public: private: SharedMutex lock{}; Ref distance_weight_curve{}; + Ref distance_weight_curve_buffer{}; public: void set_distance_weight_curve(Ref curve); @@ -99,7 +100,9 @@ private: float max_height{}; bool closed{ false }; Vector points{}; + Ref curve_left_buffer{}; Ref curve_left{}; + Ref curve_right_buffer{}; Ref curve_right{}; public: diff --git a/project/scenes/terrain_test.tscn b/project/scenes/terrain_test.tscn index 060776e4..3726979a 100644 --- a/project/scenes/terrain_test.tscn +++ b/project/scenes/terrain_test.tscn @@ -14,8 +14,8 @@ sky = SubResource("Sky_w3uoq") [sub_resource type="Curve" id="Curve_kbmr5"] _limits = [0.0, 1.0, 0.0, 100.0] -_data = [Vector2(0, 1), 0.0, -0.030682996, 0, 0, Vector2(100, 0), 0.0, 0.0, 0, 0] -point_count = 2 +_data = [Vector2(0, 1), 0.0, -0.019399326, 0, 0, Vector2(43.078506, 0.46632487), -0.02627238, -0.02627238, 0, 0, Vector2(100, 0), 0.0, 0.0, 0, 0] +point_count = 3 [sub_resource type="Curve" id="Curve_w3uoq"] _limits = [0.0, 1.0, 0.0, 101.07341] @@ -24,7 +24,12 @@ point_count = 2 [sub_resource type="Curve" id="Curve_chm2y"] _limits = [0.0, 1.0, 0.0, 300.0] -_data = [Vector2(0, 1), -0.005856493, -0.0020244052, 0, 0, Vector2(195.24551, 0.17229712), -0.0043557375, -0.0043557375, 0, 0, Vector2(300, 0), 0.00031304389, -0.05797184, 0, 0] +_data = [Vector2(0, 1), -0.005856493, -0.00037566788, 0, 0, Vector2(103.49251, 0.80491185), -0.0043557375, -0.0043557375, 0, 0, Vector2(300, 0), 0.00031304389, -0.05797184, 0, 0] +point_count = 3 + +[sub_resource type="Curve" id="Curve_o3i6r"] +_limits = [0.0, 1.0, 0.0, 5.0] +_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.5354336, 1), 0.0, 0.0, 0, 0, Vector2(5, 0), 0.0, 0.0, 0, 0] point_count = 3 [sub_resource type="BoxMesh" id="BoxMesh_kbmr5"] @@ -40,38 +45,78 @@ chunk_size = 100 thread_count = 5 [node name="TerrainModifierPath" type="TerrainModifierPath" parent="Terrain" unique_id=462259542] -transform = Transform3D(1.9138145, 0, 0, 0, 0.6860331, 0, 0, 0, 2.7343204, 190.712, 125.888245, 634.1432) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 377.77588, 108.75322, 652.4378) curve_left = SubResource("Curve_kbmr5") curve_right = SubResource("Curve_w3uoq") [node name="TerrainModifierPathPoint" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=1975236067] -transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.99999976, -5.4521523, -33.998764, -98.341095) +transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.99999976, -180.28406, -81.73056, -295.60077) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint5" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=2007122252] -transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, 33.986214, 4.6532135, -47.78543) +transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, -117.323944, -72.07858, -209.4972) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint2" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=88875414] -transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, -45.605103, -77.62694, -7.8362274) +transform = Transform3D(0.9999999, 0, 0, 0, 1, 0, 0, 0, 0.9999999, -186.47693, -104.63211, -100.22473) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint3" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=910243114] -transform = Transform3D(-0.08673841, 0, 0.9962309, 0, 1, 0, -0.99623066, 0, -0.0867384, 51.146767, 185.18083, 46.93416) +transform = Transform3D(0.9999996, 0, 0, 0, 1, 0, 0, 0, 0.9999998, -72.87836, -24.743164, -14.561768) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint4" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=738726374] -transform = Transform3D(-0.5568029, 0, 0.8306443, 0, 1, 0, -0.83064425, 0, -0.556803, 181.23196, -50.373947, -56.57155) +transform = Transform3D(0.99999976, 0, 0, 0, 1, 0, 0, 0, 0.99999976, 69.492004, -25.579193, -103.737305) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint6" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=868243973] -transform = Transform3D(-0.5568028, 0, 0.8306443, 0, 1, 0, -0.830644, 0, -0.55680287, 206.74478, -50.373917, 13.438446) +transform = Transform3D(0.9999994, 0, 0, 0, 1, 0, 0, 0, 0.9999997, 191.05371, -25.255402, -19.047241) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint7" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=601656436] -transform = Transform3D(-0.5568028, 0, 0.8306443, 0, 1, 0, -0.8306439, 0, -0.55680287, 192.3706, 12.87825, 78.11276) +transform = Transform3D(0.99999934, 0, 0, 0, 1, 0, 0, 0, 0.9999997, 160.64337, -4.200287, 123.704346) +gizmo_extents = 10.0 [node name="TerrainModifierPathPoint8" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath" unique_id=300006660] -transform = Transform3D(-0.5568028, 0, 0.8306443, 0, 1, 0, -0.8306439, 0, -0.55680287, 289.17203, 36.37845, 54.197342) +transform = Transform3D(0.99999934, 0, 0, 0, 1, 0, 0, 0, 0.9999997, 271.10236, -19.105774, 225.51074) +gizmo_extents = 10.0 [node name="TerrainModifierDistance8" type="TerrainModifierDistance" parent="Terrain" unique_id=1993490768] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 784.91595, 251.11382, 135.92102) distance_weight_curve = SubResource("Curve_chm2y") +[node name="TerrainModifierPath2" type="TerrainModifierPath" parent="Terrain" unique_id=515690726] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 368.5579, 5.0770416, 414.4787) +curve_left = SubResource("Curve_o3i6r") + +[node name="TerrainModifierPathPoint7" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=283037782] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.025024, -5.665145, -37.19107) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint6" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=1696432484] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.917389, -9.399911, -41.447937) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=870583505] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7605286, -10.70397, -33.974182) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint2" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=2085062411] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.681885, -11.332738, -8.841034) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint3" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=1608777346] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.356476, -11.604663, 8.474121) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint4" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=2089784013] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.419983, -9.546529, 16.345459) +gizmo_extents = 1.0 + +[node name="TerrainModifierPathPoint5" type="TerrainModifierPathPoint" parent="Terrain/TerrainModifierPath2" unique_id=732306824] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.16311646, -7.533708, 20.998108) +gizmo_extents = 1.0 + [node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1089775425] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 625.56537, 191.07982, 795.4992) mesh = SubResource("BoxMesh_kbmr5")