From 4c1e149aef82b6f6c7d11501541cdfcf3c58b0f2 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sun, 1 Nov 2020 03:25:48 +0000 Subject: [PATCH 001/102] Optimized iteration over all blocks of a map (but likely, ECS approach may be better in the future) --- terrain/voxel_map.cpp | 69 ++++++++++++++++++++++++++++++------------- terrain/voxel_map.h | 27 +++++++++-------- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/terrain/voxel_map.cpp b/terrain/voxel_map.cpp index 3d2d418c..c3159b30 100644 --- a/terrain/voxel_map.cpp +++ b/terrain/voxel_map.cpp @@ -59,7 +59,6 @@ VoxelBlock *VoxelMap::get_or_create_block_at_voxel_pos(Vector3i pos) { VoxelBlock *block = get_block(bpos); if (block == nullptr) { - Ref buffer(memnew(VoxelBuffer)); buffer->create(_block_size, _block_size, _block_size); buffer->set_default_values(_default_voxel); @@ -111,10 +110,15 @@ VoxelBlock *VoxelMap::get_block(Vector3i bpos) { if (_last_accessed_block && _last_accessed_block->position == bpos) { return _last_accessed_block; } - VoxelBlock **p = _blocks.getptr(bpos); - if (p) { - _last_accessed_block = *p; - CRASH_COND(_last_accessed_block == nullptr); // The map should not contain null blocks + unsigned int *iptr = _blocks_map.getptr(bpos); + if (iptr != nullptr) { + const unsigned int i = *iptr; +#ifdef DEBUG_ENABLED + CRASH_COND(i >= _blocks.size()); +#endif + VoxelBlock *block = _blocks[i]; + CRASH_COND(block == nullptr); // The map should not contain null blocks + _last_accessed_block = block; return _last_accessed_block; } return nullptr; @@ -124,10 +128,14 @@ const VoxelBlock *VoxelMap::get_block(Vector3i bpos) const { if (_last_accessed_block && _last_accessed_block->position == bpos) { return _last_accessed_block; } - const VoxelBlock *const *p = _blocks.getptr(bpos); - if (p) { + 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 VoxelBlock *block = *p; + const VoxelBlock *block = _blocks[i]; CRASH_COND(block == nullptr); // The map should not contain null blocks return block; } @@ -140,12 +148,27 @@ void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) { if (_last_accessed_block == nullptr || _last_accessed_block->position == bpos) { _last_accessed_block = block; } - _blocks.set(bpos, 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 VoxelMap::remove_block_internal(Vector3i bpos) { +void VoxelMap::remove_block_internal(Vector3i bpos, unsigned int index) { // This function assumes the block is already freed - _blocks.erase(bpos); + _blocks_map.erase(bpos); + + VoxelBlock *moved_block = _blocks.back(); + _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; + } } VoxelBlock *VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { @@ -161,7 +184,7 @@ VoxelBlock *VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { } bool VoxelMap::has_block(Vector3i pos) const { - return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks.has(pos); + return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos); } bool VoxelMap::is_block_surrounded(Vector3i pos) const { @@ -176,10 +199,10 @@ bool VoxelMap::is_block_surrounded(Vector3i pos) const { } void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask) { - Vector3i max_pos = min_pos + dst_buffer.get_size(); + const Vector3i max_pos = min_pos + dst_buffer.get_size(); - Vector3i min_block_pos = voxel_to_block(min_pos); - Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1, 1, 1)) + Vector3i(1, 1, 1); + const Vector3i min_block_pos = voxel_to_block(min_pos); + const Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1, 1, 1)) + Vector3i(1, 1, 1); // TODO Why is this function limited by this check? // Probably to make sure we are getting neighbors, however that's a worry for the caller, not this function... ERR_FAIL_COND((max_block_pos - min_block_pos) != Vector3i(3, 3, 3)); @@ -231,18 +254,24 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsign void VoxelMap::clear() { const Vector3i *key = nullptr; - while ((key = _blocks.next(key))) { - VoxelBlock *block_ptr = _blocks.get(*key); - if (block_ptr == nullptr) { - OS::get_singleton()->printerr("Unexpected nullptr in VoxelMap::clear()"); + for (auto it = _blocks.begin(); it != _blocks.end(); ++it) { + VoxelBlock *block = *it; + if (block == nullptr) { + ERR_PRINT("Unexpected nullptr in VoxelMap::clear()"); + } else { + memdelete(block); } - memdelete(block_ptr); } _blocks.clear(); + _blocks_map.clear(); _last_accessed_block = nullptr; } int VoxelMap::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(); } diff --git a/terrain/voxel_map.h b/terrain/voxel_map.h index 43433d81..e2bd3fc7 100644 --- a/terrain/voxel_map.h +++ b/terrain/voxel_map.h @@ -71,13 +71,17 @@ public: if (_last_accessed_block && _last_accessed_block->position == bpos) { _last_accessed_block = nullptr; } - VoxelBlock **pptr = _blocks.getptr(bpos); - if (pptr) { - VoxelBlock *block = *pptr; + unsigned int *iptr = _blocks_map.getptr(bpos); + if (iptr != nullptr) { + const unsigned int i = *iptr; +#ifdef DEBUG_ENABLED + CRASH_COND(i >= _blocks.size()); +#endif + VoxelBlock *block = _blocks[i]; ERR_FAIL_COND(block == nullptr); pre_delete(block); memdelete(block); - remove_block_internal(bpos); + remove_block_internal(bpos, i); } } @@ -92,13 +96,9 @@ public: int get_block_count() const; template - void for_all_blocks(Op_T op) { - const Vector3i *key = nullptr; - while ((key = _blocks.next(key))) { - VoxelBlock *block = _blocks.get(*key); - if (block != nullptr) { - op(block); - } + inline void for_all_blocks(Op_T op) { + for (auto it = _blocks.begin(); it != _blocks.end(); ++it) { + op(*it); } } @@ -107,7 +107,7 @@ public: private: void set_block(Vector3i bpos, VoxelBlock *block); VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos); - void remove_block_internal(Vector3i bpos); + void remove_block_internal(Vector3i bpos, unsigned int index); void set_block_size_pow2(unsigned int p); @@ -131,7 +131,8 @@ private: FixedArray _default_voxel; // Blocks stored with a spatial hash in all 3D directions - HashMap _blocks; + HashMap _blocks_map; + std::vector _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. From e18b3a55fd6eb701f2581bf2b57c813b2eaf2e75 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sat, 21 Nov 2020 18:15:12 +0000 Subject: [PATCH 002/102] Don't make the editor redraw every frame when gizmos are shown --- editor/voxel_debug.cpp | 73 +++++++++++++++++++++++++++-------- editor/voxel_debug.h | 4 +- util/direct_mesh_instance.cpp | 6 +++ util/direct_mesh_instance.h | 1 + util/direct_static_body.cpp | 3 -- 5 files changed, 67 insertions(+), 20 deletions(-) diff --git a/editor/voxel_debug.cpp b/editor/voxel_debug.cpp index 96637384..68cc660f 100644 --- a/editor/voxel_debug.cpp +++ b/editor/voxel_debug.cpp @@ -103,20 +103,62 @@ void free_resources() { g_finalized = true; } +class DebugRendererItem { +public: + DebugRendererItem() { + _mesh_instance.create(); + // TODO When shadow casting is on, directional shadows completely break. + // The reason is still unknown. + // It should be off anyways, but it's rather concerning. + _mesh_instance.set_cast_shadows_setting(VisualServer::SHADOW_CASTING_SETTING_OFF); + } + + void set_mesh(Ref mesh) { + if (_mesh != mesh) { + _mesh = mesh; + _mesh_instance.set_mesh(mesh); + } + } + + void set_transform(Transform t) { + if (_transform != t) { + _transform = t; + _mesh_instance.set_transform(t); + } + } + + void set_visible(bool visible) { + if (_visible != visible) { + _visible = visible; + _mesh_instance.set_visible(visible); + } + } + + void set_world(World *world) { + _mesh_instance.set_world(world); + } + +private: + Transform _transform; + bool _visible = true; + Ref _mesh; + DirectMeshInstance _mesh_instance; +}; + DebugRenderer::~DebugRenderer() { clear(); } void DebugRenderer::clear() { - for (auto it = _mesh_instances.begin(); it != _mesh_instances.end(); ++it) { + for (auto it = _items.begin(); it != _items.end(); ++it) { memdelete(*it); } - _mesh_instances.clear(); + _items.clear(); } void DebugRenderer::set_world(World *world) { _world = world; - for (auto it = _mesh_instances.begin(); it != _mesh_instances.end(); ++it) { + for (auto it = _items.begin(); it != _items.end(); ++it) { (*it)->set_world(world); } } @@ -129,28 +171,27 @@ void DebugRenderer::begin() { } void DebugRenderer::draw_box(Transform t, ColorID color) { - DirectMeshInstance *mi; - if (_current >= _mesh_instances.size()) { - mi = memnew(DirectMeshInstance); - mi->create(); - mi->set_world(_world); - _mesh_instances.push_back(mi); + DebugRendererItem *item; + if (_current >= _items.size()) { + item = memnew(DebugRendererItem); + item->set_world(_world); + _items.push_back(item); } else { - mi = _mesh_instances[_current]; + item = _items[_current]; } - mi->set_mesh(get_wirecube(color)); - mi->set_transform(t); - mi->set_visible(true); + item->set_mesh(get_wirecube(color)); + item->set_transform(t); + item->set_visible(true); ++_current; } void DebugRenderer::end() { CRASH_COND(!_inside_block); - for (unsigned int i = _current; i < _mesh_instances.size(); ++i) { - DirectMeshInstance *mi = _mesh_instances[i]; - mi->set_visible(false); + for (unsigned int i = _current; i < _items.size(); ++i) { + DebugRendererItem *item = _items[i]; + item->set_visible(false); } _inside_block = false; } diff --git a/editor/voxel_debug.h b/editor/voxel_debug.h index e358306b..7eae2a8e 100644 --- a/editor/voxel_debug.h +++ b/editor/voxel_debug.h @@ -19,6 +19,8 @@ enum ColorID { Ref get_wirecube(ColorID id); void free_resources(); +class DebugRendererItem; + class DebugRenderer { public: ~DebugRenderer(); @@ -31,7 +33,7 @@ public: void clear(); private: - std::vector _mesh_instances; + std::vector _items; unsigned int _current = 0; bool _inside_block = false; World *_world = nullptr; diff --git a/util/direct_mesh_instance.cpp b/util/direct_mesh_instance.cpp index 80b35126..f76252fb 100644 --- a/util/direct_mesh_instance.cpp +++ b/util/direct_mesh_instance.cpp @@ -75,6 +75,12 @@ void DirectMeshInstance::set_visible(bool visible) { vs.instance_set_visible(_mesh_instance, visible); } +void DirectMeshInstance::set_cast_shadows_setting(VisualServer::ShadowCastingSetting mode) { + ERR_FAIL_COND(!_mesh_instance.is_valid()); + VisualServer &vs = *VisualServer::get_singleton(); + vs.instance_geometry_set_cast_shadows_setting(_mesh_instance, mode); +} + Ref DirectMeshInstance::get_mesh() const { return _mesh; } diff --git a/util/direct_mesh_instance.h b/util/direct_mesh_instance.h index 349c31a7..934dc747 100644 --- a/util/direct_mesh_instance.h +++ b/util/direct_mesh_instance.h @@ -20,6 +20,7 @@ public: void set_mesh(Ref mesh); void set_material_override(Ref material); void set_visible(bool visible); + void set_cast_shadows_setting(VisualServer::ShadowCastingSetting mode); Ref get_mesh() const; diff --git a/util/direct_static_body.cpp b/util/direct_static_body.cpp index c022276b..bfcea025 100644 --- a/util/direct_static_body.cpp +++ b/util/direct_static_body.cpp @@ -94,11 +94,9 @@ void DirectStaticBody::set_attached_object(Object *obj) { } void DirectStaticBody::set_debug(bool enabled, World *world) { - ERR_FAIL_COND(world == nullptr); if (enabled && !_debug_mesh_instance.is_valid()) { - _debug_mesh_instance.create(); _debug_mesh_instance.set_world(world); @@ -111,7 +109,6 @@ void DirectStaticBody::set_debug(bool enabled, World *world) { } } else if (!enabled && _debug_mesh_instance.is_valid()) { - _debug_mesh_instance.destroy(); } } \ No newline at end of file From d52f882ae1fe2673ee5d1b018a3d057d8b35ffdf Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sat, 21 Nov 2020 18:28:06 +0000 Subject: [PATCH 003/102] VoxelMap is no longer bound, it had no use in the API --- config.py | 1 - edition/voxel_tool_lod_terrain.cpp | 17 ++-- edition/voxel_tool_lod_terrain.h | 4 +- edition/voxel_tool_terrain.cpp | 42 +++++---- edition/voxel_tool_terrain.h | 3 +- register_types.cpp | 1 - terrain/voxel_box_mover.cpp | 4 +- terrain/voxel_lod_terrain.cpp | 138 ++++++++++++----------------- terrain/voxel_lod_terrain.h | 6 +- terrain/voxel_map.cpp | 26 +----- terrain/voxel_map.h | 27 +++--- terrain/voxel_terrain.cpp | 89 ++++++++----------- terrain/voxel_terrain.h | 6 +- 13 files changed, 149 insertions(+), 215 deletions(-) diff --git a/config.py b/config.py index 74c45554..ce6870ec 100644 --- a/config.py +++ b/config.py @@ -15,7 +15,6 @@ def get_doc_classes(): "VoxelServer", "VoxelBuffer", - "VoxelMap", "Voxel", "VoxelLibrary", diff --git a/edition/voxel_tool_lod_terrain.cpp b/edition/voxel_tool_lod_terrain.cpp index d6a871d1..c6b1d4ef 100644 --- a/edition/voxel_tool_lod_terrain.cpp +++ b/edition/voxel_tool_lod_terrain.cpp @@ -2,36 +2,37 @@ #include "../terrain/voxel_lod_terrain.h" #include "../terrain/voxel_map.h" -VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, Ref map) { +VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelMap &map) : + _terrain(terrain), _map(map) { ERR_FAIL_COND(terrain == nullptr); - _terrain = terrain; - _map = map; // At the moment, only LOD0 is supported + // At the moment, only LOD0 is supported. // Don't destroy the terrain while a voxel tool still references it } bool VoxelToolLodTerrain::is_area_editable(const Rect3i &box) const { ERR_FAIL_COND_V(_terrain == nullptr, false); - return _map->is_area_fully_loaded(box.padded(1)); + // TODO Take volume bounds into account + return _map.is_area_fully_loaded(box.padded(1)); } uint64_t VoxelToolLodTerrain::_get_voxel(Vector3i pos) { ERR_FAIL_COND_V(_terrain == nullptr, 0); - return _map->get_voxel(pos, _channel); + return _map.get_voxel(pos, _channel); } float VoxelToolLodTerrain::_get_voxel_f(Vector3i pos) { ERR_FAIL_COND_V(_terrain == nullptr, 0); - return _map->get_voxel_f(pos, _channel); + return _map.get_voxel_f(pos, _channel); } void VoxelToolLodTerrain::_set_voxel(Vector3i pos, uint64_t v) { ERR_FAIL_COND(_terrain == nullptr); - _map->set_voxel(v, pos, _channel); + _map.set_voxel(v, pos, _channel); } void VoxelToolLodTerrain::_set_voxel_f(Vector3i pos, float v) { ERR_FAIL_COND(_terrain == nullptr); - _map->set_voxel_f(v, pos, _channel); + _map.set_voxel_f(v, pos, _channel); } void VoxelToolLodTerrain::_post_edit(const Rect3i &box) { diff --git a/edition/voxel_tool_lod_terrain.h b/edition/voxel_tool_lod_terrain.h index 61ffba2a..fd40ba68 100644 --- a/edition/voxel_tool_lod_terrain.h +++ b/edition/voxel_tool_lod_terrain.h @@ -9,7 +9,7 @@ class VoxelMap; class VoxelToolLodTerrain : public VoxelTool { GDCLASS(VoxelToolLodTerrain, VoxelTool) public: - VoxelToolLodTerrain(VoxelLodTerrain *terrain, Ref map); + VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelMap &map); bool is_area_editable(const Rect3i &box) const override; @@ -22,7 +22,7 @@ protected: private: VoxelLodTerrain *_terrain = nullptr; - Ref _map; + VoxelMap &_map; }; #endif // VOXEL_TOOL_LOD_TERRAIN_H diff --git a/edition/voxel_tool_terrain.cpp b/edition/voxel_tool_terrain.cpp index 748b0394..5515a7d3 100644 --- a/edition/voxel_tool_terrain.cpp +++ b/edition/voxel_tool_terrain.cpp @@ -7,16 +7,16 @@ VoxelToolTerrain::VoxelToolTerrain() { } -VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain, Ref map) { +VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain) { ERR_FAIL_COND(terrain == nullptr); _terrain = terrain; - _map = map; // Don't destroy the terrain while a voxel tool still references it } bool VoxelToolTerrain::is_area_editable(const Rect3i &box) const { ERR_FAIL_COND_V(_terrain == nullptr, false); - return _map->is_area_fully_loaded(box.padded(1)); + // TODO Take volume bounds into account + return _terrain->get_storage().is_area_fully_loaded(box.padded(1)); } Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, float max_distance, uint32_t collision_mask) { @@ -30,8 +30,8 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa bool operator()(Vector3i pos) { //unsigned int channel = context->channel; - Ref map = terrain.get_storage(); - int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE); + const VoxelMap &map = terrain.get_storage(); + int v0 = map.get_voxel(pos, VoxelBuffer::CHANNEL_TYPE); Ref lib_ref = terrain.get_voxel_library(); if (lib_ref.is_null()) { @@ -60,7 +60,7 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa return true; } - float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF); + float v1 = map.get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF); return v1 < 0; } }; @@ -81,22 +81,22 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) { ERR_FAIL_COND_V(_terrain == nullptr, 0); - return _map->get_voxel(pos, _channel); + return _terrain->get_storage().get_voxel(pos, _channel); } float VoxelToolTerrain::_get_voxel_f(Vector3i pos) { ERR_FAIL_COND_V(_terrain == nullptr, 0); - return _map->get_voxel_f(pos, _channel); + return _terrain->get_storage().get_voxel_f(pos, _channel); } void VoxelToolTerrain::_set_voxel(Vector3i pos, uint64_t v) { ERR_FAIL_COND(_terrain == nullptr); - _map->set_voxel(v, pos, _channel); + _terrain->get_storage().set_voxel(v, pos, _channel); } void VoxelToolTerrain::_set_voxel_f(Vector3i pos, float v) { ERR_FAIL_COND(_terrain == nullptr); - _map->set_voxel_f(v, pos, _channel); + _terrain->get_storage().set_voxel_f(v, pos, _channel); } void VoxelToolTerrain::_post_edit(const Rect3i &box) { @@ -106,18 +106,20 @@ void VoxelToolTerrain::_post_edit(const Rect3i &box) { void VoxelToolTerrain::set_voxel_metadata(Vector3i pos, Variant meta) { ERR_FAIL_COND(_terrain == nullptr); - VoxelBlock *block = _map->get_block(_map->voxel_to_block(pos)); + VoxelMap &map = _terrain->get_storage(); + VoxelBlock *block = map.get_block(map.voxel_to_block(pos)); ERR_FAIL_COND_MSG(block == nullptr, "Area not editable"); RWLockWrite lock(block->voxels->get_lock()); - block->voxels->set_voxel_metadata(_map->to_local(pos), meta); + block->voxels->set_voxel_metadata(map.to_local(pos), meta); } Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) { ERR_FAIL_COND_V(_terrain == nullptr, Variant()); - const VoxelBlock *block = _map->get_block(_map->voxel_to_block(pos)); + VoxelMap &map = _terrain->get_storage(); + VoxelBlock *block = map.get_block(map.voxel_to_block(pos)); ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable"); RWLockRead lock(block->voxels->get_lock()); - return block->voxels->get_voxel_metadata(_map->to_local(pos)); + return block->voxels->get_voxel_metadata(map.to_local(pos)); } // Executes a function on random voxels in the provided area, using the type channel. @@ -140,12 +142,14 @@ void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count, const Vector3i min_pos = Vector3i(voxel_area.position); const Vector3i max_pos = min_pos + Vector3i(voxel_area.size); - const Vector3i min_block_pos = _map->voxel_to_block(min_pos); - const Vector3i max_block_pos = _map->voxel_to_block(max_pos); + const VoxelMap &map = _terrain->get_storage(); + + const Vector3i min_block_pos = map.voxel_to_block(min_pos); + const Vector3i max_block_pos = map.voxel_to_block(max_pos); const Vector3i block_area_size = max_block_pos - min_block_pos; const int block_count = voxel_count / batch_count; - const int bs_mask = _map->get_block_size_mask(); + const int bs_mask = map.get_block_size_mask(); const VoxelBuffer::ChannelId channel = VoxelBuffer::CHANNEL_TYPE; struct Pick { @@ -162,9 +166,9 @@ void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count, Math::rand() % block_area_size.y, Math::rand() % block_area_size.z); - const Vector3i block_origin = _map->block_to_voxel(block_pos); + const Vector3i block_origin = map.block_to_voxel(block_pos); - const VoxelBlock *block = _map->get_block(block_pos); + const VoxelBlock *block = map.get_block(block_pos); if (block != nullptr) { // Doing ONLY reads here. { diff --git a/edition/voxel_tool_terrain.h b/edition/voxel_tool_terrain.h index bea8e28c..b89640c8 100644 --- a/edition/voxel_tool_terrain.h +++ b/edition/voxel_tool_terrain.h @@ -11,7 +11,7 @@ class VoxelToolTerrain : public VoxelTool { GDCLASS(VoxelToolTerrain, VoxelTool) public: VoxelToolTerrain(); - VoxelToolTerrain(VoxelTerrain *terrain, Ref map); + VoxelToolTerrain(VoxelTerrain *terrain); bool is_area_editable(const Rect3i &box) const override; Ref raycast(Vector3 pos, Vector3 dir, float max_distance, uint32_t collision_mask) override; @@ -34,7 +34,6 @@ private: static void _bind_methods(); VoxelTerrain *_terrain = nullptr; - Ref _map; }; #endif // VOXEL_TOOL_TERRAIN_H diff --git a/register_types.cpp b/register_types.cpp index 9fe923b9..e27fdb40 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -55,7 +55,6 @@ void register_voxel_types() { // Storage ClassDB::register_class(); - ClassDB::register_class(); // Nodes ClassDB::register_class(); diff --git a/terrain/voxel_box_mover.cpp b/terrain/voxel_box_mover.cpp index 0aa9c807..dedb0cae 100644 --- a/terrain/voxel_box_mover.cpp +++ b/terrain/voxel_box_mover.cpp @@ -128,9 +128,7 @@ Vector3 VoxelBoxMover::get_motion(Vector3 pos, Vector3 motion, AABB aabb, VoxelT // Collect collisions with the terrain - Ref voxels_ref = terrain->get_storage(); - ERR_FAIL_COND_V(voxels_ref.is_null(), Vector3()); - const VoxelMap &voxels = **voxels_ref; + const VoxelMap &voxels = terrain->get_storage(); const int min_x = int(Math::floor(expanded_box.position.x)); const int min_y = int(Math::floor(expanded_box.position.y)); diff --git a/terrain/voxel_lod_terrain.cpp b/terrain/voxel_lod_terrain.cpp index 52a6fa37..012de490 100644 --- a/terrain/voxel_lod_terrain.cpp +++ b/terrain/voxel_lod_terrain.cpp @@ -66,8 +66,6 @@ VoxelLodTerrain::VoxelLodTerrain() { _volume_id = VoxelServer::get_singleton()->add_volume(&_reception_buffers, VoxelServer::VOLUME_SPARSE_OCTREE); VoxelServer::get_singleton()->set_volume_octree_split_scale(_volume_id, get_lod_split_scale()); - _lods[0].map.instance(); - // TODO Being able to set a LOD smaller than the stream is probably a bad idea, // Because it prevents edits from propagating up to the last one, they will be left out of sync set_lod_count(4); @@ -119,11 +117,11 @@ Ref VoxelLodTerrain::get_stream() const { } unsigned int VoxelLodTerrain::get_block_size() const { - return _lods[0].map->get_block_size(); + return _lods[0].map.get_block_size(); } unsigned int VoxelLodTerrain::get_block_size_pow2() const { - return _lods[0].map->get_block_size_pow2(); + return _lods[0].map.get_block_size_pow2(); } void VoxelLodTerrain::set_stream(Ref p_stream) { @@ -198,7 +196,7 @@ void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) { } void VoxelLodTerrain::_set_block_size_po2(int p_block_size_po2) { - _lods[0].map->create(p_block_size_po2, 0); + _lods[0].map.create(p_block_size_po2, 0); } // Marks intersecting blocks in the area as modified, updates LODs and schedules remeshing. @@ -214,7 +212,7 @@ void VoxelLodTerrain::post_edit_area(Rect3i p_box) { void VoxelLodTerrain::post_edit_block_lod0(Vector3i block_pos_lod0) { Lod &lod0 = _lods[0]; - VoxelBlock *block = lod0.map->get_block(block_pos_lod0); + VoxelBlock *block = lod0.map.get_block(block_pos_lod0); ERR_FAIL_COND(block == nullptr); block->set_modified(true); @@ -265,10 +263,8 @@ void VoxelLodTerrain::stop_updater() { Lod &lod = _lods[i]; lod.blocks_pending_update.clear(); - if (lod.map.is_valid()) { - ResetMeshStateAction a; - lod.map->for_all_blocks(a); - } + ResetMeshStateAction a; + lod.map.for_all_blocks(a); } } @@ -348,18 +344,12 @@ void VoxelLodTerrain::reset_maps() { // Instance new maps if we have more lods, or clear them otherwise if (lod_index < get_lod_count()) { - if (lod.map.is_null()) { - lod.map.instance(); - } - lod.map->create(get_block_size_pow2(), lod_index); + lod.map.create(get_block_size_pow2(), lod_index); lod.blocks_to_load.clear(); - lod.last_view_distance_blocks = 0; } else { - if (lod.map.is_valid()) { - lod.map.unref(); - } + lod.map.clear(); } } @@ -392,7 +382,7 @@ Vector3 VoxelLodTerrain::voxel_to_block_position(Vector3 vpos, int lod_index) co ERR_FAIL_COND_V(lod_index < 0, Vector3()); ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3()); const Lod &lod = _lods[lod_index]; - Vector3i bpos = lod.map->voxel_to_block(Vector3i(vpos)) >> lod_index; + Vector3i bpos = lod.map.voxel_to_block(Vector3i(vpos)) >> lod_index; return bpos.to_vec3(); } @@ -432,11 +422,9 @@ void VoxelLodTerrain::_notification(int p_what) { case NOTIFICATION_ENTER_WORLD: { World *world = *get_world(); for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) { - if (_lods[lod_index].map.is_valid()) { - _lods[lod_index].map->for_all_blocks([world](VoxelBlock *block) { - block->set_world(world); - }); - } + _lods[lod_index].map.for_all_blocks([world](VoxelBlock *block) { + block->set_world(world); + }); } #ifdef TOOLS_ENABLED if (is_showing_gizmos()) { @@ -447,11 +435,9 @@ void VoxelLodTerrain::_notification(int p_what) { case NOTIFICATION_EXIT_WORLD: { for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) { - if (_lods[lod_index].map.is_valid()) { - _lods[lod_index].map->for_all_blocks([](VoxelBlock *block) { - block->set_world(nullptr); - }); - } + _lods[lod_index].map.for_all_blocks([](VoxelBlock *block) { + block->set_world(nullptr); + }); } #ifdef TOOLS_ENABLED _debug_renderer.set_world(nullptr); @@ -461,11 +447,9 @@ void VoxelLodTerrain::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { const bool visible = is_visible(); for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) { - if (_lods[lod_index].map.is_valid()) { - _lods[lod_index].map->for_all_blocks([visible](VoxelBlock *block) { - block->set_parent_visible(visible); - }); - } + _lods[lod_index].map.for_all_blocks([visible](VoxelBlock *block) { + block->set_parent_visible(visible); + }); } #ifdef TOOLS_ENABLED if (is_showing_gizmos()) { @@ -487,11 +471,9 @@ void VoxelLodTerrain::_notification(int p_what) { } for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) { - if (_lods[lod_index].map.is_valid()) { - _lods[lod_index].map->for_all_blocks([&transform](VoxelBlock *block) { - block->set_parent_transform(transform); - }); - } + _lods[lod_index].map.for_all_blocks([&transform](VoxelBlock *block) { + block->set_parent_transform(transform); + }); } } break; @@ -506,7 +488,7 @@ Vector3 VoxelLodTerrain::get_local_viewer_pos() const { return Vector3(); } else { - Vector3 pos = (_lods[0].last_viewer_block_pos << _lods[0].map->get_block_size_pow2()).to_vec3(); + Vector3 pos = (_lods[0].last_viewer_block_pos << _lods[0].map.get_block_size_pow2()).to_vec3(); // TODO Support for multiple viewers, this is a placeholder implementation VoxelServer::get_singleton()->for_each_viewer([&pos](const VoxelServer::Viewer &viewer, uint32_t viewer_id) { @@ -522,7 +504,7 @@ Vector3 VoxelLodTerrain::get_local_viewer_pos() const { void VoxelLodTerrain::try_schedule_loading_with_neighbors(const Vector3i &p_bpos, int lod_index) { Lod &lod = _lods[lod_index]; - const int p = lod.map->get_block_size_pow2() + lod_index; + const int p = lod.map.get_block_size_pow2() + lod_index; const int bound_min_x = _bounds_in_voxels.pos.x >> p; const int bound_min_y = _bounds_in_voxels.pos.y >> p; @@ -542,7 +524,7 @@ void VoxelLodTerrain::try_schedule_loading_with_neighbors(const Vector3i &p_bpos for (bpos.y = min_y; bpos.y < max_y; ++bpos.y) { for (bpos.z = min_z; bpos.z < max_z; ++bpos.z) { for (bpos.x = min_x; bpos.x < max_x; ++bpos.x) { - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); if (block == nullptr) { if (!lod.loading_blocks.has(bpos)) { @@ -587,7 +569,7 @@ bool VoxelLodTerrain::is_block_surrounded(const Vector3i &p_bpos, int lod_index, bool VoxelLodTerrain::check_block_loaded_and_updated(const Vector3i &p_bpos, int lod_index) { Lod &lod = _lods[lod_index]; - VoxelBlock *block = lod.map->get_block(p_bpos); + VoxelBlock *block = lod.map.get_block(p_bpos); if (block == nullptr) { try_schedule_loading_with_neighbors(p_bpos, lod_index); return false; @@ -603,7 +585,7 @@ bool VoxelLodTerrain::check_block_mesh_updated(VoxelBlock *block) { switch (block->get_mesh_state()) { case VoxelBlock::MESH_NEVER_UPDATED: case VoxelBlock::MESH_NEED_UPDATE: - if (is_block_surrounded(block->position, block->lod_index, **lod.map)) { + if (is_block_surrounded(block->position, block->lod_index, lod.map)) { lod.blocks_pending_update.push_back(block->position); block->set_mesh_state(VoxelBlock::MESH_UPDATE_NOT_SENT); } else { @@ -693,7 +675,7 @@ void VoxelLodTerrain::_process() { // Each LOD keeps a box of loaded blocks, and only some of the blocks will get polygonized. // The player can edit them so changes can be propagated to lower lods. - unsigned int block_size_po2 = _lods[0].map->get_block_size_pow2() + lod_index; + unsigned int block_size_po2 = _lods[0].map.get_block_size_pow2() + lod_index; Vector3i viewer_block_pos_within_lod = VoxelMap::voxel_to_block_b(viewer_pos, block_size_po2); const Rect3i bounds_in_blocks = Rect3i( @@ -734,7 +716,7 @@ void VoxelLodTerrain::_process() { if (padded_new_box.contains(bpos)) { return false; } else { - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); if (block != nullptr) { block->set_mesh_state(VoxelBlock::MESH_NEED_UPDATE); } @@ -777,7 +759,7 @@ void VoxelLodTerrain::_process() { Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index); - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); if (block) { block->set_visible(false); } @@ -872,7 +854,7 @@ void VoxelLodTerrain::_process() { void create_child(Vector3i node_pos, int lod_index) { Lod &lod = self->_lods[lod_index]; Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index); - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); // Never show a child that hasn't been meshed CRASH_COND(block == nullptr); @@ -886,7 +868,7 @@ void VoxelLodTerrain::_process() { void destroy_child(Vector3i node_pos, int lod_index) { Lod &lod = self->_lods[lod_index]; Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index); - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); if (block) { block->set_visible(false); @@ -897,7 +879,7 @@ void VoxelLodTerrain::_process() { void show_parent(Vector3i node_pos, int lod_index) { Lod &lod = self->_lods[lod_index]; Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index); - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); // If we teleport far away, the area we were in is going to merge, // and blocks may have been unloaded completely. @@ -948,7 +930,7 @@ void VoxelLodTerrain::_process() { Lod &lod = self->_lods[parent_lod_index]; Vector3i bpos = node_pos + (block_offset_lod0 >> parent_lod_index); - VoxelBlock *block = lod.map->get_block(bpos); + VoxelBlock *block = lod.map.get_block(bpos); if (block == nullptr) { // The block got unloaded. Exceptionally, we can join. @@ -1047,7 +1029,7 @@ void VoxelLodTerrain::_process() { continue; } - if (ob.voxels->get_size() != Vector3i(lod.map->get_block_size())) { + if (ob.voxels->get_size() != Vector3i(lod.map.get_block_size())) { // Voxel block size is incorrect, drop it ERR_PRINT("Block size obtained from stream is different from expected size"); ++_stats.dropped_block_loads; @@ -1055,7 +1037,7 @@ void VoxelLodTerrain::_process() { } // Store buffer - VoxelBlock *block = lod.map->set_block_buffer(ob.position, ob.voxels); + VoxelBlock *block = lod.map.set_block_buffer(ob.position, ob.voxels); //print_line(String("Adding block {0} at lod {1}").format(varray(eo.block_position.to_vec3(), eo.lod))); // The block will be made visible and meshed only by LodOctree block->set_visible(false); @@ -1101,7 +1083,7 @@ void VoxelLodTerrain::_process() { VOXEL_PROFILE_SCOPE(); const Vector3i block_pos = lod.blocks_pending_update[bi]; - VoxelBlock *block = lod.map->get_block(block_pos); + VoxelBlock *block = lod.map.get_block(block_pos); CRASH_COND(block == nullptr); // All blocks we get here must be in the scheduled state CRASH_COND(block->get_mesh_state() != VoxelBlock::MESH_UPDATE_NOT_SENT); @@ -1112,7 +1094,7 @@ void VoxelLodTerrain::_process() { mesh_request.lod = lod_index; for (unsigned int i = 0; i < Cube::MOORE_AREA_3D_COUNT; ++i) { const Vector3i npos = block_pos + Cube::g_ordered_moore_area_3d[i]; - VoxelBlock *nblock = lod.map->get_block(npos); + VoxelBlock *nblock = lod.map.get_block(npos); // The block can actually be null on some occasions. Not sure yet if it's that bad //CRASH_COND(nblock == nullptr); if (nblock == nullptr) { @@ -1159,7 +1141,7 @@ void VoxelLodTerrain::_process() { Lod &lod = _lods[ob.lod]; - VoxelBlock *block = lod.map->get_block(ob.position); + VoxelBlock *block = lod.map.get_block(ob.position); if (block == nullptr) { // That block is no longer loaded, drop the result ++_stats.dropped_block_meshs; @@ -1256,7 +1238,7 @@ void VoxelLodTerrain::flush_pending_lod_edits() { Lod &lod0 = _lods[0]; for (unsigned int i = 0; i < lod0.blocks_pending_lodding.size(); ++i) { const Vector3i bpos = lod0.blocks_pending_lodding[i]; - VoxelBlock *block = lod0.map->get_block(bpos); + VoxelBlock *block = lod0.map.get_block(bpos); block->set_needs_lodding(false); L::schedule_update(block, lod0.blocks_pending_update); } @@ -1274,8 +1256,8 @@ void VoxelLodTerrain::flush_pending_lod_edits() { const Vector3i src_bpos = src_lod.blocks_pending_lodding[i]; const Vector3i dst_bpos = src_bpos >> 1; - VoxelBlock *src_block = src_lod.map->get_block(src_bpos); - VoxelBlock *dst_block = dst_lod.map->get_block(dst_bpos); + VoxelBlock *src_block = src_lod.map.get_block(src_bpos); + VoxelBlock *dst_block = dst_lod.map.get_block(dst_bpos); src_block->set_needs_lodding(false); @@ -1361,11 +1343,10 @@ void VoxelLodTerrain::immerge_block(Vector3i block_pos, int lod_index) { VOXEL_PROFILE_SCOPE(); ERR_FAIL_COND(lod_index >= get_lod_count()); - ERR_FAIL_COND(_lods[lod_index].map.is_null()); Lod &lod = _lods[lod_index]; - lod.map->remove_block(block_pos, ScheduleSaveAction{ _blocks_to_save, _shader_material_pool, false }); + lod.map.remove_block(block_pos, ScheduleSaveAction{ _blocks_to_save, _shader_material_pool, false }); lod.loading_blocks.erase(block_pos); @@ -1382,7 +1363,7 @@ void VoxelLodTerrain::save_all_modified_blocks(bool with_copy) { for (int i = 0; i < _lod_count; ++i) { // That may cause a stutter, so should be used when the player won't notice - _lods[i].map->for_all_blocks(ScheduleSaveAction{ _blocks_to_save, _shader_material_pool, with_copy }); + _lods[i].map.for_all_blocks(ScheduleSaveAction{ _blocks_to_save, _shader_material_pool, with_copy }); } // And flush immediately @@ -1398,11 +1379,10 @@ void VoxelLodTerrain::add_transition_update(VoxelBlock *block) { void VoxelLodTerrain::add_transition_updates_around(Vector3i block_pos, int lod_index) { Lod &lod = _lods[lod_index]; - CRASH_COND(lod.map.is_null()); for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) { Vector3i npos = block_pos + Cube::g_side_normals[dir]; - VoxelBlock *nblock = lod.map->get_block(npos); + VoxelBlock *nblock = lod.map.get_block(npos); if (nblock != nullptr) { add_transition_update(nblock); @@ -1430,21 +1410,15 @@ void VoxelLodTerrain::process_transition_updates() { uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) const { uint8_t transition_mask = 0; - if (lod_index + 1 >= (int)_lods.size()) { + if (lod_index + 1 >= _lod_count) { return transition_mask; } const Lod &lower_lod = _lods[lod_index + 1]; - - if (!lower_lod.map.is_valid()) { - return transition_mask; - } - - Vector3i lower_pos = block_pos >> 1; - Vector3i upper_pos = block_pos << 1; - const Lod &lod = _lods[lod_index]; - CRASH_COND(!lod.map.is_valid()); + + const Vector3i lower_pos = block_pos >> 1; + const Vector3i upper_pos = block_pos << 1; // Based on octree rules, and the fact it must have run before, check neighbor blocks of same LOD: // If one is missing or not visible, it means either of the following: @@ -1455,7 +1429,7 @@ uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) { Vector3i npos = block_pos + Cube::g_side_normals[dir]; - const VoxelBlock *nblock = lod.map->get_block(npos); + const VoxelBlock *nblock = lod.map.get_block(npos); if (nblock != nullptr && nblock->is_visible()) { visible_neighbors_of_same_lod |= (1 << dir); @@ -1476,7 +1450,7 @@ uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) const Vector3i lower_neighbor_pos = (block_pos + side_normal) >> 1; if (lower_neighbor_pos != lower_pos) { - const VoxelBlock *lower_neighbor_block = lower_lod.map->get_block(lower_neighbor_pos); + const VoxelBlock *lower_neighbor_block = lower_lod.map.get_block(lower_neighbor_pos); if (lower_neighbor_block != nullptr && lower_neighbor_block->is_visible()) { // The block has a visible neighbor of lower LOD @@ -1499,7 +1473,7 @@ uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) } const Lod &upper_lod = _lods[lod_index - 1]; - const VoxelBlock *upper_neighbor_block = upper_lod.map->get_block(upper_neighbor_pos); + const VoxelBlock *upper_neighbor_block = upper_lod.map.get_block(upper_neighbor_pos); if (upper_neighbor_block == nullptr || upper_neighbor_block->is_visible() == false) { // The block has no visible neighbor yet. World border? Assume lower LOD. @@ -1599,8 +1573,8 @@ Array VoxelLodTerrain::debug_raycast_block(Vector3 world_origin, Vector3 world_d while (distance < max_distance && hits.size() == 0) { for (int lod_index = 0; lod_index < _lod_count; ++lod_index) { const Lod &lod = _lods[lod_index]; - Vector3i bpos = lod.map->voxel_to_block(Vector3i(pos)) >> lod_index; - const VoxelBlock *block = lod.map->get_block(bpos); + Vector3i bpos = lod.map.voxel_to_block(Vector3i(pos)) >> lod_index; + const VoxelBlock *block = lod.map.get_block(bpos); if (block != nullptr && block->is_visible() && block->has_mesh()) { Dictionary d; d["position"] = block->position.to_vec3(); @@ -1626,7 +1600,7 @@ Dictionary VoxelLodTerrain::debug_get_block_info(Vector3 fbpos, int lod_index) c bool meshed = false; bool visible = false; int loading_state = 0; - const VoxelBlock *block = lod.map->get_block(bpos); + const VoxelBlock *block = lod.map.get_block(bpos); if (block) { meshed = !block->has_mesh() && block->get_mesh_state() != VoxelBlock::MESH_UP_TO_DATE; @@ -1713,7 +1687,7 @@ Array VoxelLodTerrain::_b_debug_print_sdf_top_down(Vector3 center, Vector3 exten const Lod &lod = _lods[lod_index]; world_box.for_each_cell([&](const Vector3i &world_pos) { - const float v = lod.map->get_voxel_f(world_pos, VoxelBuffer::CHANNEL_SDF); + const float v = lod.map.get_voxel_f(world_pos, VoxelBuffer::CHANNEL_SDF); const Vector3i rpos = world_pos - world_box.pos; buffer.set_voxel_f(v, rpos.x, rpos.y, rpos.z, VoxelBuffer::CHANNEL_SDF); }); @@ -1728,7 +1702,7 @@ Array VoxelLodTerrain::_b_debug_print_sdf_top_down(Vector3 center, Vector3 exten int VoxelLodTerrain::_b_debug_get_block_count() const { int sum = 0; for (int lod_index = 0; lod_index < _lod_count; ++lod_index) { - sum += _lods[lod_index].map->get_block_count(); + sum += _lods[lod_index].map.get_block_count(); } return sum; } diff --git a/terrain/voxel_lod_terrain.h b/terrain/voxel_lod_terrain.h index 954593eb..aaee46d6 100644 --- a/terrain/voxel_lod_terrain.h +++ b/terrain/voxel_lod_terrain.h @@ -2,8 +2,8 @@ #define VOXEL_LOD_TERRAIN_HPP #include "../server/voxel_server.h" -#include "../util/direct_mesh_instance.h" #include "lod_octree.h" +#include "voxel_map.h" #include #include @@ -11,10 +11,8 @@ #include "../editor/voxel_debug.h" #endif -class VoxelMap; class VoxelTool; class VoxelStream; -class VoxelBlock; // Paged terrain made of voxel blocks of variable level of detail. // Designed for highest view distances, preferably using smooth voxels. @@ -192,7 +190,7 @@ private: // Each LOD works in a set of coordinates spanning 2x more voxels the higher their index is struct Lod { - Ref map; + VoxelMap map; Set loading_blocks; std::vector blocks_pending_update; diff --git a/terrain/voxel_map.cpp b/terrain/voxel_map.cpp index c3159b30..4db4f9bc 100644 --- a/terrain/voxel_map.cpp +++ b/terrain/voxel_map.cpp @@ -225,7 +225,7 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsign dst_buffer.set_channel_depth(channel, src_buffer.get_channel_depth(channel)); - Vector3i offset = block_to_voxel(bpos); + const Vector3i offset = block_to_voxel(bpos); RWLockRead lock(src_buffer.get_lock()); @@ -239,7 +239,7 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsign } else { // For now, inexistent blocks default to hardcoded defaults, corresponding to "empty space". // If we want to change this, we may have to add an API for that. - Vector3i offset = block_to_voxel(bpos); + const Vector3i offset = block_to_voxel(bpos); dst_buffer.fill_area( _default_voxel[channel], offset - min_pos, @@ -281,25 +281,3 @@ bool VoxelMap::is_area_fully_loaded(const Rect3i voxels_box) const { return has_block(pos); }); } - -void VoxelMap::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_b_get_voxel, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_voxel", "value", "x", "y", "z", "c"), &VoxelMap::_b_set_voxel, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_voxel_f", "x", "y", "z", "c"), &VoxelMap::_b_get_voxel_f, DEFVAL(VoxelBuffer::CHANNEL_SDF)); - ClassDB::bind_method(D_METHOD("set_voxel_f", "value", "x", "y", "z", "c"), &VoxelMap::_b_set_voxel_f, DEFVAL(VoxelBuffer::CHANNEL_SDF)); - ClassDB::bind_method(D_METHOD("get_voxel_v", "pos", "c"), &VoxelMap::_b_get_voxel_v, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_voxel_v", "value", "pos", "c"), &VoxelMap::_b_set_voxel_v, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_default_voxel", "channel"), &VoxelMap::get_default_voxel, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_default_voxel", "value", "channel"), &VoxelMap::set_default_voxel, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_block", "x", "y", "z"), &VoxelMap::_b_has_block); - ClassDB::bind_method(D_METHOD("get_buffer_copy", "min_pos", "out_buffer", "channel"), &VoxelMap::_b_get_buffer_copy, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_block_buffer", "block_pos", "buffer"), &VoxelMap::_b_set_block_buffer); - ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelMap::_b_voxel_to_block); - ClassDB::bind_method(D_METHOD("block_to_voxel", "block_pos"), &VoxelMap::_b_block_to_voxel); - ClassDB::bind_method(D_METHOD("get_block_size"), &VoxelMap::get_block_size); -} - -void VoxelMap::_b_get_buffer_copy(Vector3 pos, Ref dst_buffer_ref, unsigned int channel) { - ERR_FAIL_COND(dst_buffer_ref.is_null()); - get_buffer_copy(Vector3i(pos), **dst_buffer_ref, channel); -} diff --git a/terrain/voxel_map.h b/terrain/voxel_map.h index e2bd3fc7..b26f85ae 100644 --- a/terrain/voxel_map.h +++ b/terrain/voxel_map.h @@ -10,8 +10,7 @@ // Infinite voxel storage by means of octants like Gridmap, within a constant LOD. // Convenience functions to access VoxelBuffers internally will lock them to protect against multithreaded access. // However, the map itself is not thread-safe. -class VoxelMap : public Reference { - GDCLASS(VoxelMap, Reference) +class VoxelMap { public: // Converts voxel coodinates into block coordinates. // Don't use division because it introduces an offset in negative coordinates. @@ -95,6 +94,7 @@ public: int get_block_count() const; + // TODO Rename for_each_block template inline void for_all_blocks(Op_T op) { for (auto it = _blocks.begin(); it != _blocks.end(); ++it) { @@ -102,6 +102,14 @@ public: } } + // TODO Rename for_each_block + template + inline void for_all_blocks(Op_T op) const { + for (auto it = _blocks.begin(); it != _blocks.end(); ++it) { + op(*it); + } + } + bool is_area_fully_loaded(const Rect3i voxels_box) const; private: @@ -111,21 +119,6 @@ private: void set_block_size_pow2(unsigned int p); - static void _bind_methods(); - - int _b_get_voxel(int x, int y, int z, unsigned int c) { return get_voxel(Vector3i(x, y, z), c); } - void _b_set_voxel(int value, int x, int y, int z, unsigned int c) { set_voxel(value, Vector3i(x, y, z), c); } - float _b_get_voxel_f(int x, int y, int z, unsigned int c) { return get_voxel_f(Vector3i(x, y, z), c); } - void _b_set_voxel_f(float value, int x, int y, int z, unsigned int c) { set_voxel_f(value, Vector3i(x, y, z), c); } - int _b_get_voxel_v(Vector3 pos, unsigned int c) { return get_voxel(Vector3i(pos), c); } - void _b_set_voxel_v(int value, Vector3 pos, unsigned int c) { set_voxel(value, Vector3i(pos), c); } - bool _b_has_block(int x, int y, int z) { return has_block(Vector3i(x, y, z)); } - Vector3 _b_voxel_to_block(Vector3 pos) const { return voxel_to_block(Vector3i(pos)).to_vec3(); } - Vector3 _b_block_to_voxel(Vector3 pos) const { return block_to_voxel(Vector3i(pos)).to_vec3(); } - bool _b_is_block_surrounded(Vector3 pos) const { return is_block_surrounded(Vector3i(pos)); } - void _b_get_buffer_copy(Vector3 pos, Ref dst_buffer_ref, unsigned int channel = 0); - void _b_set_block_buffer(Vector3 bpos, Ref buffer) { set_block_buffer(Vector3i(bpos), buffer); } - private: // Voxel values that will be returned if access is out of map bounds FixedArray _default_voxel; diff --git a/terrain/voxel_terrain.cpp b/terrain/voxel_terrain.cpp index f0381748..19a3adfd 100644 --- a/terrain/voxel_terrain.cpp +++ b/terrain/voxel_terrain.cpp @@ -27,8 +27,6 @@ VoxelTerrain::VoxelTerrain() { _volume_id = VoxelServer::get_singleton()->add_volume(&_reception_buffers, VoxelServer::VOLUME_SPARSE_GRID); - _map.instance(); - Ref library; library.instance(); set_voxel_library(library); @@ -140,11 +138,11 @@ void VoxelTerrain::set_block_size_po2(unsigned int p_block_size_po2) { } void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) { - _map->create(p_block_size_po2, 0); + _map.create(p_block_size_po2, 0); } unsigned int VoxelTerrain::get_block_size_pow2() const { - return _map->get_block_size_pow2(); + return _map.get_block_size_pow2(); } void VoxelTerrain::restart_stream() { @@ -203,12 +201,12 @@ void VoxelTerrain::set_generate_collisions(bool enabled) { } unsigned int VoxelTerrain::get_max_view_distance() const { - return _max_view_distance_blocks * _map->get_block_size(); + return _max_view_distance_blocks * _map.get_block_size(); } void VoxelTerrain::set_max_view_distance(unsigned int distance_in_voxels) { ERR_FAIL_COND(distance_in_voxels < 0); - const unsigned int d = distance_in_voxels / _map->get_block_size(); + const unsigned int d = distance_in_voxels / _map.get_block_size(); if (d != _max_view_distance_blocks) { PRINT_VERBOSE(String("View distance changed from ") + String::num(_max_view_distance_blocks) + String(" blocks to ") + String::num(d)); @@ -229,7 +227,7 @@ Ref VoxelTerrain::get_material(unsigned int id) const { } void VoxelTerrain::make_block_dirty(Vector3i bpos) { - VoxelBlock *block = _map->get_block(bpos); + VoxelBlock *block = _map.get_block(bpos); ERR_FAIL_COND_MSG(block == nullptr, "Requested update to a block that isn't loaded"); make_block_dirty(block); } @@ -261,7 +259,7 @@ void VoxelTerrain::try_schedule_block_update(VoxelBlock *block) { } void VoxelTerrain::view_block(Vector3i bpos, bool data_flag, bool mesh_flag, bool collision_flag) { - VoxelBlock *block = _map->get_block(bpos); + VoxelBlock *block = _map.get_block(bpos); if (block == nullptr) { // The block isn't loaded @@ -311,7 +309,7 @@ void VoxelTerrain::view_block(Vector3i bpos, bool data_flag, bool mesh_flag, boo } void VoxelTerrain::unview_block(Vector3i bpos, bool data_flag, bool mesh_flag, bool collision_flag) { - VoxelBlock *block = _map->get_block(bpos); + VoxelBlock *block = _map.get_block(bpos); if (block == nullptr) { // The block isn't loaded @@ -393,9 +391,7 @@ struct ScheduleSaveAction { } // namespace void VoxelTerrain::immerge_block(Vector3i bpos) { - ERR_FAIL_COND(_map.is_null()); - - _map->remove_block(bpos, [this, bpos](VoxelBlock *block) { + _map.remove_block(bpos, [this, bpos](VoxelBlock *block) { emit_block_unloaded(block); // Note: no need to copy the block because it gets removed from the map anyways ScheduleSaveAction{ _blocks_to_save, false }(block); @@ -417,7 +413,7 @@ void VoxelTerrain::immerge_block(Vector3i bpos) { void VoxelTerrain::save_all_modified_blocks(bool with_copy) { // That may cause a stutter, so should be used when the player won't notice - _map->for_all_blocks(ScheduleSaveAction{ _blocks_to_save, with_copy }); + _map.for_all_blocks(ScheduleSaveAction{ _blocks_to_save, with_copy }); // And flush immediately send_block_data_requests(); } @@ -454,7 +450,7 @@ Dictionary VoxelTerrain::get_statistics() const { void VoxelTerrain::make_all_view_dirty() { // Mark all loaded blocks dirty within range of viewers that require meshes - _map->for_all_blocks([this](VoxelBlock *b) { + _map.for_all_blocks([this](VoxelBlock *b) { if (b->viewers.get(VoxelViewerRefCount::TYPE_MESH) > 0) { make_block_dirty(b); } @@ -490,7 +486,7 @@ void VoxelTerrain::stop_updater() { _blocks_pending_update.clear(); ResetMeshStateAction a; - _map->for_all_blocks(a); + _map.for_all_blocks(a); } void VoxelTerrain::start_streamer() { @@ -507,10 +503,10 @@ void VoxelTerrain::stop_streamer() { void VoxelTerrain::reset_map() { // Discard everything, to reload it all - _map->for_all_blocks([this](VoxelBlock *block) { + _map.for_all_blocks([this](VoxelBlock *block) { emit_block_unloaded(block); }); - _map->create(get_block_size_pow2(), 0); + _map.create(get_block_size_pow2(), 0); _loading_blocks.clear(); _blocks_pending_load.clear(); @@ -533,18 +529,18 @@ void VoxelTerrain::make_voxel_dirty(Vector3i pos) { } // Update the block in which the voxel is - const Vector3i bpos = _map->voxel_to_block(pos); + const Vector3i bpos = _map.voxel_to_block(pos); make_block_dirty(bpos); //OS::get_singleton()->print("Dirty (%i, %i, %i)\n", bpos.x, bpos.y, bpos.z); // Update neighbor blocks if the voxel is touching a boundary - const Vector3i rpos = _map->to_local(pos); + const Vector3i rpos = _map.to_local(pos); // TODO Thread-safe way of getting this parameter const bool check_corners = true; //_mesher->get_occlusion_enabled(); - const int max = _map->get_block_size() - 1; + const int max = _map.get_block_size() - 1; if (rpos.x == 0) { make_block_dirty(bpos - Vector3i(1, 0, 0)); @@ -657,7 +653,7 @@ void VoxelTerrain::make_area_dirty(Rect3i box) { max_pos += Vector3i(1, 1, 1); } else { - Vector3i min_rpos = _map->to_local(min_pos); + Vector3i min_rpos = _map.to_local(min_pos); if (min_rpos.x == 0) { --min_pos.x; } @@ -668,8 +664,8 @@ void VoxelTerrain::make_area_dirty(Rect3i box) { --min_pos.z; } - const int max = _map->get_block_size() - 1; - const Vector3i max_rpos = _map->to_local(max_pos); + const int max = _map.get_block_size() - 1; + const Vector3i max_rpos = _map.to_local(max_pos); if (max_rpos.x == max) { ++max_pos.x; } @@ -681,8 +677,8 @@ void VoxelTerrain::make_area_dirty(Rect3i box) { } } - const Vector3i min_block_pos = _map->voxel_to_block(min_pos); - const Vector3i max_block_pos = _map->voxel_to_block(max_pos); + const Vector3i min_block_pos = _map.voxel_to_block(min_pos); + const Vector3i max_block_pos = _map.voxel_to_block(max_pos); Vector3i bpos; for (bpos.z = min_block_pos.z; bpos.z <= max_block_pos.z; ++bpos.z) { @@ -731,18 +727,15 @@ void VoxelTerrain::_notification(int p_what) { break; case NOTIFICATION_ENTER_WORLD: { - ERR_FAIL_COND(_map.is_null()); - _map->for_all_blocks(SetWorldAction(*get_world())); + _map.for_all_blocks(SetWorldAction(*get_world())); } break; case NOTIFICATION_EXIT_WORLD: - ERR_FAIL_COND(_map.is_null()); - _map->for_all_blocks(SetWorldAction(nullptr)); + _map.for_all_blocks(SetWorldAction(nullptr)); break; case NOTIFICATION_VISIBILITY_CHANGED: - ERR_FAIL_COND(_map.is_null()); - _map->for_all_blocks(SetParentVisibilityAction(is_visible())); + _map.for_all_blocks(SetParentVisibilityAction(is_visible())); break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -755,7 +748,7 @@ void VoxelTerrain::_notification(int p_what) { return; } - _map->for_all_blocks([&transform](VoxelBlock *block) { + _map.for_all_blocks([&transform](VoxelBlock *block) { block->set_parent_transform(transform); }); @@ -824,8 +817,6 @@ void VoxelTerrain::_process() { // print_line(String("D:{0} M:{1}") // .format(varray(_reception_buffers.data_output.size(), _reception_buffers.mesh_output.size()))); - ERR_FAIL_COND(_map.is_null()); - ProfilingClock profiling_clock; _stats.dropped_block_loads = 0; @@ -875,7 +866,7 @@ void VoxelTerrain::_process() { p.state.view_distance_blocks = min(view_distance_voxels >> get_block_size_pow2(), _max_view_distance_blocks); - p.state.block_position = _map->voxel_to_block(Vector3i(local_position)); + p.state.block_position = _map.voxel_to_block(Vector3i(local_position)); p.state.requires_collisions = VoxelServer::get_singleton()->is_viewer_requiring_collisions(viewer_id); p.state.requires_meshes = VoxelServer::get_singleton()->is_viewer_requiring_visuals(viewer_id); }); @@ -1026,7 +1017,7 @@ void VoxelTerrain::_process() { CRASH_COND(ob.voxels.is_null()); - const Vector3i expected_block_size = Vector3i(_map->get_block_size()); + const Vector3i expected_block_size = Vector3i(_map.get_block_size()); if (ob.voxels->get_size() != expected_block_size) { // Voxel block size is incorrect, drop it ERR_PRINT(String("Block size obtained from stream is different from expected size. " @@ -1037,9 +1028,9 @@ void VoxelTerrain::_process() { } // Create or update block data - VoxelBlock *block = _map->get_block(block_pos); + VoxelBlock *block = _map.get_block(block_pos); const bool was_not_loaded = block == nullptr; - block = _map->set_block_buffer(block_pos, ob.voxels); + block = _map.set_block_buffer(block_pos, ob.voxels); block->set_world(get_world()); if (was_not_loaded) { @@ -1064,8 +1055,8 @@ void VoxelTerrain::_process() { const Vector3i npos = block_pos + ndir; // TODO What if the map is really composed of empty blocks? - if (_map->is_block_surrounded(npos)) { - VoxelBlock *nblock = _map->get_block(npos); + if (_map.is_block_surrounded(npos)) { + VoxelBlock *nblock = _map.get_block(npos); if (nblock == nullptr || nblock->get_mesh_state() == VoxelBlock::MESH_UPDATE_NOT_SENT) { // Assuming it is scheduled to be updated already. // In case of BLOCK_UPDATE_SENT, we'll have to resend it. @@ -1108,7 +1099,7 @@ void VoxelTerrain::_process() { // but that will slow down meshing a lot. // TODO This is one reason to separate terrain systems between blocky and smooth (other reason is LOD) if (!(_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF))) { - VoxelBlock *block = _map->get_block(block_pos); + VoxelBlock *block = _map.get_block(block_pos); if (block == nullptr) { continue; } else { @@ -1141,7 +1132,7 @@ void VoxelTerrain::_process() { } } - VoxelBlock *block = _map->get_block(block_pos); + VoxelBlock *block = _map.get_block(block_pos); // If we got here, it must have been because of scheduling an update CRASH_COND(block == nullptr); @@ -1153,7 +1144,7 @@ void VoxelTerrain::_process() { mesh_request.lod = 0; for (unsigned int i = 0; i < Cube::MOORE_AREA_3D_COUNT; ++i) { const Vector3i npos = block_pos + Cube::g_ordered_moore_area_3d[i]; - VoxelBlock *nblock = _map->get_block(npos); + VoxelBlock *nblock = _map.get_block(npos); // The block can actually be null on some occasions. Not sure yet if it's that bad //CRASH_COND(nblock == nullptr); if (nblock == nullptr) { @@ -1190,7 +1181,7 @@ void VoxelTerrain::_process() { for (; queue_index < _reception_buffers.mesh_output.size() && os.get_ticks_msec() < timeout; ++queue_index) { const VoxelServer::BlockMeshOutput &ob = _reception_buffers.mesh_output[queue_index]; - VoxelBlock *block = _map->get_block(ob.position); + VoxelBlock *block = _map.get_block(ob.position); if (block == nullptr) { // That block is no longer loaded, drop the result ++_stats.dropped_block_meshs; @@ -1276,7 +1267,7 @@ void VoxelTerrain::_process() { } Ref VoxelTerrain::get_voxel_tool() { - Ref vt = memnew(VoxelToolTerrain(this, _map)); + Ref vt = memnew(VoxelToolTerrain(this)); if (_stream.is_valid()) { if (_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF)) { vt->set_channel(VoxelBuffer::CHANNEL_SDF); @@ -1332,11 +1323,11 @@ Rect3i VoxelTerrain::get_bounds() const { } Vector3 VoxelTerrain::_b_voxel_to_block(Vector3 pos) { - return Vector3i(_map->voxel_to_block(pos)).to_vec3(); + return Vector3i(_map.voxel_to_block(pos)).to_vec3(); } Vector3 VoxelTerrain::_b_block_to_voxel(Vector3 pos) { - return Vector3i(_map->block_to_voxel(pos)).to_vec3(); + return Vector3i(_map.block_to_voxel(pos)).to_vec3(); } void VoxelTerrain::_b_save_modified_blocks() { @@ -1345,11 +1336,9 @@ void VoxelTerrain::_b_save_modified_blocks() { // Explicitely ask to save a block if it was modified void VoxelTerrain::_b_save_block(Vector3 p_block_pos) { - ERR_FAIL_COND(_map.is_null()); - const Vector3i block_pos(p_block_pos); - VoxelBlock *block = _map->get_block(block_pos); + VoxelBlock *block = _map.get_block(block_pos); ERR_FAIL_COND(block == nullptr); if (!block->is_modified()) { diff --git a/terrain/voxel_terrain.h b/terrain/voxel_terrain.h index e3208e50..7054ebf7 100644 --- a/terrain/voxel_terrain.h +++ b/terrain/voxel_terrain.h @@ -46,7 +46,9 @@ public: void set_material(unsigned int id, Ref material); Ref get_material(unsigned int id) const; - Ref get_storage() const { return _map; } + VoxelMap &get_storage() { return _map; } + const VoxelMap &get_storage() const { return _map; } + Ref get_voxel_tool(); void set_run_stream_in_editor(bool enable); @@ -139,7 +141,7 @@ private: std::vector _paired_viewers; // Voxel storage - Ref _map; + VoxelMap _map; // Area within which voxels can exist. // Note, these bounds might not be exactly represented. This volume is chunk-based, so the result will be From 25445f3f6cad46b729f36c2d480a21fc69ec1db7 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sat, 21 Nov 2020 18:31:28 +0000 Subject: [PATCH 004/102] Added debug function to dump a VoxelLodTerrain as nodes in a PackedScene --- terrain/voxel_block.h | 13 ++++++++++++ terrain/voxel_lod_terrain.cpp | 40 +++++++++++++++++++++++++++++++++++ terrain/voxel_lod_terrain.h | 1 + 3 files changed, 54 insertions(+) diff --git a/terrain/voxel_block.h b/terrain/voxel_block.h index e2df3a08..8db2dc9b 100644 --- a/terrain/voxel_block.h +++ b/terrain/voxel_block.h @@ -72,6 +72,19 @@ public: bool is_modified() const; void set_modified(bool modified); + template + void for_each_mesh_instance_with_transform(F f) const { + const Transform local_transform(Basis(), _position_in_voxels.to_vec3()); + const Transform 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: VoxelBlock(); diff --git a/terrain/voxel_lod_terrain.cpp b/terrain/voxel_lod_terrain.cpp index 012de490..c46a4a0c 100644 --- a/terrain/voxel_lod_terrain.cpp +++ b/terrain/voxel_lod_terrain.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include namespace { @@ -1707,6 +1709,43 @@ int VoxelLodTerrain::_b_debug_get_block_count() const { return sum; } +Error VoxelLodTerrain::_b_debug_dump_as_scene(String fpath) const { + Spatial *root = memnew(Spatial); + root->set_name(get_name()); + + for (int lod_index = 0; lod_index < _lod_count; ++lod_index) { + const Lod &lod = _lods[lod_index]; + + lod.map.for_all_blocks([root](const VoxelBlock *block) { + block->for_each_mesh_instance_with_transform([root, block](const DirectMeshInstance &dmi, Transform t) { + Ref mesh = dmi.get_mesh(); + + if (mesh.is_valid()) { + MeshInstance *mi = memnew(MeshInstance); + mi->set_mesh(mesh); + mi->set_transform(t); + // TODO Transition mesh visibility? + mi->set_visible(block->is_visible()); + root->add_child(mi); + // The owner must be set after adding to parent + mi->set_owner(root); + } + }); + }); + } + + Ref scene = memnew(PackedScene); + Error err = scene->pack(root); + if (err != OK) { + return err; + } + err = ResourceSaver::save(fpath, scene, ResourceSaver::FLAG_BUNDLE_RESOURCES); + + memdelete(root); + + return err; +} + void VoxelLodTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VoxelLodTerrain::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &VoxelLodTerrain::get_stream); @@ -1752,6 +1791,7 @@ void VoxelLodTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("debug_print_sdf_top_down", "center", "extents"), &VoxelLodTerrain::_b_debug_print_sdf_top_down); ClassDB::bind_method(D_METHOD("debug_get_block_count"), &VoxelLodTerrain::_b_debug_get_block_count); + ClassDB::bind_method(D_METHOD("debug_dump_as_scene", "path"), &VoxelLodTerrain::_b_debug_dump_as_scene); //ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelLodTerrain::_on_stream_params_changed); diff --git a/terrain/voxel_lod_terrain.h b/terrain/voxel_lod_terrain.h index aaee46d6..21d9b894 100644 --- a/terrain/voxel_lod_terrain.h +++ b/terrain/voxel_lod_terrain.h @@ -151,6 +151,7 @@ private: AABB _b_get_voxel_bounds() const; Array _b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const; int _b_debug_get_block_count() const; + Error _b_debug_dump_as_scene(String fpath) const; struct OctreeItem { LodOctree octree; From 117802c80766d040ae4d549319d90636512e3d8e Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sat, 21 Nov 2020 19:51:41 +0000 Subject: [PATCH 005/102] Unused variable --- terrain/voxel_map.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/terrain/voxel_map.cpp b/terrain/voxel_map.cpp index 4db4f9bc..48d06cb7 100644 --- a/terrain/voxel_map.cpp +++ b/terrain/voxel_map.cpp @@ -253,7 +253,6 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsign } void VoxelMap::clear() { - const Vector3i *key = nullptr; for (auto it = _blocks.begin(); it != _blocks.end(); ++it) { VoxelBlock *block = *it; if (block == nullptr) { From e73cdaa7071d424e0ef0a1da458cc00a708b8d29 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 4 Dec 2020 22:01:41 +0000 Subject: [PATCH 006/102] Exclude editor stuff when building an export template --- .github/workflows/windows.yml | 66 +++++++++++++++++++++++++++++++++++ SCsub | 13 ++++--- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 8ae687b0..ee2fbf3e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -78,3 +78,69 @@ jobs: with: name: godot.windows.opt.tools.64.exe path: bin/godot.windows.opt.tools.64.exe + + windows-template: + # Windows 10 with latest image + runs-on: "windows-latest" + name: Editor + + steps: + # Clone Godot + - uses: actions/checkout@v2 + with: + repository: godotengine/godot + ref: 3.2 + + # Clone our module under the correct directory + - uses: actions/checkout@v2 + with: + path: modules/voxel + + # Upload cache on completion and check it out now + # Editing this is pretty dangerous for Windows since it can break and needs to be properly tested with a fresh cache. + - name: Load .scons_cache directory + id: windows-editor-cache + uses: RevoluPowered/cache@v2.1 + with: + path: /.scons_cache/ + key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + restore-keys: | + ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} + ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + + # Use python 3.x release (works cross platform; best to keep self contained in it's own step) + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + # Semantic version range syntax or exact version of a Python version + python-version: '3.x' + # Optional - x64 or x86 architecture, defaults to x64 + architecture: 'x64' + + # Setup scons, print python version and scons version info, so if anything is broken it won't run the build. + - name: Configuring Python packages + run: | + python -c "import sys; print(sys.version)" + python -m pip install scons pywin32 + python --version + scons --version + + # We should always be explicit with our flags usage here since it's gonna be sure to always set those flags + - name: Compilation + env: + SCONS_CACHE: /.scons_cache/ + run: | + scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=no tests=no target=release + + # TODO Such tests are able to run from Godot 4.0 only + # Execute unit tests for the editor + #- name: Unit Tests + # run: | + # ./bin/godot.windows.opt.tools.64.exe --test + + # Make build available + - uses: actions/upload-artifact@v2 + with: + name: godot.windows.opt.64.exe + path: bin/godot.windows.opt.64.exe \ No newline at end of file diff --git a/SCsub b/SCsub index 8addac2c..352c8b4e 100644 --- a/SCsub +++ b/SCsub @@ -3,7 +3,6 @@ Import('env_modules') env_voxel = env_modules.Clone() -# TODO Exclude editor stuff when building an export template? files = [ "*.cpp", "meshers/blocky/*.cpp", @@ -20,12 +19,18 @@ files = [ "server/*.cpp", "math/*.cpp", "edition/*.cpp", - "editor/*.cpp", - "editor/graph/*.cpp", - "editor/terrain/*.cpp", "thirdparty/lz4/*.c" ] +if env["tools"]: + # Editor-only stuff + editor_files = [ + "editor/*.cpp", + "editor/graph/*.cpp", + "editor/terrain/*.cpp", + ] + files += editor_files + for f in files: env_voxel.add_source_files(env.modules_sources, f) From 2f8687fd1277eb853aa22e7e945403d56fceaf6a Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Thu, 17 Dec 2020 21:45:44 +0000 Subject: [PATCH 007/102] Added About window and list of donors --- LICENSE.md | 2 +- README.md | 19 +++ editor/about_window.cpp | 115 +++++++++++++++ editor/about_window.h | 24 +++ editor/icons/icon_voxel_terrain_large.svg | 137 ++++++++++++++++++ .../terrain/voxel_terrain_editor_plugin.cpp | 52 ++++--- editor/terrain/voxel_terrain_editor_plugin.h | 14 +- 7 files changed, 342 insertions(+), 21 deletions(-) create mode 100644 editor/about_window.cpp create mode 100644 editor/about_window.h create mode 100644 editor/icons/icon_voxel_terrain_large.svg diff --git a/LICENSE.md b/LICENSE.md index d95f17fb..8fa93ab9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ Voxel Tools for Godot Engine -------------------------------- -Copyright (c) 2016 Marc Gilleron +Copyright (c) 2016-2020 Marc Gilleron Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 82495070..20a71dbf 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,22 @@ These are some ideas that may or may not be implemented in the future: * Improving LOD performance * Other meshing algorithms (e.g. dual contouring) * GPU offloading (Maybe when Godot 4+ supports compute shaders) + + +Supporters +----------- + +This module is a non-profit project developped by voluntary contributors. The following is the list of the current donors. +Thanks for your support :) + +### Supporters + +``` +wacyym +Sergey Lapin (slapin) +Jonas (NoFr1ends) +lenis0012 +``` + +TODO Add About window + diff --git a/editor/about_window.cpp b/editor/about_window.cpp new file mode 100644 index 00000000..3cd94467 --- /dev/null +++ b/editor/about_window.cpp @@ -0,0 +1,115 @@ +#include "about_window.h" + +#include +#include +#include +#include + +VoxelAboutWindow::VoxelAboutWindow() { + // Generated with the help of https://github.com/Zylann/godot_scene_code_converter + + set_title(TTR("About Voxel Tools")); + set_resizable(true); + set_custom_minimum_size(Vector2(600, 300) * EDSCALE); + set_visible(true); + + VBoxContainer *v_box_container = memnew(VBoxContainer); + v_box_container->set_anchor(MARGIN_RIGHT, 1); + v_box_container->set_anchor(MARGIN_BOTTOM, 1); + v_box_container->set_margin(MARGIN_LEFT, 4 * EDSCALE); + v_box_container->set_margin(MARGIN_TOP, 4 * EDSCALE); + v_box_container->set_margin(MARGIN_RIGHT, -4 * EDSCALE); + v_box_container->set_margin(MARGIN_BOTTOM, -4 * EDSCALE); + + // HB + HBoxContainer *h_box_container = memnew(HBoxContainer); + h_box_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + TextureRect *texture_rect = memnew(TextureRect); + // TODO Can't access ANY icon from here because they all return the default empty icon at this stage... + //texture_rect->set_texture(get_icon("VoxelTerrainLarge", "EditorIcons")); + texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + _icon_texture_rect = texture_rect; + h_box_container->add_child(texture_rect); + + TabContainer *tab_container = memnew(TabContainer); + tab_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + // About + String about_text = "[b]Version:[/b] {version}\n" + "[b]Author:[/b] Marc Gilleron\n" + "[b]Repository:[/b] [url]https://github.com/Zylann/godot_voxel[/url]\n" + "[b]Issue tracker:[/b] [url]https://github.com/Zylann/godot_voxel/issues[/url]\n" + "\n" + "[b]Donors:[/b]\n" + "wacyym\n" + "Sergey Lapin (slapin)\n" + "Jonas (NoFr1ends)\n" + "lenis0012\n"; + { + Dictionary d; + // TODO Take version from somewhere unique + d["version"] = "godot3.2.4 dev"; + about_text = about_text.format(d); + } + RichTextLabel *rich_text_label = memnew(RichTextLabel); + rich_text_label->set_use_bbcode(true); + rich_text_label->set_bbcode(about_text); + + tab_container->add_child(rich_text_label); + + // License + RichTextLabel *rich_text_label2 = memnew(RichTextLabel); + rich_text_label2->set_text( + "Voxel Tools for Godot Engine\n" + "-------------------------------\n" + "Copyright (c) Marc Gilleron\n" + "\n" + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and\n" + "associated documentation files (the \" Software \"), to deal in the Software without restriction,\n" + "including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\n" + "and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\n" + "subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be included in all copies or substantial\n" + "portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\n" + "LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n" + "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n" + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n" + "SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."); + + tab_container->add_child(rich_text_label2); + tab_container->set_tab_title(0, TTR("About")); + tab_container->set_tab_title(1, TTR("License")); + + h_box_container->add_child(tab_container); + + v_box_container->add_child(h_box_container); + + HBoxContainer *h_box_container2 = memnew(HBoxContainer); + h_box_container2->set_alignment(BoxContainer::ALIGN_CENTER); + + Button *button = memnew(Button); + button->set_text(TTR("Ok")); + h_box_container2->add_child(button); + + v_box_container->add_child(h_box_container2); + + add_child(v_box_container); +} + +void VoxelAboutWindow::_notification(int p_what) { + if (p_what == NOTIFICATION_THEME_CHANGED) { + _icon_texture_rect->set_texture(get_icon("VoxelTerrainLarge", "EditorIcons")); + } +} + +void VoxelAboutWindow::_on_ok_button_pressed() { + hide(); +} + +void VoxelAboutWindow::_bind_methods() { + ClassDB::bind_method(D_METHOD("_on_ok_button_pressed"), &VoxelAboutWindow::_on_ok_button_pressed); +} diff --git a/editor/about_window.h b/editor/about_window.h new file mode 100644 index 00000000..ea4135b7 --- /dev/null +++ b/editor/about_window.h @@ -0,0 +1,24 @@ +#ifndef VOXEL_ABOUT_WINDOW_H +#define VOXEL_ABOUT_WINDOW_H + +#include + +class TextureRect; + +class VoxelAboutWindow : public WindowDialog { + GDCLASS(VoxelAboutWindow, WindowDialog) +public: + VoxelAboutWindow(); + +protected: + void _notification(int p_what); + +private: + void _on_ok_button_pressed(); + + static void _bind_methods(); + + TextureRect *_icon_texture_rect; +}; + +#endif // VOXEL_ABOUT_WINDOW_H diff --git a/editor/icons/icon_voxel_terrain_large.svg b/editor/icons/icon_voxel_terrain_large.svg new file mode 100644 index 00000000..e65616bb --- /dev/null +++ b/editor/icons/icon_voxel_terrain_large.svg @@ -0,0 +1,137 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/terrain/voxel_terrain_editor_plugin.cpp b/editor/terrain/voxel_terrain_editor_plugin.cpp index 757caad7..08ccd56c 100644 --- a/editor/terrain/voxel_terrain_editor_plugin.cpp +++ b/editor/terrain/voxel_terrain_editor_plugin.cpp @@ -2,14 +2,25 @@ #include "../../generators/voxel_generator.h" #include "../../terrain/voxel_lod_terrain.h" #include "../../terrain/voxel_terrain.h" +#include "../about_window.h" #include "../graph/voxel_graph_node_inspector_wrapper.h" +#include + VoxelTerrainEditorPlugin::VoxelTerrainEditorPlugin(EditorNode *p_node) { - _restart_stream_button = memnew(Button); - _restart_stream_button->set_text(TTR("Re-generate")); - _restart_stream_button->connect("pressed", this, "_on_restart_stream_button_pressed"); - _restart_stream_button->hide(); - add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, _restart_stream_button); + MenuButton *menu_button = memnew(MenuButton); + menu_button->set_text(TTR("Terrain")); + menu_button->get_popup()->add_item(TTR("Re-generate"), MENU_RESTART_STREAM); + menu_button->get_popup()->add_separator(); + menu_button->get_popup()->add_item(TTR("About Voxel Tools..."), MENU_ABOUT); + menu_button->get_popup()->connect("id_pressed", this, "_on_menu_item_selected"); + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, menu_button); + _menu_button = menu_button; + + Node *base_control = get_editor_interface()->get_base_control(); + + _about_window = memnew(VoxelAboutWindow); + base_control->add_child(_about_window); } static Node *get_as_terrain(Object *p_object) { @@ -90,7 +101,7 @@ void VoxelTerrainEditorPlugin::set_node(Node *node) { } void VoxelTerrainEditorPlugin::make_visible(bool visible) { - _restart_stream_button->set_visible(visible); + _menu_button->set_visible(visible); if (_node != nullptr) { VoxelLodTerrain *vlt = Object::cast_to(_node); @@ -106,16 +117,24 @@ void VoxelTerrainEditorPlugin::make_visible(bool visible) { // So we'll need to check if _node is null all over the place } -void VoxelTerrainEditorPlugin::_on_restart_stream_button_pressed() { - ERR_FAIL_COND(_node == nullptr); - VoxelTerrain *terrain = Object::cast_to(_node); - if (terrain != nullptr) { - terrain->restart_stream(); - return; +void VoxelTerrainEditorPlugin::_on_menu_item_selected(int id) { + switch (id) { + case MENU_RESTART_STREAM: { + ERR_FAIL_COND(_node == nullptr); + VoxelTerrain *terrain = Object::cast_to(_node); + if (terrain != nullptr) { + terrain->restart_stream(); + return; + } + VoxelLodTerrain *terrain2 = Object::cast_to(_node); + ERR_FAIL_COND(terrain2 == nullptr); + terrain2->restart_stream(); + } break; + + case MENU_ABOUT: + _about_window->popup_centered(); + break; } - VoxelLodTerrain *terrain2 = Object::cast_to(_node); - ERR_FAIL_COND(terrain2 == nullptr); - terrain2->restart_stream(); } void VoxelTerrainEditorPlugin::_on_terrain_tree_entered(Node *node) { @@ -128,8 +147,7 @@ void VoxelTerrainEditorPlugin::_on_terrain_tree_exited(Node *node) { } void VoxelTerrainEditorPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("_on_restart_stream_button_pressed"), - &VoxelTerrainEditorPlugin::_on_restart_stream_button_pressed); + ClassDB::bind_method(D_METHOD("_on_menu_item_selected", "id"), &VoxelTerrainEditorPlugin::_on_menu_item_selected); ClassDB::bind_method(D_METHOD("_on_terrain_tree_entered"), &VoxelTerrainEditorPlugin::_on_terrain_tree_entered); ClassDB::bind_method(D_METHOD("_on_terrain_tree_exited"), &VoxelTerrainEditorPlugin::_on_terrain_tree_exited); } diff --git a/editor/terrain/voxel_terrain_editor_plugin.h b/editor/terrain/voxel_terrain_editor_plugin.h index d07c8939..8af81fa4 100644 --- a/editor/terrain/voxel_terrain_editor_plugin.h +++ b/editor/terrain/voxel_terrain_editor_plugin.h @@ -3,7 +3,8 @@ #include -class Button; +class MenuButton; +class VoxelAboutWindow; class VoxelTerrainEditorPlugin : public EditorPlugin { GDCLASS(VoxelTerrainEditorPlugin, EditorPlugin) @@ -17,14 +18,21 @@ public: private: void set_node(Node *node); - void _on_restart_stream_button_pressed(); + void _on_menu_item_selected(int id); void _on_terrain_tree_entered(Node *node); void _on_terrain_tree_exited(Node *node); static void _bind_methods(); - Button *_restart_stream_button = nullptr; + enum MenuID { + MENU_RESTART_STREAM, + MENU_ABOUT + }; + Node *_node = nullptr; + + MenuButton *_menu_button = nullptr; + VoxelAboutWindow *_about_window = nullptr; }; #endif // VOXEL_TERRAIN_EDITOR_PLUGIN_H From a37b3a9c06d59671a4d8ef1e6536b8c4d715359f Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Thu, 17 Dec 2020 23:01:35 +0000 Subject: [PATCH 008/102] Don't show menu by default --- editor/terrain/voxel_terrain_editor_plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/terrain/voxel_terrain_editor_plugin.cpp b/editor/terrain/voxel_terrain_editor_plugin.cpp index 08ccd56c..361c6b48 100644 --- a/editor/terrain/voxel_terrain_editor_plugin.cpp +++ b/editor/terrain/voxel_terrain_editor_plugin.cpp @@ -14,6 +14,7 @@ VoxelTerrainEditorPlugin::VoxelTerrainEditorPlugin(EditorNode *p_node) { menu_button->get_popup()->add_separator(); menu_button->get_popup()->add_item(TTR("About Voxel Tools..."), MENU_ABOUT); menu_button->get_popup()->connect("id_pressed", this, "_on_menu_item_selected"); + menu_button->hide(); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, menu_button); _menu_button = menu_button; From 87e575e504ae05ee6e4a817e282b89405cc65620 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 18 Dec 2020 20:46:54 +0000 Subject: [PATCH 009/102] Use reinterpret_cast instead of ambiguous C-style cast --- storage/voxel_buffer.cpp | 20 ++++++++++---------- util/array_slice.h | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/storage/voxel_buffer.cpp b/storage/voxel_buffer.cpp index 8dd2c0cd..07822727 100644 --- a/storage/voxel_buffer.cpp +++ b/storage/voxel_buffer.cpp @@ -190,13 +190,13 @@ uint64_t VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) return channel.data[i]; case DEPTH_16_BIT: - return ((uint16_t *)channel.data)[i]; + return reinterpret_cast(channel.data)[i]; case DEPTH_32_BIT: - return ((uint32_t *)channel.data)[i]; + return reinterpret_cast(channel.data)[i]; case DEPTH_64_BIT: - return ((uint64_t *)channel.data)[i]; + return reinterpret_cast(channel.data)[i]; default: CRASH_NOW(); @@ -208,7 +208,7 @@ uint64_t VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) } else { return channel.defval; } -} +} // namespace void VoxelBuffer::set_voxel(uint64_t value, int x, int y, int z, unsigned int channel_index) { ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); @@ -237,15 +237,15 @@ void VoxelBuffer::set_voxel(uint64_t value, int x, int y, int z, unsigned int ch break; case DEPTH_16_BIT: - ((uint16_t *)channel.data)[i] = value; + reinterpret_cast(channel.data)[i] = value; break; case DEPTH_32_BIT: - ((uint32_t *)channel.data)[i] = value; + reinterpret_cast(channel.data)[i] = value; break; case DEPTH_64_BIT: - ((uint64_t *)channel.data)[i] = value; + reinterpret_cast(channel.data)[i] = value; break; default: @@ -293,19 +293,19 @@ void VoxelBuffer::fill(uint64_t defval, unsigned int channel_index) { case DEPTH_16_BIT: for (uint32_t i = 0; i < volume; ++i) { - ((uint16_t *)channel.data)[i] = defval; + reinterpret_cast(channel.data)[i] = defval; } break; case DEPTH_32_BIT: for (uint32_t i = 0; i < volume; ++i) { - ((uint32_t *)channel.data)[i] = defval; + reinterpret_cast(channel.data)[i] = defval; } break; case DEPTH_64_BIT: for (uint32_t i = 0; i < volume; ++i) { - ((uint64_t *)channel.data)[i] = defval; + reinterpret_cast(channel.data)[i] = defval; } break; diff --git a/util/array_slice.h b/util/array_slice.h index 0805db6c..11dec449 100644 --- a/util/array_slice.h +++ b/util/array_slice.h @@ -41,7 +41,7 @@ public: #ifdef DEBUG_ENABLED CRASH_COND(size_in_bytes % sizeof(U) != 0); #endif - return ArraySlice((U *)_ptr, 0, size_in_bytes / sizeof(U)); + return ArraySlice(reinterpret_cast(_ptr), 0, size_in_bytes / sizeof(U)); } inline T &operator[](size_t i) { From dec9b3180126e40142b65078f0b28a29a3bc56ae Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 18 Dec 2020 20:48:08 +0000 Subject: [PATCH 010/102] Rename index() to get_index() to make it less like a variable name --- storage/voxel_buffer.cpp | 11 +++++------ storage/voxel_buffer.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/storage/voxel_buffer.cpp b/storage/voxel_buffer.cpp index 07822727..765fee1a 100644 --- a/storage/voxel_buffer.cpp +++ b/storage/voxel_buffer.cpp @@ -183,7 +183,7 @@ uint64_t VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) const Channel &channel = _channels[channel_index]; if (is_position_valid(x, y, z) && channel.data) { - uint32_t i = index(x, y, z); + const uint32_t i = get_index(x, y, z); switch (channel.depth) { case DEPTH_8_BIT: @@ -203,8 +203,7 @@ uint64_t VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) return 0; } - return channel.data[index(x, y, z)]; - + return channel.data[get_index(x, y, z)]; } else { return channel.defval; } @@ -343,7 +342,7 @@ void VoxelBuffer::fill_area(uint64_t defval, Vector3i min, Vector3i max, unsigne unsigned int volume = get_volume(); for (pos.z = min.z; pos.z < max.z; ++pos.z) { for (pos.x = min.x; pos.x < max.x; ++pos.x) { - unsigned int dst_ri = index(pos.x, pos.y + min.y, pos.z); + unsigned int dst_ri = get_index(pos.x, pos.y + min.y, pos.z); CRASH_COND(dst_ri >= volume); switch (channel.depth) { @@ -520,8 +519,8 @@ void VoxelBuffer::copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i for (pos.z = 0; pos.z < area_size.z; ++pos.z) { for (pos.x = 0; pos.x < area_size.x; ++pos.x) { // Row direction is Y - unsigned int src_ri = other.index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z); - unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); + const unsigned int src_ri = other.get_index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z); + const unsigned int dst_ri = get_index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); memcpy(&channel.data[dst_ri], &other_channel.data[src_ri], area_size.y * sizeof(uint8_t)); } } diff --git a/storage/voxel_buffer.h b/storage/voxel_buffer.h index 896dd9a7..c6d519ad 100644 --- a/storage/voxel_buffer.h +++ b/storage/voxel_buffer.h @@ -111,7 +111,7 @@ public: return Rect3i(Vector3i(), _size).contains(box); } - _FORCE_INLINE_ unsigned int index(unsigned int x, unsigned int y, unsigned int z) const { + _FORCE_INLINE_ unsigned int get_index(unsigned int x, unsigned int y, unsigned int z) const { return y + _size.y * (x + _size.x * z); } From 89abb37e0ff9d3e6b24ce6637f87c60968f34ba8 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 18 Dec 2020 20:52:09 +0000 Subject: [PATCH 011/102] consts --- meshers/dmc/voxel_mesher_dmc.cpp | 2 +- storage/voxel_buffer.cpp | 8 ++++---- streams/region_file.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/meshers/dmc/voxel_mesher_dmc.cpp b/meshers/dmc/voxel_mesher_dmc.cpp index 5707b20e..3298d3e2 100644 --- a/meshers/dmc/voxel_mesher_dmc.cpp +++ b/meshers/dmc/voxel_mesher_dmc.cpp @@ -1497,7 +1497,7 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelMesher::Input const Vector3i buffer_size = voxels.get_size(); // Taking previous power of two because the algorithm uses an integer cubic octree, and data should be padded - int chunk_size = previous_power_of_2(MIN(MIN(buffer_size.x, buffer_size.y), buffer_size.z)); + const int chunk_size = previous_power_of_2(MIN(MIN(buffer_size.x, buffer_size.y), buffer_size.z)); ERR_FAIL_COND(voxels.get_size().x < chunk_size + PADDING * 2); ERR_FAIL_COND(voxels.get_size().y < chunk_size + PADDING * 2); diff --git a/storage/voxel_buffer.cpp b/storage/voxel_buffer.cpp index 765fee1a..5e20cf47 100644 --- a/storage/voxel_buffer.cpp +++ b/storage/voxel_buffer.cpp @@ -228,7 +228,7 @@ void VoxelBuffer::set_voxel(uint64_t value, int x, int y, int z, unsigned int ch } if (do_set) { - uint32_t i = index(x, y, z); + const uint32_t i = get_index(x, y, z); switch (channel.depth) { case DEPTH_8_BIT: @@ -498,7 +498,7 @@ void VoxelBuffer::copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i src_max.clamp_to(Vector3i(0, 0, 0), other._size + Vector3i(1, 1, 1)); dst_min.clamp_to(Vector3i(0, 0, 0), _size); - Vector3i area_size = src_max - src_min; + const Vector3i area_size = src_max - src_min; //Vector3i dst_max = dst_min + area_size; if (area_size == _size && area_size == other._size) { @@ -531,7 +531,7 @@ void VoxelBuffer::copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i for (pos.z = 0; pos.z < area_size.z; ++pos.z) { for (pos.x = 0; pos.x < area_size.x; ++pos.x) { for (pos.y = 0; pos.y < area_size.y; ++pos.y) { - uint64_t v = other.get_voxel(src_min + pos, channel_index); + const uint64_t v = other.get_voxel(src_min + pos, channel_index); set_voxel(v, dst_min + pos, channel_index); } } @@ -579,7 +579,7 @@ uint32_t VoxelBuffer::get_size_in_bytes_for_volume(Vector3i size, Depth depth) { // Calculate appropriate size based on bit depth const unsigned int volume = size.x * size.y * size.z; const unsigned int bits = volume * ::get_depth_bit_count(depth); - unsigned int size_in_bytes = (bits >> 3); + const unsigned int size_in_bytes = (bits >> 3); return size_in_bytes; } diff --git a/streams/region_file.cpp b/streams/region_file.cpp index 8f337036..54ba1bea 100644 --- a/streams/region_file.cpp +++ b/streams/region_file.cpp @@ -63,7 +63,7 @@ Error VoxelRegionFile::open(const String &fpath, bool create_if_not_found) { return file_error; } } else { - Error header_error = load_header(f); + const Error header_error = load_header(f); if (header_error != OK) { memdelete(f); return header_error; From 791bdafff1e67f783cf8d2287a1ca4ccd9889e11 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Fri, 18 Dec 2020 21:01:50 +0000 Subject: [PATCH 012/102] Meshers are now resources assigned on terrain nodes --- CHANGELOG.md | 2 + edition/voxel_tool_terrain.cpp | 23 ++-- .../terrain/voxel_terrain_editor_plugin.cpp | 13 ++ editor/terrain/voxel_terrain_editor_plugin.h | 1 + math/color8.h | 1 + meshers/blocky/voxel_mesher_blocky.cpp | 112 +++++++++++----- meshers/blocky/voxel_mesher_blocky.h | 38 ++++-- meshers/cubes/voxel_mesher_cubes.cpp | 124 ++++++++++------- meshers/cubes/voxel_mesher_cubes.h | 33 ++++- meshers/dmc/voxel_mesher_dmc.cpp | 125 +++++++++++------- meshers/dmc/voxel_mesher_dmc.h | 30 +++-- .../transvoxel/voxel_mesher_transvoxel.cpp | 113 ++++++++-------- meshers/transvoxel/voxel_mesher_transvoxel.h | 58 +++++--- meshers/voxel_mesher.cpp | 4 - meshers/voxel_mesher.h | 19 ++- server/voxel_server.cpp | 112 ++++++---------- server/voxel_server.h | 28 ++-- terrain/voxel_box_mover.cpp | 2 + terrain/voxel_lod_terrain.cpp | 100 ++++++++++---- terrain/voxel_lod_terrain.h | 6 + terrain/voxel_map.cpp | 1 + terrain/voxel_terrain.cpp | 117 ++++++++-------- terrain/voxel_terrain.h | 11 +- 23 files changed, 656 insertions(+), 417 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a31ffa..0c586d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Semver is not yet in place, so each version can have breaking changes, although - Added a utility class to load MagicaVoxel `.vox` files - Voxel nodes can be moved, scaled and rotated - Voxel nodes can be limited to specific bounds, rather than being infinitely paging volumes (multiples of block size) + - Meshers are now resources so you can choose and configure them per terrain - Smooth voxels - Shaders now have access to the transform of each block, useful for triplanar mapping on moving volumes @@ -29,6 +30,7 @@ Semver is not yet in place, so each version can have breaking changes, although - Defined `COLOR` channel in `VoxelBuffer`, previously known as `DATA3` - `VoxelGenerator` is no longer the base for script-based generators, use `VoxelGeneratorScript` instead - `VoxelStream` is no longer the base for script-based streams, use `VoxelStreamScript` instead + - Terrain nodes no longer produce meshes based on the voxel data. Instead they use the mesher assigned to them. - Fixes - C# should be able to properly implement generator/stream functions diff --git a/edition/voxel_tool_terrain.cpp b/edition/voxel_tool_terrain.cpp index 5515a7d3..7210ce5a 100644 --- a/edition/voxel_tool_terrain.cpp +++ b/edition/voxel_tool_terrain.cpp @@ -25,6 +25,7 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa struct RaycastPredicate { const VoxelTerrain &terrain; + const VoxelLibrary &library; const uint32_t collision_mask; bool operator()(Vector3i pos) { @@ -33,17 +34,11 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa const VoxelMap &map = terrain.get_storage(); int v0 = map.get_voxel(pos, VoxelBuffer::CHANNEL_TYPE); - Ref lib_ref = terrain.get_voxel_library(); - if (lib_ref.is_null()) { - return false; - } - const VoxelLibrary &lib = **lib_ref; - - if (lib.has_voxel(v0) == false) { + if (library.has_voxel(v0) == false) { return false; } - const Voxel &voxel = lib.get_voxel_const(v0); + const Voxel &voxel = library.get_voxel_const(v0); if (voxel.is_empty()) { return false; } @@ -65,11 +60,17 @@ Ref VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, floa } }; - Vector3i hit_pos; - Vector3i prev_pos; Ref res; - RaycastPredicate predicate = { *_terrain, collision_mask }; + Ref library_ref = _terrain->get_voxel_library(); + if (library_ref.is_null()) { + return res; + } + + Vector3i hit_pos; + Vector3i prev_pos; + + RaycastPredicate predicate = { *_terrain, **library_ref, collision_mask }; if (voxel_raycast(pos, dir, predicate, max_distance, hit_pos, prev_pos)) { res.instance(); res->position = hit_pos; diff --git a/editor/terrain/voxel_terrain_editor_plugin.cpp b/editor/terrain/voxel_terrain_editor_plugin.cpp index 361c6b48..6dfaf9bc 100644 --- a/editor/terrain/voxel_terrain_editor_plugin.cpp +++ b/editor/terrain/voxel_terrain_editor_plugin.cpp @@ -11,6 +11,7 @@ VoxelTerrainEditorPlugin::VoxelTerrainEditorPlugin(EditorNode *p_node) { MenuButton *menu_button = memnew(MenuButton); menu_button->set_text(TTR("Terrain")); menu_button->get_popup()->add_item(TTR("Re-generate"), MENU_RESTART_STREAM); + menu_button->get_popup()->add_item(TTR("Re-mesh"), MENU_REMESH); menu_button->get_popup()->add_separator(); menu_button->get_popup()->add_item(TTR("About Voxel Tools..."), MENU_ABOUT); menu_button->get_popup()->connect("id_pressed", this, "_on_menu_item_selected"); @@ -132,6 +133,18 @@ void VoxelTerrainEditorPlugin::_on_menu_item_selected(int id) { terrain2->restart_stream(); } break; + case MENU_REMESH: { + ERR_FAIL_COND(_node == nullptr); + VoxelTerrain *terrain = Object::cast_to(_node); + if (terrain != nullptr) { + terrain->remesh_all_blocks(); + return; + } + VoxelLodTerrain *terrain2 = Object::cast_to(_node); + ERR_FAIL_COND(terrain2 == nullptr); + terrain2->remesh_all_blocks(); + } break; + case MENU_ABOUT: _about_window->popup_centered(); break; diff --git a/editor/terrain/voxel_terrain_editor_plugin.h b/editor/terrain/voxel_terrain_editor_plugin.h index 8af81fa4..7c18a819 100644 --- a/editor/terrain/voxel_terrain_editor_plugin.h +++ b/editor/terrain/voxel_terrain_editor_plugin.h @@ -26,6 +26,7 @@ private: enum MenuID { MENU_RESTART_STREAM, + MENU_REMESH, MENU_ABOUT }; diff --git a/math/color8.h b/math/color8.h index eea224bd..b4ec9d21 100644 --- a/math/color8.h +++ b/math/color8.h @@ -46,6 +46,7 @@ struct Color8 { } static inline Color8 from_u32(uint32_t c) { + // rrrrrrrr gggggggg bbbbbbbb aaaaaaaa return Color8( c >> 24, (c >> 16) & 0xff, diff --git a/meshers/blocky/voxel_mesher_blocky.cpp b/meshers/blocky/voxel_mesher_blocky.cpp index f1856cb6..6d3dba7c 100644 --- a/meshers/blocky/voxel_mesher_blocky.cpp +++ b/meshers/blocky/voxel_mesher_blocky.cpp @@ -5,6 +5,7 @@ #include "../../util/utility.h" #include +// Utility functions namespace { template @@ -328,46 +329,74 @@ static void generate_blocky_mesh( } } -VoxelMesherBlocky::VoxelMesherBlocky() : - _baked_occlusion_darkness(0.8), - _bake_occlusion(true) { +thread_local VoxelMesherBlocky::Cache VoxelMesherBlocky::_cache; + +VoxelMesherBlocky::VoxelMesherBlocky() { set_padding(PADDING, PADDING); + + _parameters_lock = RWLock::create(); + + // Default library, less steps to setup in editor + Ref library; + library.instance(); + library->load_default(); + _parameters.library = library; +} + +VoxelMesherBlocky::~VoxelMesherBlocky() { + memdelete(_parameters_lock); } void VoxelMesherBlocky::set_library(Ref library) { - _library = library; + RWLockWrite wlock(_parameters_lock); + _parameters.library = library; +} + +Ref VoxelMesherBlocky::get_library() const { + RWLockRead rlock(_parameters_lock); + return _parameters.library; } void VoxelMesherBlocky::set_occlusion_darkness(float darkness) { - _baked_occlusion_darkness = darkness; - if (_baked_occlusion_darkness < 0.0) { - _baked_occlusion_darkness = 0.0; - } else if (_baked_occlusion_darkness >= 1.0) { - _baked_occlusion_darkness = 1.0; - } + RWLockWrite wlock(_parameters_lock); + _parameters.baked_occlusion_darkness = clamp(darkness, 0.0f, 1.0f); +} + +float VoxelMesherBlocky::get_occlusion_darkness() const { + RWLockRead rlock(_parameters_lock); + return _parameters.baked_occlusion_darkness; } void VoxelMesherBlocky::set_occlusion_enabled(bool enable) { - _bake_occlusion = enable; + RWLockWrite wlock(_parameters_lock); + _parameters.bake_occlusion = enable; +} + +bool VoxelMesherBlocky::get_occlusion_enabled() const { + RWLockRead rlock(_parameters_lock); + return _parameters.bake_occlusion; } void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) { const int channel = VoxelBuffer::CHANNEL_TYPE; + Parameters params; + { + RWLockRead rlock(_parameters_lock); + params = _parameters; + } - ERR_FAIL_COND(_library.is_null()); + ERR_FAIL_COND(params.library.is_null()); - for (unsigned int i = 0; i < _arrays_per_material.size(); ++i) { - Arrays &a = _arrays_per_material[i]; - a.positions.clear(); - a.normals.clear(); - a.uvs.clear(); - a.colors.clear(); - a.indices.clear(); + Cache &cache = _cache; + + for (unsigned int i = 0; i < cache.arrays_per_material.size(); ++i) { + Arrays &a = cache.arrays_per_material[i]; + a.clear(); } float baked_occlusion_darkness = 0; - if (_bake_occlusion) { - baked_occlusion_darkness = _baked_occlusion_darkness / 3.0; + if (params.bake_occlusion) { + baked_occlusion_darkness = params.baked_occlusion_darkness / 3.0f; } // The technique is Culled faces. @@ -427,18 +456,19 @@ void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelMesher::In const VoxelBuffer::Depth channel_depth = voxels.get_channel_depth(channel); { - RWLockRead lock(_library->get_baked_data_rw_lock()); - const VoxelLibrary::BakedData &library_baked_data = _library->get_baked_data(); + // We can only access baked data. Only this data is made for multithreaded access. + RWLockRead lock(params.library->get_baked_data_rw_lock()); + const VoxelLibrary::BakedData &library_baked_data = params.library->get_baked_data(); switch (channel_depth) { case VoxelBuffer::DEPTH_8_BIT: - generate_blocky_mesh(_arrays_per_material, raw_channel, - block_size, library_baked_data, _bake_occlusion, baked_occlusion_darkness); + generate_blocky_mesh(cache.arrays_per_material, raw_channel, + block_size, library_baked_data, params.bake_occlusion, baked_occlusion_darkness); break; case VoxelBuffer::DEPTH_16_BIT: - generate_blocky_mesh(_arrays_per_material, raw_channel.reinterpret_cast_to(), - block_size, library_baked_data, _bake_occlusion, baked_occlusion_darkness); + generate_blocky_mesh(cache.arrays_per_material, raw_channel.reinterpret_cast_to(), + block_size, library_baked_data, params.bake_occlusion, baked_occlusion_darkness); break; default: @@ -450,7 +480,7 @@ void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelMesher::In // TODO We could return a single byte array and use Mesh::add_surface down the line? for (unsigned int i = 0; i < MAX_MATERIALS; ++i) { - const Arrays &arrays = _arrays_per_material[i]; + const Arrays &arrays = cache.arrays_per_material[i]; if (arrays.positions.size() != 0) { Array mesh_arrays; @@ -487,14 +517,26 @@ void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelMesher::In output.primitive_type = Mesh::PRIMITIVE_TRIANGLES; } -VoxelMesher *VoxelMesherBlocky::clone() { +Ref VoxelMesherBlocky::duplicate(bool p_subresources) const { + Parameters params; + { + RWLockRead rlock(_parameters_lock); + params = _parameters; + } + + if (p_subresources && params.library.is_valid()) { + params.library = params.library->duplicate(true); + } + VoxelMesherBlocky *c = memnew(VoxelMesherBlocky); - c->set_library(_library); - c->set_occlusion_darkness(_baked_occlusion_darkness); - c->set_occlusion_enabled(_bake_occlusion); + c->_parameters = params; return c; } +int VoxelMesherBlocky::get_used_channels_mask() const { + return (1 << VoxelBuffer::CHANNEL_TYPE); +} + void VoxelMesherBlocky::_bind_methods() { ClassDB::bind_method(D_METHOD("set_library", "voxel_library"), &VoxelMesherBlocky::set_library); ClassDB::bind_method(D_METHOD("get_library"), &VoxelMesherBlocky::get_library); @@ -504,4 +546,10 @@ void VoxelMesherBlocky::_bind_methods() { ClassDB::bind_method(D_METHOD("set_occlusion_darkness", "value"), &VoxelMesherBlocky::set_occlusion_darkness); ClassDB::bind_method(D_METHOD("get_occlusion_darkness"), &VoxelMesherBlocky::get_occlusion_darkness); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "VoxelLibrary"), + "set_library", "get_library"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "occlusion_enabled"), "set_occlusion_enabled", "get_occlusion_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "occlusion_darkness", PROPERTY_HINT_RANGE, "0,1,0.01"), + "set_occlusion_darkness", "get_occlusion_darkness"); } diff --git a/meshers/blocky/voxel_mesher_blocky.h b/meshers/blocky/voxel_mesher_blocky.h index 88090359..a61ca56e 100644 --- a/meshers/blocky/voxel_mesher_blocky.h +++ b/meshers/blocky/voxel_mesher_blocky.h @@ -19,19 +19,21 @@ public: static const int PADDING = 1; VoxelMesherBlocky(); + ~VoxelMesherBlocky(); void set_library(Ref library); - Ref get_library() const { return _library; } + Ref get_library() const; void set_occlusion_darkness(float darkness); - float get_occlusion_darkness() const { return _baked_occlusion_darkness; } + float get_occlusion_darkness() const; void set_occlusion_enabled(bool enable); - bool get_occlusion_enabled() const { return _bake_occlusion; } + bool get_occlusion_enabled() const; void build(VoxelMesher::Output &output, const VoxelMesher::Input &input) override; - VoxelMesher *clone() override; + Ref duplicate(bool p_subresources = false) const override; + int get_used_channels_mask() const override; // Using std::vector because they make this mesher twice as fast than Godot Vectors. // See why: https://github.com/godotengine/godot/issues/24731 @@ -41,16 +43,36 @@ public: std::vector uvs; std::vector colors; std::vector indices; + + void clear() { + positions.clear(); + normals.clear(); + uvs.clear(); + colors.clear(); + indices.clear(); + } }; protected: static void _bind_methods(); private: - Ref _library; - FixedArray _arrays_per_material; - float _baked_occlusion_darkness; - bool _bake_occlusion; + struct Parameters { + float baked_occlusion_darkness = 0.8; + bool bake_occlusion = true; + Ref library; + }; + + struct Cache { + FixedArray arrays_per_material; + }; + + // Parameters + Parameters _parameters; + RWLock *_parameters_lock = nullptr; + + // Work cache + static thread_local Cache _cache; }; #endif // VOXEL_MESHER_BLOCKY_H diff --git a/meshers/cubes/voxel_mesher_cubes.cpp b/meshers/cubes/voxel_mesher_cubes.cpp index 99a67cb3..326034df 100644 --- a/meshers/cubes/voxel_mesher_cubes.cpp +++ b/meshers/cubes/voxel_mesher_cubes.cpp @@ -382,25 +382,30 @@ void build_voxel_mesh_as_greedy_cubes( } } +thread_local VoxelMesherCubes::Cache VoxelMesherCubes::_cache; + VoxelMesherCubes::VoxelMesherCubes() { set_padding(PADDING, PADDING); + _parameters_lock = RWLock::create(); +} + +VoxelMesherCubes::~VoxelMesherCubes() { + memdelete(_parameters_lock); } void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) { const int channel = VoxelBuffer::CHANNEL_COLOR; + Cache &cache = _cache; - for (unsigned int i = 0; i < _arrays_per_material.size(); ++i) { - Arrays &a = _arrays_per_material[i]; - a.positions.clear(); - a.normals.clear(); - a.colors.clear(); - a.indices.clear(); + for (unsigned int i = 0; i < cache.arrays_per_material.size(); ++i) { + Arrays &a = cache.arrays_per_material[i]; + a.clear(); } const VoxelBuffer &voxels = input.voxels; #ifdef TOOLS_ENABLED if (input.lod != 0) { - WARN_PRINT("VoxelMesherBlocky received lod != 0, it is not supported"); + WARN_PRINT("VoxelMesherCubes received lod != 0, it is not supported"); } #endif @@ -418,7 +423,7 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp } else if (voxels.get_channel_compression(channel) != VoxelBuffer::COMPRESSION_NONE) { // No other form of compression is allowed - ERR_PRINT("VoxelMesherBlocky received unsupported voxel compression"); + ERR_PRINT("VoxelMesherCubes received unsupported voxel compression"); return; } @@ -432,20 +437,27 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp const Vector3i block_size = voxels.get_size(); const VoxelBuffer::Depth channel_depth = voxels.get_channel_depth(channel); - switch (_color_mode) { + Parameters params; + { + RWLockRead rlock(_parameters_lock); + params = _parameters; + } + // Note, we don't lock the palette because its data has fixed-size + + switch (params.color_mode) { case COLOR_RAW: switch (channel_depth) { case VoxelBuffer::DEPTH_8_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, - _mask_memory_pool, + cache.mask_memory_pool, Color8::from_u8); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, Color8::from_u8); @@ -453,16 +465,16 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp break; case VoxelBuffer::DEPTH_16_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, - _mask_memory_pool, + cache.mask_memory_pool, Color8::from_u16); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, Color8::from_u16); @@ -476,7 +488,7 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp break; case COLOR_MESHER_PALETTE: { - ERR_FAIL_COND_MSG(_palette.is_null(), "Palette mode is used but no palette was specified"); + ERR_FAIL_COND_MSG(params.palette.is_null(), "Palette mode is used but no palette was specified"); struct GetColorFromPalette { VoxelColorPalette &palette; @@ -487,20 +499,20 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp return palette.get_color8(i); } }; - const GetColorFromPalette get_color_from_palette{ **_palette }; + const GetColorFromPalette get_color_from_palette{ **params.palette }; switch (channel_depth) { case VoxelBuffer::DEPTH_8_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, - _mask_memory_pool, + cache.mask_memory_pool, get_color_from_palette); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, get_color_from_palette); @@ -508,16 +520,16 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp break; case VoxelBuffer::DEPTH_16_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, - _mask_memory_pool, + cache.mask_memory_pool, get_color_from_palette); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, get_color_from_palette); @@ -531,7 +543,7 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp } break; case COLOR_SHADER_PALETTE: { - ERR_FAIL_COND_MSG(_palette.is_null(), "Palette mode is used but no palette was specified"); + ERR_FAIL_COND_MSG(params.palette.is_null(), "Palette mode is used but no palette was specified"); struct GetIndexFromPalette { VoxelColorPalette &palette; @@ -540,20 +552,20 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp return Color8(i, 0, 0, palette.get_color8(i).a); } }; - const GetIndexFromPalette get_index_from_palette{ **_palette }; + const GetIndexFromPalette get_index_from_palette{ **params.palette }; switch (channel_depth) { case VoxelBuffer::DEPTH_8_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, - _mask_memory_pool, + cache.mask_memory_pool, get_index_from_palette); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel, block_size, get_index_from_palette); @@ -561,16 +573,16 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp break; case VoxelBuffer::DEPTH_16_BIT: - if (_greedy_meshing) { + if (params.greedy_meshing) { build_voxel_mesh_as_greedy_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, - _mask_memory_pool, + cache.mask_memory_pool, get_index_from_palette); } else { build_voxel_mesh_as_simple_cubes( - _arrays_per_material, + cache.arrays_per_material, raw_channel.reinterpret_cast_to(), block_size, get_index_from_palette); @@ -591,7 +603,7 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp // TODO We could return a single byte array and use Mesh::add_surface down the line? for (unsigned int i = 0; i < MATERIAL_COUNT; ++i) { - const Arrays &arrays = _arrays_per_material[i]; + const Arrays &arrays = cache.arrays_per_material[i]; if (arrays.positions.size() != 0) { Array mesh_arrays; @@ -627,36 +639,56 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp } void VoxelMesherCubes::set_greedy_meshing_enabled(bool enable) { - _greedy_meshing = enable; + RWLockWrite wlock(_parameters_lock); + _parameters.greedy_meshing = enable; } bool VoxelMesherCubes::is_greedy_meshing_enabled() const { - return _greedy_meshing; + RWLockRead rlock(_parameters_lock); + return _parameters.greedy_meshing; } void VoxelMesherCubes::set_palette(Ref palette) { - _palette = palette; + RWLockWrite wlock(_parameters_lock); + _parameters.palette = palette; } Ref VoxelMesherCubes::get_palette() const { - return _palette; + RWLockRead rlock(_parameters_lock); + return _parameters.palette; } void VoxelMesherCubes::set_color_mode(ColorMode mode) { ERR_FAIL_INDEX(mode, COLOR_MODE_COUNT); - _color_mode = mode; + RWLockWrite wlock(_parameters_lock); + _parameters.color_mode = mode; } VoxelMesherCubes::ColorMode VoxelMesherCubes::get_color_mode() const { - return _color_mode; + RWLockRead rlock(_parameters_lock); + return _parameters.color_mode; } -VoxelMesher *VoxelMesherCubes::clone() { +Ref VoxelMesherCubes::duplicate(bool p_subresources) const { + Parameters params; + { + RWLockRead rlock(_parameters_lock); + params = _parameters; + } + + if (p_subresources && params.palette.is_valid()) { + params.palette = params.palette->duplicate(true); + } VoxelMesherCubes *d = memnew(VoxelMesherCubes); - d->_greedy_meshing = _greedy_meshing; + d->_parameters = params; + return d; } +int VoxelMesherCubes::get_used_channels_mask() const { + return (1 << VoxelBuffer::CHANNEL_COLOR); +} + void VoxelMesherCubes::_bind_methods() { ClassDB::bind_method(D_METHOD("set_greedy_meshing_enabled", "enable"), &VoxelMesherCubes::set_greedy_meshing_enabled); diff --git a/meshers/cubes/voxel_mesher_cubes.h b/meshers/cubes/voxel_mesher_cubes.h index 31bd2f9a..46277a84 100644 --- a/meshers/cubes/voxel_mesher_cubes.h +++ b/meshers/cubes/voxel_mesher_cubes.h @@ -30,6 +30,7 @@ public: }; VoxelMesherCubes(); + ~VoxelMesherCubes(); void build(VoxelMesher::Output &output, const VoxelMesher::Input &input) override; @@ -42,7 +43,8 @@ public: void set_palette(Ref palette); Ref get_palette() const; - VoxelMesher *clone() override; + Ref duplicate(bool p_subresources = false) const override; + int get_used_channels_mask() const override; // Using std::vector because they make this mesher twice as fast than Godot Vectors. // See why: https://github.com/godotengine/godot/issues/24731 @@ -51,17 +53,36 @@ public: std::vector normals; std::vector colors; std::vector indices; + + void clear() { + positions.clear(); + normals.clear(); + colors.clear(); + indices.clear(); + } }; protected: static void _bind_methods(); private: - FixedArray _arrays_per_material; - std::vector _mask_memory_pool; - Ref _palette; - bool _greedy_meshing = true; - ColorMode _color_mode = COLOR_RAW; + struct Parameters { + ColorMode color_mode = COLOR_RAW; + Ref palette; + bool greedy_meshing = true; + }; + + struct Cache { + FixedArray arrays_per_material; + std::vector mask_memory_pool; + }; + + // Parameters + Parameters _parameters; + RWLock *_parameters_lock = nullptr; + + // Work cache + static thread_local Cache _cache; }; VARIANT_ENUM_CAST(VoxelMesherCubes::ColorMode); diff --git a/meshers/dmc/voxel_mesher_dmc.cpp b/meshers/dmc/voxel_mesher_dmc.cpp index 3298d3e2..3a0e248f 100644 --- a/meshers/dmc/voxel_mesher_dmc.cpp +++ b/meshers/dmc/voxel_mesher_dmc.cpp @@ -1444,54 +1444,67 @@ void polygonize_volume_directly(const VoxelBuffer &voxels, Vector3i min, Vector3 #define BUILD_OCTREE_BOTTOM_UP +thread_local VoxelMesherDMC::Cache VoxelMesherDMC::_cache; + VoxelMesherDMC::VoxelMesherDMC() { set_padding(PADDING, PADDING); + _parameters_lock = RWLock::create(); +} + +VoxelMesherDMC::~VoxelMesherDMC() { + memdelete(_parameters_lock); } void VoxelMesherDMC::set_mesh_mode(MeshMode mode) { - _mesh_mode = mode; + RWLockWrite wlock(_parameters_lock); + _parameters.mesh_mode = mode; } VoxelMesherDMC::MeshMode VoxelMesherDMC::get_mesh_mode() const { - return _mesh_mode; + RWLockRead rlock(_parameters_lock); + return _parameters.mesh_mode; } void VoxelMesherDMC::set_simplify_mode(SimplifyMode mode) { - _simplify_mode = mode; + RWLockWrite wlock(_parameters_lock); + _parameters.simplify_mode = mode; } VoxelMesherDMC::SimplifyMode VoxelMesherDMC::get_simplify_mode() const { - return _simplify_mode; + RWLockRead rlock(_parameters_lock); + return _parameters.simplify_mode; } void VoxelMesherDMC::set_geometric_error(real_t geometric_error) { - _geometric_error = geometric_error; + RWLockWrite wlock(_parameters_lock); + _parameters.geometric_error = geometric_error; } float VoxelMesherDMC::get_geometric_error() const { - return _geometric_error; + RWLockRead rlock(_parameters_lock); + return _parameters.geometric_error; } void VoxelMesherDMC::set_seam_mode(SeamMode mode) { - _seam_mode = mode; + RWLockWrite wlock(_parameters_lock); + _parameters.seam_mode = mode; } VoxelMesherDMC::SeamMode VoxelMesherDMC::get_seam_mode() const { - return _seam_mode; + RWLockRead rlock(_parameters_lock); + return _parameters.seam_mode; } void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) { - // Requirements: // - Voxel data must be padded // - The non-padded area size is cubic and power of two - _stats = {}; - const VoxelBuffer &voxels = input.voxels; if (voxels.is_uniform(VoxelBuffer::CHANNEL_SDF)) { // That won't produce any polygon + _stats = {}; return; } @@ -1503,8 +1516,14 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelMesher::Input ERR_FAIL_COND(voxels.get_size().y < chunk_size + PADDING * 2); ERR_FAIL_COND(voxels.get_size().z < chunk_size + PADDING * 2); + Parameters params; + { + RWLockRead rlock(_parameters_lock); + params = _parameters; + } + // TODO Option for this in case LOD is not used - bool skirts_enabled = _seam_mode == SEAM_MARCHING_SQUARE_SKIRTS; + const bool skirts_enabled = (params.seam_mode == SEAM_MARCHING_SQUARE_SKIRTS); // Marching square skirts are a cheap way to hide LOD cracks, // however they might still be visible because of shadow mapping, and cause potential issues when used for physics. // Maybe a shader with a `light()` function can prevent shadows from being applied to these, @@ -1517,8 +1536,11 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelMesher::Input // Construct an intermediate to handle padding transparently dmc::VoxelAccess voxels_access(voxels, Vector3i(PADDING)); + Stats stats; real_t time_before = OS::get_singleton()->get_ticks_usec(); + Cache &cache = _cache; + // In an ideal world, a tiny sphere placed in the middle of an empty SDF volume will // cause corners data to change so that they indicate distance to it. // That means we could build our meshing octree top-down efficiently because corners of the volume will tell if the @@ -1537,89 +1559,88 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelMesher::Input // // TODO This option might disappear once I find a good enough solution dmc::OctreeNode *root = nullptr; - if (_simplify_mode == SIMPLIFY_OCTREE_BOTTOM_UP) { - - dmc::OctreeBuilderBottomUp octree_builder(voxels_access, _geometric_error, _octree_node_pool); + if (params.simplify_mode == SIMPLIFY_OCTREE_BOTTOM_UP) { + dmc::OctreeBuilderBottomUp octree_builder(voxels_access, params.geometric_error, cache.octree_node_pool); root = octree_builder.build(Vector3i(), chunk_size); - } else if (_simplify_mode == SIMPLIFY_OCTREE_TOP_DOWN) { - - dmc::OctreeBuilderTopDown octree_builder(voxels_access, _geometric_error, _octree_node_pool); + } else if (params.simplify_mode == SIMPLIFY_OCTREE_TOP_DOWN) { + dmc::OctreeBuilderTopDown octree_builder(voxels_access, params.geometric_error, cache.octree_node_pool); root = octree_builder.build(Vector3i(), chunk_size); } - _stats.octree_build_time = OS::get_singleton()->get_ticks_usec() - time_before; + stats.octree_build_time = OS::get_singleton()->get_ticks_usec() - time_before; Array surface; if (root != nullptr) { - - if (_mesh_mode == MESH_DEBUG_OCTREE) { + if (params.mesh_mode == MESH_DEBUG_OCTREE) { surface = dmc::generate_debug_octree_mesh(root, 1 << input.lod); } else { - time_before = OS::get_singleton()->get_ticks_usec(); - dmc::DualGridGenerator dual_grid_generator(_dual_grid, root->size); + dmc::DualGridGenerator dual_grid_generator(cache.dual_grid, root->size); dual_grid_generator.node_proc(root); // TODO Handle non-subdivided octree - _stats.dualgrid_derivation_time = OS::get_singleton()->get_ticks_usec() - time_before; + stats.dualgrid_derivation_time = OS::get_singleton()->get_ticks_usec() - time_before; - if (_mesh_mode == MESH_DEBUG_DUAL_GRID) { - surface = dmc::generate_debug_dual_grid_mesh(_dual_grid, 1 << input.lod); + if (params.mesh_mode == MESH_DEBUG_DUAL_GRID) { + surface = dmc::generate_debug_dual_grid_mesh(cache.dual_grid, 1 << input.lod); } else { - time_before = OS::get_singleton()->get_ticks_usec(); - dmc::polygonize_dual_grid(_dual_grid, voxels_access, _mesh_builder, skirts_enabled); - _stats.meshing_time = OS::get_singleton()->get_ticks_usec() - time_before; + dmc::polygonize_dual_grid(cache.dual_grid, voxels_access, cache.mesh_builder, skirts_enabled); + stats.meshing_time = OS::get_singleton()->get_ticks_usec() - time_before; } - _dual_grid.cells.clear(); + cache.dual_grid.cells.clear(); } - root->recycle(_octree_node_pool); - - } else if (_simplify_mode == SIMPLIFY_NONE) { + root->recycle(cache.octree_node_pool); + } else if (params.simplify_mode == SIMPLIFY_NONE) { // We throw away adaptivity for meshing speed. // This is essentially regular marching cubes. - time_before = OS::get_singleton()->get_ticks_usec(); - dmc::polygonize_volume_directly(voxels, Vector3i(PADDING), Vector3i(chunk_size), _mesh_builder, skirts_enabled); - _stats.meshing_time = OS::get_singleton()->get_ticks_usec() - time_before; + dmc::polygonize_volume_directly( + voxels, Vector3i(PADDING), Vector3i(chunk_size), cache.mesh_builder, skirts_enabled); + stats.meshing_time = OS::get_singleton()->get_ticks_usec() - time_before; } if (surface.empty()) { time_before = OS::get_singleton()->get_ticks_usec(); if (input.lod > 0) { - _mesh_builder.scale(1 << input.lod); + cache.mesh_builder.scale(1 << input.lod); } - surface = _mesh_builder.commit(_mesh_mode == MESH_WIREFRAME); - _stats.commit_time = OS::get_singleton()->get_ticks_usec() - time_before; + surface = cache.mesh_builder.commit(params.mesh_mode == MESH_WIREFRAME); + stats.commit_time = OS::get_singleton()->get_ticks_usec() - time_before; } // surfaces[material][array_type], for now single material output.surfaces.push_back(surface); - if (_mesh_mode == MESH_NORMAL) { + if (params.mesh_mode == MESH_NORMAL) { output.primitive_type = Mesh::PRIMITIVE_TRIANGLES; } else { output.primitive_type = Mesh::PRIMITIVE_LINES; } + + // We don't lock stats, it's not a big issue if debug displays get some weird numbers once a week + _stats = stats; } -VoxelMesher *VoxelMesherDMC::clone() { +Ref VoxelMesherDMC::duplicate(bool p_subresources) const { VoxelMesherDMC *c = memnew(VoxelMesherDMC); - c->set_mesh_mode(_mesh_mode); - c->set_simplify_mode(_simplify_mode); - c->set_geometric_error(_geometric_error); - c->set_seam_mode(_seam_mode); + RWLockRead rlock(_parameters_lock); + c->_parameters = _parameters; return c; } +int VoxelMesherDMC::get_used_channels_mask() const { + return (1 << VoxelBuffer::CHANNEL_SDF); +} + Dictionary VoxelMesherDMC::get_statistics() const { Dictionary d; d["octree_build_time"] = _stats.octree_build_time; @@ -1630,7 +1651,6 @@ Dictionary VoxelMesherDMC::get_statistics() const { } void VoxelMesherDMC::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mesh_mode", "mode"), &VoxelMesherDMC::set_mesh_mode); ClassDB::bind_method(D_METHOD("get_mesh_mode"), &VoxelMesherDMC::get_mesh_mode); @@ -1645,6 +1665,19 @@ void VoxelMesherDMC::_bind_methods() { ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelMesherDMC::get_statistics); + ADD_PROPERTY( + PropertyInfo(Variant::INT, "mesh_mode", PROPERTY_HINT_ENUM, "Normal,Wireframe,DebugOctree,DebugDualGrid"), + "set_mesh_mode", "get_mesh_mode"); + + ADD_PROPERTY( + PropertyInfo(Variant::INT, "simplify_mode", PROPERTY_HINT_ENUM, "OctreeBottomUp,OctreeTopDown,None"), + "set_simplify_mode", "get_simplify_mode"); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "geometric_error"), "set_simplify_mode", "get_simplify_mode"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "seam_mode", PROPERTY_HINT_ENUM, "None,MarchingSquareSkirts"), + "set_seam_mode", "get_seam_mode"); + BIND_ENUM_CONSTANT(MESH_NORMAL); BIND_ENUM_CONSTANT(MESH_WIREFRAME); BIND_ENUM_CONSTANT(MESH_DEBUG_OCTREE); diff --git a/meshers/dmc/voxel_mesher_dmc.h b/meshers/dmc/voxel_mesher_dmc.h index 528d1655..d68ba73f 100644 --- a/meshers/dmc/voxel_mesher_dmc.h +++ b/meshers/dmc/voxel_mesher_dmc.h @@ -88,6 +88,7 @@ public: }; VoxelMesherDMC(); + ~VoxelMesherDMC(); void set_mesh_mode(MeshMode mode); MeshMode get_mesh_mode() const; @@ -105,19 +106,32 @@ public: Dictionary get_statistics() const; - VoxelMesher *clone() override; + Ref duplicate(bool p_subresources = false) const override; + int get_used_channels_mask() const override; protected: static void _bind_methods(); private: - dmc::MeshBuilder _mesh_builder; - dmc::DualGrid _dual_grid; - dmc::OctreeNodePool _octree_node_pool; - real_t _geometric_error = 0.1; - MeshMode _mesh_mode = MESH_NORMAL; - SimplifyMode _simplify_mode = SIMPLIFY_OCTREE_BOTTOM_UP; - SeamMode _seam_mode = SEAM_NONE; + struct Parameters { + real_t geometric_error = 0.1; + MeshMode mesh_mode = MESH_NORMAL; + SimplifyMode simplify_mode = SIMPLIFY_OCTREE_BOTTOM_UP; + SeamMode seam_mode = SEAM_NONE; + }; + + struct Cache { + dmc::MeshBuilder mesh_builder; + dmc::DualGrid dual_grid; + dmc::OctreeNodePool octree_node_pool; + }; + + // Parameters + Parameters _parameters; + RWLock *_parameters_lock = nullptr; + + // Work cache + static thread_local Cache _cache; struct Stats { real_t octree_build_time = 0; diff --git a/meshers/transvoxel/voxel_mesher_transvoxel.cpp b/meshers/transvoxel/voxel_mesher_transvoxel.cpp index a5af031f..9a5fbe2c 100644 --- a/meshers/transvoxel/voxel_mesher_transvoxel.cpp +++ b/meshers/transvoxel/voxel_mesher_transvoxel.cpp @@ -3,6 +3,7 @@ #include "transvoxel_tables.cpp" #include +// Utility functions namespace { static const float TRANSITION_CELL_SCALE = 0.25; @@ -129,34 +130,39 @@ inline Vector3 normalized_not_null(Vector3 n) { } // namespace +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +thread_local VoxelMesherTransvoxelInternal VoxelMesherTransvoxel::_impl; + VoxelMesherTransvoxel::VoxelMesherTransvoxel() { - set_padding(MIN_PADDING, MAX_PADDING); + set_padding(VoxelMesherTransvoxelInternal::MIN_PADDING, VoxelMesherTransvoxelInternal::MAX_PADDING); } -void VoxelMesherTransvoxel::clear_output() { - // Important: memory is NOT deallocated. I rely on vectors keeping their capacity. - // This is extremely important for performance, while Godot Vector on the same usage caused 50% slowdown. - _output_indices.clear(); - _output_normals.clear(); - _output_vertices.clear(); - _output_extra.clear(); +VoxelMesherTransvoxel::~VoxelMesherTransvoxel() { } -void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays) { +Ref VoxelMesherTransvoxel::duplicate(bool p_subresources) const { + return memnew(VoxelMesherTransvoxel); +} +int VoxelMesherTransvoxel::get_used_channels_mask() const { + return (1 << VoxelBuffer::CHANNEL_SDF); +} + +void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays, const VoxelMesherTransvoxelInternal::MeshArrays &src) { PoolVector vertices; PoolVector normals; PoolVector extra; PoolVector indices; - raw_copy_to(vertices, _output_vertices); - raw_copy_to(normals, _output_normals); - raw_copy_to(extra, _output_extra); - raw_copy_to(indices, _output_indices); + raw_copy_to(vertices, src.vertices); + raw_copy_to(normals, src.normals); + raw_copy_to(extra, src.extra); + raw_copy_to(indices, src.indices); arrays.resize(Mesh::ARRAY_MAX); arrays[Mesh::ARRAY_VERTEX] = vertices; - if (_output_normals.size() != 0) { + if (src.normals.size() != 0) { arrays[Mesh::ARRAY_NORMAL] = normals; } arrays[Mesh::ARRAY_COLOR] = extra; @@ -164,6 +170,7 @@ void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays) { } void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) { + VoxelMesherTransvoxelInternal &impl = _impl; int channel = VoxelBuffer::CHANNEL_SDF; @@ -171,34 +178,33 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher // These vectors are re-used. // We don't know in advance how much geometry we are going to produce. // Once capacity is big enough, no more memory should be allocated - clear_output(); + impl.clear_output(); const VoxelBuffer &voxels = input.voxels; ERR_FAIL_COND(voxels.get_channel_depth(channel) != VoxelBuffer::DEPTH_8_BIT); - build_internal(voxels, channel, input.lod); + impl.build_internal(voxels, channel, input.lod); - if (_output_vertices.size() == 0) { + if (impl.get_output().vertices.size() == 0) { // The mesh can be empty return; } Array regular_arrays; - fill_surface_arrays(regular_arrays); + fill_surface_arrays(regular_arrays, impl.get_output()); output.surfaces.push_back(regular_arrays); for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) { + impl.clear_output(); - clear_output(); + impl.build_transition(voxels, channel, dir, input.lod); - build_transition(voxels, channel, dir, input.lod); - - if (_output_vertices.size() == 0) { + if (impl.get_output().vertices.size() == 0) { continue; } Array transition_arrays; - fill_surface_arrays(transition_arrays); + fill_surface_arrays(transition_arrays, impl.get_output()); output.transition_surfaces[dir].push_back(transition_arrays); } @@ -208,28 +214,35 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher // TODO For testing at the moment Ref VoxelMesherTransvoxel::build_transition_mesh(Ref voxels, int direction) { + VoxelMesherTransvoxelInternal &impl = _impl; - clear_output(); + impl.clear_output(); ERR_FAIL_COND_V(voxels.is_null(), Ref()); - build_transition(**voxels, VoxelBuffer::CHANNEL_SDF, direction, 0); + impl.build_transition(**voxels, VoxelBuffer::CHANNEL_SDF, direction, 0); Ref mesh; - if (_output_vertices.size() == 0) { + if (impl.get_output().vertices.size() == 0) { return mesh; } Array arrays; - fill_surface_arrays(arrays); + fill_surface_arrays(arrays, impl.get_output()); mesh.instance(); mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays, Array(), MESH_COMPRESSION_FLAGS); return mesh; } -void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned int channel, int lod_index) { +void VoxelMesherTransvoxel::_bind_methods() { + ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"), + &VoxelMesherTransvoxel::build_transition_mesh); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void VoxelMesherTransvoxelInternal::build_internal(const VoxelBuffer &voxels, unsigned int channel, int lod_index) { struct L { inline static Vector3i dir_to_prev_vec(uint8_t dir) { //return g_corner_dirs[mask] - Vector3(1,1,1); @@ -515,7 +528,7 @@ void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned i for (int t = 0; t < triangle_count; ++t) { for (int i = 0; i < 3; ++i) { const int index = cell_vertex_indices[regular_cell_data.get_vertex_index(t * 3 + i)]; - _output_indices.push_back(index); + _output.indices.push_back(index); } } @@ -524,7 +537,7 @@ void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned i } // z } -void VoxelMesherTransvoxel::build_transition( +void VoxelMesherTransvoxelInternal::build_transition( const VoxelBuffer &p_voxels, unsigned int channel, int direction, int lod_index) { // y y @@ -899,13 +912,13 @@ void VoxelMesherTransvoxel::build_transition( for (unsigned int ti = 0; ti < triangle_count; ++ti) { if (flip_triangles) { - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3)]); - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 1)]); - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 2)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 1)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 2)]); } else { - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 2)]); - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 1)]); - _output_indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 2)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3 + 1)]); + _output.indices.push_back(cell_vertex_indices[cell_data.get_vertex_index(ti * 3)]); } } @@ -913,7 +926,7 @@ void VoxelMesherTransvoxel::build_transition( } // for y } -void VoxelMesherTransvoxel::reset_reuse_cells(Vector3i block_size) { +void VoxelMesherTransvoxelInternal::reset_reuse_cells(Vector3i block_size) { _block_size = block_size; unsigned int deck_area = block_size.x * block_size.y; for (unsigned int i = 0; i < _cache.size(); ++i) { @@ -925,7 +938,7 @@ void VoxelMesherTransvoxel::reset_reuse_cells(Vector3i block_size) { } } -void VoxelMesherTransvoxel::reset_reuse_cells_2d(Vector3i block_size) { +void VoxelMesherTransvoxelInternal::reset_reuse_cells_2d(Vector3i block_size) { for (unsigned int i = 0; i < _cache_2d.size(); ++i) { std::vector &row = _cache_2d[i]; row.resize(block_size.x); @@ -935,36 +948,28 @@ void VoxelMesherTransvoxel::reset_reuse_cells_2d(Vector3i block_size) { } } -VoxelMesherTransvoxel::ReuseCell &VoxelMesherTransvoxel::get_reuse_cell(Vector3i pos) { +VoxelMesherTransvoxelInternal::ReuseCell &VoxelMesherTransvoxelInternal::get_reuse_cell(Vector3i pos) { unsigned int j = pos.z & 1; unsigned int i = pos.y * _block_size.y + pos.x; CRASH_COND(i >= _cache[j].size()); return _cache[j][i]; } -VoxelMesherTransvoxel::ReuseTransitionCell &VoxelMesherTransvoxel::get_reuse_cell_2d(int x, int y) { +VoxelMesherTransvoxelInternal::ReuseTransitionCell &VoxelMesherTransvoxelInternal::get_reuse_cell_2d(int x, int y) { unsigned int j = y & 1; unsigned int i = x; CRASH_COND(i >= _cache_2d[j].size()); return _cache_2d[j][i]; } -int VoxelMesherTransvoxel::emit_vertex(Vector3 primary, Vector3 normal, uint16_t border_mask, Vector3 secondary) { +int VoxelMesherTransvoxelInternal::emit_vertex( + Vector3 primary, Vector3 normal, uint16_t border_mask, Vector3 secondary) { - int vi = _output_vertices.size(); + int vi = _output.vertices.size(); - _output_vertices.push_back(primary); - _output_normals.push_back(normal); - _output_extra.push_back(Color(secondary.x, secondary.y, secondary.z, border_mask)); + _output.vertices.push_back(primary); + _output.normals.push_back(normal); + _output.extra.push_back(Color(secondary.x, secondary.y, secondary.z, border_mask)); return vi; } - -VoxelMesher *VoxelMesherTransvoxel::clone() { - return memnew(VoxelMesherTransvoxel); -} - -void VoxelMesherTransvoxel::_bind_methods() { - ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"), - &VoxelMesherTransvoxel::build_transition_mesh); -} diff --git a/meshers/transvoxel/voxel_mesher_transvoxel.h b/meshers/transvoxel/voxel_mesher_transvoxel.h index 5118e48f..805fb6a8 100644 --- a/meshers/transvoxel/voxel_mesher_transvoxel.h +++ b/meshers/transvoxel/voxel_mesher_transvoxel.h @@ -6,21 +6,29 @@ #include "../voxel_mesher.h" #include -class VoxelMesherTransvoxel : public VoxelMesher { - GDCLASS(VoxelMesherTransvoxel, VoxelMesher) - +class VoxelMesherTransvoxelInternal { public: static const int MIN_PADDING = 1; static const int MAX_PADDING = 2; - VoxelMesherTransvoxel(); + struct MeshArrays { + std::vector vertices; + std::vector normals; + std::vector extra; + std::vector indices; - void build(VoxelMesher::Output &output, const VoxelMesher::Input &input) override; + void clear() { + vertices.clear(); + normals.clear(); + extra.clear(); + indices.clear(); + } + }; - VoxelMesher *clone() override; - -protected: - static void _bind_methods(); + void build_internal(const VoxelBuffer &voxels, unsigned int channel, int lod_index); + void build_transition(const VoxelBuffer &voxels, unsigned int channel, int direction, int lod_index); + void clear_output() { _output.clear(); } + const MeshArrays &get_output() const { return _output; } private: struct ReuseCell { @@ -35,26 +43,40 @@ private: const VoxelBuffer *full_resolution_neighbor_voxels[Cube::SIDE_COUNT] = { nullptr }; }; - void build_internal(const VoxelBuffer &voxels, unsigned int channel, int lod_index); - void build_transition(const VoxelBuffer &voxels, unsigned int channel, int direction, int lod_index); - Ref build_transition_mesh(Ref voxels, int direction); void reset_reuse_cells(Vector3i block_size); void reset_reuse_cells_2d(Vector3i block_size); ReuseCell &get_reuse_cell(Vector3i pos); ReuseTransitionCell &get_reuse_cell_2d(int x, int y); int emit_vertex(Vector3 primary, Vector3 normal, uint16_t border_mask, Vector3 secondary); - void clear_output(); - void fill_surface_arrays(Array &arrays); private: + // Work cache FixedArray, 2> _cache; FixedArray, 2> _cache_2d; Vector3i _block_size; + MeshArrays _output; +}; - std::vector _output_vertices; - std::vector _output_normals; - std::vector _output_extra; - std::vector _output_indices; +class VoxelMesherTransvoxel : public VoxelMesher { + GDCLASS(VoxelMesherTransvoxel, VoxelMesher) + +public: + VoxelMesherTransvoxel(); + ~VoxelMesherTransvoxel(); + + void build(VoxelMesher::Output &output, const VoxelMesher::Input &input) override; + Ref build_transition_mesh(Ref voxels, int direction); + + Ref duplicate(bool p_subresources = false) const override; + int get_used_channels_mask() const override; + +protected: + static void _bind_methods(); + +private: + void fill_surface_arrays(Array &arrays, const VoxelMesherTransvoxelInternal::MeshArrays &src); + + static thread_local VoxelMesherTransvoxelInternal _impl; }; #endif // VOXEL_MESHER_TRANSVOXEL_H diff --git a/meshers/voxel_mesher.cpp b/meshers/voxel_mesher.cpp index 7579343c..1a4f0c5a 100644 --- a/meshers/voxel_mesher.cpp +++ b/meshers/voxel_mesher.cpp @@ -56,10 +56,6 @@ void VoxelMesher::set_padding(int minimum, int maximum) { _maximum_padding = maximum; } -VoxelMesher *VoxelMesher::clone() { - return nullptr; -} - void VoxelMesher::_bind_methods() { // Shortcut if you want to generate a mesh directly from a fixed grid of voxels. // Useful for testing the different meshers. diff --git a/meshers/voxel_mesher.h b/meshers/voxel_mesher.h index 05468ab1..5a2d7a46 100644 --- a/meshers/voxel_mesher.h +++ b/meshers/voxel_mesher.h @@ -7,8 +7,8 @@ class VoxelBuffer; -class VoxelMesher : public Reference { - GDCLASS(VoxelMesher, Reference) +class VoxelMesher : public Resource { + GDCLASS(VoxelMesher, Resource) public: struct Input { const VoxelBuffer &voxels; @@ -23,18 +23,23 @@ public: unsigned int compression_flags = Mesh::ARRAY_COMPRESS_DEFAULT; }; + // This can be called from multiple threads at once. Make sure member vars are protected or thread-local. virtual void build(Output &output, const Input &voxels); - // Get how many neighbor voxels need to be accessed around the meshed area. + // Builds a mesh from the given voxels. This function is simplified to be used by the script API. + Ref build_mesh(Ref voxels, Array materials); + + // Gets how many neighbor voxels need to be accessed around the meshed area, toward negative axes. // If this is not respected, the mesher might produce seams at the edges, or an error unsigned int get_minimum_padding() const; + + // Gets how many neighbor voxels need to be accessed around the meshed area, toward positive axes. + // If this is not respected, the mesher might produce seams at the edges, or an error unsigned int get_maximum_padding() const; - // TODO Rename duplicate() - // Must be cloneable so can be duplicated for use by more than one thread - virtual VoxelMesher *clone(); + virtual Ref duplicate(bool p_subresources = false) const { return Ref(); } - Ref build_mesh(Ref voxels, Array materials); + virtual int get_used_channels_mask() const { return 0; } protected: static void _bind_methods(); diff --git a/server/voxel_server.cpp b/server/voxel_server.cpp index ef862332..b9ead4c2 100644 --- a/server/voxel_server.cpp +++ b/server/voxel_server.cpp @@ -56,20 +56,6 @@ VoxelServer::VoxelServer() { _meshing_thread_pool.set_priority_update_period(64); _meshing_thread_pool.set_batch_count(1); - for (size_t i = 0; i < _meshing_thread_pool.get_thread_count(); ++i) { - Ref mesher; - mesher.instance(); - mesher->set_occlusion_enabled(true); - mesher->set_occlusion_darkness(0.8f); - _blocky_meshers[i] = mesher; - } - - for (size_t i = 0; i < _meshing_thread_pool.get_thread_count(); ++i) { - Ref mesher; - mesher.instance(); - _smooth_meshers[i] = mesher; - } - if (Engine::get_singleton()->is_editor_hint()) { // Default viewer const uint32_t default_viewer_id = add_viewer(); @@ -178,11 +164,11 @@ void VoxelServer::set_volume_stream(uint32_t volume_id, Ref stream) } } -void VoxelServer::set_volume_voxel_library(uint32_t volume_id, Ref library) { +void VoxelServer::set_volume_mesher(uint32_t volume_id, Ref mesher) { Volume &volume = _world.volumes.get(volume_id); - volume.voxel_library = library; + volume.mesher = mesher; volume.meshing_dependency = gd_make_shared(); - volume.meshing_dependency->library = volume.voxel_library; + volume.meshing_dependency->mesher = volume.mesher; } void VoxelServer::set_volume_octree_split_scale(uint32_t volume_id, float split_scale) { @@ -194,7 +180,7 @@ void VoxelServer::invalidate_volume_mesh_requests(uint32_t volume_id) { Volume &volume = _world.volumes.get(volume_id); volume.meshing_dependency->valid = false; volume.meshing_dependency = gd_make_shared(); - volume.meshing_dependency->library = volume.voxel_library; + volume.meshing_dependency->mesher = volume.mesher; } static inline Vector3i get_block_center(Vector3i pos, int bs, int lod) { @@ -242,11 +228,6 @@ void VoxelServer::request_block_mesh(uint32_t volume_id, BlockMeshInput &input) r->blocks = input.blocks; r->position = input.position; r->lod = input.lod; - - r->smooth_enabled = volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF); - r->blocky_enabled = volume.voxel_library.is_valid() && - volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_TYPE); - r->meshing_dependency = volume.meshing_dependency; init_priority_dependency(r->priority_dependency, input.position, input.lod, volume); @@ -266,7 +247,6 @@ void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int r.lod = lod; r.type = BlockDataRequest::TYPE_LOAD; r.block_size = volume.block_size; - r.stream_dependency = volume.stream_dependency; init_priority_dependency(r.priority_dependency, block_pos, lod, volume); @@ -287,7 +267,6 @@ void VoxelServer::request_block_save(uint32_t volume_id, Ref voxels r.lod = lod; r.type = BlockDataRequest::TYPE_SAVE; r.block_size = volume.block_size; - r.stream_dependency = volume.stream_dependency; // No priority data, saving doesnt need sorting @@ -313,7 +292,7 @@ void VoxelServer::remove_volume(uint32_t volume_id) { if (_world.volumes.count() == 0) { // To workaround https://github.com/Zylann/godot_voxel/issues/189 // When the last remaining volume got destroyed (as in game exit) - VoxelServer::get_singleton()->wait_and_clear_all_tasks(false); + wait_and_clear_all_tasks(false); } } @@ -322,7 +301,7 @@ uint32_t VoxelServer::add_viewer() { // Remove default viewer if any _world.viewers.for_each_with_id([this](Viewer &viewer, uint32_t id) { if (viewer.is_default) { - // Safe because StructDB does not shift items + // Safe because StructDB does not reallocate the data structure on removal _world.viewers.destroy(id); } }); @@ -451,8 +430,7 @@ void VoxelServer::process() { o.position = r->position; o.lod = r->lod; - o.blocky_surfaces = r->blocky_surfaces_output; - o.smooth_surfaces = r->smooth_surfaces_output; + o.surfaces = r->surfaces_output; volume->reception_buffers->mesh_output.push_back(o); } @@ -489,28 +467,28 @@ void VoxelServer::process() { } } -void VoxelServer::get_min_max_block_padding( - bool blocky_enabled, bool smooth_enabled, unsigned int &out_min_padding, unsigned int &out_max_padding) const { +// void VoxelServer::get_min_max_block_padding( +// bool blocky_enabled, bool smooth_enabled, unsigned int &out_min_padding, unsigned int &out_max_padding) const { - // const Volume &volume = _world.volumes.get(volume_id); +// // const Volume &volume = _world.volumes.get(volume_id); - // bool smooth_enabled = volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF); - // bool blocky_enabled = volume.voxel_library.is_valid() && - // volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_TYPE); +// // bool smooth_enabled = volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF); +// // bool blocky_enabled = volume.voxel_library.is_valid() && +// // volume.stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_TYPE); - out_min_padding = 0; - out_max_padding = 0; +// out_min_padding = 0; +// out_max_padding = 0; - if (blocky_enabled) { - out_min_padding = max(out_min_padding, _blocky_meshers[0]->get_minimum_padding()); - out_max_padding = max(out_max_padding, _blocky_meshers[0]->get_maximum_padding()); - } +// if (blocky_enabled) { +// out_min_padding = max(out_min_padding, _blocky_meshers[0]->get_minimum_padding()); +// out_max_padding = max(out_max_padding, _blocky_meshers[0]->get_maximum_padding()); +// } - if (smooth_enabled) { - out_min_padding = max(out_min_padding, _smooth_meshers[0]->get_minimum_padding()); - out_max_padding = max(out_max_padding, _smooth_meshers[0]->get_maximum_padding()); - } -} +// if (smooth_enabled) { +// out_min_padding = max(out_min_padding, _smooth_meshers[0]->get_minimum_padding()); +// out_max_padding = max(out_max_padding, _smooth_meshers[0]->get_maximum_padding()); +// } +// } static unsigned int debug_get_active_thread_count(const VoxelThreadPool &pool) { unsigned int active_count = 0; @@ -594,13 +572,18 @@ bool VoxelServer::BlockDataRequest::is_cancelled() { //---------------------------------------------------------------------------------------------------------------------- static void copy_block_and_neighbors(const FixedArray, Cube::MOORE_AREA_3D_COUNT> &moore_blocks, - VoxelBuffer &dst, int min_padding, int max_padding) { + VoxelBuffer &dst, int min_padding, int max_padding, int channels_mask) { VOXEL_PROFILE_SCOPE(); - FixedArray channels; - channels[0] = VoxelBuffer::CHANNEL_TYPE; - channels[1] = VoxelBuffer::CHANNEL_SDF; + FixedArray channels; + unsigned int channels_count = 0; + for (unsigned int i = 0; i < VoxelBuffer::MAX_CHANNELS; ++i) { + if (channels_mask & (1 << i) != 0) { + channels[channels_count] = i; + ++channels_count; + } + } Ref central_buffer = moore_blocks[Cube::MOORE_AREA_3D_CENTRAL_INDEX]; CRASH_COND_MSG(central_buffer.is_null(), "Central buffer must be valid"); @@ -641,36 +624,19 @@ void VoxelServer::BlockMeshRequest::run(VoxelTaskContext ctx) { VOXEL_PROFILE_SCOPE(); CRASH_COND(meshing_dependency == nullptr); - unsigned int min_padding; - unsigned int max_padding; - VoxelServer::get_singleton()->get_min_max_block_padding(blocky_enabled, smooth_enabled, min_padding, max_padding); + Ref mesher = meshing_dependency->mesher; + CRASH_COND(mesher.is_null()); + const unsigned int min_padding = mesher->get_minimum_padding(); + const unsigned int max_padding = mesher->get_maximum_padding(); // TODO Cache? Ref voxels; voxels.instance(); - copy_block_and_neighbors(blocks, **voxels, min_padding, max_padding); + copy_block_and_neighbors(blocks, **voxels, min_padding, max_padding, mesher->get_used_channels_mask()); VoxelMesher::Input input = { **voxels, lod }; - if (blocky_enabled) { - Ref library = meshing_dependency->library; - if (library.is_valid()) { - VOXEL_PROFILE_SCOPE_NAMED("Blocky meshing"); - Ref blocky_mesher = VoxelServer::get_singleton()->_blocky_meshers[ctx.thread_index]; - CRASH_COND(blocky_mesher.is_null()); - // This mesher only uses baked data from the library, which is protected by a lock - blocky_mesher->set_library(library); - blocky_mesher->build(blocky_surfaces_output, input); - blocky_mesher->set_library(Ref()); - } - } - - if (smooth_enabled) { - VOXEL_PROFILE_SCOPE_NAMED("Smooth meshing"); - Ref smooth_mesher = VoxelServer::get_singleton()->_smooth_meshers[ctx.thread_index]; - CRASH_COND(smooth_mesher.is_null()); - smooth_mesher->build(smooth_surfaces_output, input); - } + mesher->build(surfaces_output, input); has_run = true; } diff --git a/server/voxel_server.h b/server/voxel_server.h index 6676630f..167bee8e 100644 --- a/server/voxel_server.h +++ b/server/voxel_server.h @@ -21,8 +21,7 @@ public: }; Type type; - VoxelMesher::Output blocky_surfaces; - VoxelMesher::Output smooth_surfaces; + VoxelMesher::Output surfaces; Vector3i position; uint8_t lod; }; @@ -83,7 +82,7 @@ public: void set_volume_transform(uint32_t volume_id, Transform t); void set_volume_block_size(uint32_t volume_id, uint32_t block_size); void set_volume_stream(uint32_t volume_id, Ref stream); - void set_volume_voxel_library(uint32_t volume_id, Ref library); + void set_volume_mesher(uint32_t volume_id, Ref mesher); void set_volume_octree_split_scale(uint32_t volume_id, float split_scale); void invalidate_volume_mesh_requests(uint32_t volume_id); void request_block_mesh(uint32_t volume_id, BlockMeshInput &input); @@ -109,9 +108,9 @@ public: } // Gets by how much voxels must be padded with neighbors in order to be polygonized properly - void get_min_max_block_padding( - bool blocky_enabled, bool smooth_enabled, - unsigned int &out_min_padding, unsigned int &out_max_padding) const; + // void get_min_max_block_padding( + // bool blocky_enabled, bool smooth_enabled, + // unsigned int &out_min_padding, unsigned int &out_max_padding) const; void process(); void wait_and_clear_all_tasks(bool warn); @@ -148,7 +147,7 @@ private: // Data common to all requests about a particular volume struct MeshingDependency { - Ref library; + Ref mesher; bool valid = true; }; @@ -157,7 +156,7 @@ private: ReceptionBuffers *reception_buffers = nullptr; Transform transform; Ref stream; - Ref voxel_library; + Ref mesher; uint32_t block_size = 16; float octree_split_scale = 0; std::shared_ptr stream_dependency; @@ -225,14 +224,11 @@ private: Vector3i position; uint32_t volume_id; uint8_t lod; - bool smooth_enabled; - bool blocky_enabled; bool has_run = false; bool too_far = false; PriorityDependency priority_dependency; std::shared_ptr meshing_dependency; - VoxelMesher::Output blocky_surfaces_output; - VoxelMesher::Output smooth_surfaces_output; + VoxelMesher::Output surfaces_output; }; // TODO multi-world support in the future @@ -240,14 +236,6 @@ private: VoxelThreadPool _streaming_thread_pool; VoxelThreadPool _meshing_thread_pool; - - // TODO I do this because meshers have memory caches. But perhaps we could put them in thread locals? - // Used by tasks from threads. - // Meshers have internal state because they use memory caches, - // so we instanciate one per thread to be sure it's safe without having to lock. - // Options such as library etc can change per task. - FixedArray, VoxelThreadPool::MAX_THREADS> _blocky_meshers; - FixedArray, VoxelThreadPool::MAX_THREADS> _smooth_meshers; }; // TODO Hack to make VoxelServer update... need ways to integrate callbacks from main loop! diff --git a/terrain/voxel_box_mover.cpp b/terrain/voxel_box_mover.cpp index dedb0cae..2ca7c460 100644 --- a/terrain/voxel_box_mover.cpp +++ b/terrain/voxel_box_mover.cpp @@ -116,9 +116,11 @@ static Vector3 get_motion(AABB box, Vector3 motion, const std::vector &env Vector3 VoxelBoxMover::get_motion(Vector3 pos, Vector3 motion, AABB aabb, VoxelTerrain *terrain) { ERR_FAIL_COND_V(terrain == nullptr, Vector3()); + ERR_FAIL_COND_V(terrain->get_mesher().is_null(), Vector3()); Ref library_ref = terrain->get_voxel_library(); ERR_FAIL_COND_V(library_ref.is_null(), Vector3()); VoxelLibrary &library = **library_ref; + // TODO Make this work with colored cubes meshers too, but need to set a collision rule for transparent cubes AABB box(aabb.position + pos, aabb.size); AABB expanded_box = expand_with_vector(box, motion); diff --git a/terrain/voxel_lod_terrain.cpp b/terrain/voxel_lod_terrain.cpp index c46a4a0c..6d7bc502 100644 --- a/terrain/voxel_lod_terrain.cpp +++ b/terrain/voxel_lod_terrain.cpp @@ -1,6 +1,7 @@ #include "voxel_lod_terrain.h" #include "../edition/voxel_tool_lod_terrain.h" #include "../math/rect3i.h" +#include "../meshers/transvoxel/voxel_mesher_transvoxel.h" #include "../server/voxel_server.h" #include "../streams/voxel_stream_file.h" #include "../util/macros.h" @@ -48,6 +49,20 @@ Ref build_mesh(const Vector surfaces, Mesh::PrimitiveType prim return mesh; } +// To use on loaded blocks +static inline void schedule_mesh_update(VoxelBlock *block, std::vector &blocks_pending_update) { + if (block->get_mesh_state() != VoxelBlock::MESH_UPDATE_NOT_SENT) { + if (block->is_visible()) { + // Schedule an update + block->set_mesh_state(VoxelBlock::MESH_UPDATE_NOT_SENT); + blocks_pending_update.push_back(block->position); + } else { + // Just mark it as needing update, so the visibility system will schedule its update when needed + block->set_mesh_state(VoxelBlock::MESH_NEED_UPDATE); + } + } +} + } // namespace VoxelLodTerrain::VoxelLodTerrain() { @@ -72,6 +87,11 @@ VoxelLodTerrain::VoxelLodTerrain() { // Because it prevents edits from propagating up to the last one, they will be left out of sync set_lod_count(4); set_lod_split_scale(3); + + // For ease of use in editor + Ref default_mesher; + default_mesher.instance(); + _mesher = default_mesher; } VoxelLodTerrain::~VoxelLodTerrain() { @@ -87,22 +107,33 @@ VoxelLodTerrain::~VoxelLodTerrain() { } String VoxelLodTerrain::get_configuration_warning() const { + if (_mesher.is_null()) { + return TTR("This node has no mesher assigned, it wont produce any mesh visuals. " + "You can assign one on the `mesher` property."); + } + if (_stream.is_valid()) { Ref