mirror of
https://github.com/Relintai/godot_voxel.git
synced 2024-11-19 02:47:18 +01:00
Moved loading and meshing to 2 threads, will need a few bugfixes
This commit is contained in:
parent
085f94579a
commit
8f349a1c9a
43
utility.h
Normal file
43
utility.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef HEADER_VOXEL_UTILITY_H
|
||||||
|
#define HEADER_VOXEL_UTILITY_H
|
||||||
|
|
||||||
|
#include <core/vector.h>
|
||||||
|
#include <core/dvector.h>
|
||||||
|
#include "vector3i.h"
|
||||||
|
|
||||||
|
// Takes elements starting from a given position and moves them at the beginning,
|
||||||
|
// then shrink the array to fit them. Other elements are discarded.
|
||||||
|
template <typename T>
|
||||||
|
void shift_up(Vector<T> &v, int pos) {
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
for (int i = pos; i < v.size(); ++i, ++j) {
|
||||||
|
v.write[j] = v[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int remaining = v.size() - pos;
|
||||||
|
v.resize(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pops the last element of the vector and place it at the given position.
|
||||||
|
// (The element that was at this position is the one removed).
|
||||||
|
template <typename T>
|
||||||
|
void unordered_remove(Vector<T> &v, int pos) {
|
||||||
|
int last = v.size() - 1;
|
||||||
|
v.write[pos] = v[last];
|
||||||
|
v.resize(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void copy_to(PoolVector<T> &to, const Vector<T> &from) {
|
||||||
|
|
||||||
|
to.resize(from.size());
|
||||||
|
|
||||||
|
typename PoolVector<T>::Write w = to.write();
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < from.size(); ++i) {
|
||||||
|
w[i] = from[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HEADER_VOXEL_UTILITY_H
|
18
voxel_map.h
18
voxel_map.h
@ -2,21 +2,27 @@
|
|||||||
#define VOXEL_MAP_H
|
#define VOXEL_MAP_H
|
||||||
|
|
||||||
#include "voxel_buffer.h"
|
#include "voxel_buffer.h"
|
||||||
#include "voxel_block.h"
|
|
||||||
|
|
||||||
#include <core/hash_map.h>
|
#include <core/hash_map.h>
|
||||||
#include <scene/main/node.h>
|
#include <scene/main/node.h>
|
||||||
|
|
||||||
|
class VoxelBlock;
|
||||||
|
|
||||||
// Infinite voxel storage by means of octants like Gridmap
|
// Infinite voxel storage by means of octants like Gridmap
|
||||||
class VoxelMap : public Reference {
|
class VoxelMap : public Reference {
|
||||||
GDCLASS(VoxelMap, Reference)
|
GDCLASS(VoxelMap, Reference)
|
||||||
public:
|
public:
|
||||||
// Converts voxel coodinates into block coordinates
|
// Converts voxel coodinates into block coordinates.
|
||||||
_FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) const {
|
// Don't use division because it introduces an offset in negative coordinates.
|
||||||
|
static _FORCE_INLINE_ Vector3i voxel_to_block_b(Vector3i pos, int block_size_pow2) {
|
||||||
return Vector3i(
|
return Vector3i(
|
||||||
pos.x >> _block_size_pow2,
|
pos.x >> block_size_pow2,
|
||||||
pos.y >> _block_size_pow2,
|
pos.y >> block_size_pow2,
|
||||||
pos.z >> _block_size_pow2);
|
pos.z >> block_size_pow2);
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) const {
|
||||||
|
return voxel_to_block_b(pos, _block_size_pow2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_FORCE_INLINE_ Vector3i to_local(Vector3i pos) const {
|
_FORCE_INLINE_ Vector3i to_local(Vector3i pos) const {
|
||||||
|
237
voxel_mesh_updater.cpp
Normal file
237
voxel_mesh_updater.cpp
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
#include <core/os/os.h>
|
||||||
|
#include "voxel_mesh_updater.h"
|
||||||
|
#include "utility.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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) {
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
// Re-sort priority
|
||||||
|
|
||||||
|
SortArray<VoxelMeshUpdater::InputBlock, BlockUpdateComparator> sorter;
|
||||||
|
sorter.compare.center = _input.priority_position;
|
||||||
|
sorter.sort(_input.blocks.ptrw(), _input.blocks.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
88
voxel_mesh_updater.h
Normal file
88
voxel_mesh_updater.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#ifndef VOXEL_MESH_UPDATER_H
|
||||||
|
#define VOXEL_MESH_UPDATER_H
|
||||||
|
|
||||||
|
#include <core/vector.h>
|
||||||
|
#include <core/os/semaphore.h>
|
||||||
|
#include <core/os/thread.h>
|
||||||
|
|
||||||
|
#include "voxel_buffer.h"
|
||||||
|
#include "voxel_mesher.h"
|
||||||
|
#include "voxel_mesher_smooth.h"
|
||||||
|
|
||||||
|
class VoxelMeshUpdater {
|
||||||
|
public:
|
||||||
|
struct InputBlock {
|
||||||
|
Ref<VoxelBuffer> voxels;
|
||||||
|
Vector3i position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Input {
|
||||||
|
Vector<InputBlock> blocks;
|
||||||
|
Vector3i priority_position;
|
||||||
|
|
||||||
|
bool is_empty() const {
|
||||||
|
return blocks.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OutputBlock {
|
||||||
|
Array model_surfaces;
|
||||||
|
Array smooth_surfaces;
|
||||||
|
Vector3i position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
bool first;
|
||||||
|
uint64_t min_time;
|
||||||
|
uint64_t max_time;
|
||||||
|
uint32_t remaining_blocks;
|
||||||
|
|
||||||
|
Stats() : first(true), min_time(0), max_time(0), remaining_blocks(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
Vector<OutputBlock> blocks;
|
||||||
|
Stats stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshingParams {
|
||||||
|
bool baked_ao;
|
||||||
|
float baked_ao_darkness;
|
||||||
|
|
||||||
|
MeshingParams(): baked_ao(true), baked_ao_darkness(0.75)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
VoxelMeshUpdater(Ref<VoxelLibrary> library, MeshingParams params);
|
||||||
|
~VoxelMeshUpdater();
|
||||||
|
|
||||||
|
void push(const Input &input);
|
||||||
|
void pop(Output &output);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _thread_func(void *p_self);
|
||||||
|
void thread_func();
|
||||||
|
|
||||||
|
void thread_sync(int queue_index, Stats stats);
|
||||||
|
|
||||||
|
void process_block(const InputBlock &block, OutputBlock &output);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Input _shared_input;
|
||||||
|
Mutex *_input_mutex;
|
||||||
|
|
||||||
|
Output _shared_output;
|
||||||
|
Mutex *_output_mutex;
|
||||||
|
|
||||||
|
Ref<VoxelMesher> _model_mesher;
|
||||||
|
Ref<VoxelMesherSmooth> _smooth_mesher;
|
||||||
|
|
||||||
|
Input _input;
|
||||||
|
HashMap<Vector3i, int, Vector3iHasher> _block_indexes;
|
||||||
|
Output _output;
|
||||||
|
Semaphore *_semaphore;
|
||||||
|
Thread *_thread;
|
||||||
|
bool _thread_exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VOXEL_MESH_UPDATER_H
|
@ -1,20 +1,7 @@
|
|||||||
#include "voxel_mesher.h"
|
#include "voxel_mesher.h"
|
||||||
#include "voxel_library.h"
|
#include "voxel_library.h"
|
||||||
#include "cube_tables.h"
|
#include "cube_tables.h"
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void copy_to(PoolVector<T> &to, const Vector<T> &from) {
|
|
||||||
|
|
||||||
to.resize(from.size());
|
|
||||||
|
|
||||||
typename PoolVector<T>::Write w = to.write();
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < from.size(); ++i) {
|
|
||||||
w[i] = from[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
VoxelMesher::VoxelMesher()
|
VoxelMesher::VoxelMesher()
|
||||||
: _baked_occlusion_darkness(0.75),
|
: _baked_occlusion_darkness(0.75),
|
||||||
@ -24,16 +11,6 @@ void VoxelMesher::set_library(Ref<VoxelLibrary> library) {
|
|||||||
_library = library;
|
_library = library;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelMesher::set_material(Ref<Material> material, unsigned int id) {
|
|
||||||
ERR_FAIL_COND(id >= MAX_MATERIALS);
|
|
||||||
_materials[id] = material;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<Material> VoxelMesher::get_material(unsigned int id) const {
|
|
||||||
ERR_FAIL_COND_V(id >= MAX_MATERIALS, Ref<Material>());
|
|
||||||
return _materials[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoxelMesher::set_occlusion_darkness(float darkness) {
|
void VoxelMesher::set_occlusion_darkness(float darkness) {
|
||||||
_baked_occlusion_darkness = darkness;
|
_baked_occlusion_darkness = darkness;
|
||||||
if (_baked_occlusion_darkness < 0.0)
|
if (_baked_occlusion_darkness < 0.0)
|
||||||
@ -66,16 +43,33 @@ inline bool is_transparent(const VoxelLibrary &lib, int voxel_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<ArrayMesh> VoxelMesher::build_ref(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Ref<ArrayMesh> mesh) {
|
Ref<ArrayMesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh) {
|
||||||
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<ArrayMesh>());
|
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<ArrayMesh>());
|
||||||
|
|
||||||
VoxelBuffer &buffer = **buffer_ref;
|
VoxelBuffer &buffer = **buffer_ref;
|
||||||
mesh = build(buffer, channel, Vector3i(), buffer.get_size(), mesh);
|
Array surfaces = build(buffer, channel, Vector3i(), buffer.get_size());
|
||||||
|
|
||||||
|
if(mesh.is_null())
|
||||||
|
mesh.instance();
|
||||||
|
|
||||||
|
int surface = mesh->get_surface_count();
|
||||||
|
for(int i = 0; i < surfaces.size(); ++i) {
|
||||||
|
|
||||||
|
Array arrays = surfaces[i];
|
||||||
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
||||||
|
|
||||||
|
Ref<Material> material = materials[i];
|
||||||
|
if(material.is_valid()) {
|
||||||
|
mesh->surface_set_material(surface, material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max, Ref<ArrayMesh> mesh) {
|
Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max) {
|
||||||
ERR_FAIL_COND_V(_library.is_null(), Ref<ArrayMesh>());
|
ERR_FAIL_COND_V(_library.is_null(), Array());
|
||||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<ArrayMesh>());
|
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
|
||||||
|
|
||||||
const VoxelLibrary &library = **_library;
|
const VoxelLibrary &library = **_library;
|
||||||
|
|
||||||
@ -100,8 +94,6 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
|||||||
// - Slower
|
// - Slower
|
||||||
// => Could be implemented in a separate class?
|
// => Could be implemented in a separate class?
|
||||||
|
|
||||||
VOXEL_PROFILE_BEGIN("mesher_face_extraction")
|
|
||||||
|
|
||||||
// Data must be padded, hence the off-by-one
|
// Data must be padded, hence the off-by-one
|
||||||
Vector3i::sort_min_max(min, max);
|
Vector3i::sort_min_max(min, max);
|
||||||
const Vector3i pad(1, 1, 1);
|
const Vector3i pad(1, 1, 1);
|
||||||
@ -268,24 +260,17 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VOXEL_PROFILE_END("mesher_face_extraction")
|
|
||||||
|
|
||||||
// Commit mesh
|
// Commit mesh
|
||||||
|
|
||||||
Ref<ArrayMesh> mesh_ref = mesh;
|
|
||||||
if (mesh.is_null())
|
|
||||||
mesh_ref = Ref<ArrayMesh>(memnew(ArrayMesh));
|
|
||||||
|
|
||||||
VOXEL_PROFILE_BEGIN("mesher_add_surfaces")
|
|
||||||
|
|
||||||
// print_line(String("Made mesh v: ") + String::num(_arrays[0].positions.size())
|
// print_line(String("Made mesh v: ") + String::num(_arrays[0].positions.size())
|
||||||
// + String(", i: ") + String::num(_arrays[0].indices.size()));
|
// + String(", i: ") + String::num(_arrays[0].indices.size()));
|
||||||
|
|
||||||
int surface = 0;
|
Array surfaces;
|
||||||
for(int i = 0; i < MAX_MATERIALS; ++i) {
|
|
||||||
|
for (int i = 0; i < MAX_MATERIALS; ++i) {
|
||||||
|
|
||||||
const Arrays &arrays = _arrays[i];
|
const Arrays &arrays = _arrays[i];
|
||||||
if(arrays.positions.size() != 0) {
|
if (arrays.positions.size() != 0) {
|
||||||
|
|
||||||
Array mesh_arrays;
|
Array mesh_arrays;
|
||||||
mesh_arrays.resize(Mesh::ARRAY_MAX);
|
mesh_arrays.resize(Mesh::ARRAY_MAX);
|
||||||
@ -310,23 +295,15 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
|||||||
mesh_arrays[Mesh::ARRAY_INDEX] = indices;
|
mesh_arrays[Mesh::ARRAY_INDEX] = indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh_ref->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, mesh_arrays);
|
surfaces.append(mesh_arrays);
|
||||||
mesh_ref->surface_set_material(surface, _materials[i]);
|
|
||||||
|
|
||||||
++surface;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VOXEL_PROFILE_END("mesher_add_surfaces")
|
return surfaces;
|
||||||
|
|
||||||
return mesh_ref;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelMesher::_bind_methods() {
|
void VoxelMesher::_bind_methods() {
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_material", "material", "id"), &VoxelMesher::set_material);
|
|
||||||
ClassDB::bind_method(D_METHOD("get_material", "id"), &VoxelMesher::get_material);
|
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_library", "voxel_library"), &VoxelMesher::set_library);
|
ClassDB::bind_method(D_METHOD("set_library", "voxel_library"), &VoxelMesher::set_library);
|
||||||
ClassDB::bind_method(D_METHOD("get_library"), &VoxelMesher::get_library);
|
ClassDB::bind_method(D_METHOD("get_library"), &VoxelMesher::get_library);
|
||||||
|
|
||||||
@ -336,7 +313,7 @@ void VoxelMesher::_bind_methods() {
|
|||||||
ClassDB::bind_method(D_METHOD("set_occlusion_darkness", "value"), &VoxelMesher::set_occlusion_darkness);
|
ClassDB::bind_method(D_METHOD("set_occlusion_darkness", "value"), &VoxelMesher::set_occlusion_darkness);
|
||||||
ClassDB::bind_method(D_METHOD("get_occlusion_darkness"), &VoxelMesher::get_occlusion_darkness);
|
ClassDB::bind_method(D_METHOD("get_occlusion_darkness"), &VoxelMesher::get_occlusion_darkness);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("build", "voxel_buffer", "channel", "existing_mesh"), &VoxelMesher::build_ref);
|
ClassDB::bind_method(D_METHOD("build_mesh", "voxel_buffer", "channel", "materials", "existing_mesh"), &VoxelMesher::build_mesh);
|
||||||
|
|
||||||
#ifdef VOXEL_PROFILING
|
#ifdef VOXEL_PROFILING
|
||||||
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelMesher::get_profiling_info);
|
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelMesher::get_profiling_info);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#include <core/reference.h>
|
#include <core/reference.h>
|
||||||
#include <scene/resources/mesh.h>
|
#include <scene/resources/mesh.h>
|
||||||
|
|
||||||
// TODO Should be renamed VoxelMesherCubic or something like that
|
// TODO Should be renamed VoxelMesherModel or something like that
|
||||||
class VoxelMesher : public Reference {
|
class VoxelMesher : public Reference {
|
||||||
GDCLASS(VoxelMesher, Reference)
|
GDCLASS(VoxelMesher, Reference)
|
||||||
|
|
||||||
@ -17,9 +17,6 @@ public:
|
|||||||
|
|
||||||
VoxelMesher();
|
VoxelMesher();
|
||||||
|
|
||||||
void set_material(Ref<Material> material, unsigned int id);
|
|
||||||
Ref<Material> get_material(unsigned int id) const;
|
|
||||||
|
|
||||||
void set_library(Ref<VoxelLibrary> library);
|
void set_library(Ref<VoxelLibrary> library);
|
||||||
Ref<VoxelLibrary> get_library() const { return _library; }
|
Ref<VoxelLibrary> get_library() const { return _library; }
|
||||||
|
|
||||||
@ -29,8 +26,8 @@ public:
|
|||||||
void set_occlusion_enabled(bool enable);
|
void set_occlusion_enabled(bool enable);
|
||||||
bool get_occlusion_enabled() const { return _bake_occlusion; }
|
bool get_occlusion_enabled() const { return _bake_occlusion; }
|
||||||
|
|
||||||
Ref<ArrayMesh> build(const VoxelBuffer &buffer_ref, unsigned int channel, Vector3i min, Vector3i max, Ref<ArrayMesh> mesh = Ref<Mesh>());
|
Array build(const VoxelBuffer &buffer_ref, unsigned int channel, Vector3i min, Vector3i max);
|
||||||
Ref<ArrayMesh> build_ref(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
@ -45,7 +42,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ref<VoxelLibrary> _library;
|
Ref<VoxelLibrary> _library;
|
||||||
Ref<Material> _materials[MAX_MATERIALS];
|
|
||||||
Arrays _arrays[MAX_MATERIALS];
|
Arrays _arrays[MAX_MATERIALS];
|
||||||
float _baked_occlusion_darkness;
|
float _baked_occlusion_darkness;
|
||||||
bool _bake_occlusion;
|
bool _bake_occlusion;
|
||||||
|
@ -68,18 +68,29 @@ VoxelMesherSmooth::ReuseCell::ReuseCell() {
|
|||||||
VoxelMesherSmooth::VoxelMesherSmooth() {
|
VoxelMesherSmooth::VoxelMesherSmooth() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<ArrayMesh> VoxelMesherSmooth::build_ref(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh) {
|
Ref<ArrayMesh> VoxelMesherSmooth::build_mesh(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh) {
|
||||||
|
|
||||||
ERR_FAIL_COND_V(voxels_ref.is_null(), Ref<ArrayMesh>());
|
ERR_FAIL_COND_V(voxels_ref.is_null(), Ref<ArrayMesh>());
|
||||||
|
|
||||||
VoxelBuffer &voxels = **voxels_ref;
|
VoxelBuffer &buffer = **voxels_ref;
|
||||||
|
Array surfaces = build(buffer, channel);
|
||||||
|
|
||||||
return build(voxels, channel, mesh);
|
if(mesh.is_null())
|
||||||
|
mesh.instance();
|
||||||
|
|
||||||
|
//int surface = mesh->get_surface_count();
|
||||||
|
for(int i = 0; i < surfaces.size(); ++i) {
|
||||||
|
Array arrays = surfaces[i];
|
||||||
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
||||||
|
//mesh->surface_set_material(surface, _materials[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int channel, Ref<ArrayMesh> mesh) {
|
Array VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int channel) {
|
||||||
|
|
||||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<ArrayMesh>());
|
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
|
||||||
|
|
||||||
// Initialize dynamic memory:
|
// Initialize dynamic memory:
|
||||||
// These vectors are re-used.
|
// These vectors are re-used.
|
||||||
@ -90,7 +101,7 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
|||||||
m_output_normals.clear();
|
m_output_normals.clear();
|
||||||
m_output_indices.clear();
|
m_output_indices.clear();
|
||||||
|
|
||||||
build_mesh(voxels, channel);
|
build_internal(voxels, channel);
|
||||||
// OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n",
|
// OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n",
|
||||||
// m_output_vertices.size(),
|
// m_output_vertices.size(),
|
||||||
// m_output_normals.size(),
|
// m_output_normals.size(),
|
||||||
@ -98,7 +109,7 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
|||||||
|
|
||||||
if (m_output_vertices.size() == 0) {
|
if (m_output_vertices.size() == 0) {
|
||||||
// The mesh can be empty
|
// The mesh can be empty
|
||||||
return Ref<ArrayMesh>();
|
return Array();
|
||||||
}
|
}
|
||||||
|
|
||||||
PoolVector<Vector3> vertices;
|
PoolVector<Vector3> vertices;
|
||||||
@ -117,15 +128,13 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
|||||||
}
|
}
|
||||||
arrays[Mesh::ARRAY_INDEX] = indices;
|
arrays[Mesh::ARRAY_INDEX] = indices;
|
||||||
|
|
||||||
if (mesh.is_null())
|
Array surfaces;
|
||||||
mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
surfaces.append(arrays);
|
||||||
|
|
||||||
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
return surfaces;
|
||||||
|
|
||||||
return mesh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelMesherSmooth::build_mesh(const VoxelBuffer &voxels, unsigned int channel) {
|
void VoxelMesherSmooth::build_internal(const VoxelBuffer &voxels, unsigned int channel) {
|
||||||
|
|
||||||
// Each 2x2 voxel group is a "cell"
|
// Each 2x2 voxel group is a "cell"
|
||||||
|
|
||||||
@ -385,5 +394,5 @@ void VoxelMesherSmooth::emit_vertex(Vector3 primary, Vector3 normal) {
|
|||||||
|
|
||||||
void VoxelMesherSmooth::_bind_methods() {
|
void VoxelMesherSmooth::_bind_methods() {
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("build", "voxels", "channel", "existing_mesh"), &VoxelMesherSmooth::build_ref, DEFVAL(Variant()));
|
ClassDB::bind_method(D_METHOD("build", "voxels", "channel", "existing_mesh"), &VoxelMesherSmooth::build_mesh, DEFVAL(Variant()));
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ class VoxelMesherSmooth : public Reference {
|
|||||||
public:
|
public:
|
||||||
VoxelMesherSmooth();
|
VoxelMesherSmooth();
|
||||||
|
|
||||||
Ref<ArrayMesh> build_ref(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||||
Ref<ArrayMesh> build(const VoxelBuffer &voxels, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
Array build(const VoxelBuffer &voxels, unsigned int channel);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
@ -23,7 +23,7 @@ private:
|
|||||||
ReuseCell();
|
ReuseCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
void build_mesh(const VoxelBuffer &voxels, unsigned int channel);
|
void build_internal(const VoxelBuffer &voxels, unsigned int channel);
|
||||||
ReuseCell &get_reuse_cell(Vector3i pos);
|
ReuseCell &get_reuse_cell(Vector3i pos);
|
||||||
void emit_vertex(Vector3 primary, Vector3 normal);
|
void emit_vertex(Vector3 primary, Vector3 normal);
|
||||||
|
|
||||||
|
206
voxel_provider_thread.cpp
Normal file
206
voxel_provider_thread.cpp
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#include "core/os/os.h"
|
||||||
|
#include "core/os/thread.h"
|
||||||
|
#include "core/os/semaphore.h"
|
||||||
|
#include "voxel_provider_thread.h"
|
||||||
|
#include "voxel_provider.h"
|
||||||
|
#include "voxel_map.h"
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
|
|
||||||
|
VoxelProviderThread::VoxelProviderThread(Ref<VoxelProvider> provider, int block_size_pow2) {
|
||||||
|
|
||||||
|
CRASH_COND(provider.is_null());
|
||||||
|
CRASH_COND(block_size_pow2 <= 0);
|
||||||
|
|
||||||
|
_voxel_provider = provider;
|
||||||
|
_block_size_pow2 = block_size_pow2;
|
||||||
|
_input_mutex = Mutex::create();
|
||||||
|
_output_mutex = Mutex::create();
|
||||||
|
_semaphore = Semaphore::create();
|
||||||
|
_thread_exit = false;
|
||||||
|
_thread = Thread::create(_thread_func, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelProviderThread::~VoxelProviderThread() {
|
||||||
|
|
||||||
|
_thread_exit = true;
|
||||||
|
_semaphore->post();
|
||||||
|
Thread::wait_to_finish(_thread);
|
||||||
|
|
||||||
|
memdelete(_thread);
|
||||||
|
memdelete(_semaphore);
|
||||||
|
memdelete(_input_mutex);
|
||||||
|
memdelete(_output_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelProviderThread::push(const InputData &input) {
|
||||||
|
|
||||||
|
bool should_run = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
MutexLock lock(_input_mutex);
|
||||||
|
|
||||||
|
// TODO If the same update is sent twice, keep only the latest one
|
||||||
|
|
||||||
|
_shared_input.blocks_to_emerge.append_array(input.blocks_to_emerge);
|
||||||
|
_shared_input.blocks_to_immerge.append_array(input.blocks_to_immerge);
|
||||||
|
_shared_input.priority_block_position = input.priority_block_position;
|
||||||
|
|
||||||
|
should_run = !_shared_input.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the thread it should run
|
||||||
|
if(should_run) {
|
||||||
|
_semaphore->post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelProviderThread::pop(OutputData &out_data) {
|
||||||
|
|
||||||
|
MutexLock lock(_output_mutex);
|
||||||
|
|
||||||
|
out_data.emerged_blocks.append_array(_shared_output);
|
||||||
|
out_data.stats = _shared_stats;
|
||||||
|
_shared_output.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelProviderThread::_thread_func(void *p_self) {
|
||||||
|
VoxelProviderThread *self = reinterpret_cast<VoxelProviderThread*>(p_self);
|
||||||
|
self->thread_func();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelProviderThread::thread_func() {
|
||||||
|
|
||||||
|
while(!_thread_exit) {
|
||||||
|
|
||||||
|
uint32_t sync_interval = 100.0; // milliseconds
|
||||||
|
uint32_t sync_time = OS::get_singleton()->get_ticks_msec() + sync_interval;
|
||||||
|
|
||||||
|
int emerge_index = 0;
|
||||||
|
Stats stats;
|
||||||
|
|
||||||
|
thread_sync(emerge_index, stats);
|
||||||
|
|
||||||
|
while(!_input.is_empty() && !_thread_exit) {
|
||||||
|
//print_line(String("Thread runs: {0}").format(varray(_input.blocks_to_emerge.size())));
|
||||||
|
|
||||||
|
// TODO Block saving
|
||||||
|
_input.blocks_to_immerge.clear();
|
||||||
|
|
||||||
|
if(!_input.blocks_to_emerge.empty()) {
|
||||||
|
|
||||||
|
Vector3i block_pos = _input.blocks_to_emerge[emerge_index];
|
||||||
|
++emerge_index;
|
||||||
|
|
||||||
|
if(emerge_index >= _input.blocks_to_emerge.size()) {
|
||||||
|
_input.blocks_to_emerge.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int bs = 1 << _block_size_pow2;
|
||||||
|
Ref<VoxelBuffer> buffer = Ref<VoxelBuffer>(memnew(VoxelBuffer));
|
||||||
|
buffer->create(bs, bs, bs);
|
||||||
|
|
||||||
|
// Query voxel provider
|
||||||
|
Vector3i block_origin_in_voxels = block_pos * bs;
|
||||||
|
uint64_t time_before = OS::get_singleton()->get_ticks_usec();
|
||||||
|
_voxel_provider->emerge_block(buffer, block_origin_in_voxels);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmergeOutput eo;
|
||||||
|
eo.origin_in_voxels = block_origin_in_voxels;
|
||||||
|
eo.voxels = buffer;
|
||||||
|
_output.push_back(eo);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t time = OS::get_singleton()->get_ticks_msec();
|
||||||
|
if (time >= sync_time) {
|
||||||
|
|
||||||
|
thread_sync(emerge_index, stats);
|
||||||
|
|
||||||
|
sync_time = OS::get_singleton()->get_ticks_msec() + sync_interval;
|
||||||
|
emerge_index = 0;
|
||||||
|
stats = Stats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_thread_exit)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Wait for future wake-up
|
||||||
|
_semaphore->wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
print_line("Thread exits");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorts distance to viewer
|
||||||
|
// The closest block will be the first one in the array
|
||||||
|
struct BlockPositionComparator {
|
||||||
|
Vector3i center;
|
||||||
|
inline bool operator()(const Vector3i &a, const Vector3i &b) const {
|
||||||
|
return a.distance_sq(center) < b.distance_sq(center);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void VoxelProviderThread::thread_sync(int emerge_index, Stats stats) {
|
||||||
|
|
||||||
|
if (!_input.blocks_to_emerge.empty()) {
|
||||||
|
// Cleanup emerge vector
|
||||||
|
|
||||||
|
if (emerge_index >= _input.blocks_to_emerge.size()) {
|
||||||
|
_input.blocks_to_emerge.clear();
|
||||||
|
|
||||||
|
} else if (emerge_index > 0) {
|
||||||
|
|
||||||
|
// Shift up remaining items since we use a Vector
|
||||||
|
shift_up(_input.blocks_to_emerge, emerge_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get input
|
||||||
|
MutexLock lock(_input_mutex);
|
||||||
|
|
||||||
|
_input.blocks_to_emerge.append_array(_shared_input.blocks_to_emerge);
|
||||||
|
_input.blocks_to_immerge.append_array(_shared_input.blocks_to_immerge);
|
||||||
|
_input.priority_block_position = _shared_input.priority_block_position;
|
||||||
|
|
||||||
|
_shared_input.blocks_to_emerge.clear();
|
||||||
|
_shared_input.blocks_to_immerge.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.remaining_blocks = _input.blocks_to_emerge.size();
|
||||||
|
|
||||||
|
// print_line(String("VoxelProviderThread: posting {0} blocks, {1} remaining ; cost [{2}..{3}] usec")
|
||||||
|
// .format(varray(_output.size(), _input.blocks_to_emerge.size(), stats.min_time, stats.max_time)));
|
||||||
|
|
||||||
|
{
|
||||||
|
// Post output
|
||||||
|
MutexLock lock(_output_mutex);
|
||||||
|
_shared_output.append_array(_output);
|
||||||
|
_shared_stats = stats;
|
||||||
|
_output.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_input.blocks_to_emerge.empty()) {
|
||||||
|
// Re-sort priority
|
||||||
|
|
||||||
|
SortArray<Vector3i, BlockPositionComparator> sorter;
|
||||||
|
sorter.compare.center = _input.priority_block_position;
|
||||||
|
sorter.sort(_input.blocks_to_emerge.ptrw(), _input.blocks_to_emerge.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
78
voxel_provider_thread.h
Normal file
78
voxel_provider_thread.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#ifndef VOXEL_PROVIDER_THREAD_H
|
||||||
|
#define VOXEL_PROVIDER_THREAD_H
|
||||||
|
|
||||||
|
#include "core/resource.h"
|
||||||
|
#include "vector3i.h"
|
||||||
|
|
||||||
|
class VoxelProvider;
|
||||||
|
class VoxelBuffer;
|
||||||
|
class Thread;
|
||||||
|
class Semaphore;
|
||||||
|
|
||||||
|
class VoxelProviderThread {
|
||||||
|
public:
|
||||||
|
struct ImmergeInput {
|
||||||
|
Vector3i origin;
|
||||||
|
Ref<VoxelBuffer> voxels;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InputData {
|
||||||
|
Vector<ImmergeInput> blocks_to_immerge;
|
||||||
|
Vector<Vector3i> blocks_to_emerge;
|
||||||
|
Vector3i priority_block_position;
|
||||||
|
|
||||||
|
inline bool is_empty() {
|
||||||
|
return blocks_to_emerge.empty() && blocks_to_immerge.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EmergeOutput {
|
||||||
|
Ref<VoxelBuffer> voxels;
|
||||||
|
Vector3i origin_in_voxels;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
bool first;
|
||||||
|
uint64_t min_time;
|
||||||
|
uint64_t max_time;
|
||||||
|
int remaining_blocks;
|
||||||
|
|
||||||
|
Stats() : first(true), min_time(0), max_time(0), remaining_blocks(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OutputData {
|
||||||
|
Vector<EmergeOutput> emerged_blocks;
|
||||||
|
Stats stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
VoxelProviderThread(Ref<VoxelProvider> provider, int block_size_pow2);
|
||||||
|
~VoxelProviderThread();
|
||||||
|
|
||||||
|
void push(const InputData &input);
|
||||||
|
void pop(OutputData &out_data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _thread_func(void *p_self);
|
||||||
|
|
||||||
|
void thread_func();
|
||||||
|
void thread_sync(int emerge_index, Stats stats);
|
||||||
|
|
||||||
|
private:
|
||||||
|
InputData _shared_input;
|
||||||
|
Mutex *_input_mutex;
|
||||||
|
|
||||||
|
Vector<EmergeOutput> _shared_output;
|
||||||
|
Stats _shared_stats;
|
||||||
|
Mutex *_output_mutex;
|
||||||
|
|
||||||
|
Semaphore *_semaphore;
|
||||||
|
bool _thread_exit;
|
||||||
|
Thread *_thread;
|
||||||
|
InputData _input;
|
||||||
|
Vector<EmergeOutput> _output;
|
||||||
|
int _block_size_pow2;
|
||||||
|
|
||||||
|
Ref<VoxelProvider> _voxel_provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VOXEL_PROVIDER_THREAD_H
|
@ -1,6 +1,11 @@
|
|||||||
#include "voxel_terrain.h"
|
#include "voxel_terrain.h"
|
||||||
|
#include "voxel_map.h"
|
||||||
|
#include "voxel_block.h"
|
||||||
|
#include "voxel_provider_thread.h"
|
||||||
#include "voxel_raycast.h"
|
#include "voxel_raycast.h"
|
||||||
#include "rect3i.h"
|
#include "voxel_provider_test.h"
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
#include <core/os/os.h>
|
#include <core/os/os.h>
|
||||||
#include <scene/3d/mesh_instance.h>
|
#include <scene/3d/mesh_instance.h>
|
||||||
#include <core/engine.h>
|
#include <core/engine.h>
|
||||||
@ -10,22 +15,22 @@ VoxelTerrain::VoxelTerrain()
|
|||||||
: Spatial(), _generate_collisions(true) {
|
: Spatial(), _generate_collisions(true) {
|
||||||
|
|
||||||
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
||||||
_mesher = Ref<VoxelMesher>(memnew(VoxelMesher));
|
|
||||||
_mesher_smooth = Ref<VoxelMesherSmooth>(memnew(VoxelMesherSmooth));
|
|
||||||
|
|
||||||
_view_distance_blocks = 8;
|
_view_distance_blocks = 8;
|
||||||
_last_view_distance_blocks = 0;
|
_last_view_distance_blocks = 0;
|
||||||
|
|
||||||
|
_provider_thread = NULL;
|
||||||
|
_block_updater = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO UGLY! Lambdas or pointers needed... DO NOT use this outside of lambdas!
|
VoxelTerrain::~VoxelTerrain() {
|
||||||
Vector3i g_viewer_block_pos;
|
if(_provider_thread) {
|
||||||
|
memdelete(_provider_thread);
|
||||||
// Sorts distance to viewer
|
|
||||||
struct BlockUpdateComparator {
|
|
||||||
inline bool operator()(const Vector3i &a, const Vector3i &b) const {
|
|
||||||
return a.distance_sq(g_viewer_block_pos) > b.distance_sq(g_viewer_block_pos);
|
|
||||||
}
|
}
|
||||||
};
|
if(_block_updater) {
|
||||||
|
memdelete(_block_updater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO See if there is a way to specify materials in voxels directly?
|
// TODO See if there is a way to specify materials in voxels directly?
|
||||||
|
|
||||||
@ -33,8 +38,7 @@ bool VoxelTerrain::_set(const StringName &p_name, const Variant &p_value) {
|
|||||||
|
|
||||||
if (p_name.operator String().begins_with("material/")) {
|
if (p_name.operator String().begins_with("material/")) {
|
||||||
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
||||||
if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0)
|
ERR_FAIL_COND_V(idx >= VoxelMesher::MAX_MATERIALS || idx < 0, false);
|
||||||
return false;
|
|
||||||
set_material(idx, p_value);
|
set_material(idx, p_value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -46,8 +50,7 @@ bool VoxelTerrain::_get(const StringName &p_name, Variant &r_ret) const {
|
|||||||
|
|
||||||
if (p_name.operator String().begins_with("material/")) {
|
if (p_name.operator String().begins_with("material/")) {
|
||||||
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
||||||
if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0)
|
ERR_FAIL_COND_V(idx >= VoxelMesher::MAX_MATERIALS || idx < 0, false);
|
||||||
return false;
|
|
||||||
r_ret = get_material(idx);
|
r_ret = get_material(idx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -64,8 +67,20 @@ void VoxelTerrain::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||||||
|
|
||||||
void VoxelTerrain::set_provider(Ref<VoxelProvider> provider) {
|
void VoxelTerrain::set_provider(Ref<VoxelProvider> provider) {
|
||||||
if(provider != _provider) {
|
if(provider != _provider) {
|
||||||
|
|
||||||
|
if(_provider_thread) {
|
||||||
|
memdelete(_provider_thread);
|
||||||
|
_provider_thread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
|
_provider_thread = memnew(VoxelProviderThread(_provider, _map->get_block_size_pow2()));
|
||||||
|
// Ref<VoxelProviderTest> test;
|
||||||
|
// test.instance();
|
||||||
|
// _provider_thread = memnew(VoxelProviderThread(test, _map->get_block_size_pow2()));
|
||||||
|
|
||||||
// The whole map might change, so make all area dirty
|
// The whole map might change, so make all area dirty
|
||||||
|
// TODO Actually, we should regenerate the whole map, not just update all its blocks
|
||||||
make_all_view_dirty_deferred();
|
make_all_view_dirty_deferred();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,18 +90,29 @@ Ref<VoxelProvider> VoxelTerrain::get_provider() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const {
|
Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const {
|
||||||
return _mesher->get_library();
|
return _library;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
|
void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
|
||||||
if(library != _mesher->get_library()) {
|
|
||||||
|
if (library != _library) {
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
if(library->get_voxel_count() == 0) {
|
if (library->get_voxel_count() == 0) {
|
||||||
library->load_default();
|
library->load_default();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
_mesher->set_library(library);
|
_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));
|
||||||
|
|
||||||
// Voxel appearance might completely change
|
// Voxel appearance might completely change
|
||||||
make_all_view_dirty_deferred();
|
make_all_view_dirty_deferred();
|
||||||
@ -130,24 +156,34 @@ Spatial *VoxelTerrain::get_viewer(NodePath path) const {
|
|||||||
|
|
||||||
void VoxelTerrain::set_material(int id, Ref<Material> material) {
|
void VoxelTerrain::set_material(int id, Ref<Material> material) {
|
||||||
// TODO Update existing block surfaces
|
// TODO Update existing block surfaces
|
||||||
_mesher->set_material(material, id);
|
ERR_FAIL_COND(id < 0 || id >= VoxelMesher::MAX_MATERIALS);
|
||||||
|
_materials[id] = material;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<Material> VoxelTerrain::get_material(int id) const {
|
Ref<Material> VoxelTerrain::get_material(int id) const {
|
||||||
return _mesher->get_material(id);
|
ERR_FAIL_COND_V(id < 0 || id >= VoxelMesher::MAX_MATERIALS, Ref<Material>());
|
||||||
|
return _materials[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
//void VoxelTerrain::clear_update_queue() {
|
|
||||||
// _block_update_queue.clear();
|
|
||||||
// _dirty_blocks.clear();
|
|
||||||
//}
|
|
||||||
|
|
||||||
void VoxelTerrain::make_block_dirty(Vector3i bpos) {
|
void VoxelTerrain::make_block_dirty(Vector3i bpos) {
|
||||||
// TODO Immediate update viewer distance
|
// TODO Immediate update viewer distance?
|
||||||
|
|
||||||
if (is_block_dirty(bpos) == false) {
|
if (is_block_dirty(bpos) == false) {
|
||||||
|
|
||||||
|
if(_map->has_block(bpos)) {
|
||||||
|
|
||||||
|
_blocks_pending_update.push_back(bpos);
|
||||||
|
_dirty_blocks[bpos] = BLOCK_UPDATE;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_blocks_pending_load.push_back(bpos);
|
||||||
|
_dirty_blocks[bpos] = BLOCK_LOAD;
|
||||||
|
}
|
||||||
|
|
||||||
//OS::get_singleton()->print("Dirty (%i, %i, %i)", bpos.x, bpos.y, bpos.z);
|
//OS::get_singleton()->print("Dirty (%i, %i, %i)", bpos.x, bpos.y, bpos.z);
|
||||||
_block_update_queue.push_back(bpos);
|
|
||||||
_dirty_blocks[bpos] = true;
|
// TODO What if a block is made dirty, goes through threaded update, then gets changed again before it gets updated?
|
||||||
|
// this will make the second change ignored, which is not correct!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,21 +199,42 @@ void VoxelTerrain::immerge_block(Vector3i bpos) {
|
|||||||
// because it's too expensive to linear-search all blocks for each block
|
// because it's too expensive to linear-search all blocks for each block
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VoxelTerrain::is_block_dirty(Vector3i bpos) {
|
Dictionary VoxelTerrain::get_statistics() const {
|
||||||
|
|
||||||
|
Dictionary provider;
|
||||||
|
provider["min_time"] = _stats.provider.min_time;
|
||||||
|
provider["max_time"] = _stats.provider.max_time;
|
||||||
|
provider["remaining_blocks"] = _stats.provider.remaining_blocks;
|
||||||
|
|
||||||
|
Dictionary updater;
|
||||||
|
updater["min_time"] = _stats.updater.min_time;
|
||||||
|
updater["max_time"] = _stats.updater.max_time;
|
||||||
|
updater["remaining_blocks"] = _stats.updater.remaining_blocks;
|
||||||
|
updater["updated_blocks"] = _stats.updated_blocks;
|
||||||
|
updater["mesh_alloc_time"] = _stats.mesh_alloc_time;
|
||||||
|
|
||||||
|
Dictionary d;
|
||||||
|
d["provider"] = provider;
|
||||||
|
d["updater"] = updater;
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoxelTerrain::is_block_dirty(Vector3i bpos) const {
|
||||||
return _dirty_blocks.has(bpos);
|
return _dirty_blocks.has(bpos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
|
//void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
|
||||||
Vector3i max = min + size;
|
// Vector3i max = min + size;
|
||||||
Vector3i pos;
|
// Vector3i pos;
|
||||||
for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
// for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
||||||
for (pos.y = min.y; pos.y < max.y; ++pos.y) {
|
// for (pos.y = min.y; pos.y < max.y; ++pos.y) {
|
||||||
for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
// for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
||||||
make_block_dirty(pos);
|
// make_block_dirty(pos);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
void VoxelTerrain::make_all_view_dirty_deferred() {
|
void VoxelTerrain::make_all_view_dirty_deferred() {
|
||||||
// This trick will regenerate all chunks in view, according to the view distance found during block updates.
|
// This trick will regenerate all chunks in view, according to the view distance found during block updates.
|
||||||
@ -204,7 +261,8 @@ void VoxelTerrain::make_voxel_dirty(Vector3i pos) {
|
|||||||
|
|
||||||
Vector3i rpos = _map->to_local(pos);
|
Vector3i rpos = _map->to_local(pos);
|
||||||
|
|
||||||
bool check_corners = _mesher->get_occlusion_enabled();
|
// TODO Thread-safe way of getting this parameter
|
||||||
|
bool check_corners = true;//_mesher->get_occlusion_enabled();
|
||||||
|
|
||||||
const int max = _map->get_block_size() - 1;
|
const int max = _map->get_block_size() - 1;
|
||||||
|
|
||||||
@ -302,10 +360,6 @@ void VoxelTerrain::make_voxel_dirty(Vector3i pos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoxelTerrain::get_block_update_count() {
|
|
||||||
return _block_update_queue.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EnterWorldAction {
|
struct EnterWorldAction {
|
||||||
World *world;
|
World *world;
|
||||||
EnterWorldAction(World *w) : world(w) {}
|
EnterWorldAction(World *w) : world(w) {}
|
||||||
@ -365,11 +419,31 @@ void VoxelTerrain::_notification(int p_what) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelTerrain::_process() {
|
void VoxelTerrain::remove_positions_outside_box(Vector<Vector3i> &positions, Rect3i box, HashMap<Vector3i, VoxelTerrain::BlockDirtyState, Vector3iHasher> &state_map) {
|
||||||
update_blocks();
|
for(int i = 0; i < positions.size(); ++i) {
|
||||||
|
const Vector3i bpos = positions[i];
|
||||||
|
if(!box.contains(bpos)) {
|
||||||
|
int last = positions.size() - 1;
|
||||||
|
positions.write[i] = positions[last];
|
||||||
|
positions.resize(last);
|
||||||
|
state_map.erase(bpos);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelTerrain::update_blocks() {
|
static inline bool is_mesh_empty(Ref<Mesh> mesh_ref) {
|
||||||
|
if (mesh_ref.is_null())
|
||||||
|
return true;
|
||||||
|
const Mesh &mesh = **mesh_ref;
|
||||||
|
if (mesh.get_surface_count() == 0)
|
||||||
|
return true;
|
||||||
|
if (mesh.surface_get_array_len(0) == 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelTerrain::_process() {
|
||||||
|
|
||||||
OS &os = *OS::get_singleton();
|
OS &os = *OS::get_singleton();
|
||||||
Engine &engine = *Engine::get_singleton();
|
Engine &engine = *Engine::get_singleton();
|
||||||
@ -390,9 +464,8 @@ void VoxelTerrain::update_blocks() {
|
|||||||
viewer_block_pos = Vector3i();
|
viewer_block_pos = Vector3i();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find out which blocks need to appear and which need to be unloaded
|
||||||
{
|
{
|
||||||
// Find out which blocks need to appear and which need to be unloaded
|
|
||||||
|
|
||||||
//Vector3i viewer_block_pos_delta = _last_viewer_block_pos - viewer_block_pos;
|
//Vector3i viewer_block_pos_delta = _last_viewer_block_pos - viewer_block_pos;
|
||||||
Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos, Vector3i(_view_distance_blocks));
|
Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos, Vector3i(_view_distance_blocks));
|
||||||
Rect3i prev_box = Rect3i::from_center_extents(_last_viewer_block_pos, Vector3i(_last_view_distance_blocks));
|
Rect3i prev_box = Rect3i::from_center_extents(_last_viewer_block_pos, Vector3i(_last_view_distance_blocks));
|
||||||
@ -425,148 +498,197 @@ void VoxelTerrain::update_blocks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eliminate blocks in queue that aren't needed
|
// Eliminate pending blocks that aren't needed
|
||||||
for(int i = 0; i < _block_update_queue.size(); ++i) {
|
remove_positions_outside_box(_blocks_pending_load, new_box, _dirty_blocks);
|
||||||
const Vector3i bpos = _block_update_queue[i];
|
remove_positions_outside_box(_blocks_pending_update, new_box, _dirty_blocks);
|
||||||
if(!new_box.contains(bpos)) {
|
|
||||||
int last = _block_update_queue.size() - 1;
|
|
||||||
_block_update_queue.write[i] = _block_update_queue[last];
|
|
||||||
_block_update_queue.resize(last);
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_last_view_distance_blocks = _view_distance_blocks;
|
_last_view_distance_blocks = _view_distance_blocks;
|
||||||
_last_viewer_block_pos = viewer_block_pos;
|
_last_viewer_block_pos = viewer_block_pos;
|
||||||
|
|
||||||
// Sort updates so nearest blocks are done first
|
// Send block loading requests
|
||||||
VOXEL_PROFILE_BEGIN("block_update_sorting")
|
{
|
||||||
g_viewer_block_pos = viewer_block_pos;
|
VoxelProviderThread::InputData input;
|
||||||
_block_update_queue.sort_custom<BlockUpdateComparator>();
|
|
||||||
VOXEL_PROFILE_END("block_update_sorting")
|
|
||||||
|
|
||||||
uint32_t time_before = os.get_ticks_msec();
|
input.priority_block_position = viewer_block_pos;
|
||||||
uint32_t max_time = 1000 / 120;
|
input.blocks_to_emerge.append_array(_blocks_pending_load);
|
||||||
|
//input.blocks_to_immerge.append_array();
|
||||||
|
|
||||||
const unsigned int bs = _map->get_block_size();
|
//print_line(String("Sending {0} block requests").format(varray(input.blocks_to_emerge.size())));
|
||||||
const Vector3i block_size(bs, bs, bs);
|
_blocks_pending_load.clear();
|
||||||
|
|
||||||
// Update a bunch of blocks until none are left or too much time elapsed
|
_provider_thread->push(input);
|
||||||
while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) {
|
}
|
||||||
|
|
||||||
//printf("Remaining: %i\n", _block_update_queue.size());
|
// Get block loading responses
|
||||||
|
{
|
||||||
|
const unsigned int bs = _map->get_block_size();
|
||||||
|
const Vector3i block_size(bs, bs, bs);
|
||||||
|
|
||||||
// TODO Move this to a thread
|
VoxelProviderThread::OutputData output;
|
||||||
|
_provider_thread->pop(output);
|
||||||
|
//print_line(String("Receiving {0} blocks").format(varray(output.emerged_blocks.size())));
|
||||||
|
|
||||||
// Get request
|
_stats.provider = output.stats;
|
||||||
Vector3i block_pos = _block_update_queue[_block_update_queue.size() - 1];
|
|
||||||
|
|
||||||
bool entire_block_changed = false;
|
for(int i = 0; i < output.emerged_blocks.size(); ++i) {
|
||||||
|
|
||||||
if (!_map->has_block(block_pos)) {
|
const VoxelProviderThread::EmergeOutput &o = output.emerged_blocks[i];
|
||||||
// The block's data isn't loaded yet
|
|
||||||
// Create buffer
|
|
||||||
if (!_provider.is_null()) {
|
|
||||||
|
|
||||||
VOXEL_PROFILE_BEGIN("voxel_buffer_creation_gen")
|
// Check return
|
||||||
|
// TODO Shouldn't halt execution though, as it can bring the map in an invalid state!
|
||||||
|
ERR_FAIL_COND(o.voxels->get_size() != block_size);
|
||||||
|
|
||||||
Ref<VoxelBuffer> buffer_ref = Ref<VoxelBuffer>(memnew(VoxelBuffer));
|
// TODO Discard blocks out of range
|
||||||
buffer_ref->create(block_size.x, block_size.y, block_size.z);
|
|
||||||
|
|
||||||
VOXEL_PROFILE_END("voxel_buffer_creation_gen")
|
// Store buffer
|
||||||
VOXEL_PROFILE_BEGIN("block_generation")
|
Vector3i block_pos = _map->voxel_to_block(o.origin_in_voxels);
|
||||||
|
bool update_neighbors = !_map->has_block(block_pos);
|
||||||
|
_map->set_block_buffer(block_pos, o.voxels);
|
||||||
|
|
||||||
// Query voxel provider
|
// Trigger mesh updates
|
||||||
_provider->emerge_block(buffer_ref, _map->block_to_voxel(block_pos));
|
if (update_neighbors) {
|
||||||
|
// All neighbors have to be checked
|
||||||
|
Vector3i ndir;
|
||||||
|
// TODO Cache blocks in a small local grid on the stack to reduce hashing
|
||||||
|
for (ndir.z = -1; ndir.z < 2; ++ndir.z) {
|
||||||
|
for (ndir.x = -1; ndir.x < 2; ++ndir.x) {
|
||||||
|
for (ndir.y = -1; ndir.y < 2; ++ndir.y) {
|
||||||
|
Vector3i npos = block_pos + ndir;
|
||||||
|
// TODO What if the map is really composed of empty blocks?
|
||||||
|
if (_map->is_block_surrounded(npos)) {
|
||||||
|
|
||||||
// Check script return
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(npos);
|
||||||
// TODO Shouldn't halt execution though, as it can bring the map in an invalid state!
|
if (state && *state == BLOCK_UPDATE) {
|
||||||
ERR_FAIL_COND(buffer_ref->get_size() != block_size);
|
// Assuming it is scheduled to be updated already.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
VOXEL_PROFILE_END("block_generation")
|
_dirty_blocks[npos] = BLOCK_UPDATE;
|
||||||
|
_blocks_pending_update.push_back(npos);
|
||||||
// Store buffer
|
}
|
||||||
_map->set_block_buffer(block_pos, buffer_ref);
|
|
||||||
|
|
||||||
entire_block_changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update views (mesh/collisions)
|
|
||||||
|
|
||||||
if (entire_block_changed) {
|
|
||||||
// All neighbors have to be checked
|
|
||||||
Vector3i ndir;
|
|
||||||
for (ndir.z = -1; ndir.z < 2; ++ndir.z) {
|
|
||||||
for (ndir.x = -1; ndir.x < 2; ++ndir.x) {
|
|
||||||
for (ndir.y = -1; ndir.y < 2; ++ndir.y) {
|
|
||||||
Vector3i npos = block_pos + ndir;
|
|
||||||
// TODO What if the map is really composed of empty blocks?
|
|
||||||
if (_map->is_block_surrounded(npos)) {
|
|
||||||
update_block_mesh(npos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Only update the block, neighbors will probably follow if needed
|
||||||
|
_dirty_blocks[block_pos] = BLOCK_UPDATE;
|
||||||
|
_blocks_pending_update.push_back(block_pos);
|
||||||
|
//OS::get_singleton()->print("Update (%i, %i, %i)\n", block_pos.x, block_pos.y, block_pos.z);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Only update the block, neighbors will probably follow if needed
|
}
|
||||||
update_block_mesh(block_pos);
|
|
||||||
//OS::get_singleton()->print("Update (%i, %i, %i)\n", block_pos.x, block_pos.y, block_pos.z);
|
// Send mesh updates
|
||||||
|
{
|
||||||
|
VoxelMeshUpdater::Input input;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(1, 1, 1), **nbuffer, 0x3);
|
||||||
|
|
||||||
|
VoxelMeshUpdater::InputBlock iblock;
|
||||||
|
iblock.voxels = nbuffer;
|
||||||
|
iblock.position = block_pos;
|
||||||
|
input.blocks.push_back(iblock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop request
|
_block_updater->push(input);
|
||||||
_block_update_queue.resize(_block_update_queue.size() - 1);
|
_blocks_pending_update.clear();
|
||||||
_dirty_blocks.erase(block_pos);
|
}
|
||||||
|
|
||||||
|
// Get mesh updates
|
||||||
|
{
|
||||||
|
VoxelMeshUpdater::Output output;
|
||||||
|
_block_updater->pop(output);
|
||||||
|
|
||||||
|
_stats.updater = output.stats;
|
||||||
|
_stats.updated_blocks = output.blocks.size();
|
||||||
|
|
||||||
|
Ref<World> world = get_world();
|
||||||
|
|
||||||
|
uint32_t time_before = os.get_ticks_msec();
|
||||||
|
|
||||||
|
for (int i = 0; i < output.blocks.size(); ++i) {
|
||||||
|
const VoxelMeshUpdater::OutputBlock &ob = output.blocks[i];
|
||||||
|
|
||||||
|
VoxelBlock *block = _map->get_block(ob.position);
|
||||||
|
if (block == NULL) {
|
||||||
|
clear_block_update_state(ob.position);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: I allocate the mesh here because Godot doesn't supports doing it in another thread without hanging the main one.
|
||||||
|
// Hopefully Vulkan will improve this?
|
||||||
|
|
||||||
|
Ref<ArrayMesh> mesh;
|
||||||
|
mesh.instance();
|
||||||
|
|
||||||
|
int surface_index = 0;
|
||||||
|
for (int i = 0; i < ob.model_surfaces.size(); ++i) {
|
||||||
|
|
||||||
|
Array surface = ob.model_surfaces[i];
|
||||||
|
if (surface.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface);
|
||||||
|
mesh->surface_set_material(surface_index, _materials[i]);
|
||||||
|
|
||||||
|
|
||||||
|
++surface_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < ob.smooth_surfaces.size(); ++i) {
|
||||||
|
|
||||||
|
Array surface = ob.smooth_surfaces[i];
|
||||||
|
if (surface.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface);
|
||||||
|
// No material supported yet
|
||||||
|
++surface_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mesh_empty(mesh))
|
||||||
|
mesh = Ref<Mesh>();
|
||||||
|
|
||||||
|
block->set_mesh(mesh, world);
|
||||||
|
clear_block_update_state(ob.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t time_taken = os.get_ticks_msec() - time_before;
|
||||||
|
_stats.mesh_alloc_time = time_taken;
|
||||||
}
|
}
|
||||||
|
|
||||||
//print_line(String("d:") + String::num(_dirty_blocks.size()) + String(", q:") + String::num(_block_update_queue.size()));
|
//print_line(String("d:") + String::num(_dirty_blocks.size()) + String(", q:") + String::num(_block_update_queue.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool is_mesh_empty(Ref<Mesh> mesh_ref) {
|
// To be called once a block is updated
|
||||||
if (mesh_ref.is_null())
|
void VoxelTerrain::clear_block_update_state(Vector3i block_pos) {
|
||||||
return true;
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(block_pos);
|
||||||
const Mesh &mesh = **mesh_ref;
|
if (state) {
|
||||||
if (mesh.get_surface_count() == 0)
|
if (*state == BLOCK_UPDATE)
|
||||||
return true;
|
_dirty_blocks.erase(block_pos);
|
||||||
if (mesh.surface_get_array_len(0) == 0)
|
else
|
||||||
return true;
|
;//print_line("Block update found non-update state");
|
||||||
return false;
|
} else {
|
||||||
}
|
;//print_line("Block update found no update state");
|
||||||
|
|
||||||
void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
|
|
||||||
|
|
||||||
VoxelBlock *block = _map->get_block(block_pos);
|
|
||||||
if (block == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VOXEL_PROFILE_BEGIN("voxel_buffer_creation_extract")
|
|
||||||
// Create buffer padded with neighbor voxels
|
|
||||||
VoxelBuffer nbuffer;
|
|
||||||
// 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);
|
|
||||||
VOXEL_PROFILE_END("voxel_buffer_creation_extract")
|
|
||||||
|
|
||||||
VOXEL_PROFILE_BEGIN("block_extraction")
|
|
||||||
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer, 0x3);
|
|
||||||
VOXEL_PROFILE_END("block_extraction")
|
|
||||||
|
|
||||||
// TODO Re-use existing meshes to optimize memory cost
|
|
||||||
|
|
||||||
// Build cubic parts of the mesh
|
|
||||||
Ref<ArrayMesh> mesh = _mesher->build(nbuffer, Voxel::CHANNEL_TYPE, Vector3i(0, 0, 0), nbuffer.get_size() - Vector3(1, 1, 1));
|
|
||||||
// Build smooth parts of the mesh
|
|
||||||
_mesher_smooth->build(nbuffer, Voxel::CHANNEL_ISOLEVEL, mesh);
|
|
||||||
|
|
||||||
if(is_mesh_empty(mesh))
|
|
||||||
mesh = Ref<Mesh>();
|
|
||||||
|
|
||||||
Ref<World> world = get_world();
|
|
||||||
block->set_mesh(mesh, world);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//void VoxelTerrain::block_removed(VoxelBlock & block) {
|
//void VoxelTerrain::block_removed(VoxelBlock & block) {
|
||||||
@ -647,9 +769,6 @@ void VoxelTerrain::_bind_methods() {
|
|||||||
ClassDB::bind_method(D_METHOD("set_view_distance", "distance_in_voxels"), &VoxelTerrain::set_view_distance);
|
ClassDB::bind_method(D_METHOD("set_view_distance", "distance_in_voxels"), &VoxelTerrain::set_view_distance);
|
||||||
ClassDB::bind_method(D_METHOD("get_view_distance"), &VoxelTerrain::get_view_distance);
|
ClassDB::bind_method(D_METHOD("get_view_distance"), &VoxelTerrain::get_view_distance);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_block_update_count"), &VoxelTerrain::get_block_update_count);
|
|
||||||
ClassDB::bind_method(D_METHOD("get_mesher"), &VoxelTerrain::get_mesher);
|
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelTerrain::get_generate_collisions);
|
ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelTerrain::get_generate_collisions);
|
||||||
ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelTerrain::set_generate_collisions);
|
ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelTerrain::set_generate_collisions);
|
||||||
|
|
||||||
@ -661,15 +780,13 @@ void VoxelTerrain::_bind_methods() {
|
|||||||
ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
|
ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
|
||||||
ClassDB::bind_method(D_METHOD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding);
|
ClassDB::bind_method(D_METHOD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("make_block_dirty", "pos"), &VoxelTerrain::_make_block_dirty_binding);
|
//ClassDB::bind_method(D_METHOD("make_block_dirty", "pos"), &VoxelTerrain::_make_block_dirty_binding);
|
||||||
ClassDB::bind_method(D_METHOD("make_blocks_dirty", "min", "size"), &VoxelTerrain::_make_blocks_dirty_binding);
|
//ClassDB::bind_method(D_METHOD("make_blocks_dirty", "min", "size"), &VoxelTerrain::_make_blocks_dirty_binding);
|
||||||
ClassDB::bind_method(D_METHOD("make_voxel_dirty", "pos"), &VoxelTerrain::_make_voxel_dirty_binding);
|
ClassDB::bind_method(D_METHOD("make_voxel_dirty", "pos"), &VoxelTerrain::_make_voxel_dirty_binding);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("raycast", "origin", "direction", "max_distance"), &VoxelTerrain::_raycast_binding, DEFVAL(100));
|
ClassDB::bind_method(D_METHOD("raycast", "origin", "direction", "max_distance"), &VoxelTerrain::_raycast_binding, DEFVAL(100));
|
||||||
|
|
||||||
#ifdef VOXEL_PROFILING
|
ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelTerrain::get_statistics);
|
||||||
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelTerrain::get_profiling_info);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "provider", PROPERTY_HINT_RESOURCE_TYPE, "VoxelProvider"), "set_provider", "get_provider");
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "provider", PROPERTY_HINT_RESOURCE_TYPE, "VoxelProvider"), "set_provider", "get_provider");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "voxel_library", PROPERTY_HINT_RESOURCE_TYPE, "VoxelLibrary"), "set_voxel_library", "get_voxel_library");
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "voxel_library", PROPERTY_HINT_RESOURCE_TYPE, "VoxelLibrary"), "set_voxel_library", "get_voxel_library");
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
#ifndef VOXEL_TERRAIN_H
|
#ifndef VOXEL_TERRAIN_H
|
||||||
#define VOXEL_TERRAIN_H
|
#define VOXEL_TERRAIN_H
|
||||||
|
|
||||||
#include "voxel_map.h"
|
#include "vector3i.h"
|
||||||
#include "voxel_mesher.h"
|
|
||||||
#include "voxel_mesher_smooth.h"
|
|
||||||
#include "voxel_provider.h"
|
|
||||||
#include "zprofiling.h"
|
#include "zprofiling.h"
|
||||||
#include <scene/main/node.h>
|
#include "voxel_provider.h"
|
||||||
|
#include "voxel_provider_thread.h"
|
||||||
|
#include "voxel_mesh_updater.h"
|
||||||
|
#include "rect3i.h"
|
||||||
|
|
||||||
|
#include <scene/3d/spatial.h>
|
||||||
|
|
||||||
|
class VoxelMap;
|
||||||
|
class VoxelLibrary;
|
||||||
|
|
||||||
// Infinite static terrain made of voxels.
|
// Infinite static terrain made of voxels.
|
||||||
// It is loaded around VoxelTerrainStreamers.
|
// It is loaded around VoxelTerrainStreamers.
|
||||||
@ -14,6 +19,7 @@ class VoxelTerrain : public Spatial /*, public IVoxelMapObserver*/ {
|
|||||||
GDCLASS(VoxelTerrain, Spatial)
|
GDCLASS(VoxelTerrain, Spatial)
|
||||||
public:
|
public:
|
||||||
VoxelTerrain();
|
VoxelTerrain();
|
||||||
|
~VoxelTerrain();
|
||||||
|
|
||||||
void set_provider(Ref<VoxelProvider> provider);
|
void set_provider(Ref<VoxelProvider> provider);
|
||||||
Ref<VoxelProvider> get_provider() const;
|
Ref<VoxelProvider> get_provider() const;
|
||||||
@ -21,13 +27,10 @@ public:
|
|||||||
void set_voxel_library(Ref<VoxelLibrary> library);
|
void set_voxel_library(Ref<VoxelLibrary> library);
|
||||||
Ref<VoxelLibrary> get_voxel_library() const;
|
Ref<VoxelLibrary> get_voxel_library() const;
|
||||||
|
|
||||||
void force_load_blocks(Vector3i center, Vector3i extents);
|
|
||||||
int get_block_update_count();
|
|
||||||
|
|
||||||
void make_block_dirty(Vector3i bpos);
|
void make_block_dirty(Vector3i bpos);
|
||||||
void make_blocks_dirty(Vector3i min, Vector3i size);
|
//void make_blocks_dirty(Vector3i min, Vector3i size);
|
||||||
void make_voxel_dirty(Vector3i pos);
|
void make_voxel_dirty(Vector3i pos);
|
||||||
bool is_block_dirty(Vector3i bpos);
|
bool is_block_dirty(Vector3i bpos) const;
|
||||||
|
|
||||||
void set_generate_collisions(bool enabled);
|
void set_generate_collisions(bool enabled);
|
||||||
bool get_generate_collisions() const { return _generate_collisions; }
|
bool get_generate_collisions() const { return _generate_collisions; }
|
||||||
@ -41,9 +44,18 @@ public:
|
|||||||
void set_material(int id, Ref<Material> material);
|
void set_material(int id, Ref<Material> material);
|
||||||
Ref<Material> get_material(int id) const;
|
Ref<Material> get_material(int id) const;
|
||||||
|
|
||||||
Ref<VoxelMesher> get_mesher() { return _mesher; }
|
|
||||||
Ref<VoxelMap> get_map() { return _map; }
|
Ref<VoxelMap> get_map() { return _map; }
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
VoxelMeshUpdater::Stats updater;
|
||||||
|
VoxelProviderThread::Stats provider;
|
||||||
|
uint32_t mesh_alloc_time;
|
||||||
|
uint32_t updated_blocks;
|
||||||
|
|
||||||
|
Stats(): mesh_alloc_time(0), updated_blocks(0)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
|
||||||
@ -54,26 +66,27 @@ private:
|
|||||||
|
|
||||||
void _process();
|
void _process();
|
||||||
|
|
||||||
void update_blocks();
|
|
||||||
void update_block_mesh(Vector3i block_pos);
|
|
||||||
|
|
||||||
void make_all_view_dirty_deferred();
|
void make_all_view_dirty_deferred();
|
||||||
|
|
||||||
|
enum BlockDirtyState {
|
||||||
|
BLOCK_LOAD,
|
||||||
|
BLOCK_UPDATE
|
||||||
|
};
|
||||||
|
|
||||||
Spatial *get_viewer(NodePath path) const;
|
Spatial *get_viewer(NodePath path) const;
|
||||||
|
|
||||||
void immerge_block(Vector3i bpos);
|
void immerge_block(Vector3i bpos);
|
||||||
|
|
||||||
// Observer events
|
Dictionary get_statistics() const;
|
||||||
//void block_removed(VoxelBlock & block);
|
|
||||||
|
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
// Convenience
|
// Convenience
|
||||||
Vector3 _voxel_to_block_binding(Vector3 pos);
|
Vector3 _voxel_to_block_binding(Vector3 pos);
|
||||||
Vector3 _block_to_voxel_binding(Vector3 pos);
|
Vector3 _block_to_voxel_binding(Vector3 pos);
|
||||||
void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); }
|
//void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); }
|
||||||
void _make_block_dirty_binding(Vector3 bpos) { make_block_dirty(bpos); }
|
//void _make_block_dirty_binding(Vector3 bpos) { make_block_dirty(bpos); }
|
||||||
void _make_blocks_dirty_binding(Vector3 min, Vector3 size) { make_blocks_dirty(min, size); }
|
//void _make_blocks_dirty_binding(Vector3 min, Vector3 size) { make_blocks_dirty(min, size); }
|
||||||
void _make_voxel_dirty_binding(Vector3 pos) { make_voxel_dirty(pos); }
|
void _make_voxel_dirty_binding(Vector3 pos) { make_voxel_dirty(pos); }
|
||||||
|
|
||||||
Variant _raycast_binding(Vector3 origin, Vector3 direction, real_t max_distance);
|
Variant _raycast_binding(Vector3 origin, Vector3 direction, real_t max_distance);
|
||||||
@ -81,6 +94,10 @@ private:
|
|||||||
void set_voxel(Vector3 pos, int value, int c);
|
void set_voxel(Vector3 pos, int value, int c);
|
||||||
int get_voxel(Vector3 pos, int c);
|
int get_voxel(Vector3 pos, int c);
|
||||||
|
|
||||||
|
void clear_block_update_state(Vector3i block_pos);
|
||||||
|
|
||||||
|
static void remove_positions_outside_box(Vector<Vector3i> &positions, Rect3i box, HashMap<Vector3i, BlockDirtyState, Vector3iHasher> &state_map);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Voxel storage
|
// Voxel storage
|
||||||
Ref<VoxelMap> _map;
|
Ref<VoxelMap> _map;
|
||||||
@ -91,14 +108,15 @@ private:
|
|||||||
// TODO Terrains only need to handle the visible portion of voxels, which reduces the bounds blocks to handle.
|
// TODO Terrains only need to handle the visible portion of voxels, which reduces the bounds blocks to handle.
|
||||||
// Therefore, could a simple grid be better to use than a hashmap?
|
// Therefore, could a simple grid be better to use than a hashmap?
|
||||||
|
|
||||||
Vector<Vector3i> _block_update_queue;
|
Vector<Vector3i> _blocks_pending_load;
|
||||||
HashMap<Vector3i, bool, Vector3iHasher> _dirty_blocks; // only the key is relevant
|
Vector<Vector3i> _blocks_pending_update;
|
||||||
|
HashMap<Vector3i, BlockDirtyState, Vector3iHasher> _dirty_blocks; // only the key is relevant
|
||||||
Ref<VoxelMesher> _mesher;
|
|
||||||
// TODO I'm not sure it will stay here... refactoring ahead
|
|
||||||
Ref<VoxelMesherSmooth> _mesher_smooth;
|
|
||||||
|
|
||||||
Ref<VoxelProvider> _provider;
|
Ref<VoxelProvider> _provider;
|
||||||
|
VoxelProviderThread *_provider_thread;
|
||||||
|
|
||||||
|
Ref<VoxelLibrary> _library;
|
||||||
|
VoxelMeshUpdater *_block_updater;
|
||||||
|
|
||||||
NodePath _viewer_path;
|
NodePath _viewer_path;
|
||||||
Vector3i _last_viewer_block_pos;
|
Vector3i _last_viewer_block_pos;
|
||||||
@ -106,10 +124,9 @@ private:
|
|||||||
|
|
||||||
bool _generate_collisions;
|
bool _generate_collisions;
|
||||||
|
|
||||||
#ifdef VOXEL_PROFILING
|
Ref<Material> _materials[VoxelMesher::MAX_MATERIALS];
|
||||||
ZProfiler _zprofiler;
|
|
||||||
Dictionary get_profiling_info() { return _zprofiler.get_all_serialized_info(); }
|
Stats _stats;
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VOXEL_TERRAIN_H
|
#endif // VOXEL_TERRAIN_H
|
||||||
|
Loading…
Reference in New Issue
Block a user