First WIP at paging LOD

master
Marc Gilleron 2019-08-29 22:55:02 +01:00
parent db398a96e2
commit eb01f99c2c
8 changed files with 453 additions and 86 deletions

View File

@ -60,7 +60,7 @@ public:
return String("(o:{0}, s:{1})").format(varray(pos.to_vec3(), size.to_vec3()));
}
bool intersects(Rect3i other) {
bool intersects(Rect3i other) const {
if (pos.x > other.pos.x + other.size.x)
return false;
if (pos.y > other.pos.y + other.size.y)
@ -75,6 +75,58 @@ public:
return false;
return true;
}
struct NoAction {
inline void operator()(const Vector3i pos) {}
};
template <typename A>
inline void for_each_cell(A &a) const {
Vector3i max = pos + size;
Vector3i p;
for (p.z = pos.z; p.z < max.z; ++p.z) {
for (p.y = pos.y; p.y < max.y; ++p.y) {
for (p.x = pos.x; p.x < max.x; ++p.x) {
a(p);
}
}
}
}
template <typename A, typename B>
static void check_enters_and_exits(const Rect3i &old_box, const Rect3i &new_box, A &exit_action, B &enter_action) {
if (old_box.intersects(new_box)) {
Rect3i bounds = Rect3i::get_bounding_box(old_box, new_box);
Vector3i max = bounds.pos + bounds.size;
// TODO There is a better way by checking all the possible cases
Vector3i pos;
for (pos.z = bounds.pos.z; pos.z < max.z; ++pos.z) {
for (pos.y = bounds.pos.y; pos.y < max.y; ++pos.y) {
for (pos.x = bounds.pos.x; pos.x < max.x; ++pos.x) {
bool old_contains = old_box.contains(pos);
bool new_contains = new_box.contains(pos);
if (old_contains && !new_contains) {
exit_action(pos);
} else if (!old_contains && new_contains) {
enter_action(pos);
}
}
}
}
} else {
old_box.for_each_cell(exit_action);
new_box.for_each_cell(enter_action);
}
}
};
inline bool operator!=(const Rect3i &a, const Rect3i &b) {

View File

@ -48,8 +48,15 @@ void register_voxel_types() {
ClassDB::register_class<VoxelMesherBlocky>();
ClassDB::register_class<VoxelMesherTransvoxel>();
ClassDB::register_class<VoxelMesherDMC>();
#ifdef TOOLS_ENABLED
VoxelDebug::create_debug_box_mesh();
#endif
}
void unregister_voxel_types() {
// lol
#ifdef TOOLS_ENABLED
VoxelDebug::free_debug_box_mesh();
#endif
}

View File

@ -29,7 +29,7 @@ public:
// Specialization must be copyable
struct InputBlock {
InputBlockData_T data;
Vector3i position; // In LOD0 block coordinates
Vector3i position; // In LOD-relative block coordinates
uint8_t lod = 0;
bool can_be_discarded = true; // If false, will always be processed, even if the thread is told to exit
float sort_heuristic = 0;
@ -38,7 +38,7 @@ public:
// Specialization must be copyable
struct OutputBlock {
OutputBlockData_T data;
Vector3i position; // In LOD0 block coordinates
Vector3i position; // In LOD-relative block coordinates
unsigned int lod = 0;
// True if the block was actually dropped.
// Ideally the requester will agree that it doesn't need that block anymore,

View File

@ -96,7 +96,19 @@ public:
template <typename A, typename B>
void update(Vector3 view_pos, A &create_action, B &destroy_action) {
update(&_root, _max_depth, view_pos, create_action, destroy_action);
if (_root.block || _root.has_children()) {
update(&_root, _max_depth, view_pos, create_action, destroy_action);
} else {
// Treat the root in a slightly different way the first time.
// `can_do` takes child lod into account, but here it's like it is child of nothing.
// Careful when handling that case
if (create_action.can_do_root(_max_depth)) {
//print_line(String::num_int64((int64_t)this, 16) + String("Create LOD {0} pos {1} (root)").format(varray(_max_depth, _root.position.to_vec3())));
_root.block = create_action(&_root, _max_depth);
}
}
}
template <typename A>
@ -134,13 +146,14 @@ private:
if (!node->has_children()) {
// If it's not the last LOD, if close enough and custom conditions get fulfilled
if (lod > 0 && world_center.distance_to(view_pos) < split_distance && create_action.can_do(node, lod)) {
if (lod > 0 && world_center.distance_to(view_pos) < split_distance && create_action.can_do_children(node, lod - 1)) {
// Split
for (int i = 0; i < 8; ++i) {
Node *child = _pool.create();
child->position = get_child_position(node->position, i);
//print_line(String::num_int64((int64_t)this, 16) + String("Create LOD {0} pos {1} (subdiv)").format(varray(lod - 1, child->position.to_vec3())));
child->block = create_action(child, lod - 1);
node->children[i] = child;
@ -150,6 +163,7 @@ private:
}
if (node->block) {
//print_line(String::num_int64((int64_t)this, 16) + String("Destroy LOD {0} pos {1}").format(varray(lod, node->position.to_vec3())));
destroy_action(node, lod);
node->block = T();
}
@ -157,20 +171,21 @@ private:
} else {
bool no_split_child = true;
bool has_split_child = false;
for (int i = 0; i < 8; ++i) {
Node *child = node->children[i];
update(child, lod - 1, view_pos, create_action, destroy_action);
no_split_child |= child->has_children();
has_split_child |= child->has_children();
}
if (no_split_child && world_center.distance_to(view_pos) > split_distance && destroy_action.can_do(node, lod)) {
if (!has_split_child && world_center.distance_to(view_pos) > split_distance && destroy_action.can_do(node, lod)) {
// Join
if (node->has_children()) {
for (int i = 0; i < 8; ++i) {
Node *child = node->children[i];
//print_line(String::num_int64((int64_t)this, 16) + String("Destroy LOD {0} pos {1} (join)").format(varray(lod - 1, child->position.to_vec3())));
destroy_action(child, lod - 1);
child->block = T();
_pool.recycle(child);
@ -178,7 +193,10 @@ private:
node->children[0] = nullptr;
// If this is true, means the parent wasn't properly split.
// When subdividing a node, that node's block must be destroyed as it is replaced by its children.
CRASH_COND(node->block);
node->block = create_action(node, lod);
}
}
@ -201,6 +219,7 @@ private:
} else {
if (node->block) {
destroy_action(node, lod);
//print_line(String::num_int64((int64_t)this, 16) + String("Cleanup LOD {0} pos {1}").format(varray(lod, node->position.to_vec3())));
node->block = T();
}
}
@ -210,6 +229,7 @@ private:
int _max_depth = 0;
float _base_size = 16;
float _split_scale = 2.0;
// TODO May be worth making this pool external for sharing purpose
ObjectPool<Node> _pool;
};

View File

@ -7,6 +7,10 @@
#include <core/core_string_names.h>
#include <core/engine.h>
#ifdef TOOLS_ENABLED
#include <scene/3d/mesh_instance.h>
#endif
const uint32_t MAIN_THREAD_MESHING_BUDGET_MS = 8;
VoxelLodTerrain::VoxelLodTerrain() {
@ -18,7 +22,7 @@ VoxelLodTerrain::VoxelLodTerrain() {
_lods[0].map.instance();
set_lod_count(8);
set_lod_count(4);
set_lod_split_scale(3);
}
@ -125,8 +129,8 @@ void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
stop_streamer();
stop_updater();
reset_maps();
_set_block_size_po2(p_block_size_po2);
reset_maps();
if (_stream.is_valid()) {
start_streamer();
@ -151,16 +155,14 @@ int VoxelLodTerrain::get_view_distance() const {
return 0;
}
void VoxelLodTerrain::set_view_distance(int p_distance_in_voxels) {
// TODO this will be used to cap mesh visibility
void VoxelLodTerrain::set_view_distance(unsigned int p_distance_in_voxels) {
// ERR_FAIL_COND(p_distance_in_voxels < 0)
// int d = p_distance_in_voxels / get_block_size();
// if (d != _view_distance_blocks) {
// print_line(String("View distance changed from ") + String::num(_view_distance_blocks) + String(" blocks to ") + String::num(d));
// _view_distance_blocks = d;
// // Blocks too far away will be removed in _process, same for blocks to load
// }
ERR_FAIL_COND(p_distance_in_voxels == 0);
ERR_FAIL_COND(p_distance_in_voxels > 8192);
// Note: this is a hint distance, the terrain will attempt to have this radius filled with loaded voxels.
// It is possible for blocks to still load beyond that distance.
_view_distance_voxels = p_distance_in_voxels;
}
Spatial *VoxelLodTerrain::get_viewer() const {
@ -181,7 +183,7 @@ void VoxelLodTerrain::immerge_block(Vector3i block_pos, unsigned int lod_index)
Lod &lod = _lods[lod_index];
// TODO Schedule block saving when supported
// TODO Schedule block saving if modified, it's supported now!
lod.map->remove_block(block_pos, VoxelMap::NoAction());
lod.loading_blocks.erase(block_pos);
@ -256,16 +258,31 @@ void VoxelLodTerrain::stop_streamer() {
}
void VoxelLodTerrain::set_lod_split_scale(float p_lod_split_scale) {
_lod_octree.set_split_scale(p_lod_split_scale);
if (p_lod_split_scale == _lod_split_scale) {
return;
}
_lod_split_scale = p_lod_split_scale;
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
OctreeItem &item = E->value();
item.octree.set_split_scale(_lod_split_scale);
// Because `set_split_scale` may clamp it...
_lod_split_scale = item.octree.get_split_scale();
}
}
float VoxelLodTerrain::get_lod_split_scale() const {
return _lod_octree.get_split_scale();
return _lod_split_scale;
}
void VoxelLodTerrain::set_lod_count(int p_lod_count) {
ERR_FAIL_COND(p_lod_count >= MAX_LOD);
ERR_FAIL_COND(p_lod_count < 1);
if (get_lod_count() != p_lod_count) {
_set_lod_count(p_lod_count);
@ -274,15 +291,27 @@ void VoxelLodTerrain::set_lod_count(int p_lod_count) {
void VoxelLodTerrain::_set_lod_count(int p_lod_count) {
CRASH_COND(p_lod_count < 0 || p_lod_count >= MAX_LOD);
CRASH_COND(p_lod_count >= MAX_LOD);
CRASH_COND(p_lod_count < 1);
_lod_count = p_lod_count;
LodOctree<bool>::NoDestroyAction nda;
_lod_octree.create_from_lod_count(get_block_size(), p_lod_count, nda);
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
OctreeItem &item = E->value();
item.octree.create_from_lod_count(get_block_size(), p_lod_count, nda);
#ifdef TOOLS_ENABLED
destroy_octree_debug_box(item, E->key());
create_octree_debug_box(item, E->key());
#endif
}
reset_maps();
}
void VoxelLodTerrain::reset_maps() {
// Clears all blocks and reconfigures maps to account for new LOD count and block sizes
for (int lod_index = 0; lod_index < MAX_LOD; ++lod_index) {
@ -306,7 +335,7 @@ void VoxelLodTerrain::reset_maps() {
}
int VoxelLodTerrain::get_lod_count() const {
return _lod_octree.get_lod_count();
return _lod_count;
}
void VoxelLodTerrain::set_generate_collisions(bool enabled) {
@ -333,7 +362,7 @@ int VoxelLodTerrain::get_block_region_extent() const {
// This is the radius of blocks around the viewer in which we may load them.
// It depends on the LOD split scale, which tells how close to a block we need to be for it to subdivide.
// Each LOD is fractal so that value is the same for each of them.
return static_cast<int>(_lod_octree.get_split_scale()) * 2 + 2;
return static_cast<int>(_lod_split_scale) * 2 + 2;
}
Dictionary VoxelLodTerrain::get_block_info(Vector3 fbpos, unsigned int lod_index) const {
@ -547,12 +576,14 @@ void VoxelLodTerrain::_process() {
_stats.dropped_block_loads = 0;
_stats.dropped_block_meshs = 0;
_stats.blocked_lods = 0;
// Here we go...
// Remove blocks falling out of block region extent
// TODO Could it actually be enough to have a rolling update on all blocks?
{
// TODO Could it actually be enough to have a rolling update on all blocks?
// This should be the same distance relatively to each LOD
int block_region_extent = get_block_region_extent();
@ -607,73 +638,218 @@ void VoxelLodTerrain::_process() {
}
}
// Find which blocks we need to load and see
// Create and remove octrees in a grid around the viewer
{
struct SubdivideAction {
VoxelLodTerrain *self;
unsigned int blocked_count = 0;
// TODO Investigate if multi-octree can produce cracks in the terrain
// TODO Need to work when lod count changes at runtime
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
CRASH_COND(lod_index == 0);
unsigned int child_lod_index = lod_index - 1;
bool can = true;
// Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes
for (int i = 0; i < 8; ++i) {
Vector3i child_pos = LodOctree<bool>::get_child_position(node->position, i);
can &= self->check_block_loaded_and_updated(child_pos, child_lod_index);
unsigned int octree_size_po2 = get_block_size_pow2() + get_lod_count() - 1;
unsigned int octree_region_extent = 1 + _view_distance_voxels / (1 << octree_size_po2);
Vector3i viewer_octree_pos = VoxelMap::voxel_to_block_b(viewer_pos, octree_size_po2);
Rect3i new_box = Rect3i::from_center_extents(viewer_octree_pos, Vector3i(octree_region_extent));
Rect3i prev_box = Rect3i::from_center_extents(_last_viewer_octree_position, Vector3i(_last_octree_region_extent));
if (new_box != prev_box) {
struct CleanOctreeAction {
VoxelLodTerrain *self;
Vector3i block_offset_lod0;
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
Lod &lod = self->_lods[lod_index];
Vector3i bpos = node->position;
bpos += block_offset_lod0 >> lod_index;
VoxelBlock *block = lod.map->get_block(bpos);
if (block) {
block->set_visible(false);
}
}
if (!can) {
++blocked_count;
};
struct ExitAction {
VoxelLodTerrain *self;
void operator()(const Vector3i &pos) {
Map<Vector3i, OctreeItem>::Element *E = self->_lod_octrees.find(pos);
if (E == nullptr) {
return;
}
OctreeItem &item = E->value();
Vector3i block_pos_maxlod = E->key();
#ifdef TOOLS_ENABLED
self->destroy_octree_debug_box(item, block_pos_maxlod);
#endif
int last_lod_index = self->get_lod_count() - 1;
// We just drop the octree and hide blocks it was considering as visible.
// Normally such octrees shouldn't bee too deep as they will likely be at the edge
// of the loaded area, unless the player teleported far away.
CleanOctreeAction a;
a.self = self;
a.block_offset_lod0 = block_pos_maxlod << last_lod_index;
item.octree.clear(a);
// TODO Is it really needed? I think the clear() above has code doing that... and yet it looks like it doesnt
// Cleanup root as well
// Lod &lod = self->_lods[last_lod_index];
// VoxelBlock *block = lod.map->get_block(block_pos_maxlod);
// if (block) {
// block->set_visible(false);
// }
self->_lod_octrees.erase(E);
}
return can;
}
};
bool operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
Lod &lod = self->_lods[lod_index];
Vector3i bpos = node->position;
VoxelBlock *block = lod.map->get_block(bpos);
CRASH_COND(block == nullptr);
CRASH_COND(!block->has_been_meshed()); // Never show a block that hasn't been meshed
block->set_visible(true);
return true;
}
};
struct EnterAction {
VoxelLodTerrain *self;
int block_size;
void operator()(const Vector3i &pos) {
CRASH_COND(self->_lod_octrees.has(pos));
struct UnsubdivideAction {
VoxelLodTerrain *self;
unsigned int blocked_count = 0;
Map<Vector3i, OctreeItem>::Element *E = self->_lod_octrees.insert(pos, OctreeItem());
CRASH_COND(E == nullptr);
OctreeItem &item = E->value();
LodOctree<bool>::NoDestroyAction nda;
item.octree.create_from_lod_count(block_size, self->get_lod_count(), nda);
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
// Can only unsubdivide if the parent mesh is ready
const Vector3i &bpos = node->position;
bool can = self->check_block_loaded_and_updated(bpos, lod_index);
if (!can) {
++blocked_count;
#ifdef TOOLS_ENABLED
self->create_octree_debug_box(item, pos);
#endif
}
return can;
}
};
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
Lod &lod = self->_lods[lod_index];
const Vector3i &bpos = node->position;
VoxelBlock *block = lod.map->get_block(bpos);
if (block) {
block->set_visible(false);
ExitAction exit_action;
exit_action.self = this;
EnterAction enter_action;
enter_action.self = this;
enter_action.block_size = get_block_size();
Rect3i::check_enters_and_exits(prev_box, new_box, exit_action, enter_action);
}
_last_viewer_octree_position = viewer_octree_pos;
_last_octree_region_extent = octree_region_extent;
}
// Find which blocks we need to load and see, within each octree
{
// TODO Maintain a vector to make iteration faster?
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
OctreeItem &item = E->value();
Vector3i block_pos_maxlod = E->key();
Vector3i block_offset_lod0 = block_pos_maxlod << (get_lod_count() - 1);
struct SubdivideAction {
VoxelLodTerrain *self;
Vector3i block_offset_lod0;
unsigned int blocked_count = 0;
bool can_do_root(unsigned int lod_index) {
Vector3i offset = block_offset_lod0 >> lod_index;
return self->check_block_loaded_and_updated(offset, lod_index);
}
}
};
SubdivideAction subdivide_action;
subdivide_action.self = this;
bool can_do_children(LodOctree<bool>::Node *node, unsigned int child_lod_index) {
UnsubdivideAction unsubdivide_action;
unsubdivide_action.self = this;
Vector3i offset = block_offset_lod0 >> child_lod_index;
bool can = true;
_lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action);
// Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes
for (int i = 0; i < 8; ++i) {
// Ideally, this stat should stabilize to zero.
// If not, something in block management prevents LODs to properly show up and should be fixed.
_stats.blocked_lods = subdivide_action.blocked_count + unsubdivide_action.blocked_count;
// Get block pos local-to-region
Vector3i child_pos = LodOctree<bool>::get_child_position(node->position, i);
// Convert to local-to-terrain
child_pos += offset;
// We have to ping ALL children, because the reason we are here is we want them loaded
can &= self->check_block_loaded_and_updated(child_pos, child_lod_index);
}
if (!can) {
++blocked_count;
}
return can;
}
bool operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
Lod &lod = self->_lods[lod_index];
Vector3i bpos = node->position;
bpos += block_offset_lod0 >> lod_index;
VoxelBlock *block = lod.map->get_block(bpos);
CRASH_COND(block == nullptr);
CRASH_COND(!block->has_been_meshed()); // Never show a block that hasn't been meshed
block->set_visible(true);
return true;
}
};
struct UnsubdivideAction {
VoxelLodTerrain *self;
Vector3i block_offset_lod0;
unsigned int blocked_count = 0;
bool can_do(LodOctree<bool>::Node *node, unsigned int parent_lod_index) {
// Can only unsubdivide if the parent mesh is ready
Vector3i bpos = node->position;
bpos += block_offset_lod0 >> parent_lod_index;
bool can = self->check_block_loaded_and_updated(bpos, parent_lod_index);
if (!can) {
++blocked_count;
}
return can;
}
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
Lod &lod = self->_lods[lod_index];
Vector3i bpos = node->position;
bpos += block_offset_lod0 >> lod_index;
VoxelBlock *block = lod.map->get_block(bpos);
if (block) {
block->set_visible(false);
}
}
};
SubdivideAction subdivide_action;
subdivide_action.self = this;
subdivide_action.block_offset_lod0 = block_offset_lod0;
UnsubdivideAction unsubdivide_action;
unsubdivide_action.self = this;
unsubdivide_action.block_offset_lod0 = block_offset_lod0;
Vector3 relative_viewer_pos = viewer_pos - get_block_size() * block_offset_lod0.to_vec3();
item.octree.update(relative_viewer_pos, subdivide_action, unsubdivide_action);
// Ideally, this stat should stabilize to zero.
// If not, something in block management prevents LODs to properly show up and should be fixed.
_stats.blocked_lods += subdivide_action.blocked_count + unsubdivide_action.blocked_count;
}
}
_stats.time_detect_required_blocks = profiling_clock.restart();
@ -683,7 +859,11 @@ void VoxelLodTerrain::_process() {
VoxelDataLoader::Input input;
input.priority_position = viewer_block_pos;
input.priority_direction = viewer_direction;
input.use_exclusive_region = true;
// TODO Temporarily turned off, need to fix it because beyond a given LOD, it needs to be different!
//input.use_exclusive_region = true;
input.use_exclusive_region = false;
input.exclusive_region_extent = get_block_region_extent();
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
@ -773,7 +953,11 @@ void VoxelLodTerrain::_process() {
VoxelMeshUpdater::Input input;
input.priority_position = viewer_block_pos;
input.priority_direction = viewer_direction;
input.use_exclusive_region = true;
// TODO Temporarily turned off, need to fix it because beyond a given LOD, it needs to be different!
//input.use_exclusive_region = true;
input.use_exclusive_region = false;
input.exclusive_region_extent = get_block_region_extent();
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
@ -995,3 +1179,26 @@ void VoxelLodTerrain::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_lod_count"), "set_collision_lod_count", "get_collision_lod_count");
}
#ifdef TOOLS_ENABLED
void VoxelLodTerrain::create_octree_debug_box(OctreeItem &item, Vector3i pos) {
CRASH_COND(item.debug_box != nullptr);
MeshInstance *mi = memnew(MeshInstance);
mi->set_mesh(VoxelDebug::get_debug_box_mesh());
float s = 1 << (get_block_size_pow2() + get_lod_count() - 1);
mi->set_scale(Vector3(s, s, s));
mi->set_translation(pos.to_vec3() * s);
add_child(mi);
item.debug_box = mi;
}
void VoxelLodTerrain::destroy_octree_debug_box(OctreeItem &item, Vector3i pos) {
if (item.debug_box == nullptr) {
return;
}
item.debug_box->queue_delete();
item.debug_box = nullptr;
}
#endif

View File

@ -30,7 +30,7 @@ public:
void set_stream(Ref<VoxelStream> p_stream);
int get_view_distance() const;
void set_view_distance(int p_distance_in_voxels);
void set_view_distance(unsigned int p_distance_in_voxels);
void set_lod_split_scale(float p_lod_split_scale);
float get_lod_split_scale() const;
@ -106,9 +106,25 @@ private:
}
}
// TODO Dare having a grid of octrees for infinite world?
struct OctreeItem {
LodOctree<bool> octree;
#ifdef TOOLS_ENABLED
Spatial *debug_box = nullptr;
#endif
};
#ifdef TOOLS_ENABLED
void create_octree_debug_box(OctreeItem &item, Vector3i pos);
void destroy_octree_debug_box(OctreeItem &item, Vector3i pos);
#endif
// This terrain type is a sparse grid of octrees.
// Indexed by a grid coordinate whose step is the size of the highest-LOD block
// This octree doesn't hold any data... hence bool.
LodOctree<bool> _lod_octree;
Map<Vector3i, OctreeItem> _lod_octrees;
// In which octree the viewer was on last frame
Vector3i _last_viewer_octree_position;
unsigned int _last_octree_region_extent = 0;
NodePath _viewer_path;
@ -142,6 +158,9 @@ private:
};
Lod _lods[MAX_LOD];
int _lod_count = 0;
float _lod_split_scale = 0.f;
unsigned int _view_distance_voxels = 512;
Stats _stats;
};

54
util/utility.cpp Normal file
View File

@ -0,0 +1,54 @@
#if TOOLS_ENABLED
#include "utility.h"
#include "../cube_tables.h"
namespace VoxelDebug {
Ref<Mesh> g_debug_box_mesh;
void create_debug_box_mesh() {
PoolVector3Array positions;
positions.resize(8);
{
PoolVector3Array::Write w = positions.write();
for (int i = 0; i < positions.size(); ++i) {
w[i] = Cube::g_corner_position[i];
}
}
PoolIntArray indices;
indices.resize(Cube::EDGE_COUNT * 2);
{
PoolIntArray::Write w = indices.write();
int j = 0;
for (int i = 0; i < Cube::EDGE_COUNT; ++i) {
w[j++] = Cube::g_edge_corners[i][0];
w[j++] = Cube::g_edge_corners[i][1];
}
}
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = positions;
arrays[Mesh::ARRAY_INDEX] = indices;
Ref<ArrayMesh> mesh;
mesh.instance();
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arrays);
Ref<SpatialMaterial> mat;
mat.instance();
mat->set_albedo(Color(0, 1, 0));
mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
mesh->surface_set_material(0, mat);
g_debug_box_mesh = mesh;
}
void free_debug_box_mesh() {
g_debug_box_mesh.unref();
}
Ref<Mesh> get_debug_box_mesh() {
return g_debug_box_mesh;
}
} // namespace VoxelDebug
#endif

View File

@ -126,4 +126,12 @@ inline int wrap(int x, int d) {
return ((x % d) + d) % d;
}
#if TOOLS_ENABLED
namespace VoxelDebug {
void create_debug_box_mesh();
void free_debug_box_mesh();
Ref<Mesh> get_debug_box_mesh();
}
#endif
#endif // HEADER_VOXEL_UTILITY_H