From 2a3eeef52257093f3923207fbe0612714f79bf3e Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 8 Dec 2025 19:46:29 +0100 Subject: [PATCH] feat: lazy loading of terrain chunk LODs --- modules/terrain_editor/terrain_chunk.cpp | 35 ++++++++++++++++-------- modules/terrain_editor/terrain_chunk.h | 12 ++++++-- project/objects/terrain_chunk.tscn | 4 +-- project/scenes/editor.tscn | 25 +++++++++-------- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/modules/terrain_editor/terrain_chunk.cpp b/modules/terrain_editor/terrain_chunk.cpp index 0e3f474b..39cb14b8 100644 --- a/modules/terrain_editor/terrain_chunk.cpp +++ b/modules/terrain_editor/terrain_chunk.cpp @@ -21,11 +21,21 @@ void TerrainChunk::ready() { process_lod(); } +void TerrainChunk::generate_lod(size_t lod) { + MeshStatus status{ this->meshes.get(lod) }; + size_t base_detail{ lod == 0 ? this->lod0_detail : this->lod0_detail / (2 * lod + lod) }; + base_detail = base_detail > 1 ? base_detail : 1; + Vector3 const position{ get_global_position() }; + this->generator->push_task({ { position.x, position.z }, { this->size, this->size } }, status.mesh, base_detail > 1 ? base_detail : 1, callable_mp(this, &self_type::lod_generated).bind(lod)); + status.flag = MESH_DISPATCHED; + this->meshes.set(lod, status); +} + void TerrainChunk::on_terrain_changed() { if (this->generator) { - Vector3 const position{ get_global_position() }; - this->meshes.resize_initialized(3); - size_t lod{ 0 }; + if (this->meshes.size() != this->lod_count) { + this->meshes.resize_initialized(this->lod_count); + } if (this->collisions) { this->collisions->queue_free(); this->collisions = nullptr; @@ -34,28 +44,31 @@ void TerrainChunk::on_terrain_changed() { if (!status.mesh.is_valid()) { status.mesh.instantiate(); } - size_t base_detail{ lod == 0 ? this->lod0_detail : this->lod0_detail / (2 * lod) }; - status.dirty = true; - this->generator->push_task({ { position.x, position.z }, { this->size, this->size } }, status.mesh, base_detail > 1 ? base_detail : 1, callable_mp(this, &self_type::lod_generated).bind(lod)); - lod++; + status.flag = MESH_DIRTY; } + generate_lod(this->meshes.size() - 1); } } void TerrainChunk::lod_generated(size_t lod) { - this->meshes.set(lod, { this->meshes[lod].mesh, false }); + this->meshes.set(lod, { this->meshes[lod].mesh, MESH_LOADED }); } void TerrainChunk::process_lod() { size_t result{ (size_t)this->meshes.size() }; if (is_ready() && this->meshes.size() > 0) { Vector3 position{ get_global_position() }; + position.y = 0; Vector3 camera{ get_viewport()->get_camera_3d()->get_global_position() }; - float distance{ (position - camera).length() - this->size / 2.f }; + camera.y = 0; + float distance{ (position - camera).length() - this->size }; distance = distance > 0.f ? distance : 0.f; - size_t lod{ size_t(Math::floor(distance / (this->lod_end_distance / this->meshes.size()))) }; + size_t lod{ size_t(Math::floor(distance / ((this->max_lod_distance * this->size) / this->meshes.size()))) }; result = lod < this->meshes.size() ? lod : (this->meshes.size() - 1); - while (this->meshes[result].dirty && result < (this->meshes.size() - 1)) { + if (this->meshes[result].flag == MESH_DIRTY) { + generate_lod(result); + } + while (this->meshes[result].flag != MESH_LOADED && result < (this->meshes.size() - 1)) { result++; } if (this->meshes[result].mesh != this->get_mesh()) { diff --git a/modules/terrain_editor/terrain_chunk.h b/modules/terrain_editor/terrain_chunk.h index 064aed92..9f947181 100644 --- a/modules/terrain_editor/terrain_chunk.h +++ b/modules/terrain_editor/terrain_chunk.h @@ -6,11 +6,17 @@ class TerrainMeshGenerator; class TerrainChunk : public MeshInstance3D { GDCLASS(TerrainChunk, MeshInstance3D); + enum MeshStatusFlag { + MESH_DIRTY, + MESH_DISPATCHED, + MESH_LOADED + }; struct MeshStatus { Ref mesh; - bool dirty; + MeshStatusFlag flag; }; static void _bind_methods(); + void generate_lod(size_t lod); void ready(); void on_terrain_changed(); void lod_generated(size_t lod); @@ -27,10 +33,10 @@ public: private: Node *collisions{ nullptr }; - size_t collisions_lod{ 0 }; + size_t lod_count{ 3 }; Vector meshes{}; int lod0_detail{ 200 }; - float lod_end_distance{ 600 }; + float max_lod_distance{ 6 }; float size{ 200 }; TerrainMeshGenerator *generator{ nullptr }; }; diff --git a/project/objects/terrain_chunk.tscn b/project/objects/terrain_chunk.tscn index cfcae928..5b4a6a0b 100644 --- a/project/objects/terrain_chunk.tscn +++ b/project/objects/terrain_chunk.tscn @@ -4,5 +4,5 @@ [node name="TerrainChunk" type="TerrainChunk" unique_id=1453572398] material_override = ExtResource("1_6vjd7") -size = 50.0 -lod0_detail = 100 +size = 40.0 +lod0_detail = 80 diff --git a/project/scenes/editor.tscn b/project/scenes/editor.tscn index 3e10aea7..3f334f9b 100644 --- a/project/scenes/editor.tscn +++ b/project/scenes/editor.tscn @@ -31,6 +31,7 @@ noise = SubResource("FastNoiseLite_b1cmn") noise_amplitude = 5.0 [sub_resource type="FastNoiseLite" id="FastNoiseLite_ba0ut"] +frequency = 0.0163 fractal_type = 2 fractal_gain = 1.0 fractal_weighted_strength = 0.58 @@ -132,7 +133,7 @@ load_path = "res://.godot/imported/point.svg-e68fd7c1e788d2c48d769cc58eba6e98.ct primitives = [SubResource("PointPrimitive_5tm2q"), SubResource("NoisePrimitive_ba0ut"), SubResource("NoisePrimitive_pxqd5"), SubResource("NoisePrimitive_q68jb"), SubResource("PlanePrimitive_ba0ut")] vertex_color_gradient = SubResource("Gradient_b1cmn") color_gradient_end_height = 100.0 -chunk_count = 4 +chunk_count = 8 chunk_scene = ExtResource("1_pxqd5") point_primitive_object = ExtResource("1_b1cmn") @@ -176,17 +177,6 @@ current_tab = 0 layout_mode = 2 metadata/_tab_index = 0 -[node name="Tree" type="PrimitiveLayerList" parent="LeftPanel/VBoxContainer/Layers/Layers" unique_id=797700186 node_paths=PackedStringArray("terrain")] -layout_mode = 2 -size_flags_vertical = 3 -scroll_horizontal_enabled = false -terrain = NodePath("../../../../../TerrainMeshEditor") -icons = { -&"NoisePrimitive": ExtResource("5_eqbpn"), -&"PlanePrimitive": ExtResource("4_xg7d5"), -&"PointPrimitive": ExtResource("4_5lcyj") -} - [node name="HBoxContainer" type="HBoxContainer" parent="LeftPanel/VBoxContainer/Layers/Layers" unique_id=702489990] layout_mode = 2 size_flags_vertical = 8 @@ -222,6 +212,17 @@ expand_icon = true primitive_blueprint = SubResource("NoisePrimitive_5lcyj") terrain = NodePath("../../../../../../TerrainMeshEditor") +[node name="Tree" type="PrimitiveLayerList" parent="LeftPanel/VBoxContainer/Layers/Layers" unique_id=797700186 node_paths=PackedStringArray("terrain")] +layout_mode = 2 +size_flags_vertical = 3 +scroll_horizontal_enabled = false +terrain = NodePath("../../../../../TerrainMeshEditor") +icons = { +&"NoisePrimitive": ExtResource("5_eqbpn"), +&"PlanePrimitive": ExtResource("4_xg7d5"), +&"PointPrimitive": ExtResource("4_5lcyj") +} + [node name="Inspector" type="TabContainer" parent="LeftPanel/VBoxContainer" unique_id=240272030] layout_mode = 2 size_flags_vertical = 3