feat: lazy loading of terrain chunk LODs

This commit is contained in:
Sara Gerretsen 2025-12-08 19:46:29 +01:00
parent 90c46e30d2
commit 2a3eeef522
4 changed files with 48 additions and 28 deletions

View file

@ -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()) {

View file

@ -6,11 +6,17 @@ class TerrainMeshGenerator;
class TerrainChunk : public MeshInstance3D {
GDCLASS(TerrainChunk, MeshInstance3D);
enum MeshStatusFlag {
MESH_DIRTY,
MESH_DISPATCHED,
MESH_LOADED
};
struct MeshStatus {
Ref<ArrayMesh> 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<MeshStatus> meshes{};
int lod0_detail{ 200 };
float lod_end_distance{ 600 };
float max_lod_distance{ 6 };
float size{ 200 };
TerrainMeshGenerator *generator{ nullptr };
};