mirror of
https://github.com/Relintai/godot_voxel.git
synced 2024-11-11 20:35:08 +01:00
247 lines
6.1 KiB
C++
247 lines
6.1 KiB
C++
#include "voxel_mesh_updater.h"
|
|
#include "utility.h"
|
|
#include <core/os/os.h>
|
|
|
|
VoxelMeshUpdater::VoxelMeshUpdater(Ref<VoxelLibrary> library, MeshingParams params) {
|
|
|
|
CRASH_COND(library.is_null());
|
|
//CRASH_COND(params.materials.size() == 0);
|
|
|
|
_model_mesher.instance();
|
|
_model_mesher->set_library(library);
|
|
_model_mesher->set_occlusion_enabled(params.baked_ao);
|
|
_model_mesher->set_occlusion_darkness(params.baked_ao_darkness);
|
|
|
|
_smooth_mesher.instance();
|
|
|
|
_input_mutex = Mutex::create();
|
|
_output_mutex = Mutex::create();
|
|
|
|
_thread_exit = false;
|
|
_semaphore = Semaphore::create();
|
|
_thread = Thread::create(_thread_func, this);
|
|
|
|
_needs_sort = true;
|
|
}
|
|
|
|
VoxelMeshUpdater::~VoxelMeshUpdater() {
|
|
|
|
_thread_exit = true;
|
|
_semaphore->post();
|
|
Thread::wait_to_finish(_thread);
|
|
memdelete(_thread);
|
|
memdelete(_semaphore);
|
|
memdelete(_input_mutex);
|
|
memdelete(_output_mutex);
|
|
}
|
|
|
|
void VoxelMeshUpdater::push(const Input &input) {
|
|
|
|
bool should_run = false;
|
|
int replaced_blocks = 0;
|
|
|
|
{
|
|
MutexLock lock(_input_mutex);
|
|
|
|
for (int i = 0; i < input.blocks.size(); ++i) {
|
|
|
|
Vector3i pos = input.blocks[i].position;
|
|
|
|
// If a block is exactly on the priority position, update it instantly on the main thread
|
|
// This is to eliminate latency for player's actions, assuming updating a block isn't slower than a frame
|
|
/*if (pos == _shared_input.priority_position) {
|
|
|
|
OutputBlock ob;
|
|
process_block(_shared_input.blocks[i], ob);
|
|
|
|
{
|
|
MutexLock lock2(_output_mutex);
|
|
_shared_output.blocks.push_back(ob);
|
|
}
|
|
|
|
continue;
|
|
}*/
|
|
|
|
int *index = _block_indexes.getptr(pos);
|
|
|
|
if (index) {
|
|
// The block is already in the update queue, replace it
|
|
++replaced_blocks;
|
|
_shared_input.blocks.write[*index] = input.blocks[i];
|
|
|
|
} else {
|
|
|
|
int j = _shared_input.blocks.size();
|
|
_shared_input.blocks.push_back(input.blocks[i]);
|
|
_block_indexes[pos] = j;
|
|
}
|
|
}
|
|
|
|
if (_shared_input.priority_position != input.priority_position || input.blocks.size() > 0) {
|
|
_needs_sort = true;
|
|
}
|
|
|
|
_shared_input.priority_position = input.priority_position;
|
|
should_run = !_shared_input.is_empty();
|
|
}
|
|
|
|
if (replaced_blocks > 0)
|
|
print_line(String("VoxelMeshUpdater: {0} blocks already in queue were replaced").format(varray(replaced_blocks)));
|
|
|
|
if (should_run) {
|
|
_semaphore->post();
|
|
}
|
|
}
|
|
|
|
void VoxelMeshUpdater::pop(Output &output) {
|
|
|
|
MutexLock lock(_output_mutex);
|
|
|
|
output.blocks.append_array(_shared_output.blocks);
|
|
output.stats = _shared_output.stats;
|
|
_shared_output.blocks.clear();
|
|
}
|
|
|
|
void VoxelMeshUpdater::_thread_func(void *p_self) {
|
|
VoxelMeshUpdater *self = reinterpret_cast<VoxelMeshUpdater *>(p_self);
|
|
self->thread_func();
|
|
}
|
|
|
|
void VoxelMeshUpdater::thread_func() {
|
|
|
|
while (!_thread_exit) {
|
|
|
|
uint32_t sync_interval = 50.0; // milliseconds
|
|
uint32_t sync_time = OS::get_singleton()->get_ticks_msec() + sync_interval;
|
|
|
|
int queue_index = 0;
|
|
Stats stats;
|
|
|
|
thread_sync(queue_index, stats);
|
|
|
|
while (!_input.blocks.empty() && !_thread_exit) {
|
|
|
|
if (!_input.blocks.empty()) {
|
|
|
|
InputBlock block = _input.blocks[queue_index];
|
|
++queue_index;
|
|
|
|
if (queue_index >= _input.blocks.size()) {
|
|
_input.blocks.clear();
|
|
}
|
|
|
|
uint64_t time_before = OS::get_singleton()->get_ticks_usec();
|
|
|
|
OutputBlock ob;
|
|
process_block(block, ob);
|
|
|
|
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - time_before;
|
|
|
|
// Do some stats
|
|
if (stats.first) {
|
|
stats.first = false;
|
|
stats.min_time = time_taken;
|
|
stats.max_time = time_taken;
|
|
} else {
|
|
if (time_taken < stats.min_time)
|
|
stats.min_time = time_taken;
|
|
if (time_taken > stats.max_time)
|
|
stats.max_time = time_taken;
|
|
}
|
|
|
|
_output.blocks.push_back(ob);
|
|
}
|
|
|
|
uint32_t time = OS::get_singleton()->get_ticks_msec();
|
|
if (time >= sync_time || _input.blocks.empty()) {
|
|
|
|
thread_sync(queue_index, stats);
|
|
|
|
sync_time = OS::get_singleton()->get_ticks_msec() + sync_interval;
|
|
queue_index = 0;
|
|
stats = Stats();
|
|
}
|
|
}
|
|
|
|
if (_thread_exit)
|
|
break;
|
|
|
|
// Wait for future wake-up
|
|
_semaphore->wait();
|
|
}
|
|
}
|
|
|
|
void VoxelMeshUpdater::process_block(const InputBlock &block, OutputBlock &output) {
|
|
|
|
CRASH_COND(block.voxels.is_null());
|
|
|
|
// Build cubic parts of the mesh
|
|
output.model_surfaces = _model_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 = _smooth_mesher->build(**block.voxels, Voxel::CHANNEL_ISOLEVEL);
|
|
|
|
output.position = block.position;
|
|
}
|
|
|
|
// Sorts distance to viewer
|
|
// The closest block will be the first one in the array
|
|
struct BlockUpdateComparator {
|
|
Vector3i center;
|
|
inline bool operator()(const VoxelMeshUpdater::InputBlock &a, const VoxelMeshUpdater::InputBlock &b) const {
|
|
return a.position.distance_sq(center) < b.position.distance_sq(center);
|
|
}
|
|
};
|
|
|
|
void VoxelMeshUpdater::thread_sync(int queue_index, Stats stats) {
|
|
|
|
if (!_input.blocks.empty()) {
|
|
// Cleanup input vector
|
|
|
|
if (queue_index >= _input.blocks.size()) {
|
|
_input.blocks.clear();
|
|
|
|
} else if (queue_index > 0) {
|
|
|
|
// Shift up remaining items since we use a Vector
|
|
shift_up(_input.blocks, queue_index);
|
|
}
|
|
}
|
|
|
|
stats.remaining_blocks = _input.blocks.size();
|
|
bool needs_sort;
|
|
|
|
{
|
|
// Get input
|
|
MutexLock lock(_input_mutex);
|
|
|
|
_input.blocks.append_array(_shared_input.blocks);
|
|
_input.priority_position = _shared_input.priority_position;
|
|
|
|
_shared_input.blocks.clear();
|
|
_block_indexes.clear();
|
|
|
|
needs_sort = _needs_sort;
|
|
_needs_sort = false;
|
|
}
|
|
|
|
if (!_output.blocks.empty()) {
|
|
|
|
// print_line(String("VoxelMeshUpdater: posting {0} blocks, {1} remaining ; cost [{2}..{3}] usec")
|
|
// .format(varray(_output.blocks.size(), _input.blocks.size(), stats.min_time, stats.max_time)));
|
|
|
|
// Post output
|
|
MutexLock lock(_output_mutex);
|
|
_shared_output.blocks.append_array(_output.blocks);
|
|
_shared_output.stats = stats;
|
|
_output.blocks.clear();
|
|
}
|
|
|
|
if (!_input.blocks.empty() && needs_sort) {
|
|
// Re-sort priority
|
|
|
|
SortArray<VoxelMeshUpdater::InputBlock, BlockUpdateComparator> sorter;
|
|
sorter.compare.center = _input.priority_position;
|
|
sorter.sort(_input.blocks.ptrw(), _input.blocks.size());
|
|
}
|
|
}
|