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
|
||||
|
||||
#include "voxel_buffer.h"
|
||||
#include "voxel_block.h"
|
||||
|
||||
#include <core/hash_map.h>
|
||||
#include <scene/main/node.h>
|
||||
|
||||
class VoxelBlock;
|
||||
|
||||
// Infinite voxel storage by means of octants like Gridmap
|
||||
class VoxelMap : public Reference {
|
||||
GDCLASS(VoxelMap, Reference)
|
||||
public:
|
||||
// Converts voxel coodinates into block coordinates
|
||||
_FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) const {
|
||||
// Converts voxel coodinates into block coordinates.
|
||||
// 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(
|
||||
pos.x >> _block_size_pow2,
|
||||
pos.y >> _block_size_pow2,
|
||||
pos.z >> _block_size_pow2);
|
||||
pos.x >> block_size_pow2,
|
||||
pos.y >> 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 {
|
||||
|
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_library.h"
|
||||
#include "cube_tables.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];
|
||||
}
|
||||
}
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
VoxelMesher::VoxelMesher()
|
||||
: _baked_occlusion_darkness(0.75),
|
||||
@ -24,16 +11,6 @@ void VoxelMesher::set_library(Ref<VoxelLibrary> 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) {
|
||||
_baked_occlusion_darkness = darkness;
|
||||
if (_baked_occlusion_darkness < 0.0)
|
||||
@ -66,16 +43,33 @@ inline bool is_transparent(const VoxelLibrary &lib, int voxel_id) {
|
||||
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>());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max, Ref<ArrayMesh> mesh) {
|
||||
ERR_FAIL_COND_V(_library.is_null(), Ref<ArrayMesh>());
|
||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<ArrayMesh>());
|
||||
Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max) {
|
||||
ERR_FAIL_COND_V(_library.is_null(), Array());
|
||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
|
||||
|
||||
const VoxelLibrary &library = **_library;
|
||||
|
||||
@ -100,8 +94,6 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
||||
// - Slower
|
||||
// => Could be implemented in a separate class?
|
||||
|
||||
VOXEL_PROFILE_BEGIN("mesher_face_extraction")
|
||||
|
||||
// Data must be padded, hence the off-by-one
|
||||
Vector3i::sort_min_max(min, max);
|
||||
const Vector3i pad(1, 1, 1);
|
||||
@ -268,20 +260,13 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
||||
}
|
||||
}
|
||||
|
||||
VOXEL_PROFILE_END("mesher_face_extraction")
|
||||
|
||||
// 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())
|
||||
// + String(", i: ") + String::num(_arrays[0].indices.size()));
|
||||
|
||||
int surface = 0;
|
||||
Array surfaces;
|
||||
|
||||
for (int i = 0; i < MAX_MATERIALS; ++i) {
|
||||
|
||||
const Arrays &arrays = _arrays[i];
|
||||
@ -310,23 +295,15 @@ Ref<ArrayMesh> VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe
|
||||
mesh_arrays[Mesh::ARRAY_INDEX] = indices;
|
||||
}
|
||||
|
||||
mesh_ref->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, mesh_arrays);
|
||||
mesh_ref->surface_set_material(surface, _materials[i]);
|
||||
|
||||
++surface;
|
||||
surfaces.append(mesh_arrays);
|
||||
}
|
||||
}
|
||||
|
||||
VOXEL_PROFILE_END("mesher_add_surfaces")
|
||||
|
||||
return mesh_ref;
|
||||
return surfaces;
|
||||
}
|
||||
|
||||
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("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("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
|
||||
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelMesher::get_profiling_info);
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <core/reference.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 {
|
||||
GDCLASS(VoxelMesher, Reference)
|
||||
|
||||
@ -17,9 +17,6 @@ public:
|
||||
|
||||
VoxelMesher();
|
||||
|
||||
void set_material(Ref<Material> material, unsigned int id);
|
||||
Ref<Material> get_material(unsigned int id) const;
|
||||
|
||||
void set_library(Ref<VoxelLibrary> library);
|
||||
Ref<VoxelLibrary> get_library() const { return _library; }
|
||||
|
||||
@ -29,8 +26,8 @@ public:
|
||||
void set_occlusion_enabled(bool enable);
|
||||
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>());
|
||||
Ref<ArrayMesh> build_ref(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||
Array build(const VoxelBuffer &buffer_ref, unsigned int channel, Vector3i min, Vector3i max);
|
||||
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
@ -45,7 +42,6 @@ private:
|
||||
};
|
||||
|
||||
Ref<VoxelLibrary> _library;
|
||||
Ref<Material> _materials[MAX_MATERIALS];
|
||||
Arrays _arrays[MAX_MATERIALS];
|
||||
float _baked_occlusion_darkness;
|
||||
bool _bake_occlusion;
|
||||
|
@ -68,18 +68,29 @@ VoxelMesherSmooth::ReuseCell::ReuseCell() {
|
||||
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>());
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int channel, Ref<ArrayMesh> mesh) {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<ArrayMesh>());
|
||||
Array VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int channel) {
|
||||
|
||||
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
|
||||
|
||||
// Initialize dynamic memory:
|
||||
// These vectors are re-used.
|
||||
@ -90,7 +101,7 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
||||
m_output_normals.clear();
|
||||
m_output_indices.clear();
|
||||
|
||||
build_mesh(voxels, channel);
|
||||
build_internal(voxels, channel);
|
||||
// OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n",
|
||||
// m_output_vertices.size(),
|
||||
// m_output_normals.size(),
|
||||
@ -98,7 +109,7 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
||||
|
||||
if (m_output_vertices.size() == 0) {
|
||||
// The mesh can be empty
|
||||
return Ref<ArrayMesh>();
|
||||
return Array();
|
||||
}
|
||||
|
||||
PoolVector<Vector3> vertices;
|
||||
@ -117,15 +128,13 @@ Ref<ArrayMesh> VoxelMesherSmooth::build(const VoxelBuffer &voxels, unsigned int
|
||||
}
|
||||
arrays[Mesh::ARRAY_INDEX] = indices;
|
||||
|
||||
if (mesh.is_null())
|
||||
mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
||||
Array surfaces;
|
||||
surfaces.append(arrays);
|
||||
|
||||
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
||||
|
||||
return mesh;
|
||||
return surfaces;
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@ -385,5 +394,5 @@ void VoxelMesherSmooth::emit_vertex(Vector3 primary, Vector3 normal) {
|
||||
|
||||
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:
|
||||
VoxelMesherSmooth();
|
||||
|
||||
Ref<ArrayMesh> build_ref(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>());
|
||||
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
|
||||
Array build(const VoxelBuffer &voxels, unsigned int channel);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
@ -23,7 +23,7 @@ private:
|
||||
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);
|
||||
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_map.h"
|
||||
#include "voxel_block.h"
|
||||
#include "voxel_provider_thread.h"
|
||||
#include "voxel_raycast.h"
|
||||
#include "rect3i.h"
|
||||
#include "voxel_provider_test.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <core/os/os.h>
|
||||
#include <scene/3d/mesh_instance.h>
|
||||
#include <core/engine.h>
|
||||
@ -10,22 +15,22 @@ VoxelTerrain::VoxelTerrain()
|
||||
: Spatial(), _generate_collisions(true) {
|
||||
|
||||
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
||||
_mesher = Ref<VoxelMesher>(memnew(VoxelMesher));
|
||||
_mesher_smooth = Ref<VoxelMesherSmooth>(memnew(VoxelMesherSmooth));
|
||||
|
||||
_view_distance_blocks = 8;
|
||||
_last_view_distance_blocks = 0;
|
||||
|
||||
_provider_thread = NULL;
|
||||
_block_updater = NULL;
|
||||
}
|
||||
|
||||
// TODO UGLY! Lambdas or pointers needed... DO NOT use this outside of lambdas!
|
||||
Vector3i g_viewer_block_pos;
|
||||
|
||||
// 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);
|
||||
VoxelTerrain::~VoxelTerrain() {
|
||||
if(_provider_thread) {
|
||||
memdelete(_provider_thread);
|
||||
}
|
||||
if(_block_updater) {
|
||||
memdelete(_block_updater);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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/")) {
|
||||
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
||||
if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0)
|
||||
return false;
|
||||
ERR_FAIL_COND_V(idx >= VoxelMesher::MAX_MATERIALS || idx < 0, false);
|
||||
set_material(idx, p_value);
|
||||
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/")) {
|
||||
int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
||||
if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0)
|
||||
return false;
|
||||
ERR_FAIL_COND_V(idx >= VoxelMesher::MAX_MATERIALS || idx < 0, false);
|
||||
r_ret = get_material(idx);
|
||||
return true;
|
||||
}
|
||||
@ -64,8 +67,20 @@ void VoxelTerrain::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
|
||||
void VoxelTerrain::set_provider(Ref<VoxelProvider> provider) {
|
||||
if(provider != _provider) {
|
||||
|
||||
if(_provider_thread) {
|
||||
memdelete(_provider_thread);
|
||||
_provider_thread = NULL;
|
||||
}
|
||||
|
||||
_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
|
||||
// TODO Actually, we should regenerate the whole map, not just update all its blocks
|
||||
make_all_view_dirty_deferred();
|
||||
}
|
||||
}
|
||||
@ -75,18 +90,29 @@ Ref<VoxelProvider> VoxelTerrain::get_provider() const {
|
||||
}
|
||||
|
||||
Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const {
|
||||
return _mesher->get_library();
|
||||
return _library;
|
||||
}
|
||||
|
||||
void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
|
||||
if(library != _mesher->get_library()) {
|
||||
|
||||
if (library != _library) {
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (library->get_voxel_count() == 0) {
|
||||
library->load_default();
|
||||
}
|
||||
#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
|
||||
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) {
|
||||
// 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 {
|
||||
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) {
|
||||
// TODO Immediate update viewer distance
|
||||
// TODO Immediate update viewer distance?
|
||||
|
||||
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);
|
||||
_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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
|
||||
Vector3i max = min + size;
|
||||
Vector3i pos;
|
||||
for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
||||
for (pos.y = min.y; pos.y < max.y; ++pos.y) {
|
||||
for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
||||
make_block_dirty(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
|
||||
// Vector3i max = min + size;
|
||||
// Vector3i pos;
|
||||
// for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
||||
// for (pos.y = min.y; pos.y < max.y; ++pos.y) {
|
||||
// for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
||||
// make_block_dirty(pos);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
void VoxelTerrain::make_all_view_dirty_deferred() {
|
||||
// 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);
|
||||
|
||||
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;
|
||||
|
||||
@ -302,10 +360,6 @@ void VoxelTerrain::make_voxel_dirty(Vector3i pos) {
|
||||
}
|
||||
}
|
||||
|
||||
int VoxelTerrain::get_block_update_count() {
|
||||
return _block_update_queue.size();
|
||||
}
|
||||
|
||||
struct EnterWorldAction {
|
||||
World *world;
|
||||
EnterWorldAction(World *w) : world(w) {}
|
||||
@ -365,11 +419,31 @@ void VoxelTerrain::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelTerrain::_process() {
|
||||
update_blocks();
|
||||
void VoxelTerrain::remove_positions_outside_box(Vector<Vector3i> &positions, Rect3i box, HashMap<Vector3i, VoxelTerrain::BlockDirtyState, Vector3iHasher> &state_map) {
|
||||
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();
|
||||
Engine &engine = *Engine::get_singleton();
|
||||
@ -390,9 +464,8 @@ void VoxelTerrain::update_blocks() {
|
||||
viewer_block_pos = Vector3i();
|
||||
}
|
||||
|
||||
{
|
||||
// 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;
|
||||
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));
|
||||
@ -425,148 +498,197 @@ void VoxelTerrain::update_blocks() {
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminate blocks in queue that aren't needed
|
||||
for(int i = 0; i < _block_update_queue.size(); ++i) {
|
||||
const Vector3i bpos = _block_update_queue[i];
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Eliminate pending blocks that aren't needed
|
||||
remove_positions_outside_box(_blocks_pending_load, new_box, _dirty_blocks);
|
||||
remove_positions_outside_box(_blocks_pending_update, new_box, _dirty_blocks);
|
||||
}
|
||||
|
||||
_last_view_distance_blocks = _view_distance_blocks;
|
||||
_last_viewer_block_pos = viewer_block_pos;
|
||||
|
||||
// Sort updates so nearest blocks are done first
|
||||
VOXEL_PROFILE_BEGIN("block_update_sorting")
|
||||
g_viewer_block_pos = viewer_block_pos;
|
||||
_block_update_queue.sort_custom<BlockUpdateComparator>();
|
||||
VOXEL_PROFILE_END("block_update_sorting")
|
||||
// Send block loading requests
|
||||
{
|
||||
VoxelProviderThread::InputData input;
|
||||
|
||||
uint32_t time_before = os.get_ticks_msec();
|
||||
uint32_t max_time = 1000 / 120;
|
||||
input.priority_block_position = viewer_block_pos;
|
||||
input.blocks_to_emerge.append_array(_blocks_pending_load);
|
||||
//input.blocks_to_immerge.append_array();
|
||||
|
||||
//print_line(String("Sending {0} block requests").format(varray(input.blocks_to_emerge.size())));
|
||||
_blocks_pending_load.clear();
|
||||
|
||||
_provider_thread->push(input);
|
||||
}
|
||||
|
||||
// Get block loading responses
|
||||
{
|
||||
const unsigned int bs = _map->get_block_size();
|
||||
const Vector3i block_size(bs, bs, bs);
|
||||
|
||||
// Update a bunch of blocks until none are left or too much time elapsed
|
||||
while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) {
|
||||
VoxelProviderThread::OutputData output;
|
||||
_provider_thread->pop(output);
|
||||
//print_line(String("Receiving {0} blocks").format(varray(output.emerged_blocks.size())));
|
||||
|
||||
//printf("Remaining: %i\n", _block_update_queue.size());
|
||||
_stats.provider = output.stats;
|
||||
|
||||
// TODO Move this to a thread
|
||||
for(int i = 0; i < output.emerged_blocks.size(); ++i) {
|
||||
|
||||
// Get request
|
||||
Vector3i block_pos = _block_update_queue[_block_update_queue.size() - 1];
|
||||
const VoxelProviderThread::EmergeOutput &o = output.emerged_blocks[i];
|
||||
|
||||
bool entire_block_changed = false;
|
||||
|
||||
if (!_map->has_block(block_pos)) {
|
||||
// The block's data isn't loaded yet
|
||||
// Create buffer
|
||||
if (!_provider.is_null()) {
|
||||
|
||||
VOXEL_PROFILE_BEGIN("voxel_buffer_creation_gen")
|
||||
|
||||
Ref<VoxelBuffer> buffer_ref = Ref<VoxelBuffer>(memnew(VoxelBuffer));
|
||||
buffer_ref->create(block_size.x, block_size.y, block_size.z);
|
||||
|
||||
VOXEL_PROFILE_END("voxel_buffer_creation_gen")
|
||||
VOXEL_PROFILE_BEGIN("block_generation")
|
||||
|
||||
// Query voxel provider
|
||||
_provider->emerge_block(buffer_ref, _map->block_to_voxel(block_pos));
|
||||
|
||||
// Check script return
|
||||
// Check return
|
||||
// TODO Shouldn't halt execution though, as it can bring the map in an invalid state!
|
||||
ERR_FAIL_COND(buffer_ref->get_size() != block_size);
|
||||
ERR_FAIL_COND(o.voxels->get_size() != block_size);
|
||||
|
||||
VOXEL_PROFILE_END("block_generation")
|
||||
// TODO Discard blocks out of range
|
||||
|
||||
// Store buffer
|
||||
_map->set_block_buffer(block_pos, buffer_ref);
|
||||
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);
|
||||
|
||||
entire_block_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update views (mesh/collisions)
|
||||
|
||||
if (entire_block_changed) {
|
||||
// Trigger mesh updates
|
||||
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)) {
|
||||
update_block_mesh(npos);
|
||||
|
||||
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(npos);
|
||||
if (state && *state == BLOCK_UPDATE) {
|
||||
// Assuming it is scheduled to be updated already.
|
||||
continue;
|
||||
}
|
||||
|
||||
_dirty_blocks[npos] = BLOCK_UPDATE;
|
||||
_blocks_pending_update.push_back(npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only update the block, neighbors will probably follow if needed
|
||||
update_block_mesh(block_pos);
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop request
|
||||
_block_update_queue.resize(_block_update_queue.size() - 1);
|
||||
_dirty_blocks.erase(block_pos);
|
||||
// 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);
|
||||
}
|
||||
|
||||
_block_updater->push(input);
|
||||
_blocks_pending_update.clear();
|
||||
}
|
||||
|
||||
// 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()));
|
||||
}
|
||||
|
||||
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;
|
||||
// To be called once a block is updated
|
||||
void VoxelTerrain::clear_block_update_state(Vector3i block_pos) {
|
||||
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(block_pos);
|
||||
if (state) {
|
||||
if (*state == BLOCK_UPDATE)
|
||||
_dirty_blocks.erase(block_pos);
|
||||
else
|
||||
;//print_line("Block update found non-update state");
|
||||
} 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) {
|
||||
@ -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("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("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("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_blocks_dirty", "min", "size"), &VoxelTerrain::_make_blocks_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_voxel_dirty", "pos"), &VoxelTerrain::_make_voxel_dirty_binding);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("raycast", "origin", "direction", "max_distance"), &VoxelTerrain::_raycast_binding, DEFVAL(100));
|
||||
|
||||
#ifdef VOXEL_PROFILING
|
||||
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelTerrain::get_profiling_info);
|
||||
#endif
|
||||
ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelTerrain::get_statistics);
|
||||
|
||||
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");
|
||||
|
@ -1,12 +1,17 @@
|
||||
#ifndef VOXEL_TERRAIN_H
|
||||
#define VOXEL_TERRAIN_H
|
||||
|
||||
#include "voxel_map.h"
|
||||
#include "voxel_mesher.h"
|
||||
#include "voxel_mesher_smooth.h"
|
||||
#include "voxel_provider.h"
|
||||
#include "vector3i.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.
|
||||
// It is loaded around VoxelTerrainStreamers.
|
||||
@ -14,6 +19,7 @@ class VoxelTerrain : public Spatial /*, public IVoxelMapObserver*/ {
|
||||
GDCLASS(VoxelTerrain, Spatial)
|
||||
public:
|
||||
VoxelTerrain();
|
||||
~VoxelTerrain();
|
||||
|
||||
void set_provider(Ref<VoxelProvider> provider);
|
||||
Ref<VoxelProvider> get_provider() const;
|
||||
@ -21,13 +27,10 @@ public:
|
||||
void set_voxel_library(Ref<VoxelLibrary> library);
|
||||
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_blocks_dirty(Vector3i min, Vector3i size);
|
||||
//void make_blocks_dirty(Vector3i min, Vector3i size);
|
||||
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);
|
||||
bool get_generate_collisions() const { return _generate_collisions; }
|
||||
@ -41,9 +44,18 @@ public:
|
||||
void set_material(int id, Ref<Material> material);
|
||||
Ref<Material> get_material(int id) const;
|
||||
|
||||
Ref<VoxelMesher> get_mesher() { return _mesher; }
|
||||
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:
|
||||
void _notification(int p_what);
|
||||
|
||||
@ -54,26 +66,27 @@ private:
|
||||
|
||||
void _process();
|
||||
|
||||
void update_blocks();
|
||||
void update_block_mesh(Vector3i block_pos);
|
||||
|
||||
void make_all_view_dirty_deferred();
|
||||
|
||||
enum BlockDirtyState {
|
||||
BLOCK_LOAD,
|
||||
BLOCK_UPDATE
|
||||
};
|
||||
|
||||
Spatial *get_viewer(NodePath path) const;
|
||||
|
||||
void immerge_block(Vector3i bpos);
|
||||
|
||||
// Observer events
|
||||
//void block_removed(VoxelBlock & block);
|
||||
Dictionary get_statistics() const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
// Convenience
|
||||
Vector3 _voxel_to_block_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 _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 _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_blocks_dirty_binding(Vector3 min, Vector3 size) { make_blocks_dirty(min, size); }
|
||||
void _make_voxel_dirty_binding(Vector3 pos) { make_voxel_dirty(pos); }
|
||||
|
||||
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);
|
||||
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:
|
||||
// Voxel storage
|
||||
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.
|
||||
// Therefore, could a simple grid be better to use than a hashmap?
|
||||
|
||||
Vector<Vector3i> _block_update_queue;
|
||||
HashMap<Vector3i, bool, 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;
|
||||
Vector<Vector3i> _blocks_pending_load;
|
||||
Vector<Vector3i> _blocks_pending_update;
|
||||
HashMap<Vector3i, BlockDirtyState, Vector3iHasher> _dirty_blocks; // only the key is relevant
|
||||
|
||||
Ref<VoxelProvider> _provider;
|
||||
VoxelProviderThread *_provider_thread;
|
||||
|
||||
Ref<VoxelLibrary> _library;
|
||||
VoxelMeshUpdater *_block_updater;
|
||||
|
||||
NodePath _viewer_path;
|
||||
Vector3i _last_viewer_block_pos;
|
||||
@ -106,10 +124,9 @@ private:
|
||||
|
||||
bool _generate_collisions;
|
||||
|
||||
#ifdef VOXEL_PROFILING
|
||||
ZProfiler _zprofiler;
|
||||
Dictionary get_profiling_info() { return _zprofiler.get_all_serialized_info(); }
|
||||
#endif
|
||||
Ref<Material> _materials[VoxelMesher::MAX_MATERIALS];
|
||||
|
||||
Stats _stats;
|
||||
};
|
||||
|
||||
#endif // VOXEL_TERRAIN_H
|
||||
|
Loading…
Reference in New Issue
Block a user