mirror of
https://github.com/Relintai/godot_voxel.git
synced 2024-12-22 01:26:50 +01:00
Added smooth_meshing option to VoxelTerrain and handle padding differences it introduces
This commit is contained in:
parent
fe37ed3674
commit
d6bb354bd9
@ -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));
|
||||
|
@ -12,9 +12,11 @@ VoxelMeshUpdater::VoxelMeshUpdater(Ref<VoxelLibrary> 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;
|
||||
}
|
||||
|
@ -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<VoxelLibrary> library, MeshingParams params);
|
||||
|
@ -55,7 +55,7 @@ Ref<ArrayMesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int
|
||||
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<ArrayMesh>());
|
||||
|
||||
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<ArrayMesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> 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)));
|
||||
|
||||
|
@ -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<ArrayMesh> build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||
|
||||
protected:
|
||||
|
@ -10,9 +10,7 @@
|
||||
#include <core/os/os.h>
|
||||
#include <scene/3d/mesh_instance.h>
|
||||
|
||||
VoxelTerrain::VoxelTerrain() :
|
||||
Spatial(),
|
||||
_generate_collisions(true) {
|
||||
VoxelTerrain::VoxelTerrain() {
|
||||
|
||||
_map = Ref<VoxelMap>(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<VoxelLibrary> 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<Material> 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<Mesh>(), Ref<World>());
|
||||
_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<Mesh>(), Ref<World>());
|
||||
_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<VoxelBuffer> 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);
|
||||
|
@ -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> material);
|
||||
Ref<Material> get_material(int id) const;
|
||||
|
||||
bool is_smooth_meshing_enabled() const;
|
||||
void set_smooth_meshing_enabled(bool enabled);
|
||||
|
||||
Ref<VoxelMap> 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<Material> _materials[VoxelMesher::MAX_MATERIALS];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user