mirror of
https://github.com/Relintai/godot_voxel.git
synced 2024-12-22 01:26:50 +01:00
Fixed view distance + dynamic load/unload around viewer
This commit is contained in:
parent
488ca3e6fd
commit
115e0e2870
62
rect3i.h
Normal file
62
rect3i.h
Normal 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
|
@ -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) {}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
48
voxel_map.h
48
voxel_map.h
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user