diff --git a/dmc/voxel_mesher_dmc.cpp b/dmc/voxel_mesher_dmc.cpp index cd3e49d..8421c5c 100644 --- a/dmc/voxel_mesher_dmc.cpp +++ b/dmc/voxel_mesher_dmc.cpp @@ -1326,7 +1326,12 @@ Array VoxelMesherDMC::build(const VoxelBuffer &voxels) { _stats = { 0 }; - int padding = 1; + if (voxels.is_uniform(VoxelBuffer::CHANNEL_ISOLEVEL)) { + // That won't produce any polygon + return Array(); + } + + int padding = 2; 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)); diff --git a/voxel_mesh_updater.cpp b/voxel_mesh_updater.cpp index 5f53054..3aa3a29 100644 --- a/voxel_mesh_updater.cpp +++ b/voxel_mesh_updater.cpp @@ -12,9 +12,11 @@ VoxelMeshUpdater::VoxelMeshUpdater(Ref library, MeshingParams para _blocky_mesher->set_occlusion_enabled(params.baked_ao); _blocky_mesher->set_occlusion_darkness(params.baked_ao_darkness); - _dmc_mesher.instance(); - _dmc_mesher->set_geometric_error(0.05); - _dmc_mesher->set_octree_mode(VoxelMesherDMC::OCTREE_NONE); + if (params.smooth_surface) { + _dmc_mesher.instance(); + _dmc_mesher->set_geometric_error(0.05); + _dmc_mesher->set_octree_mode(VoxelMesherDMC::OCTREE_NONE); + } _input_mutex = Mutex::create(); _output_mutex = Mutex::create(); @@ -177,10 +179,18 @@ void VoxelMeshUpdater::process_block(const InputBlock &block, OutputBlock &outpu CRASH_COND(block.voxels.is_null()); + int padding = 1; + if (_dmc_mesher.is_valid()) { + padding = 2; + } + // Build cubic parts of the mesh - output.model_surfaces = _blocky_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, Vector3i(0, 0, 0), block.voxels->get_size() - Vector3(1, 1, 1)); - // Build smooth parts of the mesh - output.smooth_surfaces = _dmc_mesher->build(**block.voxels); + output.model_surfaces = _blocky_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, padding); + + if (_dmc_mesher.is_valid()) { + // Build smooth parts of the mesh + output.smooth_surfaces = _dmc_mesher->build(**block.voxels); + } output.position = block.position; } diff --git a/voxel_mesh_updater.h b/voxel_mesh_updater.h index 5dbe048..61483e2 100644 --- a/voxel_mesh_updater.h +++ b/voxel_mesh_updater.h @@ -52,10 +52,12 @@ public: struct MeshingParams { bool baked_ao; float baked_ao_darkness; + bool smooth_surface; MeshingParams() : baked_ao(true), - baked_ao_darkness(0.75) {} + baked_ao_darkness(0.75), + smooth_surface(false) {} }; VoxelMeshUpdater(Ref library, MeshingParams params); diff --git a/voxel_mesher.cpp b/voxel_mesher.cpp index 8501e2e..6c98fa2 100644 --- a/voxel_mesher.cpp +++ b/voxel_mesher.cpp @@ -55,7 +55,7 @@ Ref VoxelMesher::build_mesh(Ref buffer_ref, unsigned int ERR_FAIL_COND_V(buffer_ref.is_null(), Ref()); VoxelBuffer &buffer = **buffer_ref; - Array surfaces = build(buffer, channel, Vector3i(), buffer.get_size()); + Array surfaces = build(buffer, channel, MINIMUM_PADDING); if (mesh.is_null()) mesh.instance(); @@ -75,11 +75,12 @@ Ref VoxelMesher::build_mesh(Ref buffer_ref, unsigned int return mesh; } -Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max) { - uint64_t time_before = OS::get_singleton()->get_ticks_usec(); +Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, int padding) { + //uint64_t time_before = OS::get_singleton()->get_ticks_usec(); ERR_FAIL_COND_V(_library.is_null(), Array()); ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array()); + ERR_FAIL_COND_V(padding < MINIMUM_PADDING, Array()); const VoxelLibrary &library = **_library; @@ -105,10 +106,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector // => Could be implemented in a separate class? // Data must be padded, hence the off-by-one - Vector3i::sort_min_max(min, max); - const Vector3i pad(1, 1, 1); - min.clamp_to(pad, max); - max.clamp_to(min, buffer.get_size() - pad); + Vector3i min = Vector3i(padding); + Vector3i max = buffer.get_size() - Vector3i(padding); int index_offset = 0; @@ -176,8 +175,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector corner_neighbor_lut[Cube::CORNER_TOP_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT]; corner_neighbor_lut[Cube::CORNER_TOP_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT]; - uint64_t time_prep = OS::get_singleton()->get_ticks_usec() - time_before; - time_before = OS::get_singleton()->get_ticks_usec(); + //uint64_t time_prep = OS::get_singleton()->get_ticks_usec() - time_before; + //time_before = OS::get_singleton()->get_ticks_usec(); for (unsigned int z = min.z; z < max.z; ++z) { for (unsigned int x = min.x; x < max.x; ++x) { @@ -360,8 +359,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector } } - uint64_t time_meshing = OS::get_singleton()->get_ticks_usec() - time_before; - time_before = OS::get_singleton()->get_ticks_usec(); + //uint64_t time_meshing = OS::get_singleton()->get_ticks_usec() - time_before; + //time_before = OS::get_singleton()->get_ticks_usec(); // Commit mesh @@ -412,7 +411,7 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector } } - uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before; + //uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before; //print_line(String("P: {0}, M: {1}, C: {2}").format(varray(time_prep, time_meshing, time_commit))); diff --git a/voxel_mesher.h b/voxel_mesher.h index a34c7e8..bc538f4 100644 --- a/voxel_mesher.h +++ b/voxel_mesher.h @@ -14,6 +14,7 @@ class VoxelMesher : public Reference { public: static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed. + static const int MINIMUM_PADDING = 1; VoxelMesher(); @@ -26,7 +27,7 @@ public: void set_occlusion_enabled(bool enable); bool get_occlusion_enabled() const { return _bake_occlusion; } - Array build(const VoxelBuffer &buffer_ref, unsigned int channel, Vector3i min, Vector3i max); + Array build(const VoxelBuffer &buffer_ref, unsigned int channel, int padding); Ref build_mesh(Ref buffer_ref, unsigned int channel, Array materials, Ref mesh = Ref()); protected: diff --git a/voxel_terrain.cpp b/voxel_terrain.cpp index dd1570d..47d06f5 100644 --- a/voxel_terrain.cpp +++ b/voxel_terrain.cpp @@ -10,9 +10,7 @@ #include #include -VoxelTerrain::VoxelTerrain() : - Spatial(), - _generate_collisions(true) { +VoxelTerrain::VoxelTerrain() { _map = Ref(memnew(VoxelMap)); @@ -24,6 +22,7 @@ VoxelTerrain::VoxelTerrain() : _generate_collisions = false; _run_in_editor = false; + _smooth_meshing_enabled = false; } VoxelTerrain::~VoxelTerrain() { @@ -108,15 +107,7 @@ void VoxelTerrain::set_voxel_library(Ref library) { #endif _library = library; - if (_block_updater) { - memdelete(_block_updater); - _block_updater = NULL; - } - - // TODO Thread-safe way to change those parameters - VoxelMeshUpdater::MeshingParams params; - - _block_updater = memnew(VoxelMeshUpdater(_library, params)); + reset_updater(); // Voxel appearance might completely change make_all_view_dirty_deferred(); @@ -169,6 +160,18 @@ Ref VoxelTerrain::get_material(int id) const { return _materials[id]; } +bool VoxelTerrain::is_smooth_meshing_enabled() const { + return _smooth_meshing_enabled; +} + +void VoxelTerrain::set_smooth_meshing_enabled(bool enabled) { + if (_smooth_meshing_enabled != enabled) { + _smooth_meshing_enabled = enabled; + reset_updater(); + make_all_view_dirty_deferred(); + } +} + void VoxelTerrain::make_block_dirty(Vector3i bpos) { // TODO Immediate update viewer distance? @@ -269,6 +272,29 @@ void VoxelTerrain::make_all_view_dirty_deferred() { // make_blocks_dirty(-radius, 2*radius); } +void VoxelTerrain::reset_updater() { + + if (_block_updater) { + memdelete(_block_updater); + _block_updater = NULL; + } + + // TODO Thread-safe way to change those parameters + VoxelMeshUpdater::MeshingParams params; + params.smooth_surface = _smooth_meshing_enabled; + + _block_updater = memnew(VoxelMeshUpdater(_library, params)); +} + +int VoxelTerrain::get_block_padding() const { + // How many neighbor voxels we should pad for mesh updates to be seamless + // TODO Generalize padding retrieval, or split terrain systems because blocky and smooth are two different beasts + // - Blocky needs padding of 1 + // - Transvoxel needs padding of 2 + // - DMC needs padding of 2 + return _smooth_meshing_enabled ? 2 : 1; +} + inline int get_border_index(int x, int max) { return x == 0 ? 0 : x != max ? 1 : 2; } @@ -688,44 +714,54 @@ void VoxelTerrain::_process() { for (int i = 0; i < _blocks_pending_update.size(); ++i) { Vector3i block_pos = _blocks_pending_update[i]; - VoxelBlock *block = _map->get_block(block_pos); - if (block == NULL) { - continue; - } + // Check if the block is worth meshing + // Smooth meshing works on more neighbors, so checking a single block isn't enough to ignore it, + // 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 (!_smooth_meshing_enabled) { + VoxelBlock *block = _map->get_block(block_pos); + if (block == NULL) { + continue; + } else { + CRASH_COND(block->voxels.is_null()); - CRASH_COND(block->voxels.is_null()); + int air_type = 0; + if ( + block->voxels->is_uniform(Voxel::CHANNEL_TYPE) && + block->voxels->is_uniform(Voxel::CHANNEL_ISOLEVEL) && + block->voxels->get_voxel(0, 0, 0, Voxel::CHANNEL_TYPE) == air_type) { + + VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos); + CRASH_COND(block_state == NULL); + CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT); + + // The block contains empty voxels + block->set_mesh(Ref(), Ref()); + _dirty_blocks.erase(block_pos); + + // Optional, but I guess it might spare some memory + block->voxels->clear_channel(Voxel::CHANNEL_TYPE, air_type); + + continue; + } + } + } VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos); CRASH_COND(block_state == NULL); CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT); - int air_type = 0; - if ( - block->voxels->is_uniform(Voxel::CHANNEL_TYPE) && - block->voxels->is_uniform(Voxel::CHANNEL_ISOLEVEL) && - block->voxels->get_voxel(0, 0, 0, Voxel::CHANNEL_TYPE) == air_type) { - - // The block contains empty voxels - block->set_mesh(Ref(), Ref()); - _dirty_blocks.erase(block_pos); - - // Optional, but I guess it might spare some memory - block->voxels->clear_channel(Voxel::CHANNEL_TYPE, air_type); - - continue; - } - // Create buffer padded with neighbor voxels Ref nbuffer; nbuffer.instance(); - // TODO Make the buffer re-usable - // TODO Padding set to 3 at the moment because Transvoxel works on 2x2 cells. - // It should change for a smarter padding (if smooth isn't used for example). - unsigned int block_size = _map->get_block_size(); - //nbuffer->create(block_size + 3, block_size + 3, block_size + 3); - nbuffer->create(block_size + 2, block_size + 2, block_size + 2); - _map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(1, 1, 1), **nbuffer, 0x3); + // TODO Make the buffer re-usable + unsigned int block_size = _map->get_block_size(); + unsigned int padding = get_block_padding(); + nbuffer->create(block_size + 2 * padding, block_size + 2 * padding, block_size + 2 * padding); + + unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_ISOLEVEL); + _map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(padding), **nbuffer, channels_mask); VoxelMeshUpdater::InputBlock iblock; iblock.voxels = nbuffer; @@ -932,6 +968,9 @@ void VoxelTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("get_viewer_path"), &VoxelTerrain::get_viewer_path); ClassDB::bind_method(D_METHOD("set_viewer_path", "path"), &VoxelTerrain::set_viewer_path); + ClassDB::bind_method(D_METHOD("is_smooth_meshing_enabled"), &VoxelTerrain::is_smooth_meshing_enabled); + ClassDB::bind_method(D_METHOD("set_smooth_meshing_enabled", "enabled"), &VoxelTerrain::set_smooth_meshing_enabled); + ClassDB::bind_method(D_METHOD("get_storage"), &VoxelTerrain::get_map); ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding); @@ -950,6 +989,7 @@ void VoxelTerrain::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_meshing_enabled"), "set_smooth_meshing_enabled", "is_smooth_meshing_enabled"); BIND_ENUM_CONSTANT(BLOCK_NONE); BIND_ENUM_CONSTANT(BLOCK_LOAD); diff --git a/voxel_terrain.h b/voxel_terrain.h index e473537..1c2965f 100644 --- a/voxel_terrain.h +++ b/voxel_terrain.h @@ -13,8 +13,9 @@ class VoxelMap; class VoxelLibrary; -// Infinite static terrain made of voxels. -// It is loaded around VoxelTerrainStreamers. +// Infinite paged terrain made of voxel blocks. +// Voxels are polygonized around the viewer. +// Data is streamed using a VoxelProvider. class VoxelTerrain : public Spatial { GDCLASS(VoxelTerrain, Spatial) public: @@ -53,6 +54,9 @@ public: void set_material(int id, Ref material); Ref get_material(int id) const; + bool is_smooth_meshing_enabled() const; + void set_smooth_meshing_enabled(bool enabled); + Ref get_map() { return _map; } struct Stats { @@ -93,6 +97,8 @@ private: void _process(); void make_all_view_dirty_deferred(); + void reset_updater(); + int get_block_padding() const; Spatial *get_viewer(NodePath path) const; @@ -142,6 +148,7 @@ private: bool _generate_collisions; bool _run_in_editor; + bool _smooth_meshing_enabled; Ref _materials[VoxelMesher::MAX_MATERIALS];