diff --git a/register_types.cpp b/register_types.cpp index 4671441..827f684 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -2,6 +2,8 @@ #include "voxel_buffer.h" #include "voxel_mesher.h" #include "voxel_library.h" +#include "voxel_map.h" +#include "voxel_terrain.h" void register_voxel_types() { @@ -9,6 +11,8 @@ void register_voxel_types() { ObjectTypeDB::register_type(); ObjectTypeDB::register_type(); ObjectTypeDB::register_type(); + ObjectTypeDB::register_type(); + ObjectTypeDB::register_type(); } diff --git a/vector3i.h b/vector3i.h index d6d830a..b066e34 100644 --- a/vector3i.h +++ b/vector3i.h @@ -28,10 +28,22 @@ struct Vector3i { z = Math::floor(f.z); } - _FORCE_INLINE_ Vector3 to_vec3() { + _FORCE_INLINE_ Vector3 to_vec3() const { return Vector3(x, y, z); } + _FORCE_INLINE_ int volume() const { + return x*y*z; + } + + _FORCE_INLINE_ int length_sq() const { + return x*x + y*y + z*z; + } + + _FORCE_INLINE_ float length() const { + return Math::sqrt(length_sq()); + } + _FORCE_INLINE_ Vector3i & operator=(const Vector3i & other) { x = other.x; y = other.y; @@ -60,9 +72,9 @@ struct Vector3i { if (y < min.y) y = min.y; if (z < min.z) z = min.z; - if (x >= max.x) x = max.x; - if (y >= max.y) y = max.y; - if (z >= max.z) z = max.z; + if (x >= max.x) x = max.x - 1; + if (y >= max.y) y = max.y - 1; + if (z >= max.z) z = max.z - 1; } _FORCE_INLINE_ bool is_contained_in(const Vector3i & min, const Vector3i & max) { @@ -78,6 +90,21 @@ struct Vector3i { ); } + static void sort_min_max(Vector3i & a, Vector3i & b) { + sort_min_max(a.x, b.x); + sort_min_max(a.y, b.y); + sort_min_max(a.z, b.z); + } + +private: + static _FORCE_INLINE_ void sort_min_max(int & a, int & b) { + if (a > b) { + int temp = a; + a = b; + b = a; + } + } + }; _FORCE_INLINE_ Vector3i operator+(const Vector3i a, const Vector3i & b) { diff --git a/voxel_buffer.cpp b/voxel_buffer.cpp index 88af1ae..7dd6e20 100644 --- a/voxel_buffer.cpp +++ b/voxel_buffer.cpp @@ -93,20 +93,24 @@ void VoxelBuffer::fill(int defval, unsigned int channel_index) { void VoxelBuffer::fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index) { ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + Vector3i::sort_min_max(min, max); + min.clamp_to(Vector3i(0, 0, 0), _size); - max.clamp_to(Vector3i(0, 0, 0), _size); + max.clamp_to(Vector3i(0, 0, 0), _size + Vector3i(1,1,1)); Vector3i area_size = max - min; Channel & channel = _channels[channel_index]; - if (channel.data == NULL && channel.defval == defval) - return; - else - create_channel(channel_index, _size); + if (channel.data == NULL) { + if (channel.defval == defval) + return; + else + create_channel(channel_index, _size); + } Vector3i pos; for (pos.z = min.z; pos.z < max.z; ++pos.z) { for (pos.x = min.x; pos.x < max.x; ++pos.x) { - unsigned int dst_ri = row_index(pos.x, pos.y, pos.z); + unsigned int dst_ri = index(pos.x, pos.y, pos.z); memset(&channel.data[dst_ri], defval, area_size.y * sizeof(uint8_t)); } } @@ -164,12 +168,14 @@ void VoxelBuffer::copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3 Channel & channel = _channels[channel_index]; const Channel & other_channel = other._channels[channel_index]; + Vector3i::sort_min_max(src_min, src_max); + src_min.clamp_to(Vector3i(0, 0, 0), other._size); - src_max.clamp_to(Vector3i(0, 0, 0), other._size); + src_max.clamp_to(Vector3i(0, 0, 0), other._size + Vector3i(1,1,1)); dst_min.clamp_to(Vector3i(0, 0, 0), _size); Vector3i area_size = src_max - src_min; - Vector3i dst_max = src_min + area_size; + //Vector3i dst_max = dst_min + area_size; if (area_size == _size) { copy_from(other, channel_index); @@ -184,8 +190,8 @@ void VoxelBuffer::copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3 for (pos.z = 0; pos.z < area_size.z; ++pos.z) { for (pos.x = 0; pos.x < area_size.x; ++pos.x) { // Row direction is Y - unsigned int src_ri = other.row_index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z); - unsigned int dst_ri = row_index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); + unsigned int src_ri = other.index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z); + unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); memcpy(&channel.data[dst_ri], &other_channel.data[src_ri], area_size.y * sizeof(uint8_t)); } } @@ -198,7 +204,7 @@ void VoxelBuffer::copy_from(const VoxelBuffer & other, Vector3i src_min, Vector3 Vector3i pos; for (pos.z = 0; pos.z < area_size.z; ++pos.z) { for (pos.x = 0; pos.x < area_size.x; ++pos.x) { - unsigned int dst_ri = row_index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); + unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); memset(&channel.data[dst_ri], other_channel.defval, area_size.y * sizeof(uint8_t)); } } diff --git a/voxel_map.cpp b/voxel_map.cpp new file mode 100644 index 0000000..e61024d --- /dev/null +++ b/voxel_map.cpp @@ -0,0 +1,236 @@ +#include "voxel_map.h" + +VoxelMap::VoxelMap() : _last_accessed_block(NULL) { + for (unsigned int i = 0; i < VoxelBuffer::MAX_CHANNELS; ++i) { + _default_voxel[i] = 0; + } +} + +VoxelMap::~VoxelMap() { + +} + +int VoxelMap::get_voxel(Vector3i pos, unsigned int c) { + Vector3i bpos = voxel_to_block(pos); + VoxelBlock * block = get_block(bpos); + if (block == NULL) { + return _default_voxel[c]; + } + return block->voxels->get_voxel(pos - block_to_voxel(bpos), c); +} + +MeshInstance * VoxelBlock::get_mesh_instance(const Node & root) { + if (mesh_instance_path.is_empty()) + return NULL; + Node * n = root.get_node(mesh_instance_path); + if (n == NULL) + return NULL; + return n->cast_to(); +} + +VoxelBlock::~VoxelBlock() { + +} + +// Helper +VoxelBlock * VoxelBlock::create(VoxelMap & map, Vector3i bpos, VoxelBuffer * buffer) { + VoxelBlock * block = memnew(VoxelBlock); + block->pos = bpos; + if (buffer) { + const int bs = VoxelBlock::SIZE; + ERR_FAIL_COND_V(buffer->get_size() != Vector3i(bs, bs, bs), NULL); + } + else { + buffer = memnew(VoxelBuffer); + } + ERR_FAIL_COND_V(buffer == NULL, NULL); + block->voxels = Ref(buffer); + //block->map = ↦ + return block; +} + +void VoxelMap::set_voxel(int value, Vector3i pos, unsigned int c) { + Vector3i bpos = voxel_to_block(pos); + VoxelBlock * block = get_block(pos); + if (block == NULL) { + set_block(bpos, VoxelBlock::create(*this, bpos)); + } + block->voxels->set_voxel(value, pos - block_to_voxel(bpos), c); +} + +void VoxelMap::set_default_voxel(int value, unsigned int channel) { + ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS); + _default_voxel[channel] = value; +} + +int VoxelMap::get_default_voxel(unsigned int channel) { + ERR_FAIL_INDEX_V(channel, VoxelBuffer::MAX_CHANNELS, 0); + return _default_voxel[channel]; +} + +VoxelBlock * VoxelMap::get_block(Vector3i bpos) { + if (_last_accessed_block && _last_accessed_block->pos == bpos) { + return _last_accessed_block; + } + Ref * p = _blocks.getptr(bpos); + if (p) { + _last_accessed_block = p->ptr(); + return _last_accessed_block; + } + return NULL; +} + +void VoxelMap::set_block(Vector3i bpos, VoxelBlock * block) { + if (_last_accessed_block == NULL || _last_accessed_block->pos == bpos) { + _last_accessed_block = block; + } + _blocks.set(bpos, block); +} + +void VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { + ERR_FAIL_COND(buffer.is_null()); + VoxelBlock * block = get_block(bpos); + if (block == NULL) { + block = VoxelBlock::create(*this, bpos, *buffer); + set_block(bpos, block); + } + else { + block->voxels = buffer; + } +} + +bool VoxelMap::has_block(Vector3i pos) const { + return /*(_last_accessed_block != NULL && _last_accessed_block->pos == pos) ||*/ _blocks.has(pos); +} + +Vector3i g_moore_neighboring_3d[26] = { + Vector3i(-1,-1,-1), + Vector3i(0,-1,-1), + Vector3i(1,-1,-1), + Vector3i(-1,-1,0), + Vector3i(0,-1,0), + Vector3i(1,-1,0), + Vector3i(-1,-1,1), + Vector3i(0,-1,1), + Vector3i(1,-1,1), + + Vector3i(-1,0,-1), + Vector3i(0,0,-1), + Vector3i(1,0,-1), + Vector3i(-1,0,0), + //Vector3i(0,0,0), + Vector3i(1,0,0), + Vector3i(-1,0,1), + Vector3i(0,0,1), + Vector3i(1,0,1), + + Vector3i(-1,1,-1), + Vector3i(0,1,-1), + Vector3i(1,1,-1), + Vector3i(-1,1,0), + Vector3i(0,1,0), + Vector3i(1,1,0), + Vector3i(-1,1,1), + Vector3i(0,1,1), + Vector3i(1,1,1), +}; + +bool VoxelMap::is_block_surrounded(Vector3i pos) const { + for (unsigned int i = 0; i < 26; ++i) { + Vector3i bpos = pos + g_moore_neighboring_3d[i]; + if (!has_block(bpos)) { + return false; + } + } + return true; +} + +void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel) { + ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS); + + Vector3i max_pos = min_pos + dst_buffer.get_size(); + + Vector3i min_block_pos = voxel_to_block(min_pos); + Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1,1,1)) + Vector3i(1,1,1); + ERR_FAIL_COND((max_block_pos - min_block_pos) != Vector3(3, 3, 3)); + + Vector3i bpos; + for (bpos.z = min_block_pos.z; bpos.z < max_block_pos.z; ++bpos.z) { + for (bpos.x = min_block_pos.x; bpos.x < max_block_pos.x; ++bpos.x) { + for (bpos.y = min_block_pos.y; bpos.y < max_block_pos.y; ++bpos.y) { + + VoxelBlock * block = get_block(bpos); + if (block) { + + VoxelBuffer & src_buffer = **block->voxels; + Vector3i offset = block_to_voxel(bpos); + // Note: copy_from takes care of clamping the area if it's on an edge + dst_buffer.copy_from(src_buffer, min_pos - offset, max_pos - offset, offset - min_pos, channel); + } + else { + Vector3i offset = block_to_voxel(bpos); + dst_buffer.fill_area( + _default_voxel[channel], + offset - min_pos, + offset - min_pos + Vector3i(VoxelBlock::SIZE,VoxelBlock::SIZE, VoxelBlock::SIZE) + ); + } + + } + } + } +} + +void VoxelMap::remove_blocks_not_in_area(Vector3i min, Vector3i max) { + + Vector3i::sort_min_max(min, max); + + Vector to_remove; + Vector3i * key = NULL; + + while (_blocks.next(key)) { + + Ref & block_ref = _blocks.get(*key); + ERR_FAIL_COND(block_ref.is_null()); // Should never trigger + VoxelBlock & block = **block_ref; + + if (!block.pos.is_contained_in(min, max)) { + + //if (_observer) + // _observer->block_removed(block); + + to_remove.push_back(*key); + + if (&block == _last_accessed_block) + _last_accessed_block = NULL; + } + } + + for (unsigned int i = 0; i < to_remove.size(); ++i) { + _blocks.erase(to_remove[i]); + } +} + +void VoxelMap::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("set_voxel", "value", "x", "y", "z", "c"), &VoxelMap::_set_voxel_binding, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("get_default_voxel", "channel"), &VoxelMap::get_default_voxel, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("set_default_voxel", "value", "channel"), &VoxelMap::set_default_voxel, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("has_block", "x", "y", "z"), &VoxelMap::_has_block_binding); + ObjectTypeDB::bind_method(_MD("get_buffer_copy", "min_pos", "out_buffer:VoxelBuffer", "channel"), &VoxelMap::_get_buffer_copy_binding, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("set_block_buffer", "block_pos", "buffer:VoxelBuffer"), &VoxelMap::_set_block_buffer_binding); + ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelMap::_voxel_to_block_binding); + ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelMap::_block_to_voxel_binding); + ObjectTypeDB::bind_method(_MD("get_block_size"), &VoxelMap::get_block_size); + + //ADD_PROPERTY(PropertyInfo(Variant::INT, "iterations"), _SCS("set_iterations"), _SCS("get_iterations")); + +} + + +void VoxelMap::_get_buffer_copy_binding(Vector3 pos, Ref dst_buffer_ref, unsigned int channel) { + ERR_FAIL_COND(dst_buffer_ref.is_null()); + get_buffer_copy(Vector3i(pos), **dst_buffer_ref, channel); +} + diff --git a/voxel_map.h b/voxel_map.h new file mode 100644 index 0000000..c5c4c0f --- /dev/null +++ b/voxel_map.h @@ -0,0 +1,155 @@ +#ifndef VOXEL_MAP_H +#define VOXEL_MAP_H + +#include +#include +#include +#include "voxel_buffer.h" + +class VoxelMap; + +// Fixed-size voxel container used in VoxelMap. Used internally. +class VoxelBlock : public Reference { + OBJ_TYPE(VoxelBlock, Reference) +public: + static const int SIZE_POW2 = 4; // 3=>8, 4=>16, 5=>32... + static const int SIZE = 1 << SIZE_POW2; + + Ref voxels; // SIZE*SIZE*SIZE voxels + Vector3i pos; + NodePath mesh_instance_path; + //VoxelMap * map; + + MeshInstance * get_mesh_instance(const Node & root); + + static VoxelBlock * create(VoxelMap & map, Vector3i bpos, VoxelBuffer * buffer = 0); + + ~VoxelBlock(); + +private: + VoxelBlock() : Reference(), voxels(NULL) {} + +}; + +//class IVoxelMapObserver { +//public: +// virtual void block_removed(VoxelBlock & block) = 0; +//}; + +// Infinite voxel storage by means of octants like Gridmap +class VoxelMap : public Reference { + OBJ_TYPE(VoxelMap, Reference) + + // Voxel values that will be returned if access is out of map bounds + uint8_t _default_voxel[VoxelBuffer::MAX_CHANNELS]; + + // Blocks stored with a spatial hash in all 3D directions + HashMap, Vector3iHasher> _blocks; + + // Voxel access will most frequently be in contiguous areas, so the same blocks are accessed. + // To prevent too much hashing, this reference is checked before. + VoxelBlock * _last_accessed_block; + + //IVoxelMapObserver * _observer; + +public: + VoxelMap(); + ~VoxelMap(); + + int get_voxel(Vector3i pos, unsigned int c = 0); + void set_voxel(int value, Vector3i pos, unsigned int c = 0); + + void set_default_voxel(int value, unsigned int channel=0); + int get_default_voxel(unsigned int channel=0); + + // Converts voxel coodinates into block coordinates + static _FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) { + return Vector3i( + //pos.x < 0 ? (pos.x + 1) / VoxelBlock::SIZE - 1 : pos.x / VoxelBlock::SIZE, + //pos.y < 0 ? (pos.y + 1) / VoxelBlock::SIZE - 1 : pos.y / VoxelBlock::SIZE, + //pos.z < 0 ? (pos.z + 1) / VoxelBlock::SIZE - 1 : pos.z / VoxelBlock::SIZE + pos.x >> VoxelBlock::SIZE_POW2, + pos.y >> VoxelBlock::SIZE_POW2, + pos.z >> VoxelBlock::SIZE_POW2 + ); + } + + // Converts block coodinates into voxel coordinates + static _FORCE_INLINE_ Vector3i block_to_voxel(Vector3i bpos) { + return bpos * VoxelBlock::SIZE; + } + + // Gets a copy of all voxels in the area starting at min_pos having the same size as dst_buffer. + void get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel = 0); + + // 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 buffer); + + void remove_blocks_not_in_area(Vector3i min, Vector3i max); + + _FORCE_INLINE_ Ref get_block_ref(Vector3i bpos) { return get_block(bpos); } + + bool has_block(Vector3i pos) const; + bool is_block_surrounded(Vector3i pos) const; + + //void set_observer(IVoxelMapObserver * observer) { _observer = observer; } + +private: + VoxelBlock * get_block(Vector3i bpos); + void set_block(Vector3i bpos, VoxelBlock * block); + + _FORCE_INLINE_ int get_block_size() const { return VoxelBlock::SIZE; } + + static void _bind_methods(); + + _FORCE_INLINE_ int _get_voxel_binding(int x, int y, int z, unsigned int c = 0) { return get_voxel(Vector3i(x, y, z), c); } + _FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int c = 0) { set_voxel(value, Vector3i(x, y, z), c); } + _FORCE_INLINE_ bool _has_block_binding(int x, int y, int z) { return has_block(Vector3i(x, y, z)); } + _FORCE_INLINE_ Vector3 _voxel_to_block_binding(Vector3 pos) const { return voxel_to_block(Vector3i(pos)).to_vec3(); } + _FORCE_INLINE_ Vector3 _block_to_voxel_binding(Vector3 pos) const { return block_to_voxel(Vector3i(pos)).to_vec3(); } + bool _is_block_surrounded(Vector3 pos) const { return is_block_surrounded(Vector3i(pos)); } + void _get_buffer_copy_binding(Vector3 pos, Ref dst_buffer_ref, unsigned int channel = 0); + void _set_block_buffer_binding(Vector3 bpos, Ref buffer) { set_block_buffer(Vector3i(bpos), buffer); } + +}; + +//class VoxelSector { +//public: +// static const int SIZE = 16; +// +//private: +// Ref blocks[SIZE * SIZE * SIZE]; +//}; + +//template +//struct VoxelTree { +// +// const int SIZE = 1 << P; +// const int VOLUME = P*P*P; +// +// uint8_t get_voxel(int x, int y, int z) { +// unsigned int i = index(x / SIZE, y / SIZE, z / SIZE); +// ERR_FAIL_COND_V(i >= VOLUME, 0); +// if (subtrees) { +// VoxelTree * subtree = subtrees[i]; +// if (subtree) { +// return subtree->get_voxel(x, y, z); +// } +// } +// else if (blocks[i].is_valid()) { +// return blocks[i]->voxels.get_voxel(x, y, z); +// } +// return 0; +// } +// +// unsigned int index(int x, int y, int z) { +// return (pos.z * SIZE + pos.x) * SIZE + pos.y; +// } +// +// Ref * blocks; +// VoxelTree * subtrees; +// int level; +//}; + +#endif // VOXEL_MAP_H + diff --git a/voxel_mesher.cpp b/voxel_mesher.cpp index 7365bc1..8a567ea 100644 --- a/voxel_mesher.cpp +++ b/voxel_mesher.cpp @@ -165,11 +165,14 @@ inline bool is_transparent(const VoxelLibrary & lib, int voxel_id) { return true; } -Ref VoxelMesher::build(Ref buffer_ref) { +Ref VoxelMesher::build_ref(Ref buffer_ref) { ERR_FAIL_COND_V(buffer_ref.is_null(), Ref()); + return build(**buffer_ref); +} + +Ref VoxelMesher::build(const VoxelBuffer & buffer) { ERR_FAIL_COND_V(_library.is_null(), Ref()); - const VoxelBuffer & buffer = **buffer_ref; const VoxelLibrary & library = **_library; for (unsigned int i = 0; i < MAX_MATERIALS; ++i) { @@ -335,7 +338,6 @@ void VoxelMesher::_bind_methods() { ObjectTypeDB::bind_method(_MD("set_library", "voxel_library"), &VoxelMesher::set_library); ObjectTypeDB::bind_method(_MD("set_occlusion_enabled", "enable"), &VoxelMesher::set_occlusion_enabled); ObjectTypeDB::bind_method(_MD("set_occlusion_darkness", "value"), &VoxelMesher::set_occlusion_darkness); - ObjectTypeDB::bind_method(_MD("build", "voxel_buffer"), &VoxelMesher::build); + ObjectTypeDB::bind_method(_MD("build", "voxel_buffer"), &VoxelMesher::build_ref); } - diff --git a/voxel_mesher.h b/voxel_mesher.h index f3c0905..da6626d 100644 --- a/voxel_mesher.h +++ b/voxel_mesher.h @@ -33,7 +33,8 @@ public: void set_occlusion_enabled(bool enable); - Ref build(Ref buffer_ref); + Ref build(const VoxelBuffer & buffer_ref); + Ref build_ref(Ref buffer_ref); protected: diff --git a/voxel_terrain.cpp b/voxel_terrain.cpp new file mode 100644 index 0000000..c495a43 --- /dev/null +++ b/voxel_terrain.cpp @@ -0,0 +1,197 @@ +#include "voxel_terrain.h" +#include +#include + +VoxelTerrain::VoxelTerrain(): Node(), _min_y(-4), _max_y(4) { + + _map = Ref(memnew(VoxelMap)); + _mesher = Ref(memnew(VoxelMesher)); +} + +struct BlockUpdateComparator0 { + inline bool operator()(const Vector3i & a, const Vector3i & b) const { + return a.length_sq() > b.length_sq(); + } +}; + +void VoxelTerrain::force_load_blocks(Vector3i center, Vector3i extents) { + //Vector3i min = center - extents; + //Vector3i max = center + extents + Vector3i(1,1,1); + //Vector3i size = max - min; + + _block_update_queue.clear(); + + Vector3i pos; + for (pos.z = -extents.z; pos.z <= extents.z; ++pos.z) { + for (pos.x = -extents.x; pos.x <= extents.x; ++pos.x) { + for (pos.y = -extents.y; pos.y <= extents.y; ++pos.y) { + _block_update_queue.push_back(pos); + } + } + } + + _block_update_queue.sort_custom(); + +} + +int VoxelTerrain::get_block_update_count() { + return _block_update_queue.size(); +} + +void VoxelTerrain::_notification(int p_what) { + + switch (p_what) { + + case NOTIFICATION_ENTER_TREE: + set_process(true); + break; + + case NOTIFICATION_PROCESS: + _process(); + break; + + case NOTIFICATION_EXIT_TREE: + break; + + default: + break; + } +} + +void VoxelTerrain::_process() { + update_blocks(); +} + +void VoxelTerrain::update_blocks() { + OS & os = *OS::get_singleton(); + + uint32_t time_before = os.get_ticks_msec(); + uint32_t max_time = 1000 / 60; + + while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) { + + // TODO Move this to a thread + // TODO Have VoxelTerrainGenerator in C++ + // TODO Keep track of MeshInstances! + + // Get request + Vector3i block_pos = _block_update_queue[_block_update_queue.size() - 1]; + + if (!_map->has_block(block_pos)) { + + // Get script + ScriptInstance * script = get_script_instance(); + if (script == NULL) { + return; + } + + // Create buffer + Ref buffer_ref = Ref(memnew(VoxelBuffer)); + const Vector3i block_size(VoxelBlock::SIZE, VoxelBlock::SIZE, VoxelBlock::SIZE); + buffer_ref->create(block_size.x, block_size.y, block_size.y); + + // Call script to generate buffer + Variant arg1 = buffer_ref; + Variant arg2 = block_pos.to_vec3(); + const Variant * args[2] = { &arg1, &arg2 }; + Variant::CallError err; // wut + script->call_multilevel("_generate_block", args, 2); + + // Check script return + ERR_FAIL_COND(buffer_ref->get_size() != block_size); + + // Store buffer + _map->set_block_buffer(block_pos, buffer_ref); + + // Update meshes + Vector3i ndir; + for (ndir.z = -1; ndir.z < 2; ++ndir.z) { + for (ndir.x = -1; ndir.x < 2; ++ndir.x) { + for (ndir.y = -1; ndir.y < 2; ++ndir.y) { + Vector3i npos = block_pos + ndir; + if (_map->is_block_surrounded(npos)) { + update_block_mesh(npos); + } + } + } + } + //update_block_mesh(block_pos); + + } + + // Pop request + _block_update_queue.resize(_block_update_queue.size() - 1); + } +} + +void VoxelTerrain::update_block_mesh(Vector3i block_pos) { + Ref block_ref = _map->get_block_ref(block_pos); + if (block_ref.is_null()) { + return; + } + if (block_ref->voxels->is_uniform(0) && block_ref->voxels->get_voxel(0, 0, 0, 0) == 0) { + return; + } + + // Create buffer padded with neighbor voxels + VoxelBuffer nbuffer; + nbuffer.create(VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2); + _map->get_buffer_copy(VoxelMap::block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer); + + // TEST + //if (block_pos == Vector3i(0, 0, 0)) { + // printf(">>>\n"); + // String os; + // for (unsigned int y = 0; y < nbuffer.get_size().y; ++y) { + // for (unsigned int z = 0; z < nbuffer.get_size().z; ++z) { + // for (unsigned int x = 0; x < nbuffer.get_size().x; ++x) { + // if (nbuffer.get_voxel(x, y, z) == 0) + // os += '-'; + // else + // os += 'O'; + // } + // os += '\n'; + // } + // os += '\n'; + // } + // wprintf(os.c_str()); + //} + + // Build mesh (that part is the most CPU-intensive) + Ref mesh = _mesher->build(nbuffer); + + MeshInstance * mesh_instance = block_ref->get_mesh_instance(*this); + if (mesh_instance == NULL) { + // Create and spawn mesh + mesh_instance = memnew(MeshInstance); + mesh_instance->set_mesh(mesh); + mesh_instance->set_translation(VoxelMap::block_to_voxel(block_pos).to_vec3()); + add_child(mesh_instance); + block_ref->mesh_instance_path = mesh_instance->get_path(); + } + else { + // Update mesh + mesh_instance->set_mesh(mesh); + } +} + +//void VoxelTerrain::block_removed(VoxelBlock & block) { +// MeshInstance * mesh_instance = block.get_mesh_instance(*this); +// if (mesh_instance) { +// mesh_instance->queue_delete(); +// } +//} + +void VoxelTerrain::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("get_block_update_count"), &VoxelTerrain::get_block_update_count); + ObjectTypeDB::bind_method(_MD("get_mesher:VoxelMesher"), &VoxelTerrain::get_mesher); + + // TODO Make those two static in VoxelMap? + ObjectTypeDB::bind_method(_MD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding); + ObjectTypeDB::bind_method(_MD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding); + + ObjectTypeDB::bind_method(_MD("force_load_blocks", "center", "extents"), &VoxelTerrain::_force_load_blocks_binding); + +} + diff --git a/voxel_terrain.h b/voxel_terrain.h new file mode 100644 index 0000000..747a67b --- /dev/null +++ b/voxel_terrain.h @@ -0,0 +1,52 @@ +#ifndef VOXEL_TERRAIN_H +#define VOXEL_TERRAIN_H + +#include +#include "voxel_map.h" +#include "voxel_mesher.h" + +// Infinite static terrain made of voxels. +// It is loaded around VoxelTerrainStreamers. +class VoxelTerrain : public Node /*, public IVoxelMapObserver*/ { + OBJ_TYPE(VoxelTerrain, Node) + + // Parameters + int _min_y; // In blocks, not voxels + int _max_y; + + // Voxel storage + Ref _map; + + Vector _block_update_queue; + Ref _mesher; + +public: + VoxelTerrain(); + + void force_load_blocks(Vector3i center, Vector3i extents); + + int get_block_update_count(); + + Ref get_mesher() { return _mesher; } + +protected: + void _notification(int p_what); + void _process(); + + void update_blocks(); + void update_block_mesh(Vector3i block_pos); + + // Observer events + //void block_removed(VoxelBlock & block); + + static void _bind_methods(); + + // Convenience + Vector3 _voxel_to_block_binding(Vector3 pos) { return Vector3i(VoxelMap::voxel_to_block(pos)).to_vec3(); } + Vector3 _block_to_voxel_binding(Vector3 pos) { return Vector3i(VoxelMap::block_to_voxel(pos)).to_vec3(); } + void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); } + +}; + +#endif // VOXEL_TERRAIN_H +