Fixed view distance + dynamic load/unload around viewer

This commit is contained in:
Marc Gilleron 2017-08-28 01:47:38 +02:00
parent 488ca3e6fd
commit 115e0e2870
8 changed files with 226 additions and 56 deletions

62
rect3i.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef RECT3I_H
#define RECT3I_H
#include "vector3i.h"
//#include <math_funcs.h>
class Rect3i {
public:
Vector3i pos;
Vector3i size;
Rect3i() {}
Rect3i(Vector3i p_pos, Vector3i p_size) : pos(p_pos), size(p_size) {}
Rect3i(const Rect3i &other) : pos(other.pos), size(other.size) {}
static inline Rect3i from_center_extents(Vector3i center, Vector3i extents) {
return Rect3i(center - extents, 2*extents);
}
static inline Rect3i get_bounding_box(Rect3i a, Rect3i b) {
Rect3i box;
box.pos.x = MIN(a.pos.x, b.pos.x);
box.pos.y = MIN(a.pos.y, b.pos.y);
box.pos.z = MIN(a.pos.z, b.pos.z);
Vector3i max_a = a.pos + a.size;
Vector3i max_b = b.pos + b.size;
box.size.x = MAX(max_a.x, max_b.x) - box.pos.x;
box.size.y = MAX(max_a.y, max_b.y) - box.pos.y;
box.size.z = MAX(max_a.z, max_b.z) - box.pos.z;
return box;
}
bool inline contains(Vector3i p_pos) const {
Vector3i end = pos + size;
return p_pos.x >= pos.x
&& p_pos.y >= pos.y
&& p_pos.z >= pos.z
&& p_pos.x < end.x
&& p_pos.y < end.y
&& p_pos.z < end.z;
}
String to_string() const {
return String("(o:{0}, s:{1})").format(varray(pos.to_vec3(), size.to_vec3()));
}
};
inline bool operator!=(const Rect3i & a, const Rect3i & b) {
return a.pos != b.pos || a.size != b.size;
}
#endif // RECT3I_H

View File

