diff --git a/modules/terrain/terrain.cpp b/modules/terrain/terrain.cpp index 2ec49a9a..ac4f8fc8 100644 --- a/modules/terrain/terrain.cpp +++ b/modules/terrain/terrain.cpp @@ -9,6 +9,7 @@ void Terrain::_bind_methods() { } void Terrain::ready() { + threads.resize_initialized(22); construct_chunk_grid(); generate_meshes(); } @@ -27,8 +28,15 @@ void Terrain::update_modifier_list() { on_terrain_changed(); } +void Terrain::child_order_changed() { + // TODO: check if the order of _modifiers_ actually changed + if (is_ready()) { + update_modifier_list(); + } +} + void Terrain::child_entered(Node *node) { - if (cast_to(node)) { + if (cast_to(node) != nullptr && is_ready()) { update_modifier_list(); } } @@ -39,7 +47,9 @@ void Terrain::child_exiting(Node *node) { if (Engine::get_singleton()->is_editor_hint() && !is_queued_for_deletion()) { mod->disconnect(TerrainModifier::sig_changed, callable_mp(this, &self_type::on_terrain_changed)); } - on_terrain_changed(); + if (is_ready()) { + on_terrain_changed(); + } } } @@ -58,7 +68,7 @@ void Terrain::_notification(int what) { if (!is_ready()) { connect("child_entered_tree", callable_mp(this, &self_type::child_entered)); connect("child_exiting_tree", callable_mp(this, &self_type::child_exiting)); - connect("child_order_changed", callable_mp(this, &self_type::update_modifier_list)); + connect("child_order_changed", callable_mp(this, &self_type::child_order_changed)); } return; case NOTIFICATION_READY: @@ -67,6 +77,24 @@ void Terrain::_notification(int what) { } } +void Terrain::generate_meshes_thread(void *terrain) { + Terrain *self{ static_cast(terrain) }; + for (;;) { + self->workload_lock.lock(); + if (self->workload.is_empty()) { + self->workload_lock.unlock(); + return; + } + TerrainChunkMesh *mesh{ self->workload[0] }; + self->workload.remove_at(0); + self->workload_lock.unlock(); + if (!mesh->is_inside_tree()) { + return; + } + mesh->update_mesh(); + } +} + void Terrain::construct_chunk_grid() { size_t const chunks_per_side{ this->side_length / this->chunk_size }; Vector3 const origin{ (float)this->chunk_size / 2.f, 0.f, (float)this->chunk_size / 2.f }; @@ -85,11 +113,17 @@ void Terrain::construct_chunk_grid() { } void Terrain::generate_meshes() { - for (TerrainChunkMesh *mesh : this->meshes) { - if (!mesh->is_inside_tree()) { - return; + if (!is_inside_tree()) { + return; + } + this->workload.append_array(this->meshes); + for (Thread &thread : this->threads) { + thread.start(&self_type::generate_meshes_thread, this); + } + for (Thread &thread : this->threads) { + if (thread.is_started()) { + thread.wait_to_finish(); } - mesh->update_mesh(); } } @@ -124,6 +158,10 @@ void Terrain::set_chunk_size(size_t size) { } } +size_t Terrain::get_chunk_size() const { + return this->chunk_size; +} + void Terrain::set_detail(size_t detail) { this->detail = detail; if (is_inside_tree()) { diff --git a/modules/terrain/terrain.h b/modules/terrain/terrain.h index f738dd7a..594dc810 100644 --- a/modules/terrain/terrain.h +++ b/modules/terrain/terrain.h @@ -1,5 +1,7 @@ #pragma once +#include "core/os/mutex.h" +#include "core/os/thread.h" #include "core/templates/vector.h" #include "macros.h" #include "scene/main/node.h" @@ -13,6 +15,7 @@ class Terrain : public Node { void ready(); void update_modifier_list(); + void child_order_changed(); void child_entered(Node *node); void child_exiting(Node *node); @@ -20,6 +23,7 @@ class Terrain : public Node { protected: void _notification(int what); + static void generate_meshes_thread(void *terrain); public: void construct_chunk_grid(); @@ -41,6 +45,9 @@ public: size_t get_detail() const; private: - Vector meshes; - Vector modifiers; + Vector workload{}; + Mutex workload_lock; + Vector meshes{}; + Vector modifiers{}; + LocalVector threads{}; }; diff --git a/modules/terrain/terrain_chunk.cpp b/modules/terrain/terrain_chunk.cpp index 5591bb79..a429a1c4 100644 --- a/modules/terrain/terrain_chunk.cpp +++ b/modules/terrain/terrain_chunk.cpp @@ -11,12 +11,12 @@ void TerrainChunkMesh::generate_vertices() { ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate_vertices: points per side <= 0"); float const half_extent{ (float)this->size / 2.f }; float const point_distance{ (float)this->size / ((float)points_per_side() - 1) }; - Vector3 origin{ this->get_global_position() - Vector3{ half_extent, 0, half_extent } }; + Vector3 origin{ this->safe_position - Vector3{ half_extent, 0, half_extent } }; for (size_t x{ 0 }; x < points_per_side(); ++x) { for (size_t y{ 0 }; y < points_per_side(); ++y) { Vector2 const coordinate{ origin.x + point_distance * x, origin.z + point_distance * y }; this->surface->set_uv({ (float)x / (float)points_per_side(), (float)y / (float)points_per_side() }); - this->surface->add_vertex({ coordinate.x - get_global_position().x, this->terrain->height_at(coordinate), coordinate.y - get_global_position().z }); + this->surface->add_vertex({ coordinate.x - this->safe_position.x, this->terrain->height_at(coordinate), coordinate.y - this->safe_position.z }); } } } @@ -51,17 +51,30 @@ void TerrainChunkMesh::generate_faces() { } } +void TerrainChunkMesh::_notification(int what) { + switch (what) { + default: + return; + case NOTIFICATION_READY: + if (!get_mesh().is_valid()) { + set_mesh(memnew(ArrayMesh)); + } + this->safe_position = get_global_position(); + return; + } +} + void TerrainChunkMesh::update_mesh() { ERR_FAIL_COND_EDMSG(this->size <= 0.f, "TerrainChunkMesh::generate: size <= 0"); ERR_FAIL_COND_EDMSG(points_per_side() <= 0, "TerrainChunkMesh::generate: points per side <= 0"); - this->set_mesh(memnew(ArrayMesh)); this->surface->clear(); this->surface->begin(Mesh::PRIMITIVE_TRIANGLES); generate_vertices(); generate_faces(); this->surface->generate_normals(); this->surface->generate_tangents(); - this->surface->commit(this->mesh); + callable_mp(Ref(this->mesh).ptr(), &ArrayMesh::clear_surfaces).call_deferred(); + callable_mp(Ref(this->mesh).ptr(), &ArrayMesh::add_surface_from_arrays).call_deferred(Mesh::PRIMITIVE_TRIANGLES, this->surface->commit_to_arrays(), TypedArray(), Dictionary(), 0); } size_t TerrainChunkMesh::points_per_side() const { diff --git a/modules/terrain/terrain_chunk.h b/modules/terrain/terrain_chunk.h index e248da8b..86fa47f1 100644 --- a/modules/terrain/terrain_chunk.h +++ b/modules/terrain/terrain_chunk.h @@ -11,11 +11,15 @@ class TerrainChunkMesh : public MeshInstance3D { void generate_vertices(); void generate_faces(); +protected: + void _notification(int what); + public: void update_mesh(); size_t points_per_side() const; private: + Vector3 safe_position{}; Ref surface{ memnew(SurfaceTool) }; Terrain *terrain{ nullptr }; size_t detail{ 1 }; diff --git a/modules/terrain/terrain_modifier.cpp b/modules/terrain/terrain_modifier.cpp index d3b9ee93..d85ac271 100644 --- a/modules/terrain/terrain_modifier.cpp +++ b/modules/terrain/terrain_modifier.cpp @@ -18,10 +18,12 @@ void TerrainModifier::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { set_notify_transform(true); } + this->thread_safe_global_position = get_global_position(); case NOTIFICATION_TRANSFORM_CHANGED: if (Engine::get_singleton()->is_editor_hint()) { emit_signal(sig_changed); } + this->thread_safe_global_position = get_global_position(); return; } } @@ -58,12 +60,20 @@ void TerrainModifier::changed() { emit_signal(sig_changed); } +void TerrainModifier::changed_deferred() { + callable_mp(this, &self_type::changed).call_deferred(); +} + float TerrainModifier::evaluate_at(Vector2 world_coordinate, float before) { - Vector3 const global_position{ get_global_position() }; + Vector3 const global_position{ get_thread_safe_global_position() }; world_coordinate -= { global_position.x, global_position.z }; return blend(before, 0.0); } +Vector3 TerrainModifier::get_thread_safe_global_position() const { + return this->thread_safe_global_position; +} + void TerrainModifier::set_blend_distance(float value) { this->blend_distance = value; emit_signal(sig_changed); @@ -89,24 +99,40 @@ void TerrainModifierDistance::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, distance_height_curve, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); } +void TerrainModifierDistance::curves_changed() { + this->mtx.lock(); + this->safe_curves_dirty = true; + this->mtx.unlock(); + changed(); +} + float TerrainModifierDistance::distance_at(Vector2 const &world_coordinate) { - Vector3 const global_position{ get_global_position() }; + Vector3 const global_position{ get_thread_safe_global_position() }; return world_coordinate.distance_to({ global_position.x, global_position.z }); } float TerrainModifierDistance::evaluate_at(Vector2 world_coordinate, float before) { - if (this->distance_weight_curve.is_null() || this->distance_height_curve.is_null()) { + this->mtx.lock(); + if (this->safe_curves_dirty) { + this->safe_weight_curve = this->distance_weight_curve->duplicate_deep(); + this->safe_weight_curve->bake(); + this->safe_height_curve = this->distance_height_curve->duplicate_deep(); + this->safe_height_curve->bake(); + this->safe_curves_dirty = false; + } + this->mtx.unlock(); + if (this->safe_weight_curve.is_null() || this->safe_height_curve.is_null()) { return before; } float const distance{ distance_at(world_coordinate) }; - float const height_offset{ - std::clamp(distance, this->distance_height_curve->get_min_domain(), this->distance_height_curve->get_max_domain()) - }; float const weight_offset{ - std::clamp(distance, this->distance_weight_curve->get_min_domain(), this->distance_weight_curve->get_max_domain()) + std::clamp(distance, this->safe_weight_curve->get_min_domain(), this->safe_weight_curve->get_max_domain()) }; - float const weight{ this->distance_weight_curve->sample_baked(weight_offset) }; - return weight <= 0.f ? before : Math::lerp(before, blend(before, this->distance_height_curve->sample_baked(height_offset) + get_global_position().y), weight); + float const weight{ this->safe_weight_curve->sample_baked(weight_offset) }; + float const height_offset{ + std::clamp(distance, this->safe_height_curve->get_min_domain(), this->safe_height_curve->get_max_domain()) + }; + return weight <= 0.f ? before : Math::lerp(before, blend(before, this->safe_height_curve->sample_baked(height_offset) + get_thread_safe_global_position().y), weight); } PackedStringArray TerrainModifierDistance::get_configuration_warnings() const { @@ -123,12 +149,13 @@ PackedStringArray TerrainModifierDistance::get_configuration_warnings() const { void TerrainModifierDistance::set_distance_weight_curve(Ref curve) { if (Engine::get_singleton()->is_editor_hint()) { if (this->distance_weight_curve.is_valid()) { - this->distance_weight_curve->disconnect_changed(callable_mp(cast_to(this), &self_type::changed)); + this->distance_weight_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed)); } if (curve.is_valid()) { - curve->connect_changed(callable_mp(cast_to(this), &self_type::changed)); + curve->connect_changed(callable_mp(this, &self_type::curves_changed)); } } + curves_changed(); this->distance_weight_curve = curve; update_configuration_warnings(); changed(); @@ -141,12 +168,13 @@ Ref TerrainModifierDistance::get_distance_weight_curve() const { void TerrainModifierDistance::set_distance_height_curve(Ref curve) { if (Engine::get_singleton()->is_editor_hint()) { if (this->distance_height_curve.is_valid()) { - this->distance_height_curve->disconnect_changed(callable_mp(cast_to(this), &self_type::changed)); + this->distance_height_curve->disconnect_changed(callable_mp(this, &self_type::curves_changed)); } if (curve.is_valid()) { - curve->connect_changed(callable_mp(cast_to(this), &self_type::changed)); + curve->connect_changed(callable_mp(this, &self_type::curves_changed)); } } + curves_changed(); this->distance_height_curve = curve; update_configuration_warnings(); changed(); diff --git a/modules/terrain/terrain_modifier.h b/modules/terrain/terrain_modifier.h index 8189c124..333f0306 100644 --- a/modules/terrain/terrain_modifier.h +++ b/modules/terrain/terrain_modifier.h @@ -16,6 +16,7 @@ public: protected: void _notification(int what); void changed(); + void changed_deferred(); float blend(float under, float over); public: @@ -24,8 +25,10 @@ public: private: float blend_distance{ 10.0 }; BlendMode blend_mode{ Add }; + Vector3 thread_safe_global_position{}; public: + Vector3 get_thread_safe_global_position() const; void set_blend_distance(float value); float get_blend_distance() const; void set_blend_mode(BlendMode mode); @@ -39,6 +42,7 @@ MAKE_TYPE_INFO(TerrainModifier::BlendMode, Variant::INT); class TerrainModifierDistance : public TerrainModifier { GDCLASS(TerrainModifierDistance, TerrainModifier); static void _bind_methods(); + void curves_changed(); protected: virtual float distance_at(Vector2 const &world_coordinate); @@ -48,9 +52,14 @@ public: PackedStringArray get_configuration_warnings() const override; private: + Mutex mtx{}; Ref distance_weight_curve{}; Ref distance_height_curve{}; + bool safe_curves_dirty{ false }; + Ref safe_weight_curve{}; + Ref safe_height_curve{}; + public: void set_distance_weight_curve(Ref curve); Ref get_distance_weight_curve() const; diff --git a/project/scenes/terrain_test.tscn b/project/scenes/terrain_test.tscn index 03e87e99..808568a5 100644 --- a/project/scenes/terrain_test.tscn +++ b/project/scenes/terrain_test.tscn @@ -31,7 +31,7 @@ point_count = 1 [sub_resource type="Curve" id="Curve_nonsf"] _limits = [0.0, 1.0, 0.0, 200.0] -_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(79.132034, 0.41618794), -0.0118739465, -0.0118739465, 0, 0, Vector2(200, 0), 0.0, 0.0, 0, 0] +_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(91.651596, 0.37567776), -0.0118739465, -0.0118739465, 0, 0, Vector2(200, 0), 0.0, 0.0, 0, 0] point_count = 3 [sub_resource type="Curve" id="Curve_4kj3c"] @@ -47,21 +47,22 @@ point_count = 1 environment = SubResource("Environment_o3i6r") [node name="Terrain" type="Terrain" parent="." unique_id=1169843565] +chunk_size = 25 [node name="TerrainModifierDistance" type="TerrainModifierDistance" parent="Terrain" unique_id=1885116624] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 158.74449, 17.772339, 10.108765) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 155.48662, 58.282597, 65.78725) blend_distance = 4.0 distance_weight_curve = SubResource("Curve_kbmr5") distance_height_curve = SubResource("Curve_w3uoq") [node name="TerrainModifierDistance3" type="TerrainModifierDistance" parent="Terrain" unique_id=1846439541] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 39.480015, 90.075294, 51.646927) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 39.480015, 95.0672, 37.15439) blend_distance = 4.0 distance_weight_curve = SubResource("Curve_chm2y") distance_height_curve = SubResource("Curve_o3i6r") [node name="TerrainModifierDistance2" type="TerrainModifierDistance" parent="Terrain" unique_id=2110821264] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 146.0053, -20.503748, 160.74619) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 183.81352, -21.613277, 195.71535) blend_mode = 1 distance_weight_curve = SubResource("Curve_nonsf") distance_height_curve = SubResource("Curve_4kj3c")