Separate mesh block classes to remove unnecessary data in each

# Conflicts:
#	terrain/voxel_lod_terrain.cpp
This commit is contained in:
Marc Gilleron 2022-03-20 22:04:53 +00:00
parent ee60e0556e
commit e96f9ff562
13 changed files with 654 additions and 613 deletions

45
terrain/free_mesh_task.h Normal file
View 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

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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