First WIP at paging LOD
This commit is contained in:
parent
db398a96e2
commit
eb01f99c2c
@ -60,7 +60,7 @@ public:
|
|||||||
return String("(o:{0}, s:{1})").format(varray(pos.to_vec3(), size.to_vec3()));
|
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)
|
if (pos.x > other.pos.x + other.size.x)
|
||||||
return false;
|
return false;
|
||||||
if (pos.y > other.pos.y + other.size.y)
|
if (pos.y > other.pos.y + other.size.y)
|
||||||
@ -75,6 +75,58 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
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) {
|
inline bool operator!=(const Rect3i &a, const Rect3i &b) {
|
||||||
|
@ -48,8 +48,15 @@ void register_voxel_types() {
|
|||||||
ClassDB::register_class<VoxelMesherBlocky>();
|
ClassDB::register_class<VoxelMesherBlocky>();
|
||||||
ClassDB::register_class<VoxelMesherTransvoxel>();
|
ClassDB::register_class<VoxelMesherTransvoxel>();
|
||||||
ClassDB::register_class<VoxelMesherDMC>();
|
ClassDB::register_class<VoxelMesherDMC>();
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
VoxelDebug::create_debug_box_mesh();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void unregister_voxel_types() {
|
void unregister_voxel_types() {
|
||||||
// lol
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
VoxelDebug::free_debug_box_mesh();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ public:
|
|||||||
// Specialization must be copyable
|
// Specialization must be copyable
|
||||||
struct InputBlock {
|
struct InputBlock {
|
||||||
InputBlockData_T data;
|
InputBlockData_T data;
|
||||||
Vector3i position; // In LOD0 block coordinates
|
Vector3i position; // In LOD-relative block coordinates
|
||||||
uint8_t lod = 0;
|
uint8_t lod = 0;
|
||||||
bool can_be_discarded = true; // If false, will always be processed, even if the thread is told to exit
|
bool can_be_discarded = true; // If false, will always be processed, even if the thread is told to exit
|
||||||
float sort_heuristic = 0;
|
float sort_heuristic = 0;
|
||||||
@ -38,7 +38,7 @@ public:
|
|||||||
// Specialization must be copyable
|
// Specialization must be copyable
|
||||||
struct OutputBlock {
|
struct OutputBlock {
|
||||||
OutputBlockData_T data;
|
OutputBlockData_T data;
|
||||||
Vector3i position; // In LOD0 block coordinates
|
Vector3i position; // In LOD-relative block coordinates
|
||||||
unsigned int lod = 0;
|
unsigned int lod = 0;
|
||||||
// True if the block was actually dropped.
|
// True if the block was actually dropped.
|
||||||
// Ideally the requester will agree that it doesn't need that block anymore,
|
// Ideally the requester will agree that it doesn't need that block anymore,
|
||||||
|
@ -96,7 +96,19 @@ public:
|
|||||||
|
|
||||||
template <typename A, typename B>
|
template <typename A, typename B>
|
||||||
void update(Vector3 view_pos, A &create_action, B &destroy_action) {
|
void update(Vector3 view_pos, A &create_action, B &destroy_action) {
|
||||||
|
|
||||||
|
if (_root.block || _root.has_children()) {
|
||||||
update(&_root, _max_depth, view_pos, create_action, destroy_action);
|
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>
|
template <typename A>
|
||||||
@ -134,13 +146,14 @@ private:
|
|||||||
if (!node->has_children()) {
|
if (!node->has_children()) {
|
||||||
|
|
||||||
// If it's not the last LOD, if close enough and custom conditions get fulfilled
|
// 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
|
// Split
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
|
||||||
Node *child = _pool.create();
|
Node *child = _pool.create();
|
||||||
|
|
||||||
child->position = get_child_position(node->position, i);
|
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);
|
child->block = create_action(child, lod - 1);
|
||||||
|
|
||||||
node->children[i] = child;
|
node->children[i] = child;
|
||||||
@ -150,6 +163,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node->block) {
|
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);
|
destroy_action(node, lod);
|
||||||
node->block = T();
|
node->block = T();
|
||||||
}
|
}
|
||||||
@ -157,20 +171,21 @@ private:
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bool no_split_child = true;
|
bool has_split_child = false;
|
||||||
|
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
Node *child = node->children[i];
|
Node *child = node->children[i];
|
||||||
update(child, lod - 1, view_pos, create_action, destroy_action);
|
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
|
// Join
|
||||||
if (node->has_children()) {
|
if (node->has_children()) {
|
||||||
|
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
Node *child = node->children[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);
|
destroy_action(child, lod - 1);
|
||||||
child->block = T();
|
child->block = T();
|
||||||
_pool.recycle(child);
|
_pool.recycle(child);
|
||||||
@ -178,7 +193,10 @@ private:
|
|||||||
|
|
||||||
node->children[0] = nullptr;
|
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);
|
CRASH_COND(node->block);
|
||||||
|
|
||||||
node->block = create_action(node, lod);
|
node->block = create_action(node, lod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,6 +219,7 @@ private:
|
|||||||
} else {
|
} else {
|
||||||
if (node->block) {
|
if (node->block) {
|
||||||
destroy_action(node, lod);
|
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();
|
node->block = T();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,6 +229,7 @@ private:
|
|||||||
int _max_depth = 0;
|
int _max_depth = 0;
|
||||||
float _base_size = 16;
|
float _base_size = 16;
|
||||||
float _split_scale = 2.0;
|
float _split_scale = 2.0;
|
||||||
|
// TODO May be worth making this pool external for sharing purpose
|
||||||
ObjectPool<Node> _pool;
|
ObjectPool<Node> _pool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
#include <core/core_string_names.h>
|
#include <core/core_string_names.h>
|
||||||
#include <core/engine.h>
|
#include <core/engine.h>
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
#include <scene/3d/mesh_instance.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
const uint32_t MAIN_THREAD_MESHING_BUDGET_MS = 8;
|
const uint32_t MAIN_THREAD_MESHING_BUDGET_MS = 8;
|
||||||
|
|
||||||
VoxelLodTerrain::VoxelLodTerrain() {
|
VoxelLodTerrain::VoxelLodTerrain() {
|
||||||
@ -18,7 +22,7 @@ VoxelLodTerrain::VoxelLodTerrain() {
|
|||||||
|
|
||||||
_lods[0].map.instance();
|
_lods[0].map.instance();
|
||||||
|
|
||||||
set_lod_count(8);
|
set_lod_count(4);
|
||||||
set_lod_split_scale(3);
|
set_lod_split_scale(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,8 +129,8 @@ void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
|
|||||||
stop_streamer();
|
stop_streamer();
|
||||||
stop_updater();
|
stop_updater();
|
||||||
|
|
||||||
reset_maps();
|
|
||||||
_set_block_size_po2(p_block_size_po2);
|
_set_block_size_po2(p_block_size_po2);
|
||||||
|
reset_maps();
|
||||||
|
|
||||||
if (_stream.is_valid()) {
|
if (_stream.is_valid()) {
|
||||||
start_streamer();
|
start_streamer();
|
||||||
@ -151,16 +155,14 @@ int VoxelLodTerrain::get_view_distance() const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelLodTerrain::set_view_distance(int p_distance_in_voxels) {
|
void VoxelLodTerrain::set_view_distance(unsigned int p_distance_in_voxels) {
|
||||||
// TODO this will be used to cap mesh visibility
|
|
||||||
|
|
||||||
// ERR_FAIL_COND(p_distance_in_voxels < 0)
|
ERR_FAIL_COND(p_distance_in_voxels == 0);
|
||||||
// int d = p_distance_in_voxels / get_block_size();
|
ERR_FAIL_COND(p_distance_in_voxels > 8192);
|
||||||
// if (d != _view_distance_blocks) {
|
|
||||||
// print_line(String("View distance changed from ") + String::num(_view_distance_blocks) + String(" blocks to ") + String::num(d));
|
// Note: this is a hint distance, the terrain will attempt to have this radius filled with loaded voxels.
|
||||||
// _view_distance_blocks = d;
|
// It is possible for blocks to still load beyond that distance.
|
||||||
// // Blocks too far away will be removed in _process, same for blocks to load
|
_view_distance_voxels = p_distance_in_voxels;
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spatial *VoxelLodTerrain::get_viewer() const {
|
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];
|
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.map->remove_block(block_pos, VoxelMap::NoAction());
|
||||||
|
|
||||||
lod.loading_blocks.erase(block_pos);
|
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) {
|
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 {
|
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) {
|
void VoxelLodTerrain::set_lod_count(int p_lod_count) {
|
||||||
|
|
||||||
ERR_FAIL_COND(p_lod_count >= MAX_LOD);
|
ERR_FAIL_COND(p_lod_count >= MAX_LOD);
|
||||||
|
ERR_FAIL_COND(p_lod_count < 1);
|
||||||
|
|
||||||
if (get_lod_count() != p_lod_count) {
|
if (get_lod_count() != p_lod_count) {
|
||||||
_set_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) {
|
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;
|
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();
|
reset_maps();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelLodTerrain::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) {
|
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 {
|
int VoxelLodTerrain::get_lod_count() const {
|
||||||
return _lod_octree.get_lod_count();
|
return _lod_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelLodTerrain::set_generate_collisions(bool enabled) {
|
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.
|
// 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.
|
// 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.
|
// 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 {
|
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_loads = 0;
|
||||||
_stats.dropped_block_meshs = 0;
|
_stats.dropped_block_meshs = 0;
|
||||||
|
_stats.blocked_lods = 0;
|
||||||
|
|
||||||
// Here we go...
|
// Here we go...
|
||||||
|
|
||||||
// Remove blocks falling out of block region extent
|
// 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
|
// This should be the same distance relatively to each LOD
|
||||||
int block_region_extent = get_block_region_extent();
|
int block_region_extent = get_block_region_extent();
|
||||||
|
|
||||||
@ -607,33 +638,164 @@ void VoxelLodTerrain::_process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find which blocks we need to load and see
|
// Create and remove octrees in a grid around the viewer
|
||||||
{
|
{
|
||||||
|
// TODO Investigate if multi-octree can produce cracks in the terrain
|
||||||
|
// TODO Need to work when lod count changes at runtime
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnterAction {
|
||||||
|
VoxelLodTerrain *self;
|
||||||
|
int block_size;
|
||||||
|
void operator()(const Vector3i &pos) {
|
||||||
|
CRASH_COND(self->_lod_octrees.has(pos));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
self->create_octree_debug_box(item, pos);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
struct SubdivideAction {
|
||||||
VoxelLodTerrain *self;
|
VoxelLodTerrain *self;
|
||||||
|
Vector3i block_offset_lod0;
|
||||||
unsigned int blocked_count = 0;
|
unsigned int blocked_count = 0;
|
||||||
|
|
||||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
bool can_do_root(unsigned int lod_index) {
|
||||||
CRASH_COND(lod_index == 0);
|
Vector3i offset = block_offset_lod0 >> lod_index;
|
||||||
unsigned int child_lod_index = lod_index - 1;
|
return self->check_block_loaded_and_updated(offset, lod_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_do_children(LodOctree<bool>::Node *node, unsigned int child_lod_index) {
|
||||||
|
|
||||||
|
Vector3i offset = block_offset_lod0 >> child_lod_index;
|
||||||
bool can = true;
|
bool can = true;
|
||||||
|
|
||||||
// Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes
|
// Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
|
||||||
|
// Get block pos local-to-region
|
||||||
Vector3i child_pos = LodOctree<bool>::get_child_position(node->position, i);
|
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);
|
can &= self->check_block_loaded_and_updated(child_pos, child_lod_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!can) {
|
if (!can) {
|
||||||
++blocked_count;
|
++blocked_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return can;
|
return can;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
bool operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||||
|
|
||||||
Lod &lod = self->_lods[lod_index];
|
Lod &lod = self->_lods[lod_index];
|
||||||
|
|
||||||
Vector3i bpos = node->position;
|
Vector3i bpos = node->position;
|
||||||
|
bpos += block_offset_lod0 >> lod_index;
|
||||||
|
|
||||||
VoxelBlock *block = lod.map->get_block(bpos);
|
VoxelBlock *block = lod.map->get_block(bpos);
|
||||||
|
|
||||||
CRASH_COND(block == nullptr);
|
CRASH_COND(block == nullptr);
|
||||||
CRASH_COND(!block->has_been_meshed()); // Never show a block that hasn't been meshed
|
CRASH_COND(!block->has_been_meshed()); // Never show a block that hasn't been meshed
|
||||||
|
|
||||||
block->set_visible(true);
|
block->set_visible(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -641,21 +803,31 @@ void VoxelLodTerrain::_process() {
|
|||||||
|
|
||||||
struct UnsubdivideAction {
|
struct UnsubdivideAction {
|
||||||
VoxelLodTerrain *self;
|
VoxelLodTerrain *self;
|
||||||
|
Vector3i block_offset_lod0;
|
||||||
unsigned int blocked_count = 0;
|
unsigned int blocked_count = 0;
|
||||||
|
|
||||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
bool can_do(LodOctree<bool>::Node *node, unsigned int parent_lod_index) {
|
||||||
|
|
||||||
// Can only unsubdivide if the parent mesh is ready
|
// Can only unsubdivide if the parent mesh is ready
|
||||||
const Vector3i &bpos = node->position;
|
Vector3i bpos = node->position;
|
||||||
bool can = self->check_block_loaded_and_updated(bpos, lod_index);
|
bpos += block_offset_lod0 >> parent_lod_index;
|
||||||
|
|
||||||
|
bool can = self->check_block_loaded_and_updated(bpos, parent_lod_index);
|
||||||
|
|
||||||
if (!can) {
|
if (!can) {
|
||||||
++blocked_count;
|
++blocked_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return can;
|
return can;
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||||
|
|
||||||
Lod &lod = self->_lods[lod_index];
|
Lod &lod = self->_lods[lod_index];
|
||||||
const Vector3i &bpos = node->position;
|
|
||||||
|
Vector3i bpos = node->position;
|
||||||
|
bpos += block_offset_lod0 >> lod_index;
|
||||||
|
|
||||||
VoxelBlock *block = lod.map->get_block(bpos);
|
VoxelBlock *block = lod.map->get_block(bpos);
|
||||||
if (block) {
|
if (block) {
|
||||||
block->set_visible(false);
|
block->set_visible(false);
|
||||||
@ -665,15 +837,19 @@ void VoxelLodTerrain::_process() {
|
|||||||
|
|
||||||
SubdivideAction subdivide_action;
|
SubdivideAction subdivide_action;
|
||||||
subdivide_action.self = this;
|
subdivide_action.self = this;
|
||||||
|
subdivide_action.block_offset_lod0 = block_offset_lod0;
|
||||||
|
|
||||||
UnsubdivideAction unsubdivide_action;
|
UnsubdivideAction unsubdivide_action;
|
||||||
unsubdivide_action.self = this;
|
unsubdivide_action.self = this;
|
||||||
|
unsubdivide_action.block_offset_lod0 = block_offset_lod0;
|
||||||
|
|
||||||
_lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action);
|
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.
|
// Ideally, this stat should stabilize to zero.
|
||||||
// If not, something in block management prevents LODs to properly show up and should be fixed.
|
// 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.blocked_lods += subdivide_action.blocked_count + unsubdivide_action.blocked_count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_stats.time_detect_required_blocks = profiling_clock.restart();
|
_stats.time_detect_required_blocks = profiling_clock.restart();
|
||||||
@ -683,7 +859,11 @@ void VoxelLodTerrain::_process() {
|
|||||||
VoxelDataLoader::Input input;
|
VoxelDataLoader::Input input;
|
||||||
input.priority_position = viewer_block_pos;
|
input.priority_position = viewer_block_pos;
|
||||||
input.priority_direction = viewer_direction;
|
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();
|
input.exclusive_region_extent = get_block_region_extent();
|
||||||
|
|
||||||
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
||||||
@ -773,7 +953,11 @@ void VoxelLodTerrain::_process() {
|
|||||||
VoxelMeshUpdater::Input input;
|
VoxelMeshUpdater::Input input;
|
||||||
input.priority_position = viewer_block_pos;
|
input.priority_position = viewer_block_pos;
|
||||||
input.priority_direction = viewer_direction;
|
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();
|
input.exclusive_region_extent = get_block_region_extent();
|
||||||
|
|
||||||
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
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::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");
|
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
|
||||||
|
@ -30,7 +30,7 @@ public:
|
|||||||
void set_stream(Ref<VoxelStream> p_stream);
|
void set_stream(Ref<VoxelStream> p_stream);
|
||||||
|
|
||||||
int get_view_distance() const;
|
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);
|
void set_lod_split_scale(float p_lod_split_scale);
|
||||||
float get_lod_split_scale() const;
|
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.
|
// 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;
|
NodePath _viewer_path;
|
||||||
|
|
||||||
@ -142,6 +158,9 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Lod _lods[MAX_LOD];
|
Lod _lods[MAX_LOD];
|
||||||
|
int _lod_count = 0;
|
||||||
|
float _lod_split_scale = 0.f;
|
||||||
|
unsigned int _view_distance_voxels = 512;
|
||||||
|
|
||||||
Stats _stats;
|
Stats _stats;
|
||||||
};
|
};
|
||||||
|
54
util/utility.cpp
Normal file
54
util/utility.cpp
Normal 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
|
@ -126,4 +126,12 @@ inline int wrap(int x, int d) {
|
|||||||
return ((x % d) + d) % 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
|
#endif // HEADER_VOXEL_UTILITY_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user