diff --git a/modules/terrain/register_types.cpp b/modules/terrain/register_types.cpp index 150ee0c3..c2a9aa1d 100644 --- a/modules/terrain/register_types.cpp +++ b/modules/terrain/register_types.cpp @@ -13,7 +13,6 @@ void initialize_terrain_module(ModuleInitializationLevel p_level) { ClassDB::register_abstract_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); } diff --git a/modules/terrain/shared_mutex.cpp b/modules/terrain/shared_mutex.cpp new file mode 100644 index 00000000..a9175632 --- /dev/null +++ b/modules/terrain/shared_mutex.cpp @@ -0,0 +1,27 @@ +#include "shared_mutex.h" + +void SharedMutex::lock_shared() { + this->lock.lock(); + this->shared_count++; + this->lock.unlock(); +} + +void SharedMutex::unlock_shared() { + this->lock.lock(); + this->shared_count--; + this->lock.unlock(); +} + +void SharedMutex::lock_exclusive() { + while (true) { + this->lock.lock(); + if (this->shared_count == 0) { + return; + } + this->lock.unlock(); + } +} + +void SharedMutex::unlock_exclusive() { + this->lock.unlock(); +} diff --git a/modules/terrain/shared_mutex.h b/modules/terrain/shared_mutex.h new file mode 100644 index 00000000..44c6b11b --- /dev/null +++ b/modules/terrain/shared_mutex.h @@ -0,0 +1,46 @@ +#pragma once + +#include "core/os/mutex.h" + +struct SharedMutex { + void lock_shared(); + void unlock_shared(); + void lock_exclusive(); + void unlock_exclusive(); + +private: + Mutex lock{}; + int shared_count{}; + +public: + class LockExclusive { + SharedMutex &mtx; + + public: + explicit LockExclusive(SharedMutex &mtx) : mtx{ mtx } { + this->mtx.lock_exclusive(); + } + ~LockExclusive() { + this->mtx.unlock_exclusive(); + } + LockExclusive() = delete; + LockExclusive(LockExclusive &) = delete; + LockExclusive(LockExclusive &&) = delete; + LockExclusive &operator=(LockExclusive &) = delete; + }; + class LockShared { + SharedMutex &mtx; + + public: + explicit LockShared(SharedMutex &mtx) : mtx{ mtx } { + this->mtx.lock_shared(); + } + ~LockShared() { + this->mtx.unlock_shared(); + } + LockShared() = delete; + LockShared(LockShared &) = delete; + LockShared(LockShared &&) = delete; + LockShared &operator=(LockShared &) = delete; + }; +}; diff --git a/modules/terrain/terrain_modifier.cpp b/modules/terrain/terrain_modifier.cpp index 9bacee2a..f1459ffd 100644 --- a/modules/terrain/terrain_modifier.cpp +++ b/modules/terrain/terrain_modifier.cpp @@ -4,6 +4,7 @@ #include "core/variant/variant.h" #include "macros.h" #include "scene/main/node.h" +#include "scene/resources/curve.h" #include "terrain/terrain.h" #include @@ -22,6 +23,7 @@ void TerrainModifier::_notification(int what) { set_notify_transform(true); } this->thread_safe_global_position = get_global_position(); + return; case NOTIFICATION_TRANSFORM_CHANGED: this->thread_safe_global_position = get_global_position(); return; @@ -55,32 +57,6 @@ Vector3 TerrainModifier::get_thread_safe_global_position() const { return this->thread_safe_global_position; } -void SharedMutex::lock_shared() { - this->lock.lock(); - this->shared_count++; - this->lock.unlock(); -} - -void SharedMutex::unlock_shared() { - this->lock.lock(); - this->shared_count--; - this->lock.unlock(); -} - -void SharedMutex::lock_exclusive() { - while (true) { - this->lock.lock(); - if (this->shared_count == 0) { - return; - } - this->lock.unlock(); - } -} - -void SharedMutex::unlock_exclusive() { - this->lock.unlock(); -} - void TerrainModifierDistance::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, distance_weight_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); } @@ -89,29 +65,32 @@ void TerrainModifierDistance::curves_changed() { if (!update_bounds()) { push_changed(get_bounds()); } - this->lock.lock_exclusive(); + SharedMutex::LockExclusive exclusive{ this->lock }; 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() { - Rect2 const before{ get_bounds() }; + bool changed{ false }; Rect2 bounds{}; - Vector3 position{ get_thread_safe_global_position() }; - bounds.position = { position.x, position.z }; - bounds.size = { 0, 0 }; - this->lock.lock_shared(); - if (this->distance_weight_curve.is_valid()) { - float const max_radius{ this->distance_weight_curve->get_max_domain() }; - float const max_diameter{ 2.f * max_radius }; - bounds.size = { max_diameter, max_diameter }; - bounds.position -= { max_radius, max_radius }; + { + SharedMutex::LockShared shared{ this->lock }; + Rect2 const before{ get_bounds() }; + Vector3 position{ get_thread_safe_global_position() }; + bounds.position = { position.x, position.z }; + bounds.size = { 0, 0 }; + if (this->distance_weight_curve.is_valid()) { + float const max_radius{ this->distance_weight_curve->get_max_domain() }; + float const max_diameter{ 2.f * max_radius }; + bounds.size = { max_diameter, max_diameter }; + bounds.position -= { max_radius, max_radius }; + } + changed = before != bounds; } - bool const changed{ before != bounds }; - this->lock.unlock_shared(); - this->lock.lock_exclusive(); - set_bounds(bounds); - this->lock.unlock_exclusive(); + { + SharedMutex::LockExclusive exclusive{ this->lock }; + set_bounds(bounds); + } + return changed; } @@ -139,21 +118,17 @@ float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) { } float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) { - this->lock.lock_shared(); + SharedMutex::LockShared shared{ this->lock }; 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_buffer->get_max_domain()) { - this->lock.unlock_shared(); return before; } 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(); - return out; } @@ -166,17 +141,18 @@ PackedStringArray TerrainModifierDistance::get_configuration_warnings() const { } void TerrainModifierDistance::set_distance_weight_curve(Ref curve) { - this->lock.lock_exclusive(); - if (Engine::get_singleton()->is_editor_hint()) { - if (this->distance_weight_curve.is_valid()) { - this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed)); - } - if (curve.is_valid()) { - curve->connect_changed(callable_mp(this, &self_type::curves_changed)); + { + SharedMutex::LockExclusive exclusive{ this->lock }; + if (Engine::get_singleton()->is_editor_hint()) { + if (this->distance_weight_curve.is_valid()) { + this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed)); + } + if (curve.is_valid()) { + curve->connect_changed(callable_mp(this, &self_type::curves_changed)); + } } + this->distance_weight_curve = curve; } - this->distance_weight_curve = curve; - this->lock.unlock_exclusive(); curves_changed(); update_configuration_warnings(); } @@ -185,32 +161,6 @@ Ref TerrainModifierDistance::get_distance_weight_curve() const { return this->distance_weight_curve; } -void TerrainModifierPathPoint::_bind_methods() {} - -void TerrainModifierPathPoint::_notification(int what) { - switch (what) { - default: - return; - case NOTIFICATION_ENTER_TREE: - this->path = cast_to(get_parent()); - return; - case NOTIFICATION_READY: - set_notify_transform(true); - if (this->path) { - this->path->path_changed(); - } - return; - case NOTIFICATION_TRANSFORM_CHANGED: - if (this->path && is_inside_tree()) { - this->path->path_changed(); - } - return; - case NOTIFICATION_EXIT_TREE: - this->path = nullptr; - return; - } -} - void TerrainModifierPath::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, curve_left, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); BIND_HPROPERTY(Variant::OBJECT, curve_right, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); @@ -220,43 +170,45 @@ void TerrainModifierPath::curves_changed() { if (!update_bounds()) { push_changed(get_bounds()); } - this->lock.lock_exclusive(); + SharedMutex::LockExclusive exclusive{ this->lock }; 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() { - Vector2 min{}, max{}; - this->lock.lock_shared(); - if (this->points.is_empty() || this->curve_left.is_null()) { - Vector3 point{ this->get_thread_safe_global_position() }; - min.x = point.x; - min.y = point.z; - max = min; - } else { - max = min = { this->points[0].x, this->points[0].y }; - for (Vector3 const &point : this->points) { - max.x = max.x > point.x ? max.x : point.x; - max.y = max.y > point.z ? max.y : point.z; - min.x = min.x < point.x ? min.x : point.x; - min.y = min.y < point.z ? min.y : point.z; + bool changed{ false }; + Rect2 bounds{}; + { + SharedMutex::LockShared shared{ this->lock }; + if (this->path == nullptr) { + return false; } - float max_distance_left{ this->curve_left->get_max_domain() }; - float max_distance{ max_distance_left }; - if (this->curve_right.is_valid()) { - float max_distance_right{ this->curve_right->get_max_domain() }; - max_distance = max_distance_right > max_distance ? max_distance_right : max_distance; + Vector2 min{}, max{}; + float margin{ 0.f }; + if (this->curve_left_buffer.is_valid()) { + float const domain{ this->curve_left_buffer->get_max_domain() }; + margin = domain > margin ? domain : margin; } - min -= { max_distance, max_distance }; - max += { max_distance * 2.f, max_distance * 2.f }; + if (this->curve_right_buffer.is_valid()) { + float const domain{ this->curve_right_buffer->get_max_domain() }; + margin = domain > margin ? domain : margin; + } + for (Vector3 point : this->path->get_curve()->get_points()) { + min.x = point.x < min.x ? point.x : min.x; + min.y = point.z < min.y ? point.z : min.y; + max.x = point.x > max.x ? point.x : max.x; + max.y = point.z > max.y ? point.z : max.y; + } + min -= { margin, margin }; + max += Vector2{ margin, margin }; + Vector3 offset{ this->path->get_global_position() }; + bounds = { Vector2{ offset.x, offset.z } + min, max - min }; + changed = bounds != get_bounds(); + } + { + SharedMutex::LockExclusive exclusive{ this->lock }; + set_bounds(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; } @@ -265,8 +217,9 @@ void TerrainModifierPath::_notification(int what) { default: return; case NOTIFICATION_READY: - update_bounds(); + children_changed(); set_notify_transform(true); + update_bounds(); return; case NOTIFICATION_TRANSFORM_CHANGED: if (is_inside_tree()) { @@ -276,97 +229,82 @@ void TerrainModifierPath::_notification(int what) { } return; case NOTIFICATION_CHILD_ORDER_CHANGED: - path_changed(); + if (!is_ready()) { + children_changed(); + } return; } } -float TerrainModifierPath::evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance, float &out_percentage) { - Vector2 a2{ a.x, a.z }, b2{ b.x, b.z }; - Vector2 const relative_coordinate{ world_coordinate - a2 }; - Vector2 const difference2{ b2 - a2 }; - float w{ difference2.normalized().dot(relative_coordinate) / difference2.length() }; - Vector3 const difference{ b - a }; - float const w_clamped{ (w > 0 ? (w < 1 ? w : 1) : 0) }; - Vector3 const closest_on_line{ a + difference * w_clamped }; - Vector2 const right{ -difference.z, difference.x }; - out_dot = right.normalized().dot(relative_coordinate); - out_distance = world_coordinate.distance_to({ closest_on_line.x, closest_on_line.z }); - out_percentage = w_clamped; - return a.y + (b.y - a.y) * w_clamped; -} - float TerrainModifierPath::evaluate_at(Vector2 world_coordinate, float before) { - this->lock.lock_shared(); - if (this->curve_left_buffer.is_null() || this->points.size() <= 1) { - this->lock.unlock_shared(); + SharedMutex::LockShared shared{ this->lock }; + if (this->path == nullptr || this->curve_left_buffer.is_null()) { return before; } Ref right_curve{ this->curve_right_buffer }; if (right_curve.is_null()) { right_curve = this->curve_left_buffer; } - float out_height{ before }; - long const count{ this->closed ? this->points.size() : this->points.size() - 1 }; - for (int i{ 0 }; i < count; i++) { - Vector3 const ipos{ this->points[i] }; - Vector3 const next_pos{ this->points[Math::wrapi(i + 1, 0, this->points.size())] }; - if (ipos == next_pos) { - continue; - } - float dot, distance, percentage; - bool const is_start{ !this->closed && i == 0 }, is_end{ !this->closed && i == count - 1 }; - float height{ evaluate_line(ipos, is_start, next_pos, is_end, world_coordinate, dot, distance, percentage) }; - 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 }; - if (percentage > 0.f && percentage < 1.f) { - separation = Math::round(separation); - } - float const weight{ left * (1.f - separation) + right * separation }; - float const blended_height{ Math::lerp(out_height, height, weight) }; - out_height = blended_height; - } - this->lock.unlock_shared(); + Ref curve{ this->path->get_curve()->duplicate_deep() }; + Vector3 const relative_position{ Vector3(world_coordinate.x, this->global_path_transform.origin.y, world_coordinate.y) - this->global_path_transform.origin }; + real_t offset{ curve->get_closest_offset(relative_position) }; + Transform3D curve_point{ curve->sample_baked_with_rotation(offset, false, true) }; + curve_point *= this->global_path_transform; + Vector3 const right_direction{ curve_point.basis.get_column(0) }; + Vector2 const right2d{ Vector2{ right_direction.x, right_direction.z }.normalized() }; + Vector2 const world_curve_point{ Vector2{ curve_point.origin.x, curve_point.origin.z } }; + float const dot{ right2d.dot(world_coordinate - world_curve_point) }; + float const right_left_ratio{ (dot + 1.f) / 2.f }; + float const distance{ world_curve_point.distance_to(world_curve_point) }; + float const left_weight{ this->curve_left_buffer->sample(distance) }; + float const right_weight{ right_curve->sample(distance) }; + float const weight{ right_weight * right_left_ratio + left_weight * (1.f - right_left_ratio) }; + float const out_height{ before * (1.f - weight) + curve_point.origin.y * weight }; return out_height; } void TerrainModifierPath::path_changed() { - if (!is_inside_tree()) { - return; - } - this->lock.lock_exclusive(); - this->points.clear(); - this->min_height = INFINITY; - this->max_height = -INFINITY; - Vector3 last{ INFINITY, INFINITY, INFINITY }; - for (Variant var : get_children()) { - if (TerrainModifierPathPoint * point{ cast_to(var) }) { - Vector3 position{ point->get_global_position() }; - if (position != last) { - this->points.push_back(position); - if (position.y > this->max_height) { - this->max_height = position.y; - } - if (position.y < this->min_height) { - this->min_height = position.y; - } - } + { + SharedMutex::LockExclusive exclusive{ this->lock }; + if (this->path) { + this->path_buffer = this->path->get_curve(); + this->global_path_transform = this->path->get_global_transform(); } - last = var; } - this->lock.unlock_exclusive(); if (!update_bounds()) { push_changed(get_bounds()); } } +void TerrainModifierPath::children_changed() { + if (!is_inside_tree()) { + return; + } + { + SharedMutex::LockExclusive exclusive{ this->lock }; + if (this->path) { + this->path->disconnect("curve_changed", callable_mp(this, &self_type::path_changed)); + } + for (Variant var : get_children()) { + if (Path3D * path{ cast_to(var) }) { + this->path = path; + this->global_path_transform = path->get_global_transform(); + this->path->connect("curve_changed", callable_mp(this, &self_type::path_changed)); + break; + } + } + } + path_changed(); +} + PackedStringArray TerrainModifierPath::get_configuration_warnings() const { PackedStringArray warnings{ super_type::get_configuration_warnings() }; if (this->curve_left.is_null()) { warnings.push_back("curve_left is invalid, add a valid curve_left"); } + if (this->path == nullptr) { + warnings.push_back("path is unassigned"); + } return warnings; } @@ -384,11 +322,12 @@ void TerrainModifierPath::set_curve_left(Ref curve) { } } this->curve_left = curve; - this->lock.unlock_exclusive(); if (!curve.is_valid() && this->curve_right.is_valid()) { - curve = this->curve_right; + this->curve_left = this->curve_right; + this->lock.unlock_exclusive(); set_curve_right(nullptr); } else { + this->lock.unlock_exclusive(); curves_changed(); update_configuration_warnings(); } @@ -399,20 +338,21 @@ 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)); + { + SharedMutex::LockExclusive exclusive{ this->lock }; + if (curve.is_valid() && curve == this->curve_left) { + curve = curve->duplicate(); } - if (curve.is_valid()) { - curve->connect_changed(callable_mp(this, &self_type::curves_changed)); + 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)); + } + if (curve.is_valid()) { + curve->connect_changed(callable_mp(this, &self_type::curves_changed)); + } } + this->curve_right = curve; } - this->curve_right = curve; - this->lock.unlock_exclusive(); curves_changed(); update_configuration_warnings(); } diff --git a/modules/terrain/terrain_modifier.h b/modules/terrain/terrain_modifier.h index fc318fb9..daaaf89b 100644 --- a/modules/terrain/terrain_modifier.h +++ b/modules/terrain/terrain_modifier.h @@ -4,7 +4,9 @@ #include "core/variant/variant.h" #include "macros.h" #include "scene/3d/marker_3d.h" +#include "scene/3d/path_3d.h" #include "scene/resources/curve.h" +#include "shared_mutex.h" #include class Terrain; @@ -40,17 +42,6 @@ public: } }; -struct SharedMutex { - void lock_shared(); - void unlock_shared(); - void lock_exclusive(); - void unlock_exclusive(); - -private: - Mutex lock{}; - int shared_count{}; -}; - class TerrainModifierDistance : public TerrainModifier { GDCLASS(TerrainModifierDistance, TerrainModifier); static void _bind_methods(); @@ -75,17 +66,6 @@ public: Ref get_distance_weight_curve() const; }; -class TerrainModifierPathPoint : public Marker3D { - GDCLASS(TerrainModifierPathPoint, Marker3D); - static void _bind_methods(); - -protected: - void _notification(int what); - -public: - class TerrainModifierPath *path{ nullptr }; -}; - class TerrainModifierPath : public TerrainModifier { GDCLASS(TerrainModifierPath, TerrainModifier); static void _bind_methods(); @@ -94,19 +74,18 @@ class TerrainModifierPath : public TerrainModifier { protected: void _notification(int what); - float evaluate_line(Vector3 a, bool a_end, Vector3 b, bool b_end, Vector2 world_coordinate, float &out_dot, float &out_distance, float &out_percentage); public: float evaluate_at(Vector2 world_coordinate, float before) override; void path_changed(); + void children_changed(); PackedStringArray get_configuration_warnings() const override; private: SharedMutex lock{}; - float min_height{}; - float max_height{}; - bool closed{ false }; - Vector points{}; + Path3D *path{ nullptr }; + Transform3D global_path_transform{}; + Ref path_buffer{}; Ref curve_left_buffer{}; Ref curve_left{}; Ref curve_right_buffer{}; diff --git a/project/scenes/terrain_test.scn b/project/scenes/terrain_test.scn index 5ea793d4..bef610df 100644 Binary files a/project/scenes/terrain_test.scn and b/project/scenes/terrain_test.scn differ