mirror of
https://github.com/Relintai/godot_voxel.git
synced 2025-02-10 21:00:08 +01:00
Load blocks on demand instead of speculatively, it's faster
This commit is contained in:
parent
035e45d0a3
commit
73d5d9993c
@ -164,8 +164,6 @@ void VoxelLodTerrain::set_lod_count(unsigned int p_lod_count) {
|
||||
|
||||
Lod &lod = _lods[lod_index];
|
||||
|
||||
lod.blocks_in_meshing_area.clear();
|
||||
|
||||
// Instance new maps if we have more lods, or clear them otherwise
|
||||
if (lod_index < get_lod_count()) {
|
||||
|
||||
@ -225,8 +223,6 @@ Dictionary VoxelLodTerrain::get_block_info(Vector3 fbpos, unsigned int lod_index
|
||||
}
|
||||
}
|
||||
|
||||
bool in_show_area = lod.blocks_in_meshing_area.has(bpos);
|
||||
|
||||
bool meshed = false;
|
||||
bool visible = false;
|
||||
const VoxelBlock *block = lod.map->get_block(bpos);
|
||||
@ -236,7 +232,6 @@ Dictionary VoxelLodTerrain::get_block_info(Vector3 fbpos, unsigned int lod_index
|
||||
}
|
||||
|
||||
d["state"] = state;
|
||||
d["in_show_area"] = in_show_area;
|
||||
d["meshed"] = meshed;
|
||||
d["visible"] = visible;
|
||||
return d;
|
||||
@ -329,41 +324,6 @@ Vector3 VoxelLodTerrain::get_viewer_pos() const {
|
||||
return Vector3();
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::make_block_dirty(Vector3i bpos, unsigned int lod_index) {
|
||||
|
||||
CRASH_COND(lod_index >= get_lod_count());
|
||||
|
||||
// TODO Immediate update viewer distance?
|
||||
|
||||
Lod &lod = _lods[lod_index];
|
||||
|
||||
Map<Vector3i, BlockState>::Element *state = lod.block_states.find(bpos);
|
||||
|
||||
if (state == NULL) {
|
||||
// The block is not dirty, so it will either be loaded or updated
|
||||
|
||||
if (lod.map->has_block(bpos)) {
|
||||
|
||||
lod.blocks_pending_update.push_back(bpos);
|
||||
lod.block_states.insert(bpos, BLOCK_UPDATE_NOT_SENT);
|
||||
|
||||
} else {
|
||||
|
||||
lod.blocks_to_load.push_back(bpos);
|
||||
lod.block_states.insert(bpos, BLOCK_LOAD);
|
||||
}
|
||||
|
||||
} else if (state->value() == BLOCK_UPDATE_SENT) {
|
||||
// The updater is already processing the block,
|
||||
// but the block was modified again so we schedule another update
|
||||
state->value() = BLOCK_UPDATE_NOT_SENT;
|
||||
lod.blocks_pending_update.push_back(bpos);
|
||||
|
||||
} else if (state->value() == BLOCK_UPDATE_NOT_SENT) {
|
||||
WARN_PRINT("Block already marked as BLOCK_UPDATE_NOT_SENT");
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_positions_outside_box(
|
||||
std::vector<Vector3i> &positions,
|
||||
Rect3i box,
|
||||
@ -381,6 +341,34 @@ static void remove_positions_outside_box(
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::load_block_and_neighbors(const Vector3i &p_bpos, unsigned int lod_index) {
|
||||
CRASH_COND(lod_index >= get_lod_count());
|
||||
Lod &lod = _lods[lod_index];
|
||||
|
||||
Vector3i bpos;
|
||||
|
||||
for (int y = -1; y < 2; ++y) {
|
||||
for (int z = -1; z < 2; ++z) {
|
||||
for (int x = -1; x < 2; ++x) {
|
||||
|
||||
bpos.x = p_bpos.x + x;
|
||||
bpos.y = p_bpos.y + y;
|
||||
bpos.z = p_bpos.z + z;
|
||||
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
|
||||
if (block == nullptr) {
|
||||
Map<Vector3i, BlockState>::Element *E = lod.block_states.find(bpos);
|
||||
if (E == nullptr) {
|
||||
lod.blocks_to_load.push_back(bpos);
|
||||
lod.block_states.insert(bpos, BLOCK_LOAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::_process() {
|
||||
|
||||
if (get_lod_count() == 0) {
|
||||
@ -397,10 +385,10 @@ void VoxelLodTerrain::_process() {
|
||||
|
||||
// Here we go...
|
||||
|
||||
// Find out which blocks _data_ need to be loaded
|
||||
// Remove blocks falling out of block region extent
|
||||
{
|
||||
// This should be the same distance relatively to each LOD
|
||||
int view_distance_blocks_within_lod = get_block_region_extent();
|
||||
int block_region_extent = get_block_region_extent();
|
||||
|
||||
for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
||||
Lod &lod = _lods[lod_index];
|
||||
@ -411,13 +399,8 @@ void VoxelLodTerrain::_process() {
|
||||
int block_size_po2 = _lods[0].map->get_block_size_pow2() + lod_index;
|
||||
Vector3i viewer_block_pos_within_lod = VoxelMap::voxel_to_block_b(viewer_pos, block_size_po2);
|
||||
|
||||
Rect3i new_box = Rect3i::from_center_extents(
|
||||
viewer_block_pos_within_lod,
|
||||
Vector3i(view_distance_blocks_within_lod));
|
||||
|
||||
Rect3i prev_box = Rect3i::from_center_extents(
|
||||
lod.last_viewer_block_pos,
|
||||
Vector3i(lod.last_view_distance_blocks));
|
||||
Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos_within_lod, Vector3i(block_region_extent));
|
||||
Rect3i prev_box = Rect3i::from_center_extents(lod.last_viewer_block_pos, Vector3i(lod.last_view_distance_blocks));
|
||||
|
||||
// Eliminate pending blocks that aren't needed
|
||||
remove_positions_outside_box(lod.blocks_to_load, new_box, lod.block_states);
|
||||
@ -442,10 +425,6 @@ void VoxelLodTerrain::_process() {
|
||||
if (prev_contains && !new_contains) {
|
||||
// Unload block
|
||||
immerge_block(pos, lod_index);
|
||||
|
||||
} else if (!prev_contains && new_contains) {
|
||||
// Load or update block
|
||||
make_block_dirty(pos, lod_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,10 +432,105 @@ void VoxelLodTerrain::_process() {
|
||||
}
|
||||
|
||||
lod.last_viewer_block_pos = viewer_block_pos_within_lod;
|
||||
lod.last_view_distance_blocks = view_distance_blocks_within_lod;
|
||||
lod.last_view_distance_blocks = block_region_extent;
|
||||
}
|
||||
}
|
||||
|
||||
// Find which blocks we need to load and see
|
||||
{
|
||||
struct SubdivideAction {
|
||||
VoxelLodTerrain *self;
|
||||
int blocked_count = 0;
|
||||
|
||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
CRASH_COND(lod_index == 0);
|
||||
|
||||
unsigned int child_lod_index = lod_index - 1;
|
||||
Lod &lod = self->_lods[child_lod_index];
|
||||
|
||||
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);
|
||||
VoxelBlock *block = lod.map->get_block(child_pos);
|
||||
|
||||
if (block == nullptr) {
|
||||
can = false;
|
||||
self->load_block_and_neighbors(child_pos, child_lod_index);
|
||||
|
||||
} else if (!block->has_been_meshed) {
|
||||
can = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
CRASH_COND(block == nullptr);
|
||||
block->set_visible(true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct UnsubdivideAction {
|
||||
VoxelLodTerrain *self;
|
||||
int blocked_count = 0;
|
||||
|
||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
// Can only unsubdivide if the parent mesh is ready
|
||||
Lod &lod = self->_lods[lod_index];
|
||||
const Vector3i &bpos = node->position;
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
|
||||
bool can = true;
|
||||
|
||||
if (block == nullptr) {
|
||||
can = false;
|
||||
self->load_block_and_neighbors(bpos, lod_index);
|
||||
|
||||
} else if (!block->has_been_meshed) {
|
||||
can = false;
|
||||
}
|
||||
|
||||
if (!can) {
|
||||
++blocked_count;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SubdivideAction subdivide_action;
|
||||
subdivide_action.self = this;
|
||||
|
||||
UnsubdivideAction unsubdivide_action;
|
||||
unsubdivide_action.self = this;
|
||||
|
||||
_lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action);
|
||||
|
||||
// Ideally, this stat should stabilize to zero.
|
||||
// If not, something in the meshing process prevents LODs to properly show up and should be fixed.
|
||||
_stats.blocked_lods = subdivide_action.blocked_count + unsubdivide_action.blocked_count;
|
||||
}
|
||||
|
||||
// Send block loading requests
|
||||
{
|
||||
VoxelProviderThread::InputData input;
|
||||
@ -476,6 +550,7 @@ void VoxelLodTerrain::_process() {
|
||||
lod.blocks_to_load.clear();
|
||||
}
|
||||
|
||||
//print_line(String("Sending {0}").format(varray(input.blocks_to_emerge.size())));
|
||||
_provider_thread->push(input);
|
||||
}
|
||||
|
||||
@ -489,6 +564,8 @@ void VoxelLodTerrain::_process() {
|
||||
_provider_thread->pop(output);
|
||||
_stats.provider = output.stats;
|
||||
|
||||
//print_line(String("Loaded {0} blocks").format(varray(output.emerged_blocks.size())));
|
||||
|
||||
for (int i = 0; i < output.emerged_blocks.size(); ++i) {
|
||||
|
||||
const VoxelProviderThread::EmergeOutput &eo = output.emerged_blocks[i];
|
||||
@ -514,7 +591,10 @@ void VoxelLodTerrain::_process() {
|
||||
|
||||
// Store buffer
|
||||
bool check_neighbors = !lod.map->has_block(eo.block_position);
|
||||
lod.map->set_block_buffer(eo.block_position, eo.voxels);
|
||||
VoxelBlock *block = lod.map->set_block_buffer(eo.block_position, eo.voxels);
|
||||
//print_line(String("Adding block {0} at lod {1}").format(varray(eo.block_position.to_vec3(), eo.lod)));
|
||||
// The block will be made visible only by LodOctree
|
||||
block->set_visible(false);
|
||||
|
||||
lod.block_states.erase(state);
|
||||
|
||||
@ -602,6 +682,7 @@ void VoxelLodTerrain::_process() {
|
||||
lod.blocks_pending_update.clear();
|
||||
}
|
||||
|
||||
//print_line(String("Sending {0} updates").format(varray(input.blocks.size())));
|
||||
_block_updater->push(input);
|
||||
}
|
||||
|
||||
@ -680,7 +761,6 @@ void VoxelLodTerrain::_process() {
|
||||
}
|
||||
|
||||
block->set_mesh(mesh, world);
|
||||
block->set_visible(lod.blocks_in_meshing_area.has(ob.position));
|
||||
block->has_been_meshed = true;
|
||||
}
|
||||
|
||||
@ -689,89 +769,6 @@ void VoxelLodTerrain::_process() {
|
||||
|
||||
_stats.time_process_update_responses = profiling_clock.restart();
|
||||
|
||||
// Find out which blocks need to be shown
|
||||
{
|
||||
struct SubdivideAction {
|
||||
VoxelLodTerrain *self;
|
||||
int non_meshed_blocks = 0;
|
||||
|
||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
CRASH_COND(lod_index == 0);
|
||||
|
||||
unsigned int child_lod_index = lod_index - 1;
|
||||
Lod &lod = self->_lods[child_lod_index];
|
||||
|
||||
// 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);
|
||||
VoxelBlock *block = lod.map->get_block(child_pos);
|
||||
if (block == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!block->has_been_meshed) {
|
||||
++non_meshed_blocks;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
Lod &lod = self->_lods[lod_index];
|
||||
Vector3i bpos = node->position;
|
||||
// TODO Meshing area is useless because now we prevent the octree from changing if blocks aren't available
|
||||
lod.blocks_in_meshing_area.insert(bpos);
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
CRASH_COND(block == nullptr);
|
||||
block->set_visible(true);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct UnsubdivideAction {
|
||||
VoxelLodTerrain *self;
|
||||
int non_meshed_blocks = 0;
|
||||
|
||||
bool can_do(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
// Can only unsubdivide if the parent mesh is ready
|
||||
Lod &lod = self->_lods[lod_index];
|
||||
VoxelBlock *block = lod.map->get_block(node->position);
|
||||
if (block == nullptr) {
|
||||
// Ok, that block got unloaded? Might happen if you teleport away
|
||||
return true;
|
||||
}
|
||||
if (block->has_been_meshed) {
|
||||
return true;
|
||||
} else {
|
||||
++non_meshed_blocks;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(LodOctree<bool>::Node *node, unsigned int lod_index) {
|
||||
Lod &lod = self->_lods[lod_index];
|
||||
Vector3i bpos = node->position;
|
||||
lod.blocks_in_meshing_area.erase(bpos);
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
if (block) {
|
||||
block->set_visible(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SubdivideAction subdivide_action;
|
||||
subdivide_action.self = this;
|
||||
|
||||
UnsubdivideAction unsubdivide_action;
|
||||
unsubdivide_action.self = this;
|
||||
|
||||
_lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action);
|
||||
|
||||
// Ideally, this stat should stabilize to zero.
|
||||
// If not, something in the meshing process prevents LODs to properly show up and should be fixed.
|
||||
_stats.blocked_lods = subdivide_action.non_meshed_blocks + unsubdivide_action.non_meshed_blocks;
|
||||
}
|
||||
|
||||
_stats.time_process_lod = profiling_clock.restart();
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ private:
|
||||
void immerge_block(Vector3i block_pos, unsigned int lod_index);
|
||||
void reset_updater();
|
||||
Vector3 get_viewer_pos() const;
|
||||
void make_block_dirty(Vector3i bpos, unsigned int lod_index);
|
||||
void load_block_and_neighbors(const Vector3i &p_bpos, unsigned int lod_index);
|
||||
|
||||
template <typename A>
|
||||
void for_all_blocks(A &action) {
|
||||
@ -111,10 +111,6 @@ private:
|
||||
Map<Vector3i, BlockState> block_states;
|
||||
std::vector<Vector3i> blocks_pending_update;
|
||||
|
||||
// Reflects LodOctree but only in this LOD
|
||||
// TODO Would be nice to use LodOctree directly!
|
||||
Set<Vector3i> blocks_in_meshing_area;
|
||||
|
||||
// These are relative to this LOD, in block coordinates
|
||||
Vector3i last_viewer_block_pos;
|
||||
int last_view_distance_blocks = 0;
|
||||
|
@ -138,8 +138,13 @@ void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) {
|
||||
_blocks.set(bpos, block);
|
||||
}
|
||||
|
||||
void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
|
||||
ERR_FAIL_COND(buffer.is_null());
|
||||
void VoxelMap::remove_block_internal(Vector3i bpos) {
|
||||
// This function assumes the block is already freed
|
||||
_blocks.erase(bpos);
|
||||
}
|
||||
|
||||
VoxelBlock *VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
|
||||
ERR_FAIL_COND_V(buffer.is_null(), nullptr);
|
||||
VoxelBlock *block = get_block(bpos);
|
||||
if (block == NULL) {
|
||||
block = VoxelBlock::create(bpos, *buffer, _block_size, _lod_index);
|
||||
@ -147,6 +152,7 @@ void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
|
||||
} else {
|
||||
block->voxels = buffer;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
bool VoxelMap::has_block(Vector3i pos) const {
|
||||
@ -224,6 +230,10 @@ void VoxelMap::clear() {
|
||||
_last_accessed_block = NULL;
|
||||
}
|
||||
|
||||
int VoxelMap::get_block_count() const {
|
||||
return _blocks.size();
|
||||
}
|
||||
|
||||
void VoxelMap::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0));
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
void get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask = 1);
|
||||
|
||||
// Moves the given buffer into a block of the map. The buffer is referenced, no copy is made.
|
||||
void set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
|
||||
VoxelBlock *set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
|
||||
|
||||
struct NoAction {
|
||||
inline void operator()(VoxelBlock *block) {}
|
||||
@ -76,7 +76,7 @@ public:
|
||||
ERR_FAIL_COND(block == NULL);
|
||||
pre_delete(block);
|
||||
memdelete(block);
|
||||
_blocks.erase(bpos);
|
||||
remove_block_internal(bpos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +118,8 @@ public:
|
||||
|
||||
void clear();
|
||||
|
||||
int get_block_count() const;
|
||||
|
||||
template <typename Op_T>
|
||||
void for_all_blocks(Op_T op) {
|
||||
const Vector3i *key = NULL;
|
||||
@ -132,6 +134,7 @@ public:
|
||||
private:
|
||||
void set_block(Vector3i bpos, VoxelBlock *block);
|
||||
VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
|
||||
void remove_block_internal(Vector3i bpos);
|
||||
|
||||
void set_block_size_pow2(unsigned int p);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user