Separate mesh block classes to remove unnecessary data in each
# Conflicts: # terrain/voxel_lod_terrain.cpp
This commit is contained in:
parent
ee60e0556e
commit
e96f9ff562
45
terrain/free_mesh_task.h
Normal file
45
terrain/free_mesh_task.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef VOXEL_FREE_MESH_TASK_H
|
||||
#define VOXEL_FREE_MESH_TASK_H
|
||||
|
||||
#include "../server/voxel_server.h"
|
||||
#include "../util/godot/direct_mesh_instance.h"
|
||||
#include "../util/tasks/progressive_task_runner.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
// Had to resort to this in Godot4 because deleting meshes is particularly expensive,
|
||||
// because of the Vulkan allocator used by the renderer.
|
||||
// It is a deferred cost, so had to use a different type of task
|
||||
class FreeMeshTask : public zylann::IProgressiveTask {
|
||||
public:
|
||||
static inline void try_add_and_destroy(DirectMeshInstance &mi) {
|
||||
if (mi.get_mesh().is_valid()) {
|
||||
add(mi.get_mesh());
|
||||
}
|
||||
mi.destroy();
|
||||
}
|
||||
|
||||
static void add(Ref<Mesh> mesh) {
|
||||
CRASH_COND(mesh.is_null());
|
||||
FreeMeshTask *task = memnew(FreeMeshTask(mesh));
|
||||
VoxelServer::get_singleton()->push_main_thread_progressive_task(task);
|
||||
}
|
||||
|
||||
FreeMeshTask(Ref<Mesh> p_mesh) : mesh(p_mesh) {}
|
||||
|
||||
void run() override {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (mesh->reference_get_count() > 1) {
|
||||
WARN_PRINT("Mesh has more than one ref left, task spreading will not be effective at smoothing "
|
||||
"destruction cost");
|
||||
}
|
||||
#endif
|
||||
mesh.unref();
|
||||
}
|
||||
|
||||
Ref<Mesh> mesh;
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_FREE_MESH_TASK_H
|
@ -76,7 +76,7 @@ Ref<ArrayMesh> build_mesh(
|
||||
struct BeforeUnloadMeshAction {
|
||||
std::vector<Ref<ShaderMaterial>> &shader_material_pool;
|
||||
|
||||
void operator()(VoxelMeshBlock &block) {
|
||||
void operator()(VoxelMeshBlockVLT &block) {
|
||||
VOXEL_PROFILE_SCOPE_NAMED("Recycle material");
|
||||
// Recycle material
|
||||
Ref<ShaderMaterial> sm = block.get_shader_material();
|
||||
@ -289,7 +289,7 @@ Ref<VoxelGenerator> VoxelLodTerrain::get_generator() const {
|
||||
void VoxelLodTerrain::_on_gi_mode_changed() {
|
||||
const GIMode gi_mode = get_gi_mode();
|
||||
for (unsigned int lod_index = 0; lod_index < _update_data->state.lods.size(); ++lod_index) {
|
||||
_mesh_maps_per_lod[lod_index].for_each_block([gi_mode](VoxelMeshBlock &block) { //
|
||||
_mesh_maps_per_lod[lod_index].for_each_block([gi_mode](VoxelMeshBlockVLT &block) { //
|
||||
block.set_gi_mode(DirectMeshInstance::GIMode(gi_mode));
|
||||
});
|
||||
}
|
||||
@ -402,17 +402,17 @@ void VoxelLodTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
|
||||
// Reset mesh maps
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
VoxelLodTerrainUpdateData::Lod &lod = state.lods[lod_index];
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
if (_instancer != nullptr) {
|
||||
// Unload instances
|
||||
VoxelInstancer *instancer = _instancer;
|
||||
mesh_map.for_each_block([lod_index, instancer](VoxelMeshBlock &block) {
|
||||
mesh_map.for_each_block([lod_index, instancer](VoxelMeshBlockVLT &block) {
|
||||
instancer->on_mesh_block_exit(block.position, lod_index);
|
||||
});
|
||||
}
|
||||
// Unload mesh blocks
|
||||
mesh_map.for_each_block(BeforeUnloadMeshAction{ _shader_material_pool });
|
||||
mesh_map.create(po2, lod_index);
|
||||
mesh_map.clear();
|
||||
// Reset view distance cache so they will be re-entered
|
||||
lod.last_view_distance_mesh_blocks = 0;
|
||||
}
|
||||
@ -456,7 +456,7 @@ bool VoxelLodTerrain::is_threaded_update_enabled() const {
|
||||
return _threaded_update_enabled;
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::set_mesh_block_active(VoxelMeshBlock &block, bool active) {
|
||||
void VoxelLodTerrain::set_mesh_block_active(VoxelMeshBlockVLT &block, bool active) {
|
||||
if (block.active == active) {
|
||||
return;
|
||||
}
|
||||
@ -468,22 +468,22 @@ void VoxelLodTerrain::set_mesh_block_active(VoxelMeshBlock &block, bool active)
|
||||
return;
|
||||
}
|
||||
|
||||
VoxelMeshBlock::FadingState fading_state;
|
||||
VoxelMeshBlockVLT::FadingState fading_state;
|
||||
// Initial progress has to be set too because it sometimes happens that a LOD must appear before its parent
|
||||
// finished fading in. So the parent will have to fade out from solid with the same duration.
|
||||
float initial_progress;
|
||||
if (active) {
|
||||
block.set_visible(true);
|
||||
fading_state = VoxelMeshBlock::FADING_IN;
|
||||
fading_state = VoxelMeshBlockVLT::FADING_IN;
|
||||
initial_progress = 0.f;
|
||||
} else {
|
||||
fading_state = VoxelMeshBlock::FADING_OUT;
|
||||
fading_state = VoxelMeshBlockVLT::FADING_OUT;
|
||||
initial_progress = 1.f;
|
||||
}
|
||||
|
||||
if (block.fading_state != fading_state) {
|
||||
if (block.fading_state == VoxelMeshBlock::FADING_NONE) {
|
||||
Map<Vector3i, VoxelMeshBlock *> &fading_blocks = _fading_blocks_per_lod[block.lod_index];
|
||||
if (block.fading_state == VoxelMeshBlockVLT::FADING_NONE) {
|
||||
Map<Vector3i, VoxelMeshBlockVLT *> &fading_blocks = _fading_blocks_per_lod[block.lod_index];
|
||||
// Must not have duplicates
|
||||
ERR_FAIL_COND(fading_blocks.has(block.position));
|
||||
fading_blocks.insert(block.position, &block);
|
||||
@ -827,11 +827,11 @@ void VoxelLodTerrain::reset_maps() {
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < state.lods.size(); ++lod_index) {
|
||||
VoxelLodTerrainUpdateData::Lod &lod = state.lods[lod_index];
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
|
||||
// Instance new maps if we have more lods, or clear them otherwise
|
||||
if (lod_index < lod_count) {
|
||||
mesh_map.create(settings.mesh_block_size_po2, lod_index);
|
||||
mesh_map.clear();
|
||||
// Reset view distance cache so blocks will be re-entered due to the difference
|
||||
lod.last_view_distance_data_blocks = 0;
|
||||
lod.last_view_distance_mesh_blocks = 0;
|
||||
@ -878,8 +878,8 @@ void VoxelLodTerrain::set_collision_layer(int layer) {
|
||||
|
||||
_collision_layer = layer;
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([layer](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([layer](VoxelMeshBlockVLT &block) { //
|
||||
block.set_collision_layer(layer);
|
||||
});
|
||||
}
|
||||
@ -894,8 +894,8 @@ void VoxelLodTerrain::set_collision_mask(int mask) {
|
||||
|
||||
_collision_mask = mask;
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([mask](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([mask](VoxelMeshBlockVLT &block) { //
|
||||
block.set_collision_mask(mask);
|
||||
});
|
||||
}
|
||||
@ -910,8 +910,8 @@ void VoxelLodTerrain::set_collision_margin(float margin) {
|
||||
|
||||
_collision_margin = margin;
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([margin](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([margin](VoxelMeshBlockVLT &block) { //
|
||||
block.set_collision_margin(margin);
|
||||
});
|
||||
}
|
||||
@ -941,8 +941,7 @@ Vector3i VoxelLodTerrain::voxel_to_mesh_block_position(Vector3 vpos, int lod_ind
|
||||
ERR_FAIL_COND_V(lod_index < 0, Vector3i());
|
||||
ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3i());
|
||||
const unsigned int mesh_block_size_po2 = _update_data->settings.mesh_block_size_po2;
|
||||
const Vector3i bpos =
|
||||
VoxelMeshMap::voxel_to_block_b(Vector3iUtil::from_floored(vpos), mesh_block_size_po2) >> lod_index;
|
||||
const Vector3i bpos = (Vector3iUtil::from_floored(vpos) >> mesh_block_size_po2) >> lod_index;
|
||||
return bpos;
|
||||
}
|
||||
|
||||
@ -983,8 +982,8 @@ void VoxelLodTerrain::_notification(int p_what) {
|
||||
World3D *world = *get_world_3d();
|
||||
VoxelLodTerrainUpdateData::State &state = _update_data->state;
|
||||
for (unsigned int lod_index = 0; lod_index < state.lods.size(); ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([world](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([world](VoxelMeshBlockVLT &block) { //
|
||||
block.set_world(world);
|
||||
});
|
||||
}
|
||||
@ -1000,8 +999,8 @@ void VoxelLodTerrain::_notification(int p_what) {
|
||||
case NOTIFICATION_EXIT_WORLD: {
|
||||
VoxelLodTerrainUpdateData::State &state = _update_data->state;
|
||||
for (unsigned int lod_index = 0; lod_index < state.lods.size(); ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([](VoxelMeshBlockVLT &block) { //
|
||||
block.set_world(nullptr);
|
||||
});
|
||||
}
|
||||
@ -1015,8 +1014,8 @@ void VoxelLodTerrain::_notification(int p_what) {
|
||||
VoxelLodTerrainUpdateData::State &state = _update_data->state;
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < state.lods.size(); ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([visible](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([visible](VoxelMeshBlockVLT &block) { //
|
||||
block.set_parent_visible(visible);
|
||||
});
|
||||
}
|
||||
@ -1043,8 +1042,8 @@ void VoxelLodTerrain::_notification(int p_what) {
|
||||
VoxelLodTerrainUpdateData::State &state = _update_data->state;
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < state.lods.size(); ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([&transform](VoxelMeshBlock &block) { //
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
mesh_map.for_each_block([&transform](VoxelMeshBlockVLT &block) { //
|
||||
block.set_parent_transform(transform);
|
||||
});
|
||||
}
|
||||
@ -1148,11 +1147,11 @@ void VoxelLodTerrain::apply_main_thread_update_tasks() {
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < _update_data->settings.lod_count; ++lod_index) {
|
||||
VoxelLodTerrainUpdateData::Lod &lod = _update_data->state.lods[lod_index];
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
|
||||
for (unsigned int i = 0; i < lod.mesh_blocks_to_activate.size(); ++i) {
|
||||
const Vector3i bpos = lod.mesh_blocks_to_activate[i];
|
||||
VoxelMeshBlock *block = mesh_map.get_block(bpos);
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(bpos);
|
||||
// Can be null if there is actually no surface at this location
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
@ -1163,7 +1162,7 @@ void VoxelLodTerrain::apply_main_thread_update_tasks() {
|
||||
|
||||
for (unsigned int i = 0; i < lod.mesh_blocks_to_deactivate.size(); ++i) {
|
||||
const Vector3i bpos = lod.mesh_blocks_to_deactivate[i];
|
||||
VoxelMeshBlock *block = mesh_map.get_block(bpos);
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(bpos);
|
||||
// Can be null if there is actually no surface at this location
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
@ -1197,7 +1196,7 @@ void VoxelLodTerrain::apply_main_thread_update_tasks() {
|
||||
|
||||
for (unsigned int i = 0; i < lod.mesh_blocks_to_update_transitions.size(); ++i) {
|
||||
const VoxelLodTerrainUpdateData::TransitionUpdate tu = lod.mesh_blocks_to_update_transitions[i];
|
||||
VoxelMeshBlock *block = mesh_map.get_block(tu.block_position);
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(tu.block_position);
|
||||
// Can be null if there is actually no surface at this location
|
||||
if (block == nullptr) {
|
||||
/*
|
||||
@ -1371,8 +1370,8 @@ void VoxelLodTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob)
|
||||
// By that, I mean being able to call into RenderingServer and PhysicsServer,
|
||||
// without inducing a stall of the main thread.
|
||||
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[ob.lod];
|
||||
VoxelMeshBlock *block = mesh_map.get_block(ob.position);
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[ob.lod];
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(ob.position);
|
||||
|
||||
const VoxelMesher::Output &mesh_data = ob.surfaces;
|
||||
|
||||
@ -1392,7 +1391,7 @@ void VoxelLodTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob)
|
||||
}
|
||||
|
||||
if (block == nullptr) {
|
||||
block = VoxelMeshBlock::create(ob.position, get_mesh_block_size(), ob.lod);
|
||||
block = memnew(VoxelMeshBlockVLT(ob.position, get_mesh_block_size(), ob.lod));
|
||||
block->active = active;
|
||||
block->set_visible(active);
|
||||
mesh_map.set_block(ob.position, block);
|
||||
@ -1492,12 +1491,12 @@ void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec)
|
||||
const unsigned int lod_count = _update_data->settings.lod_count;
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
std::vector<Vector3i> &deferred_collision_updates = _deferred_collision_updates_per_lod[lod_index];
|
||||
|
||||
for (unsigned int i = 0; i < deferred_collision_updates.size(); ++i) {
|
||||
const Vector3i block_pos = deferred_collision_updates[i];
|
||||
VoxelMeshBlock *block = mesh_map.get_block(block_pos);
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(block_pos);
|
||||
|
||||
if (block == nullptr || block->has_deferred_collider_update == false) {
|
||||
// Block was unloaded or no longer needs a collision update
|
||||
@ -1550,19 +1549,19 @@ void VoxelLodTerrain::process_fading_blocks(float delta) {
|
||||
const float speed = _lod_fade_duration < 0.001f ? 99999.f : delta / _lod_fade_duration;
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < _fading_blocks_per_lod.size(); ++lod_index) {
|
||||
Map<Vector3i, VoxelMeshBlock *> &fading_blocks = _fading_blocks_per_lod[lod_index];
|
||||
Map<Vector3i, VoxelMeshBlockVLT *> &fading_blocks = _fading_blocks_per_lod[lod_index];
|
||||
|
||||
Map<Vector3i, VoxelMeshBlock *>::Element *e = fading_blocks.front();
|
||||
Map<Vector3i, VoxelMeshBlockVLT *>::Element *e = fading_blocks.front();
|
||||
|
||||
while (e != nullptr) {
|
||||
VoxelMeshBlock *block = e->value();
|
||||
VoxelMeshBlockVLT *block = e->value();
|
||||
// The collection of fading blocks must only contain fading blocks
|
||||
ERR_FAIL_COND(block->fading_state == VoxelMeshBlock::FADING_NONE);
|
||||
ERR_FAIL_COND(block->fading_state == VoxelMeshBlockVLT::FADING_NONE);
|
||||
|
||||
const bool finished = block->update_fading(speed);
|
||||
|
||||
if (finished) {
|
||||
Map<Vector3i, VoxelMeshBlock *>::Element *next = e->next();
|
||||
Map<Vector3i, VoxelMeshBlockVLT *>::Element *next = e->next();
|
||||
fading_blocks.erase(e);
|
||||
e = next;
|
||||
|
||||
@ -1589,11 +1588,11 @@ Array VoxelLodTerrain::get_mesh_block_surface(Vector3i block_pos, int lod_index)
|
||||
const int lod_count = _update_data->settings.lod_count;
|
||||
ERR_FAIL_COND_V(lod_index < 0 || lod_index >= lod_count, Array());
|
||||
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
|
||||
Ref<Mesh> mesh;
|
||||
{
|
||||
const VoxelMeshBlock *block = mesh_map.get_block(block_pos);
|
||||
const VoxelMeshBlockVLT *block = mesh_map.get_block(block_pos);
|
||||
if (block != nullptr) {
|
||||
mesh = block->get_mesh();
|
||||
}
|
||||
@ -1610,9 +1609,9 @@ void VoxelLodTerrain::get_meshed_block_positions_at_lod(int lod_index, std::vect
|
||||
const int lod_count = _update_data->settings.lod_count;
|
||||
ERR_FAIL_COND(lod_index < 0 || lod_index >= lod_count);
|
||||
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
|
||||
mesh_map.for_each_block([&out_positions](const VoxelMeshBlock &block) {
|
||||
mesh_map.for_each_block([&out_positions](const VoxelMeshBlockVLT &block) {
|
||||
if (block.has_mesh()) {
|
||||
out_positions.push_back(block.position);
|
||||
}
|
||||
@ -1788,13 +1787,15 @@ Array VoxelLodTerrain::debug_raycast_mesh_block(Vector3 world_origin, Vector3 wo
|
||||
const float step = 2.f;
|
||||
float distance = 0.f;
|
||||
const unsigned int lod_count = _update_data->settings.lod_count;
|
||||
const unsigned int mesh_block_size_po2 = _update_data->settings.mesh_block_size_po2;
|
||||
|
||||
Array hits;
|
||||
while (distance < max_distance && hits.size() == 0) {
|
||||
const Vector3i posi = Vector3iUtil::from_floored(pos);
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const Vector3i bpos = mesh_map.voxel_to_block(Vector3iUtil::from_floored(pos)) >> lod_index;
|
||||
const VoxelMeshBlock *block = mesh_map.get_block(bpos);
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const Vector3i bpos = (posi << mesh_block_size_po2) >> lod_index;
|
||||
const VoxelMeshBlockVLT *block = mesh_map.get_block(bpos);
|
||||
if (block != nullptr && block->is_visible() && block->has_mesh()) {
|
||||
Dictionary d;
|
||||
d["position"] = block->position;
|
||||
@ -1851,10 +1852,10 @@ Dictionary VoxelLodTerrain::debug_get_mesh_block_info(Vector3 fbpos, int lod_ind
|
||||
bool meshed = false;
|
||||
bool visible = false;
|
||||
bool active = false;
|
||||
int mesh_state = VoxelMeshBlock::MESH_NEVER_UPDATED;
|
||||
int mesh_state = VoxelLodTerrainUpdateData::MESH_NEVER_UPDATED;
|
||||
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshBlock *block = mesh_map.get_block(bpos);
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshBlockVLT *block = mesh_map.get_block(bpos);
|
||||
|
||||
if (block != nullptr) {
|
||||
int recomputed_transition_mask;
|
||||
@ -1862,12 +1863,15 @@ Dictionary VoxelLodTerrain::debug_get_mesh_block_info(Vector3 fbpos, int lod_ind
|
||||
const VoxelLodTerrainUpdateData::Lod &lod = _update_data->state.lods[lod_index];
|
||||
RWLockRead rlock(lod.mesh_map_state.map_lock);
|
||||
recomputed_transition_mask = VoxelLodTerrainUpdateTask::get_transition_mask(
|
||||
_update_data->state, block->position, block->lod_index, lod_count);
|
||||
_update_data->state, bpos, block->lod_index, lod_count);
|
||||
auto it = lod.mesh_map_state.map.find(bpos);
|
||||
if (it != lod.mesh_map_state.map.end()) {
|
||||
mesh_state = it->second.state;
|
||||
}
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
meshed = block->has_mesh();
|
||||
mesh_state = block->get_mesh_state();
|
||||
visible = block->is_visible();
|
||||
active = block->active;
|
||||
d["transition_mask"] = block->get_transition_mask();
|
||||
@ -2112,7 +2116,7 @@ int VoxelLodTerrain::_b_debug_get_mesh_block_count() const {
|
||||
int sum = 0;
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
sum += mesh_map.get_block_count();
|
||||
}
|
||||
return sum;
|
||||
@ -2135,9 +2139,9 @@ Error VoxelLodTerrain::_b_debug_dump_as_scene(String fpath, bool include_instanc
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
const VoxelMeshMap &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
const VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
|
||||
|
||||
mesh_map.for_each_block([root](const VoxelMeshBlock &block) {
|
||||
mesh_map.for_each_block([root](const VoxelMeshBlockVLT &block) {
|
||||
block.for_each_mesh_instance_with_transform([root, &block](const DirectMeshInstance &dmi, Transform3D t) {
|
||||
Ref<Mesh> mesh = dmi.get_mesh();
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "../storage/voxel_data_map.h"
|
||||
#include "lod_octree.h"
|
||||
#include "voxel_lod_terrain_update_data.h"
|
||||
#include "voxel_mesh_block_vlt.h"
|
||||
#include "voxel_mesh_map.h"
|
||||
#include "voxel_node.h"
|
||||
|
||||
@ -262,7 +263,7 @@ private:
|
||||
|
||||
Vector3 get_local_viewer_pos() const;
|
||||
void _set_lod_count(int p_lod_count);
|
||||
void set_mesh_block_active(VoxelMeshBlock &block, bool active);
|
||||
void set_mesh_block_active(VoxelMeshBlockVLT &block, bool active);
|
||||
|
||||
void _on_stream_params_changed();
|
||||
|
||||
@ -296,7 +297,7 @@ private:
|
||||
Ref<Material> _material;
|
||||
std::vector<Ref<ShaderMaterial>> _shader_material_pool;
|
||||
|
||||
FixedArray<VoxelMeshMap, constants::MAX_LOD> _mesh_maps_per_lod;
|
||||
FixedArray<VoxelMeshMap<VoxelMeshBlockVLT>, constants::MAX_LOD> _mesh_maps_per_lod;
|
||||
|
||||
bool _generate_collisions = true;
|
||||
unsigned int _collision_lod_count = 0;
|
||||
@ -310,7 +311,7 @@ private:
|
||||
// Note, direct pointers to mesh blocks should be safe because these blocks are always destroyed from the same
|
||||
// thread that updates fading blocks. If a mesh block is destroyed, these maps should be updated at the same time.
|
||||
// TODO Optimization: use FlatMap? Need to check how many blocks get in there, probably not many
|
||||
FixedArray<Map<Vector3i, VoxelMeshBlock *>, constants::MAX_LOD> _fading_blocks_per_lod;
|
||||
FixedArray<Map<Vector3i, VoxelMeshBlockVLT *>, constants::MAX_LOD> _fading_blocks_per_lod;
|
||||
|
||||
VoxelInstancer *_instancer = nullptr;
|
||||
|
||||
|
@ -816,7 +816,6 @@ static void process_octrees_fitting(VoxelLodTerrainUpdateData::State &state,
|
||||
const VoxelLodTerrainUpdateData::Settings &settings;
|
||||
VoxelDataLodMap &data;
|
||||
std::vector<VoxelLodTerrainUpdateData::BlockLocation> &data_blocks_to_load;
|
||||
//std::vector<VoxelMeshBlock *> &mesh_blocks_to_add;
|
||||
Vector3i block_offset_lod0;
|
||||
unsigned int blocked_count = 0;
|
||||
float lod_distance_octree_space;
|
||||
|
@ -1,79 +1,21 @@
|
||||
#include "voxel_mesh_block.h"
|
||||
#include "../constants/voxel_string_names.h"
|
||||
#include "../server/voxel_server.h"
|
||||
#include "../util/godot/funcs.h"
|
||||
#include "../util/macros.h"
|
||||
#include "../util/profiling.h"
|
||||
#include "free_mesh_task.h"
|
||||
|
||||
#include <scene/3d/node_3d.h>
|
||||
#include <scene/resources/concave_polygon_shape_3d.h>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
VoxelMeshBlock *VoxelMeshBlock::create(Vector3i bpos, unsigned int size, unsigned int p_lod_index) {
|
||||
VoxelMeshBlock *block = memnew(VoxelMeshBlock);
|
||||
block->position = bpos;
|
||||
block->lod_index = p_lod_index;
|
||||
block->_position_in_voxels = bpos * (size << p_lod_index);
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<SpatialMaterial> debug_material;
|
||||
debug_material.instance();
|
||||
int checker = (bpos.x + bpos.y + bpos.z) & 1;
|
||||
Color debug_color =
|
||||
Color(0.8, 0.4, 0.8).linear_interpolate(Color(0.0, 0.0, 0.5), static_cast<float>(p_lod_index) / 8.f);
|
||||
debug_color = debug_color.lightened(checker * 0.1f);
|
||||
debug_material->set_albedo(debug_color);
|
||||
block->_debug_material = debug_material;
|
||||
|
||||
Ref<SpatialMaterial> debug_transition_material;
|
||||
debug_transition_material.instance();
|
||||
debug_transition_material->set_albedo(Color(1, 1, 0));
|
||||
block->_debug_transition_material = debug_transition_material;
|
||||
#endif
|
||||
|
||||
return block;
|
||||
VoxelMeshBlock::VoxelMeshBlock(Vector3i bpos) {
|
||||
position = bpos;
|
||||
}
|
||||
|
||||
VoxelMeshBlock::VoxelMeshBlock() {}
|
||||
|
||||
VoxelMeshBlock::~VoxelMeshBlock() {
|
||||
// Had to resort to this in Godot4 because deleting meshes is particularly expensive,
|
||||
// because of the Vulkan allocator used by the renderer.
|
||||
// It is a deferred cost, so had to use a different type of task
|
||||
class FreeMeshTask : public zylann::IProgressiveTask {
|
||||
public:
|
||||
static inline void try_add_and_destroy(DirectMeshInstance &mi) {
|
||||
if (mi.get_mesh().is_valid()) {
|
||||
add(mi.get_mesh());
|
||||
}
|
||||
mi.destroy();
|
||||
}
|
||||
|
||||
static void add(Ref<Mesh> mesh) {
|
||||
CRASH_COND(mesh.is_null());
|
||||
FreeMeshTask *task = memnew(FreeMeshTask());
|
||||
task->mesh = mesh;
|
||||
VoxelServer::get_singleton()->push_main_thread_progressive_task(task);
|
||||
}
|
||||
|
||||
void run() override {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (mesh->reference_get_count() > 1) {
|
||||
WARN_PRINT("Mesh has more than one ref left, task spreading will not be effective at smoothing "
|
||||
"destruction cost");
|
||||
}
|
||||
#endif
|
||||
mesh.unref();
|
||||
}
|
||||
|
||||
Ref<Mesh> mesh;
|
||||
};
|
||||
|
||||
FreeMeshTask::try_add_and_destroy(_mesh_instance);
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
FreeMeshTask::try_add_and_destroy(_transition_mesh_instances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_world(Ref<World3D> p_world) {
|
||||
@ -93,12 +35,6 @@ void VoxelMeshBlock::set_gi_mode(DirectMeshInstance::GIMode mode) {
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_gi_mode(mode);
|
||||
}
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_gi_mode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_mesh(Ref<Mesh> mesh, DirectMeshInstance::GIMode gi_mode) {
|
||||
@ -139,34 +75,6 @@ Ref<Mesh> VoxelMeshBlock::get_mesh() const {
|
||||
return Ref<Mesh>();
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_transition_mesh(Ref<Mesh> mesh, int side, DirectMeshInstance::GIMode gi_mode) {
|
||||
DirectMeshInstance &mesh_instance = _transition_mesh_instances[side];
|
||||
|
||||
if (mesh.is_valid()) {
|
||||
if (!mesh_instance.is_valid()) {
|
||||
// Create instance if it doesn't exist
|
||||
mesh_instance.create();
|
||||
mesh_instance.set_gi_mode(gi_mode);
|
||||
set_mesh_instance_visible(mesh_instance, _visible && _parent_visible && _is_transition_visible(side));
|
||||
}
|
||||
|
||||
mesh_instance.set_mesh(mesh);
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
mesh_instance.set_material_override(_shader_material);
|
||||
}
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
mesh_instance.set_material_override(_debug_transition_material);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
if (mesh_instance.is_valid()) {
|
||||
// Delete instance if it exists
|
||||
mesh_instance.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelMeshBlock::has_mesh() const {
|
||||
return _mesh_instance.get_mesh().is_valid();
|
||||
}
|
||||
@ -177,14 +85,6 @@ void VoxelMeshBlock::drop_mesh() {
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_mesh_state(MeshState ms) {
|
||||
_mesh_state = ms;
|
||||
}
|
||||
|
||||
VoxelMeshBlock::MeshState VoxelMeshBlock::get_mesh_state() const {
|
||||
return _mesh_state;
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_visible(bool visible) {
|
||||
if (_visible == visible) {
|
||||
return;
|
||||
@ -201,12 +101,6 @@ void VoxelMeshBlock::_set_visible(bool visible) {
|
||||
if (_mesh_instance.is_valid()) {
|
||||
set_mesh_instance_visible(_mesh_instance, visible);
|
||||
}
|
||||
for (unsigned int dir = 0; dir < _transition_mesh_instances.size(); ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if (mi.is_valid()) {
|
||||
set_mesh_instance_visible(mi, visible && _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
if (_static_body.is_valid()) {
|
||||
_static_body.set_shape_enabled(0, visible);
|
||||
}
|
||||
@ -217,13 +111,6 @@ void VoxelMeshBlock::set_shader_material(Ref<ShaderMaterial> material) {
|
||||
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_material_override(_shader_material);
|
||||
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_material_override(_shader_material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
@ -232,49 +119,6 @@ void VoxelMeshBlock::set_shader_material(Ref<ShaderMaterial> material) {
|
||||
}
|
||||
}
|
||||
|
||||
//void VoxelMeshBlock::set_transition_bit(uint8_t side, bool value) {
|
||||
// CRASH_COND(side >= Cube::SIDE_COUNT);
|
||||
// uint32_t m = _transition_mask;
|
||||
// if (value) {
|
||||
// m |= (1 << side);
|
||||
// } else {
|
||||
// m &= ~(1 << side);
|
||||
// }
|
||||
// set_transition_mask(m);
|
||||
//}
|
||||
|
||||
void VoxelMeshBlock::set_transition_mask(uint8_t m) {
|
||||
CRASH_COND(m >= (1 << Cube::SIDE_COUNT));
|
||||
const uint8_t diff = _transition_mask ^ m;
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
_transition_mask = m;
|
||||
if (_shader_material.is_valid()) {
|
||||
// TODO Needs translation here, because Cube:: tables use slightly different order...
|
||||
// We may get rid of this once cube tables respects -x+x-y+y-z+z order
|
||||
uint8_t bits[Cube::SIDE_COUNT];
|
||||
for (unsigned int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
bits[dir] = (m >> dir) & 1;
|
||||
}
|
||||
uint8_t tm = bits[Cube::SIDE_NEGATIVE_X];
|
||||
tm |= bits[Cube::SIDE_POSITIVE_X] << 1;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Y] << 2;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Y] << 3;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Z] << 4;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Z] << 5;
|
||||
|
||||
// TODO Godot 4: we may replace this with a per-instance parameter so we can lift material access limitation
|
||||
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_transition_mask, tm);
|
||||
}
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if ((diff & (1 << dir)) && mi.is_valid()) {
|
||||
set_mesh_instance_visible(mi, _visible && _parent_visible && _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlock::set_parent_visible(bool parent_visible) {
|
||||
if (_parent_visible && parent_visible) {
|
||||
return;
|
||||
@ -292,13 +136,6 @@ void VoxelMeshBlock::set_parent_transform(const Transform3D &parent_transform) {
|
||||
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_transform(world_transform);
|
||||
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_transform(world_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_static_body.is_valid()) {
|
||||
@ -367,56 +204,4 @@ void VoxelMeshBlock::drop_collision() {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `true` when finished
|
||||
bool VoxelMeshBlock::update_fading(float speed) {
|
||||
// TODO Should probably not be on the block directly?
|
||||
// Because we may want to fade transition meshes only
|
||||
|
||||
bool finished = false;
|
||||
|
||||
// x is progress in 0..1
|
||||
// y is direction: 1 fades in, 0 fades out
|
||||
Vector2 p;
|
||||
|
||||
switch (fading_state) {
|
||||
case FADING_IN:
|
||||
fading_progress += speed;
|
||||
if (fading_progress >= 1.f) {
|
||||
fading_progress = 1.f;
|
||||
fading_state = FADING_NONE;
|
||||
finished = true;
|
||||
}
|
||||
p.x = fading_progress;
|
||||
p.y = 1.f;
|
||||
break;
|
||||
|
||||
case FADING_OUT:
|
||||
fading_progress -= speed;
|
||||
if (fading_progress < 0.f) {
|
||||
fading_progress = 0.f;
|
||||
fading_state = FADING_NONE;
|
||||
finished = true;
|
||||
set_visible(false);
|
||||
}
|
||||
p.x = 1.f - fading_progress;
|
||||
p.y = 0.f;
|
||||
break;
|
||||
|
||||
case FADING_NONE:
|
||||
p.x = 1.f;
|
||||
p.y = active ? 1.f : 0.f;
|
||||
break;
|
||||
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_lod_fade, p);
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
@ -16,45 +16,16 @@ namespace zylann::voxel {
|
||||
|
||||
// Stores mesh and collider for one chunk of the rendered volume.
|
||||
// It doesn't store voxel data, because it may be using different block size, or different data structure.
|
||||
// IMPORTANT: This is not an abstract class. It exists to share common code between variants of it.
|
||||
// Only explicit instances are used, no virtuals.
|
||||
class VoxelMeshBlock : public NonCopyable {
|
||||
public:
|
||||
// TODO This is now only relevant for `VoxelTerrain`
|
||||
enum MeshState {
|
||||
MESH_NEVER_UPDATED = 0, // TODO Redundant with MESH_NEED_UPDATE?
|
||||
MESH_UP_TO_DATE,
|
||||
MESH_NEED_UPDATE, // The mesh is out of date but was not yet scheduled for update
|
||||
MESH_UPDATE_NOT_SENT, // The mesh is out of date and was scheduled for update, but no request have been sent yet
|
||||
MESH_UPDATE_SENT // The mesh is out of date, and an update request was sent, pending response
|
||||
};
|
||||
|
||||
enum FadingState { //
|
||||
FADING_NONE,
|
||||
FADING_IN,
|
||||
FADING_OUT
|
||||
};
|
||||
|
||||
Vector3i position; // In blocks
|
||||
uint8_t lod_index = 0;
|
||||
RefCount mesh_viewers;
|
||||
RefCount collision_viewers;
|
||||
bool got_first_mesh_update = false;
|
||||
|
||||
uint32_t last_collider_update_time = 0;
|
||||
bool has_deferred_collider_update = false;
|
||||
std::vector<Array> deferred_collider_data;
|
||||
|
||||
FadingState fading_state = FADING_NONE;
|
||||
float fading_progress = 0.f;
|
||||
// Voxel LOD works by splitting a block into up to 8 higher-resolution blocks.
|
||||
// The parent block and its children can be called a "LOD group".
|
||||
// Only non-overlapping blocks in a LOD group can be considered active at once.
|
||||
// So when LOD fading is used, we no longer use `visible` to find which block is active,
|
||||
// because blocks can use a cross-fade effect. Overlapping blocks of the same LOD group can be visible at once.
|
||||
// Hence the need to use this boolean.
|
||||
bool active = false;
|
||||
|
||||
static VoxelMeshBlock *create(Vector3i bpos, unsigned int size, unsigned int p_lod_index);
|
||||
protected:
|
||||
VoxelMeshBlock(Vector3i bpos);
|
||||
|
||||
public:
|
||||
~VoxelMeshBlock();
|
||||
|
||||
void set_world(Ref<World3D> p_world);
|
||||
@ -63,7 +34,6 @@ public:
|
||||
|
||||
void set_mesh(Ref<Mesh> mesh, DirectMeshInstance::GIMode gi_mode);
|
||||
Ref<Mesh> get_mesh() const;
|
||||
void set_transition_mesh(Ref<Mesh> mesh, int side, DirectMeshInstance::GIMode gi_mode);
|
||||
bool has_mesh() const;
|
||||
void drop_mesh();
|
||||
|
||||
@ -87,43 +57,14 @@ public:
|
||||
|
||||
// State
|
||||
|
||||
void set_mesh_state(MeshState ms);
|
||||
MeshState get_mesh_state() const;
|
||||
|
||||
void set_visible(bool visible);
|
||||
bool is_visible() const;
|
||||
|
||||
void set_parent_visible(bool parent_visible);
|
||||
void set_parent_transform(const Transform3D &parent_transform);
|
||||
|
||||
void set_transition_mask(uint8_t m);
|
||||
//void set_transition_bit(uint8_t side, bool value);
|
||||
inline uint8_t get_transition_mask() const {
|
||||
return _transition_mask;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void for_each_mesh_instance_with_transform(F f) const {
|
||||
const Transform3D local_transform(Basis(), _position_in_voxels);
|
||||
const Transform3D world_transform = local_transform;
|
||||
f(_mesh_instance, world_transform);
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
const DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
f(mi, world_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool update_fading(float speed);
|
||||
|
||||
private:
|
||||
VoxelMeshBlock();
|
||||
|
||||
protected:
|
||||
void _set_visible(bool visible);
|
||||
inline bool _is_transition_visible(int side) const {
|
||||
return _transition_mask & (1 << side);
|
||||
}
|
||||
|
||||
inline void set_mesh_instance_visible(DirectMeshInstance &mi, bool visible) {
|
||||
if (visible) {
|
||||
@ -133,26 +74,17 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Vector3i _position_in_voxels;
|
||||
|
||||
Ref<ShaderMaterial> _shader_material;
|
||||
DirectMeshInstance _mesh_instance;
|
||||
FixedArray<DirectMeshInstance, Cube::SIDE_COUNT> _transition_mesh_instances;
|
||||
DirectStaticBody _static_body;
|
||||
Ref<World3D> _world;
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<Material> _debug_material;
|
||||
Ref<Material> _debug_transition_material;
|
||||
#endif
|
||||
|
||||
// Must match default value of `active`
|
||||
bool _visible = false;
|
||||
|
||||
bool _parent_visible = true;
|
||||
MeshState _mesh_state = MESH_NEVER_UPDATED;
|
||||
uint8_t _transition_mask = 0;
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
239
terrain/voxel_mesh_block_vlt.cpp
Normal file
239
terrain/voxel_mesh_block_vlt.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
#include "voxel_mesh_block_vlt.h"
|
||||
#include "../constants/voxel_string_names.h"
|
||||
#include "../util/profiling.h"
|
||||
#include "free_mesh_task.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
VoxelMeshBlockVLT::VoxelMeshBlockVLT(const Vector3i bpos, unsigned int size, unsigned int p_lod_index) :
|
||||
VoxelMeshBlock(bpos) {
|
||||
_position_in_voxels = bpos * (size << p_lod_index);
|
||||
lod_index = p_lod_index;
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<SpatialMaterial> debug_material;
|
||||
debug_material.instance();
|
||||
int checker = (bpos.x + bpos.y + bpos.z) & 1;
|
||||
Color debug_color =
|
||||
Color(0.8, 0.4, 0.8).linear_interpolate(Color(0.0, 0.0, 0.5), static_cast<float>(p_lod_index) / 8.f);
|
||||
debug_color = debug_color.lightened(checker * 0.1f);
|
||||
debug_material->set_albedo(debug_color);
|
||||
block->_debug_material = debug_material;
|
||||
|
||||
Ref<SpatialMaterial> debug_transition_material;
|
||||
debug_transition_material.instance();
|
||||
debug_transition_material->set_albedo(Color(1, 1, 0));
|
||||
block->_debug_transition_material = debug_transition_material;
|
||||
#endif
|
||||
}
|
||||
|
||||
VoxelMeshBlockVLT::~VoxelMeshBlockVLT() {
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
FreeMeshTask::try_add_and_destroy(_transition_mesh_instances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_gi_mode(DirectMeshInstance::GIMode mode) {
|
||||
VoxelMeshBlock::set_gi_mode(mode);
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_gi_mode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_transition_mesh(Ref<Mesh> mesh, int side, DirectMeshInstance::GIMode gi_mode) {
|
||||
DirectMeshInstance &mesh_instance = _transition_mesh_instances[side];
|
||||
|
||||
if (mesh.is_valid()) {
|
||||
if (!mesh_instance.is_valid()) {
|
||||
// Create instance if it doesn't exist
|
||||
mesh_instance.create();
|
||||
mesh_instance.set_gi_mode(gi_mode);
|
||||
set_mesh_instance_visible(mesh_instance, _visible && _parent_visible && _is_transition_visible(side));
|
||||
}
|
||||
|
||||
mesh_instance.set_mesh(mesh);
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
mesh_instance.set_material_override(_shader_material);
|
||||
}
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
mesh_instance.set_material_override(_debug_transition_material);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
if (mesh_instance.is_valid()) {
|
||||
// Delete instance if it exists
|
||||
mesh_instance.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_visible(bool visible) {
|
||||
if (_visible == visible) {
|
||||
return;
|
||||
}
|
||||
_visible = visible;
|
||||
_set_visible(_visible && _parent_visible);
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::_set_visible(bool visible) {
|
||||
VoxelMeshBlock::_set_visible(visible);
|
||||
for (unsigned int dir = 0; dir < _transition_mesh_instances.size(); ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if (mi.is_valid()) {
|
||||
set_mesh_instance_visible(mi, visible && _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_shader_material(Ref<ShaderMaterial> material) {
|
||||
_shader_material = material;
|
||||
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_material_override(_shader_material);
|
||||
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_material_override(_shader_material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
const Transform3D local_transform(Basis(), _position_in_voxels);
|
||||
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_block_local_transform, local_transform);
|
||||
}
|
||||
}
|
||||
|
||||
//void VoxelMeshBlock::set_transition_bit(uint8_t side, bool value) {
|
||||
// CRASH_COND(side >= Cube::SIDE_COUNT);
|
||||
// uint32_t m = _transition_mask;
|
||||
// if (value) {
|
||||
// m |= (1 << side);
|
||||
// } else {
|
||||
// m &= ~(1 << side);
|
||||
// }
|
||||
// set_transition_mask(m);
|
||||
//}
|
||||
|
||||
void VoxelMeshBlockVLT::set_transition_mask(uint8_t m) {
|
||||
CRASH_COND(m >= (1 << Cube::SIDE_COUNT));
|
||||
const uint8_t diff = _transition_mask ^ m;
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
_transition_mask = m;
|
||||
if (_shader_material.is_valid()) {
|
||||
// TODO Needs translation here, because Cube:: tables use slightly different order...
|
||||
// We may get rid of this once cube tables respects -x+x-y+y-z+z order
|
||||
uint8_t bits[Cube::SIDE_COUNT];
|
||||
for (unsigned int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
bits[dir] = (m >> dir) & 1;
|
||||
}
|
||||
uint8_t tm = bits[Cube::SIDE_NEGATIVE_X];
|
||||
tm |= bits[Cube::SIDE_POSITIVE_X] << 1;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Y] << 2;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Y] << 3;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Z] << 4;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Z] << 5;
|
||||
|
||||
// TODO Godot 4: we may replace this with a per-instance parameter so we can lift material access limitation
|
||||
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_transition_mask, tm);
|
||||
}
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if ((diff & (1 << dir)) && mi.is_valid()) {
|
||||
set_mesh_instance_visible(mi, _visible && _parent_visible && _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_parent_visible(bool parent_visible) {
|
||||
if (_parent_visible && parent_visible) {
|
||||
return;
|
||||
}
|
||||
_parent_visible = parent_visible;
|
||||
_set_visible(_visible && _parent_visible);
|
||||
}
|
||||
|
||||
void VoxelMeshBlockVLT::set_parent_transform(const Transform3D &parent_transform) {
|
||||
VOXEL_PROFILE_SCOPE();
|
||||
|
||||
if (_mesh_instance.is_valid() || _static_body.is_valid()) {
|
||||
const Transform3D local_transform(Basis(), _position_in_voxels);
|
||||
const Transform3D world_transform = parent_transform * local_transform;
|
||||
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_transform(world_transform);
|
||||
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
mi.set_transform(world_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_static_body.is_valid()) {
|
||||
_static_body.set_transform(world_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `true` when finished
|
||||
bool VoxelMeshBlockVLT::update_fading(float speed) {
|
||||
// TODO Should probably not be on the block directly?
|
||||
// Because we may want to fade transition meshes only
|
||||
|
||||
bool finished = false;
|
||||
|
||||
// x is progress in 0..1
|
||||
// y is direction: 1 fades in, 0 fades out
|
||||
Vector2 p;
|
||||
|
||||
switch (fading_state) {
|
||||
case FADING_IN:
|
||||
fading_progress += speed;
|
||||
if (fading_progress >= 1.f) {
|
||||
fading_progress = 1.f;
|
||||
fading_state = FADING_NONE;
|
||||
finished = true;
|
||||
}
|
||||
p.x = fading_progress;
|
||||
p.y = 1.f;
|
||||
break;
|
||||
|
||||
case FADING_OUT:
|
||||
fading_progress -= speed;
|
||||
if (fading_progress < 0.f) {
|
||||
fading_progress = 0.f;
|
||||
fading_state = FADING_NONE;
|
||||
finished = true;
|
||||
set_visible(false);
|
||||
}
|
||||
p.x = 1.f - fading_progress;
|
||||
p.y = 0.f;
|
||||
break;
|
||||
|
||||
case FADING_NONE:
|
||||
p.x = 1.f;
|
||||
p.y = active ? 1.f : 0.f;
|
||||
break;
|
||||
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_lod_fade, p);
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
86
terrain/voxel_mesh_block_vlt.h
Normal file
86
terrain/voxel_mesh_block_vlt.h
Normal file
@ -0,0 +1,86 @@
|
||||
#ifndef VOXEL_MESH_BLOCK_VLT_H
|
||||
#define VOXEL_MESH_BLOCK_VLT_H
|
||||
|
||||
#include "voxel_mesh_block.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
// Stores mesh and collider for one chunk of `VoxelTerrain`.
|
||||
// It doesn't store voxel data, because it may be using different block size, or different data structure.
|
||||
class VoxelMeshBlockVLT : public VoxelMeshBlock {
|
||||
public:
|
||||
enum FadingState { //
|
||||
FADING_NONE,
|
||||
FADING_IN,
|
||||
FADING_OUT
|
||||
};
|
||||
|
||||
uint8_t lod_index = 0;
|
||||
|
||||
FadingState fading_state = FADING_NONE;
|
||||
float fading_progress = 0.f;
|
||||
// Voxel LOD works by splitting a block into up to 8 higher-resolution blocks.
|
||||
// The parent block and its children can be called a "LOD group".
|
||||
// Only non-overlapping blocks in a LOD group can be considered active at once.
|
||||
// So when LOD fading is used, we no longer use `visible` to find which block is active,
|
||||
// because blocks can use a cross-fade effect. Overlapping blocks of the same LOD group can be visible at once.
|
||||
// Hence the need to use this boolean.
|
||||
bool active = false;
|
||||
|
||||
bool got_first_mesh_update = false;
|
||||
|
||||
uint32_t last_collider_update_time = 0;
|
||||
bool has_deferred_collider_update = false;
|
||||
std::vector<Array> deferred_collider_data;
|
||||
|
||||
VoxelMeshBlockVLT(const Vector3i bpos, unsigned int size, unsigned int p_lod_index);
|
||||
~VoxelMeshBlockVLT();
|
||||
|
||||
void set_visible(bool visible);
|
||||
bool update_fading(float speed);
|
||||
|
||||
void set_parent_visible(bool parent_visible);
|
||||
|
||||
void set_transition_mask(uint8_t m);
|
||||
inline uint8_t get_transition_mask() const {
|
||||
return _transition_mask;
|
||||
}
|
||||
|
||||
void set_gi_mode(DirectMeshInstance::GIMode mode);
|
||||
void set_transition_mesh(Ref<Mesh> mesh, int side, DirectMeshInstance::GIMode gi_mode);
|
||||
void set_shader_material(Ref<ShaderMaterial> material);
|
||||
void set_parent_transform(const Transform3D &parent_transform);
|
||||
|
||||
template <typename F>
|
||||
void for_each_mesh_instance_with_transform(F f) const {
|
||||
const Transform3D local_transform(Basis(), _position_in_voxels);
|
||||
const Transform3D world_transform = local_transform;
|
||||
f(_mesh_instance, world_transform);
|
||||
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
const DirectMeshInstance &mi = _transition_mesh_instances[i];
|
||||
if (mi.is_valid()) {
|
||||
f(mi, world_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void _set_visible(bool visible);
|
||||
|
||||
inline bool _is_transition_visible(int side) const {
|
||||
return _transition_mask & (1 << side);
|
||||
}
|
||||
|
||||
FixedArray<DirectMeshInstance, Cube::SIDE_COUNT> _transition_mesh_instances;
|
||||
|
||||
uint8_t _transition_mask = 0;
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<Material> _debug_material;
|
||||
Ref<Material> _debug_transition_material;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_MESH_BLOCK_VLT_H
|
42
terrain/voxel_mesh_block_vt.h
Normal file
42
terrain/voxel_mesh_block_vt.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef VOXEL_MESH_BLOCK_VT_H
|
||||
#define VOXEL_MESH_BLOCK_VT_H
|
||||
|
||||
#include "voxel_mesh_block.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
// Stores mesh and collider for one chunk of `VoxelLodTerrain`.
|
||||
// It doesn't store voxel data, because it may be using different block size, or different data structure.
|
||||
class VoxelMeshBlockVT : public VoxelMeshBlock {
|
||||
public:
|
||||
// TODO This is now only relevant for `VoxelTerrain`
|
||||
enum MeshState {
|
||||
MESH_NEVER_UPDATED = 0, // TODO Redundant with MESH_NEED_UPDATE?
|
||||
MESH_UP_TO_DATE,
|
||||
MESH_NEED_UPDATE, // The mesh is out of date but was not yet scheduled for update
|
||||
MESH_UPDATE_NOT_SENT, // The mesh is out of date and was scheduled for update, but no request have been sent yet
|
||||
MESH_UPDATE_SENT // The mesh is out of date, and an update request was sent, pending response
|
||||
};
|
||||
|
||||
RefCount mesh_viewers;
|
||||
RefCount collision_viewers;
|
||||
|
||||
VoxelMeshBlockVT(const Vector3i bpos, unsigned int size) : VoxelMeshBlock(bpos) {
|
||||
_position_in_voxels = bpos * size;
|
||||
}
|
||||
|
||||
void set_mesh_state(MeshState ms) {
|
||||
_mesh_state = ms;
|
||||
}
|
||||
|
||||
MeshState get_mesh_state() const {
|
||||
return _mesh_state;
|
||||
}
|
||||
|
||||
private:
|
||||
MeshState _mesh_state = MESH_NEVER_UPDATED;
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_MESH_BLOCK_VT_H
|
@ -1,171 +0,0 @@
|
||||
#include "voxel_mesh_map.h"
|
||||
#include "../constants/cube_tables.h"
|
||||
#include "../constants/voxel_constants.h"
|
||||
#include "../server/voxel_server.h"
|
||||
#include "../util/macros.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
VoxelMeshMap::VoxelMeshMap() : _last_accessed_block(nullptr) {
|
||||
// TODO Make it configurable in editor (with all necessary notifications and updatings!)
|
||||
set_block_size_pow2(constants::DEFAULT_BLOCK_SIZE_PO2);
|
||||
}
|
||||
|
||||
VoxelMeshMap::~VoxelMeshMap() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void VoxelMeshMap::create(unsigned int block_size_po2, int lod_index) {
|
||||
clear();
|
||||
set_block_size_pow2(block_size_po2);
|
||||
set_lod_index(lod_index);
|
||||
}
|
||||
|
||||
void VoxelMeshMap::set_block_size_pow2(unsigned int p) {
|
||||
ERR_FAIL_COND_MSG(p < 1, "Block size is too small");
|
||||
ERR_FAIL_COND_MSG(p > 8, "Block size is too big");
|
||||
|
||||
_block_size_pow2 = p;
|
||||
_block_size = 1 << _block_size_pow2;
|
||||
_block_size_mask = _block_size - 1;
|
||||
}
|
||||
|
||||
void VoxelMeshMap::set_lod_index(int lod_index) {
|
||||
ERR_FAIL_COND_MSG(lod_index < 0, "LOD index can't be negative");
|
||||
ERR_FAIL_COND_MSG(lod_index >= 32, "LOD index is too big");
|
||||
|
||||
_lod_index = lod_index;
|
||||
}
|
||||
|
||||
unsigned int VoxelMeshMap::get_lod_index() const {
|
||||
return _lod_index;
|
||||
}
|
||||
|
||||
VoxelMeshBlock *VoxelMeshMap::get_block(Vector3i bpos) {
|
||||
if (_last_accessed_block && _last_accessed_block->position == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
unsigned int *iptr = _blocks_map.getptr(bpos);
|
||||
if (iptr != nullptr) {
|
||||
const unsigned int i = *iptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(i >= _blocks.size());
|
||||
#endif
|
||||
VoxelMeshBlock *block = _blocks[i];
|
||||
CRASH_COND(block == nullptr); // The map should not contain null blocks
|
||||
_last_accessed_block = block;
|
||||
return _last_accessed_block;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const VoxelMeshBlock *VoxelMeshMap::get_block(Vector3i bpos) const {
|
||||
if (_last_accessed_block != nullptr && _last_accessed_block->position == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
const unsigned int *iptr = _blocks_map.getptr(bpos);
|
||||
if (iptr != nullptr) {
|
||||
const unsigned int i = *iptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(i >= _blocks.size());
|
||||
#endif
|
||||
// TODO This function can't cache _last_accessed_block, because it's const, so repeated accesses are hashing
|
||||
// again...
|
||||
const VoxelMeshBlock *block = _blocks[i];
|
||||
CRASH_COND(block == nullptr); // The map should not contain null blocks
|
||||
return block;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VoxelMeshMap::set_block(Vector3i bpos, VoxelMeshBlock *block) {
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
CRASH_COND(bpos != block->position);
|
||||
if (_last_accessed_block == nullptr || _last_accessed_block->position == bpos) {
|
||||
_last_accessed_block = block;
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(_blocks_map.has(bpos));
|
||||
#endif
|
||||
unsigned int i = _blocks.size();
|
||||
_blocks.push_back(block);
|
||||
_blocks_map.set(bpos, i);
|
||||
}
|
||||
|
||||
void VoxelMeshMap::remove_block_internal(Vector3i bpos, unsigned int index) {
|
||||
// TODO `erase` can occasionally be very slow (milliseconds) if the map contains lots of items.
|
||||
// This might be caused by internal rehashing/resizing.
|
||||
// We should look for a faster container, or reduce the number of entries.
|
||||
|
||||
// This function assumes the block is already freed
|
||||
_blocks_map.erase(bpos);
|
||||
|
||||
VoxelMeshBlock *moved_block = _blocks.back();
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(index >= _blocks.size());
|
||||
#endif
|
||||
_blocks[index] = moved_block;
|
||||
_blocks.pop_back();
|
||||
|
||||
if (index < _blocks.size()) {
|
||||
unsigned int *moved_block_index = _blocks_map.getptr(moved_block->position);
|
||||
CRASH_COND(moved_block_index == nullptr);
|
||||
*moved_block_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMeshMap::queue_free_mesh_block(VoxelMeshBlock *block) {
|
||||
// We spread this out because of physics
|
||||
// TODO Could it be enough to do both render and physic deallocation with the task in ~VoxelMeshBlock()?
|
||||
struct FreeMeshBlockTask : public zylann::ITimeSpreadTask {
|
||||
void run(TimeSpreadTaskContext &ctx) override {
|
||||
memdelete(block);
|
||||
}
|
||||
VoxelMeshBlock *block = nullptr;
|
||||
};
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
FreeMeshBlockTask *task = memnew(FreeMeshBlockTask);
|
||||
task->block = block;
|
||||
VoxelServer::get_singleton()->push_main_thread_time_spread_task(task);
|
||||
}
|
||||
|
||||
bool VoxelMeshMap::has_block(Vector3i pos) const {
|
||||
return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos);
|
||||
}
|
||||
|
||||
bool VoxelMeshMap::is_block_surrounded(Vector3i pos) const {
|
||||
// TODO If that check proves to be too expensive with all blocks we deal with, cache it in VoxelBlocks
|
||||
for (unsigned int i = 0; i < Cube::MOORE_NEIGHBORING_3D_COUNT; ++i) {
|
||||
Vector3i bpos = pos + Cube::g_moore_neighboring_3d[i];
|
||||
if (!has_block(bpos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VoxelMeshMap::clear() {
|
||||
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
|
||||
VoxelMeshBlock *block = *it;
|
||||
if (block == nullptr) {
|
||||
ERR_PRINT("Unexpected nullptr in VoxelMap::clear()");
|
||||
} else {
|
||||
memdelete(block);
|
||||
}
|
||||
}
|
||||
_blocks.clear();
|
||||
_blocks_map.clear();
|
||||
_last_accessed_block = nullptr;
|
||||
}
|
||||
|
||||
int VoxelMeshMap::get_block_count() const {
|
||||
#ifdef DEBUG_ENABLED
|
||||
const unsigned int blocks_map_size = _blocks_map.size();
|
||||
CRASH_COND(_blocks.size() != blocks_map_size);
|
||||
#endif
|
||||
return _blocks.size();
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
@ -1,53 +1,28 @@
|
||||
#ifndef VOXEL_MESH_MAP_H
|
||||
#define VOXEL_MESH_MAP_H
|
||||
|
||||
#include "voxel_mesh_block.h"
|
||||
#include "../constants/cube_tables.h"
|
||||
#include "../constants/voxel_constants.h"
|
||||
#include "../server/voxel_server.h"
|
||||
#include "../util/macros.h"
|
||||
#include "voxel_mesh_map.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
// Stores meshes and colliders in an infinite sparse grid of chunks (aka blocks).
|
||||
template <typename MeshBlock_T>
|
||||
class VoxelMeshMap {
|
||||
public:
|
||||
// Converts voxel coodinates into block coordinates.
|
||||
// Don't use division because it introduces an offset in negative coordinates.
|
||||
static inline Vector3i voxel_to_block_b(Vector3i pos, int block_size_pow2) {
|
||||
return pos >> block_size_pow2;
|
||||
}
|
||||
VoxelMeshMap() : _last_accessed_block(nullptr) {}
|
||||
|
||||
inline Vector3i voxel_to_block(Vector3i pos) const {
|
||||
return voxel_to_block_b(pos, _block_size_pow2);
|
||||
~VoxelMeshMap() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline Vector3i to_local(Vector3i pos) const {
|
||||
return Vector3i(pos.x & _block_size_mask, pos.y & _block_size_mask, pos.z & _block_size_mask);
|
||||
}
|
||||
|
||||
// Converts block coodinates into voxel coordinates
|
||||
inline Vector3i block_to_voxel(Vector3i bpos) const {
|
||||
return bpos * _block_size;
|
||||
}
|
||||
|
||||
VoxelMeshMap();
|
||||
~VoxelMeshMap();
|
||||
|
||||
void create(unsigned int block_size_po2, int lod_index);
|
||||
|
||||
inline unsigned int get_block_size() const {
|
||||
return _block_size;
|
||||
}
|
||||
inline unsigned int get_block_size_pow2() const {
|
||||
return _block_size_pow2;
|
||||
}
|
||||
inline unsigned int get_block_size_mask() const {
|
||||
return _block_size_mask;
|
||||
}
|
||||
|
||||
void set_lod_index(int lod_index);
|
||||
unsigned int get_lod_index() const;
|
||||
|
||||
struct NoAction {
|
||||
inline void operator()(VoxelMeshBlock &block) {}
|
||||
inline void operator()(MeshBlock_T &block) {}
|
||||
};
|
||||
|
||||
template <typename Action_T>
|
||||
@ -61,7 +36,7 @@ public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(i >= _blocks.size());
|
||||
#endif
|
||||
VoxelMeshBlock *block = _blocks[i];
|
||||
MeshBlock_T *block = _blocks[i];
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
pre_delete(*block);
|
||||
queue_free_mesh_block(block);
|
||||
@ -69,22 +44,98 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
VoxelMeshBlock *get_block(Vector3i bpos);
|
||||
const VoxelMeshBlock *get_block(Vector3i bpos) const;
|
||||
MeshBlock_T *get_block(Vector3i bpos) {
|
||||
if (_last_accessed_block && _last_accessed_block->position == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
unsigned int *iptr = _blocks_map.getptr(bpos);
|
||||
if (iptr != nullptr) {
|
||||
const unsigned int i = *iptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(i >= _blocks.size());
|
||||
#endif
|
||||
MeshBlock_T *block = _blocks[i];
|
||||
CRASH_COND(block == nullptr); // The map should not contain null blocks
|
||||
_last_accessed_block = block;
|
||||
return _last_accessed_block;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set_block(Vector3i bpos, VoxelMeshBlock *block);
|
||||
const MeshBlock_T *get_block(Vector3i bpos) const {
|
||||
if (_last_accessed_block != nullptr && _last_accessed_block->position == bpos) {
|
||||
return _last_accessed_block;
|
||||
}
|
||||
const unsigned int *iptr = _blocks_map.getptr(bpos);
|
||||
if (iptr != nullptr) {
|
||||
const unsigned int i = *iptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(i >= _blocks.size());
|
||||
#endif
|
||||
// TODO This function can't cache _last_accessed_block, because it's const, so repeated accesses are hashing
|
||||
// again...
|
||||
const MeshBlock_T *block = _blocks[i];
|
||||
CRASH_COND(block == nullptr); // The map should not contain null blocks
|
||||
return block;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool has_block(Vector3i pos) const;
|
||||
bool is_block_surrounded(Vector3i pos) const;
|
||||
void set_block(Vector3i bpos, MeshBlock_T *block) {
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
CRASH_COND(bpos != block->position);
|
||||
if (_last_accessed_block == nullptr || _last_accessed_block->position == bpos) {
|
||||
_last_accessed_block = block;
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(_blocks_map.has(bpos));
|
||||
#endif
|
||||
unsigned int i = _blocks.size();
|
||||
_blocks.push_back(block);
|
||||
_blocks_map.set(bpos, i);
|
||||
}
|
||||
|
||||
void clear();
|
||||
bool has_block(Vector3i pos) const {
|
||||
return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos);
|
||||
}
|
||||
|
||||
int get_block_count() const;
|
||||
bool is_block_surrounded(Vector3i pos) const {
|
||||
// TODO If that check proves to be too expensive with all blocks we deal with, cache it in VoxelBlocks
|
||||
for (unsigned int i = 0; i < Cube::MOORE_NEIGHBORING_3D_COUNT; ++i) {
|
||||
const Vector3i bpos = pos + Cube::g_moore_neighboring_3d[i];
|
||||
if (!has_block(bpos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
|
||||
MeshBlock_T *block = *it;
|
||||
if (block == nullptr) {
|
||||
ERR_PRINT("Unexpected nullptr in VoxelMap::clear()");
|
||||
} else {
|
||||
memdelete(block);
|
||||
}
|
||||
}
|
||||
_blocks.clear();
|
||||
_blocks_map.clear();
|
||||
_last_accessed_block = nullptr;
|
||||
}
|
||||
|
||||
unsigned int get_block_count() const {
|
||||
#ifdef DEBUG_ENABLED
|
||||
const unsigned int blocks_map_size = _blocks_map.size();
|
||||
CRASH_COND(_blocks.size() != blocks_map_size);
|
||||
#endif
|
||||
return _blocks.size();
|
||||
}
|
||||
|
||||
template <typename Op_T>
|
||||
inline void for_each_block(Op_T op) {
|
||||
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
|
||||
VoxelMeshBlock *block = *it;
|
||||
MeshBlock_T *block = *it;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(block == nullptr);
|
||||
#endif
|
||||
@ -95,7 +146,7 @@ public:
|
||||
template <typename Op_T>
|
||||
inline void for_each_block(Op_T op) const {
|
||||
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
|
||||
const VoxelMeshBlock *block = *it;
|
||||
const MeshBlock_T *block = *it;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(block == nullptr);
|
||||
#endif
|
||||
@ -104,29 +155,53 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
//VoxelMeshBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
|
||||
VoxelMeshBlock *create_default_block(Vector3i bpos);
|
||||
void remove_block_internal(Vector3i bpos, unsigned int index);
|
||||
void queue_free_mesh_block(VoxelMeshBlock *block);
|
||||
void remove_block_internal(Vector3i bpos, unsigned int index) {
|
||||
// TODO `erase` can occasionally be very slow (milliseconds) if the map contains lots of items.
|
||||
// This might be caused by internal rehashing/resizing.
|
||||
// We should look for a faster container, or reduce the number of entries.
|
||||
|
||||
void set_block_size_pow2(unsigned int p);
|
||||
// This function assumes the block is already freed
|
||||
_blocks_map.erase(bpos);
|
||||
|
||||
MeshBlock_T *moved_block = _blocks.back();
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(index >= _blocks.size());
|
||||
#endif
|
||||
_blocks[index] = moved_block;
|
||||
_blocks.pop_back();
|
||||
|
||||
if (index < _blocks.size()) {
|
||||
unsigned int *moved_block_index = _blocks_map.getptr(moved_block->position);
|
||||
CRASH_COND(moved_block_index == nullptr);
|
||||
*moved_block_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void queue_free_mesh_block(MeshBlock_T *block) {
|
||||
// We spread this out because of physics
|
||||
// TODO Could it be enough to do both render and physic deallocation with the task in ~MeshBlock_T()?
|
||||
struct FreeMeshBlockTask : public zylann::ITimeSpreadTask {
|
||||
void run(TimeSpreadTaskContext &ctx) override {
|
||||
memdelete(block);
|
||||
}
|
||||
MeshBlock_T *block = nullptr;
|
||||
};
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
FreeMeshBlockTask *task = memnew(FreeMeshBlockTask);
|
||||
task->block = block;
|
||||
VoxelServer::get_singleton()->push_main_thread_time_spread_task(task);
|
||||
}
|
||||
|
||||
private:
|
||||
// Blocks stored with a spatial hash in all 3D directions.
|
||||
// RELATIONSHIP = 2 because it delivers better performance with this kind of key and hash (less collisions).
|
||||
HashMap<Vector3i, unsigned int, Vector3iHasher, HashMapComparatorDefault<Vector3i>, 3, 2> _blocks_map;
|
||||
// Blocks are stored in a vector to allow faster iteration over all of them
|
||||
std::vector<VoxelMeshBlock *> _blocks;
|
||||
std::vector<MeshBlock_T *> _blocks;
|
||||
|
||||
// Voxel access will most frequently be in contiguous areas, so the same blocks are accessed.
|
||||
// To prevent too much hashing, this reference is checked before.
|
||||
mutable VoxelMeshBlock *_last_accessed_block;
|
||||
|
||||
unsigned int _block_size;
|
||||
unsigned int _block_size_pow2;
|
||||
unsigned int _block_size_mask;
|
||||
|
||||
unsigned int _lod_index = 0;
|
||||
mutable MeshBlock_T *_last_accessed_block;
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
@ -192,7 +192,7 @@ unsigned int VoxelTerrain::get_data_block_size_pow2() const {
|
||||
}
|
||||
|
||||
unsigned int VoxelTerrain::get_mesh_block_size_pow2() const {
|
||||
return _mesh_map.get_block_size_pow2();
|
||||
return _mesh_block_size_po2;
|
||||
}
|
||||
|
||||
void VoxelTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
|
||||
@ -215,8 +215,10 @@ void VoxelTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mesh_block_size_po2 = po2;
|
||||
|
||||
// Unload all mesh blocks regardless of refcount
|
||||
_mesh_map.create(po2, 0);
|
||||
_mesh_map.clear();
|
||||
|
||||
// Make paired viewers re-view the new meshable area
|
||||
for (unsigned int i = 0; i < _paired_viewers.size(); ++i) {
|
||||
@ -263,7 +265,7 @@ void VoxelTerrain::_on_stream_params_changed() {
|
||||
|
||||
void VoxelTerrain::_on_gi_mode_changed() {
|
||||
const GIMode gi_mode = get_gi_mode();
|
||||
_mesh_map.for_each_block([gi_mode](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([gi_mode](VoxelMeshBlockVT &block) { //
|
||||
block.set_gi_mode(DirectMeshInstance::GIMode(gi_mode));
|
||||
});
|
||||
}
|
||||
@ -316,7 +318,7 @@ void VoxelTerrain::set_generate_collisions(bool enabled) {
|
||||
|
||||
void VoxelTerrain::set_collision_layer(int layer) {
|
||||
_collision_layer = layer;
|
||||
_mesh_map.for_each_block([layer](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([layer](VoxelMeshBlockVT &block) { //
|
||||
block.set_collision_layer(layer);
|
||||
});
|
||||
}
|
||||
@ -327,7 +329,7 @@ int VoxelTerrain::get_collision_layer() const {
|
||||
|
||||
void VoxelTerrain::set_collision_mask(int mask) {
|
||||
_collision_mask = mask;
|
||||
_mesh_map.for_each_block([mask](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([mask](VoxelMeshBlockVT &block) { //
|
||||
block.set_collision_mask(mask);
|
||||
});
|
||||
}
|
||||
@ -338,7 +340,7 @@ int VoxelTerrain::get_collision_mask() const {
|
||||
|
||||
void VoxelTerrain::set_collision_margin(float margin) {
|
||||
_collision_margin = margin;
|
||||
_mesh_map.for_each_block([margin](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([margin](VoxelMeshBlockVT &block) { //
|
||||
block.set_collision_margin(margin);
|
||||
});
|
||||
}
|
||||
@ -400,8 +402,8 @@ Ref<Material> VoxelTerrain::get_material(unsigned int id) const {
|
||||
return _materials[id];
|
||||
}
|
||||
|
||||
void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlock &mesh_block) {
|
||||
if (mesh_block.get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_NOT_SENT) {
|
||||
void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
|
||||
if (mesh_block.get_mesh_state() == VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT) {
|
||||
// Already in the list
|
||||
return;
|
||||
}
|
||||
@ -429,7 +431,7 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlock &mesh_block) {
|
||||
if (data_available) {
|
||||
// Regardless of if the updater is updating the block already,
|
||||
// the block could have been modified again so we schedule another update
|
||||
mesh_block.set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
||||
mesh_block.set_mesh_state(VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT);
|
||||
_blocks_pending_update.push_back(mesh_block.position);
|
||||
}
|
||||
}
|
||||
@ -482,11 +484,11 @@ void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision
|
||||
return;
|
||||
}
|
||||
|
||||
VoxelMeshBlock *block = _mesh_map.get_block(bpos);
|
||||
VoxelMeshBlockVT *block = _mesh_map.get_block(bpos);
|
||||
|
||||
if (block == nullptr) {
|
||||
// Create if not found
|
||||
block = VoxelMeshBlock::create(bpos, get_mesh_block_size(), 0);
|
||||
block = memnew(VoxelMeshBlockVT(bpos, get_mesh_block_size()));
|
||||
block->set_world(get_world_3d());
|
||||
_mesh_map.set_block(bpos, block);
|
||||
}
|
||||
@ -547,7 +549,7 @@ void VoxelTerrain::unview_data_block(Vector3i bpos) {
|
||||
}
|
||||
|
||||
void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
|
||||
VoxelMeshBlock *block = _mesh_map.get_block(bpos);
|
||||
VoxelMeshBlockVT *block = _mesh_map.get_block(bpos);
|
||||
// Mesh blocks are created on first view call,
|
||||
// so that would mean we unview one without viewing it in the first place
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
@ -618,8 +620,8 @@ void VoxelTerrain::unload_data_block(Vector3i bpos) {
|
||||
void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
|
||||
std::vector<Vector3i> &blocks_pending_update = _blocks_pending_update;
|
||||
|
||||
_mesh_map.remove_block(bpos, [&blocks_pending_update](const VoxelMeshBlock &block) {
|
||||
if (block.get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_NOT_SENT) {
|
||||
_mesh_map.remove_block(bpos, [&blocks_pending_update](const VoxelMeshBlockVT &block) {
|
||||
if (block.get_mesh_state() == VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT) {
|
||||
// That block was in the list of blocks to update later in the process loop, we'll need to unregister it.
|
||||
// We expect that block to be in that list. If it isn't, something wrong happened with its state.
|
||||
ERR_FAIL_COND(!unordered_remove_value(blocks_pending_update, block.position));
|
||||
@ -677,15 +679,15 @@ void VoxelTerrain::stop_updater() {
|
||||
|
||||
_blocks_pending_update.clear();
|
||||
|
||||
_mesh_map.for_each_block([](VoxelMeshBlock &block) {
|
||||
if (block.get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_SENT) {
|
||||
block.set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
||||
_mesh_map.for_each_block([](VoxelMeshBlockVT &block) {
|
||||
if (block.get_mesh_state() == VoxelMeshBlockVT::MESH_UPDATE_SENT) {
|
||||
block.set_mesh_state(VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VoxelTerrain::remesh_all_blocks() {
|
||||
_mesh_map.for_each_block([this](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([this](VoxelMeshBlockVT &block) { //
|
||||
try_schedule_mesh_update(block);
|
||||
});
|
||||
}
|
||||
@ -744,7 +746,7 @@ void VoxelTerrain::reset_map() {
|
||||
});
|
||||
_data_map.create(get_data_block_size_pow2(), 0);
|
||||
|
||||
_mesh_map.create(get_mesh_block_size_pow2(), 0);
|
||||
_mesh_map.clear();
|
||||
|
||||
_loading_blocks.clear();
|
||||
_blocks_pending_load.clear();
|
||||
@ -763,7 +765,7 @@ void VoxelTerrain::try_schedule_mesh_update_from_data(const Box3i &box_in_voxels
|
||||
// We pad by 1 because neighbor blocks might be affected visually (for example, baked ambient occlusion)
|
||||
const Box3i mesh_box = box_in_voxels.padded(1).downscaled(get_mesh_block_size());
|
||||
mesh_box.for_each_cell([this](Vector3i pos) {
|
||||
VoxelMeshBlock *block = _mesh_map.get_block(pos);
|
||||
VoxelMeshBlockVT *block = _mesh_map.get_block(pos);
|
||||
// There isn't necessarily a mesh block, if the edit happens in a boundary,
|
||||
// or if it is done next to a viewer that doesn't need meshes
|
||||
if (block != nullptr) {
|
||||
@ -797,7 +799,7 @@ void VoxelTerrain::_notification(int p_what) {
|
||||
struct SetWorldAction {
|
||||
World3D *world;
|
||||
SetWorldAction(World3D *w) : world(w) {}
|
||||
void operator()(VoxelMeshBlock &block) {
|
||||
void operator()(VoxelMeshBlockVT &block) {
|
||||
block.set_world(world);
|
||||
}
|
||||
};
|
||||
@ -805,7 +807,7 @@ void VoxelTerrain::_notification(int p_what) {
|
||||
struct SetParentVisibilityAction {
|
||||
bool visible;
|
||||
SetParentVisibilityAction(bool v) : visible(v) {}
|
||||
void operator()(VoxelMeshBlock &block) {
|
||||
void operator()(VoxelMeshBlockVT &block) {
|
||||
block.set_parent_visible(visible);
|
||||
}
|
||||
};
|
||||
@ -849,7 +851,7 @@ void VoxelTerrain::_notification(int p_what) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mesh_map.for_each_block([&transform](VoxelMeshBlock &block) { //
|
||||
_mesh_map.for_each_block([&transform](VoxelMeshBlockVT &block) { //
|
||||
block.set_parent_transform(transform);
|
||||
});
|
||||
|
||||
@ -1321,11 +1323,11 @@ void VoxelTerrain::process_meshing() {
|
||||
for (size_t bi = 0; bi < _blocks_pending_update.size(); ++bi) {
|
||||
const Vector3i mesh_block_pos = _blocks_pending_update[bi];
|
||||
|
||||
VoxelMeshBlock *mesh_block = _mesh_map.get_block(mesh_block_pos);
|
||||
VoxelMeshBlockVT *mesh_block = _mesh_map.get_block(mesh_block_pos);
|
||||
|
||||
// If we got here, it must have been because of scheduling an update
|
||||
ERR_CONTINUE(mesh_block == nullptr);
|
||||
ERR_CONTINUE(mesh_block->get_mesh_state() != VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
||||
ERR_CONTINUE(mesh_block->get_mesh_state() != VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT);
|
||||
|
||||
// Pad by 1 because meshing requires neighbors
|
||||
const Box3i data_box =
|
||||
@ -1370,7 +1372,7 @@ void VoxelTerrain::process_meshing() {
|
||||
//print_line(String("DDD request {0}").format(varray(mesh_request.render_block_position.to_vec3())));
|
||||
VoxelServer::get_singleton()->request_block_mesh(_volume_id, mesh_request);
|
||||
|
||||
mesh_block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_SENT);
|
||||
mesh_block->set_mesh_state(VoxelMeshBlockVT::MESH_UPDATE_SENT);
|
||||
}
|
||||
|
||||
_blocks_pending_update.clear();
|
||||
@ -1386,7 +1388,7 @@ void VoxelTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob) {
|
||||
VOXEL_PROFILE_SCOPE();
|
||||
//print_line(String("DDD receive {0}").format(varray(ob.position.to_vec3())));
|
||||
|
||||
VoxelMeshBlock *block = _mesh_map.get_block(ob.position);
|
||||
VoxelMeshBlockVT *block = _mesh_map.get_block(ob.position);
|
||||
if (block == nullptr) {
|
||||
//print_line("- no longer loaded");
|
||||
// That block is no longer loaded, drop the result
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "../storage/voxel_data_map.h"
|
||||
#include "../util/godot/funcs.h"
|
||||
#include "voxel_data_block_enter_info.h"
|
||||
#include "voxel_mesh_block_vt.h"
|
||||
#include "voxel_mesh_map.h"
|
||||
#include "voxel_node.h"
|
||||
|
||||
@ -161,7 +162,7 @@ private:
|
||||
void unload_data_block(Vector3i bpos);
|
||||
void unload_mesh_block(Vector3i bpos);
|
||||
//void make_data_block_dirty(Vector3i bpos);
|
||||
void try_schedule_mesh_update(VoxelMeshBlock &block);
|
||||
void try_schedule_mesh_update(VoxelMeshBlockVT &block);
|
||||
void try_schedule_mesh_update_from_data(const Box3i &box_in_voxels);
|
||||
|
||||
void save_all_modified_blocks(bool with_copy);
|
||||
@ -221,7 +222,8 @@ private:
|
||||
// Voxel storage
|
||||
VoxelDataMap _data_map;
|
||||
// Mesh storage
|
||||
VoxelMeshMap _mesh_map;
|
||||
VoxelMeshMap<VoxelMeshBlockVT> _mesh_map;
|
||||
uint32_t _mesh_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
|
||||
|
||||
// Area within which voxels can exist.
|
||||
// Note, these bounds might not be exactly represented. This volume is chunk-based, so the result will be
|
||||
|
Loading…
x
Reference in New Issue
Block a user