@ -18,6 +18,9 @@ struct Vector3i {
_FORCE_INLINE_ Vector3i()
: x(0), y(0), z(0) {}
_FORCE_INLINE_ Vector3i(int xyz)
: x(xyz), y(xyz), z(xyz) {}
_FORCE_INLINE_ Vector3i(int px, int py, int pz)
: x(px), y(py), z(pz) {}

View File

@ -125,8 +125,10 @@ bool VoxelBuffer::is_uniform(unsigned int channel_index) const {
const Channel &channel = _channels[channel_index];
if (channel.data == NULL)
// Channel has been optimized
return true;
// Channel isn't optimized, so must look at each voxel
uint8_t voxel = channel.data[0];
unsigned int volume = get_volume();
for (unsigned int i = 0; i < volume; ++i) {

View File

@ -153,43 +153,14 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsign
}
}
void VoxelMap::remove_blocks_not_in_area(Vector3i min, Vector3i max) {
Vector3i::sort_min_max(min, max);
Vector<Vector3i> to_remove;
const Vector3i *key = NULL;
while (key = _blocks.next(key)) {
VoxelBlock *block_ref = _blocks.get(*key);
ERR_FAIL_COND(block_ref == NULL); // Should never trigger
if (block_ref->pos.is_contained_in(min, max)) {
//if (_observer)
// _observer->block_removed(block);
to_remove.push_back(*key);
if (block_ref == _last_accessed_block)
_last_accessed_block = NULL;
}
}
for (unsigned int i = 0; i < to_remove.size(); ++i) {
_blocks.erase(to_remove[i]);
}
}
void VoxelMap::clear() {
const Vector3i *key = NULL;
while (key = _blocks.next(key)) {
VoxelBlock *block_ref = _blocks.get(*key);
if (block_ref == NULL) {
VoxelBlock *block_ptr = _blocks.get(*key);
if (block_ptr == NULL) {
OS::get_singleton()->printerr("Unexpected NULL in VoxelMap::clear()");
}
memdelete(block_ref);
memdelete(block_ptr);
}
_blocks.clear();
_last_accessed_block = NULL;

View File

@ -50,7 +50,53 @@ public:
// 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);
void remove_blocks_not_in_area(Vector3i min, Vector3i max);
struct NoAction {
inline void operator()(VoxelBlock *block) {}
};
template <typename Action_T>
void remove_block(Vector3i bpos, Action_T pre_delete) {
if(_last_accessed_block && _last_accessed_block->pos == bpos)
_last_accessed_block = NULL;
VoxelBlock **pptr = _blocks.getptr(bpos);
if(pptr) {
VoxelBlock *block = *pptr;
ERR_FAIL_COND(block == NULL);
pre_delete(block);
memdelete(block);
_blocks.erase(bpos);
}
}
/*template <typename Action_T>
void remove_blocks_not_in_area(Vector3i min, Vector3i max, Action_T pre_delete = NoAction()) {
Vector3i::sort_min_max(min, max);
Vector<Vector3i> to_remove;
const Vector3i *key = NULL;
while (key = _blocks.next(key)) {
VoxelBlock *block_ptr = _blocks.get(*key);
ERR_FAIL_COND(block_ptr == NULL); // Should never trigger
if (block_ptr->pos.is_contained_in(min, max)) {
to_remove.push_back(*key);
if (block_ptr == _last_accessed_block)
_last_accessed_block = NULL;
pre_delete(block_ptr);
memdelete(block_ptr);
}
}
for (unsigned int i = 0; i < to_remove.size(); ++i) {
_blocks.erase(to_remove[i]);
}
}*/
VoxelBlock *get_block(Vector3i bpos);

View File

@ -130,7 +130,7 @@ void VoxelMesherSmooth::build_mesh(const VoxelBuffer &voxels, unsigned int chann
// Each 2x2 voxel group is a "cell"
if(voxels.is_uniform(channel)) {
// Nothing to extract, because constant isolevels never cross the surface
// Nothing to extract, because constant isolevels never cross the threshold and describe no surface
return;
}

View File

@ -1,5 +1,6 @@
#include "voxel_terrain.h"
#include "voxel_raycast.h"
#include "rect3i.h"
#include <os/os.h>
#include <scene/3d/mesh_instance.h>
#include <engine.h>
@ -13,9 +14,11 @@ VoxelTerrain::VoxelTerrain()
_mesher_smooth = Ref<VoxelMesherSmooth>(memnew(VoxelMesherSmooth));
_view_distance_blocks = 8;
_last_view_distance_blocks = 0;
}
Vector3i g_viewer_block_pos; // TODO UGLY! Lambdas or pointers needed...
// TODO UGLY! Lambdas or pointers needed... DO NOT use this outside of lambdas!
Vector3i g_viewer_block_pos;
// Sorts distance to viewer
struct BlockUpdateComparator {
@ -62,7 +65,8 @@ void VoxelTerrain::_get_property_list(List<PropertyInfo> *p_list) const {
void VoxelTerrain::set_provider(Ref<VoxelProvider> provider) {
if(provider != _provider) {
_provider = provider;
make_all_view_dirty();
// The whole map might change, so make all area dirty
make_all_view_dirty_deferred();
}
}
@ -83,7 +87,9 @@ void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
}
#endif
_mesher->set_library(library);
make_all_view_dirty();
// Voxel appearance might completely change
make_all_view_dirty_deferred();
}
}
@ -99,16 +105,13 @@ void VoxelTerrain::set_view_distance(int distance_in_voxels) {
ERR_FAIL_COND(distance_in_voxels < 0)
int d = distance_in_voxels / _map->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;
make_all_view_dirty();
// TODO Immerge blocks too far away
// TODO Cancel updates that are scheduled too far away
// Blocks too far away will be removed in _process, same for blocks to load
}
}
void VoxelTerrain::set_viewer_path(NodePath path) {
if (!path.is_empty())
ERR_FAIL_COND(get_viewer(path) == NULL);
_viewer_path = path;
}
@ -148,6 +151,18 @@ void VoxelTerrain::make_block_dirty(Vector3i bpos) {
}
}
void VoxelTerrain::immerge_block(Vector3i bpos) {
ERR_FAIL_COND(_map.is_null());
// TODO Schedule block saving when supported
_map->remove_block(bpos, VoxelMap::NoAction());
_dirty_blocks.erase(bpos);
// Blocks in the update queue will be cancelled in _process,
// because it's too expensive to linear-search all blocks for each block
}
bool VoxelTerrain::is_block_dirty(Vector3i bpos) {
return _dirty_blocks.has(bpos);
}
@ -164,10 +179,14 @@ void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
}
}
void VoxelTerrain::make_all_view_dirty() {
Vector3i radius(_view_distance_blocks, _view_distance_blocks, _view_distance_blocks);
// TODO Take viewer and fixed range into account
make_blocks_dirty(-radius, 2*radius);
void VoxelTerrain::make_all_view_dirty_deferred() {
// This trick will regenerate all chunks in view, according to the view distance found during block updates.
// The point of doing this instead of immediately scheduling updates is that it will
// always use an up-to-date view distance, which is not necessarily loaded yet on initialization.
_last_view_distance_blocks = 0;
// Vector3i radius(_view_distance_blocks, _view_distance_blocks, _view_distance_blocks);
// make_blocks_dirty(-radius, 2*radius);
}
inline int get_border_index(int x, int max) {
@ -302,12 +321,13 @@ void VoxelTerrain::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE:
break;
case NOTIFICATION_READY:
// TODO This should also react to viewer movement
make_all_view_dirty();
break;
// case NOTIFICATION_READY:
// break;
// TODO Listen for transform changes
// TODO Listen for NOTIFICATION_VISIBILITY_CHANGED
// TODO Listen for NOTIFICATION_ENTER_WORLD
// TODO Listen for NOTIFICATION_EXIT_WORLD
default:
break;
@ -319,19 +339,79 @@ void VoxelTerrain::_process() {
}
void VoxelTerrain::update_blocks() {
OS &os = *OS::get_singleton();
Engine &engine = *Engine::get_singleton();
ERR_FAIL_COND(_map.is_null());
// Get viewer location
Spatial *viewer = get_viewer(_viewer_path);
if (viewer)
g_viewer_block_pos = _map->voxel_to_block(viewer->get_translation());
else
g_viewer_block_pos = Vector3i();
// TODO Transform to local (Spatial Transform)
Vector3i viewer_block_pos;
if(engine.is_editor_hint()) {
// TODO Use editor's camera here
viewer_block_pos = Vector3i();
} else {
Spatial *viewer = get_viewer(_viewer_path);
if (viewer)
viewer_block_pos = _map->voxel_to_block(viewer->get_translation());
else
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));
if(prev_box != new_box) {
//print_line(String("Loaded area changed: from ") + prev_box.to_string() + String(" to ") + new_box.to_string());
Rect3i bounds = Rect3i::get_bounding_box(prev_box, new_box);
Vector3i max = bounds.pos + bounds.size;
// TODO There should be a way to only iterate relevant blocks
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 prev_contains = prev_box.contains(pos);
bool new_contains = new_box.contains(pos);
if(prev_contains && !new_contains) {
// Unload block
immerge_block(pos);
} else if(!prev_contains && new_contains) {
// Load or update block
make_block_dirty(pos);
}
}
}
}
}
// 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[i] = _block_update_queue[last];
_block_update_queue.resize(last);
--i;
}
}
}
_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")
@ -409,6 +489,8 @@ void VoxelTerrain::update_blocks() {
_block_update_queue.resize(_block_update_queue.size() - 1);
_dirty_blocks.erase(block_pos);
}
//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) {

View File

@ -57,10 +57,12 @@ private:
void update_blocks();
void update_block_mesh(Vector3i block_pos);
void make_all_view_dirty();
void make_all_view_dirty_deferred();
Spatial *get_viewer(NodePath path) const;
void immerge_block(Vector3i bpos);
// Observer events
//void block_removed(VoxelBlock & block);
@ -99,6 +101,8 @@ private:
Ref<VoxelProvider> _provider;
NodePath _viewer_path;
Vector3i _last_viewer_block_pos;
int _last_view_distance_blocks;
bool _generate_collisions;