Removed VoxelMap and VoxelBlock, superseded by their specialized versions

master
Marc Gilleron 2021-04-15 20:25:55 +01:00
parent 63d9361a87
commit 96bcc44d00
7 changed files with 2 additions and 1003 deletions

View File

@ -32,7 +32,7 @@
#include "terrain/instancing/voxel_instancer.h"
#include "terrain/voxel_box_mover.h"
#include "terrain/voxel_lod_terrain.h"
#include "terrain/voxel_map.h"
#include "terrain/voxel_mesh_block.h"
#include "terrain/voxel_terrain.h"
#include "terrain/voxel_viewer.h"
#include "util/macros.h"
@ -129,7 +129,7 @@ void register_voxel_types() {
// Engine::get_singleton()->add_singleton(Engine::Singleton("SingletonName",singleton_instance));
PRINT_VERBOSE(String("Size of VoxelBuffer: {0}").format(varray((int)sizeof(VoxelBuffer))));
PRINT_VERBOSE(String("Size of VoxelBlock: {0}").format(varray((int)sizeof(VoxelBlock))));
PRINT_VERBOSE(String("Size of VoxelMeshBlock: {0}").format(varray((int)sizeof(VoxelMeshBlock))));
PRINT_VERBOSE(String("Size of VoxelTerrain: {0}").format(varray((int)sizeof(VoxelTerrain))));
PRINT_VERBOSE(String("Size of VoxelLodTerrain: {0}").format(varray((int)sizeof(VoxelLodTerrain))));
PRINT_VERBOSE(String("Size of VoxelInstancer: {0}").format(varray((int)sizeof(VoxelInstancer))));

View File

@ -1,363 +0,0 @@
#include "voxel_block.h"
#include "../constants/voxel_string_names.h"
#include "../util/godot/funcs.h"
#include "../util/macros.h"
#include "../util/profiling.h"
#include <scene/3d/spatial.h>
#include <scene/resources/concave_polygon_shape.h>
// Helper
VoxelBlock *VoxelBlock::create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int size, unsigned int p_lod_index) {
const int bs = size;
ERR_FAIL_COND_V(buffer.is_null(), nullptr);
ERR_FAIL_COND_V(buffer->get_size() != Vector3i(bs, bs, bs), nullptr);
VoxelBlock *block = memnew(VoxelBlock);
block->position = bpos;
block->lod_index = p_lod_index;
block->_position_in_voxels = bpos * (size << p_lod_index);
block->voxels = buffer;
#ifdef VOXEL_DEBUG_LOD_MATERIALS
Ref<SpatialMaterial> debug_material;
debug_material.instance();
int checker = (bpos.x + bpos.y + bpos.z) & 1;
Color debug_color = Color(0.8, 0.4, 0.8).linear_interpolate(Color(0.0, 0.0, 0.5), static_cast<float>(p_lod_index) / 8.f);
debug_color = debug_color.lightened(checker * 0.1f);
debug_material->set_albedo(debug_color);
block->_debug_material = debug_material;
Ref<SpatialMaterial> debug_transition_material;
debug_transition_material.instance();
debug_transition_material->set_albedo(Color(1, 1, 0));
block->_debug_transition_material = debug_transition_material;
#endif
return block;
}
VoxelBlock::VoxelBlock() {
}
VoxelBlock::~VoxelBlock() {
}
void VoxelBlock::set_world(Ref<World> p_world) {
if (_world != p_world) {
_world = p_world;
// To update world. I replaced visibility by presence in world because Godot 3 culling performance is horrible
_set_visible(_visible && _parent_visible);
if (_static_body.is_valid()) {
_static_body.set_world(*p_world);
}
}
}
void VoxelBlock::set_mesh(Ref<Mesh> mesh) {
// TODO Don't add mesh instance to the world if it's not visible.
// I suspect Godot is trying to include invisible mesh instances into the culling process,
// which is killing performance when LOD is used (i.e many meshes are in pool but hidden)
// This needs investigation.
if (mesh.is_valid()) {
if (!_mesh_instance.is_valid()) {
// Create instance if it doesn't exist
_mesh_instance.create();
set_mesh_instance_visible(_mesh_instance, _visible && _parent_visible);
}
_mesh_instance.set_mesh(mesh);
if (_shader_material.is_valid()) {
_mesh_instance.set_material_override(_shader_material);
}
#ifdef VOXEL_DEBUG_LOD_MATERIALS
_mesh_instance.set_material_override(_debug_material);
#endif
} else {
if (_mesh_instance.is_valid()) {
// Delete instance if it exists
_mesh_instance.destroy();
}
}
}
Ref<Mesh> VoxelBlock::get_mesh() const {
if (_mesh_instance.is_valid()) {
return _mesh_instance.get_mesh();
}
return Ref<Mesh>();
}
void VoxelBlock::set_transition_mesh(Ref<Mesh> mesh, int side) {
DirectMeshInstance &mesh_instance = _transition_mesh_instances[side];
if (mesh.is_valid()) {
if (!mesh_instance.is_valid()) {
// Create instance if it doesn't exist
mesh_instance.create();
set_mesh_instance_visible(mesh_instance, _visible && _parent_visible && _is_transition_visible(side));
}
mesh_instance.set_mesh(mesh);
if (_shader_material.is_valid()) {
mesh_instance.set_material_override(_shader_material);
}
#ifdef VOXEL_DEBUG_LOD_MATERIALS
mesh_instance.set_material_override(_debug_transition_material);
#endif
} else {
if (mesh_instance.is_valid()) {
// Delete instance if it exists
mesh_instance.destroy();
}
}
}
bool VoxelBlock::has_mesh() const {
return _mesh_instance.get_mesh().is_valid();
}
void VoxelBlock::drop_mesh() {
if (_mesh_instance.is_valid()) {
_mesh_instance.destroy();
}
}
void VoxelBlock::set_mesh_state(MeshState ms) {
_mesh_state = ms;
}
VoxelBlock::MeshState VoxelBlock::get_mesh_state() const {
return _mesh_state;
}
void VoxelBlock::set_visible(bool visible) {
if (_visible == visible) {
return;
}
_visible = visible;
_set_visible(_visible && _parent_visible);
}
bool VoxelBlock::is_visible() const {
return _visible;
}
void VoxelBlock::_set_visible(bool visible) {
if (_mesh_instance.is_valid()) {
set_mesh_instance_visible(_mesh_instance, visible);
}
for (unsigned int dir = 0; dir < _transition_mesh_instances.size(); ++dir) {
DirectMeshInstance &mi = _transition_mesh_instances[dir];
if (mi.is_valid()) {
set_mesh_instance_visible(mi, visible && _is_transition_visible(dir));
}
}
if (_static_body.is_valid()) {
_static_body.set_shape_enabled(0, visible);
}
}
void VoxelBlock::set_shader_material(Ref<ShaderMaterial> material) {
_shader_material = material;
if (_mesh_instance.is_valid()) {
_mesh_instance.set_material_override(_shader_material);
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
DirectMeshInstance &mi = _transition_mesh_instances[dir];
if (mi.is_valid()) {
mi.set_material_override(_shader_material);
}
}
}
if (_shader_material.is_valid()) {
const Transform local_transform(Basis(), _position_in_voxels.to_vec3());
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_block_local_transform, local_transform);
}
}
//void VoxelBlock::set_transition_bit(uint8_t side, bool value) {
// CRASH_COND(side >= Cube::SIDE_COUNT);
// uint32_t m = _transition_mask;
// if (value) {
// m |= (1 << side);
// } else {
// m &= ~(1 << side);
// }
// set_transition_mask(m);
//}
void VoxelBlock::set_transition_mask(uint8_t m) {
CRASH_COND(m >= (1 << Cube::SIDE_COUNT));
const uint8_t diff = _transition_mask ^ m;
if (diff == 0) {
return;
}
_transition_mask = m;
if (_shader_material.is_valid()) {
// TODO Needs translation here, because Cube:: tables use slightly different order...
// We may get rid of this once cube tables respects -x+x-y+y-z+z order
uint8_t bits[Cube::SIDE_COUNT];
for (unsigned int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
bits[dir] = (m >> dir) & 1;
}
uint8_t tm = bits[Cube::SIDE_NEGATIVE_X];
tm |= bits[Cube::SIDE_POSITIVE_X] << 1;
tm |= bits[Cube::SIDE_NEGATIVE_Y] << 2;
tm |= bits[Cube::SIDE_POSITIVE_Y] << 3;
tm |= bits[Cube::SIDE_NEGATIVE_Z] << 4;
tm |= bits[Cube::SIDE_POSITIVE_Z] << 5;
// TODO Godot 4: we may replace this with a per-instance parameter so we can lift material access limitation
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_transition_mask, tm);
}
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
DirectMeshInstance &mi = _transition_mesh_instances[dir];
if ((diff & (1 << dir)) && mi.is_valid()) {
set_mesh_instance_visible(mi, _visible && _parent_visible && _is_transition_visible(dir));
}
}
}
void VoxelBlock::set_parent_visible(bool parent_visible) {
if (_parent_visible && parent_visible) {
return;
}
_parent_visible = parent_visible;
_set_visible(_visible && _parent_visible);
}
void VoxelBlock::set_parent_transform(const Transform &parent_transform) {
VOXEL_PROFILE_SCOPE();
if (_mesh_instance.is_valid() || _static_body.is_valid()) {
const Transform local_transform(Basis(), _position_in_voxels.to_vec3());
const Transform world_transform = parent_transform * local_transform;
if (_mesh_instance.is_valid()) {
_mesh_instance.set_transform(world_transform);
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
DirectMeshInstance &mi = _transition_mesh_instances[i];
if (mi.is_valid()) {
mi.set_transform(world_transform);
}
}
}
if (_static_body.is_valid()) {
_static_body.set_transform(world_transform);
}
}
}
void VoxelBlock::set_needs_lodding(bool need_lodding) {
_needs_lodding = need_lodding;
}
bool VoxelBlock::is_modified() const {
return _modified;
}
void VoxelBlock::set_modified(bool modified) {
#ifdef TOOLS_ENABLED
if (_modified == false && modified) {
PRINT_VERBOSE(String("Marking block {0} as modified").format(varray(position.to_vec3())));
}
#endif
_modified = modified;
}
void VoxelBlock::set_collision_mesh(Vector<Array> surface_arrays, bool debug_collision, Spatial *node) {
if (surface_arrays.size() == 0) {
drop_collision();
return;
}
ERR_FAIL_COND(node == nullptr);
ERR_FAIL_COND_MSG(node->get_world() != _world, "Physics body and attached node must be from the same world");
if (!_static_body.is_valid()) {
_static_body.create();
_static_body.set_world(*_world);
// This allows collision signals to provide the terrain node in the `collider` field
_static_body.set_attached_object(node);
} else {
_static_body.remove_shape(0);
}
Ref<Shape> shape = create_concave_polygon_shape(surface_arrays);
_static_body.add_shape(shape);
_static_body.set_debug(debug_collision, *_world);
_static_body.set_shape_enabled(0, _visible);
}
void VoxelBlock::drop_collision() {
if (_static_body.is_valid()) {
_static_body.destroy();
}
}
// Returns `true` when finished
bool VoxelBlock::update_fading(float speed) {
// TODO Should probably not be on the block directly?
// Because we may want to fade transition meshes only
bool finished = false;
// x is progress in 0..1
// y is direction: 1 fades in, 0 fades out
Vector2 p;
switch (fading_state) {
case FADING_IN:
fading_progress += speed;
if (fading_progress >= 1.f) {
fading_progress = 1.f;
fading_state = FADING_NONE;
finished = true;
}
p.x = fading_progress;
p.y = 1.f;
break;
case FADING_OUT:
fading_progress -= speed;
if (fading_progress < 0.f) {
fading_progress = 0.f;
fading_state = FADING_NONE;
finished = true;
set_visible(false);
}
p.x = 1.f - fading_progress;
p.y = 0.f;
break;
case FADING_NONE:
p.x = 1.f;
p.y = active ? 1.f : 0.f;
break;
default:
CRASH_NOW();
break;
}
if (_shader_material.is_valid()) {
_shader_material->set_shader_param(VoxelStringNames::get_singleton()->u_lod_fade, p);
}
return finished;
}

View File

@ -1,153 +0,0 @@
#ifndef VOXEL_BLOCK_H
#define VOXEL_BLOCK_H
#include "../constants/cube_tables.h"
#include "../storage/voxel_buffer.h"
#include "../util/godot/direct_mesh_instance.h"
#include "../util/godot/direct_static_body.h"
#include "voxel_viewer_ref_count.h"
//#define VOXEL_DEBUG_LOD_MATERIALS
class Spatial;
// Internal structure holding a reference to mesh visuals, physics and a block of voxel data.
class VoxelBlock {
public:
enum MeshState {
MESH_NEVER_UPDATED = 0, // TODO Redundant with MESH_NEED_UPDATE?
MESH_UP_TO_DATE,
MESH_NEED_UPDATE, // The mesh is out of date but was not yet scheduled for update
MESH_UPDATE_NOT_SENT, // The mesh is out of date and was scheduled for update, but no request have been sent yet
MESH_UPDATE_SENT // The mesh is out of date, and an update request was sent, pending response
};
enum FadingState {
FADING_NONE,
FADING_IN,
FADING_OUT
};
Ref<VoxelBuffer> voxels;
Vector3i position;
unsigned int lod_index = 0;
bool pending_transition_update = false;
VoxelViewerRefCount viewers;
bool got_first_mesh_update = false;
uint32_t last_collider_update_time = 0;
bool has_deferred_collider_update = false;
Vector<Array> deferred_collider_data;
FadingState fading_state = FADING_NONE;
float fading_progress = 0.f;
// Voxel LOD works by splitting a block into up to 8 higher-resolution blocks.
// The parent block and its children can be called a "LOD group".
// Only non-overlapping blocks in a LOD group can be considered active at once.
// So when LOD fading is used, we no longer use `visible` to find which block is active,
// because blocks can use a cross-fade effect. Overlapping blocks of the same LOD group can be visible at once.
// Hence the need to use this boolean.
bool active = false;
static VoxelBlock *create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int size, unsigned int p_lod_index);
~VoxelBlock();
void set_world(Ref<World> p_world);
// Visuals
void set_mesh(Ref<Mesh> mesh);
Ref<Mesh> get_mesh() const;
void set_transition_mesh(Ref<Mesh> mesh, int side);
bool has_mesh() const;
void drop_mesh();
void set_shader_material(Ref<ShaderMaterial> material);
inline Ref<ShaderMaterial> get_shader_material() const { return _shader_material; }
// Collisions
void set_collision_mesh(Vector<Array> surface_arrays, bool debug_collision, Spatial *node);
void drop_collision();
// TODO Collision layer and mask
// State
void set_mesh_state(MeshState ms);
MeshState get_mesh_state() const;
void set_visible(bool visible);
bool is_visible() const;
void set_parent_visible(bool parent_visible);
void set_parent_transform(const Transform &parent_transform);
void set_transition_mask(uint8_t m);
//void set_transition_bit(uint8_t side, bool value);
inline uint8_t get_transition_mask() const { return _transition_mask; }
void set_needs_lodding(bool need_lodding);
inline bool get_needs_lodding() const { return _needs_lodding; }
bool is_modified() const;
void set_modified(bool modified);
template <typename F>
void for_each_mesh_instance_with_transform(F f) const {
const Transform local_transform(Basis(), _position_in_voxels.to_vec3());
const Transform world_transform = local_transform;
f(_mesh_instance, world_transform);
for (unsigned int i = 0; i < _transition_mesh_instances.size(); ++i) {
const DirectMeshInstance &mi = _transition_mesh_instances[i];
if (mi.is_valid()) {
f(mi, world_transform);
}
}
}
bool update_fading(float speed);
private:
VoxelBlock();
void _set_visible(bool visible);
inline bool _is_transition_visible(int side) const { return _transition_mask & (1 << side); }
inline void set_mesh_instance_visible(DirectMeshInstance &mi, bool visible) {
if (visible) {
mi.set_world(*_world);
} else {
mi.set_world(nullptr);
}
}
private:
Vector3i _position_in_voxels;
Ref<ShaderMaterial> _shader_material;
DirectMeshInstance _mesh_instance;
FixedArray<DirectMeshInstance, Cube::SIDE_COUNT> _transition_mesh_instances;
DirectStaticBody _static_body;
Ref<World> _world;
#ifdef VOXEL_DEBUG_LOD_MATERIALS
Ref<Material> _debug_material;
Ref<Material> _debug_transition_material;
#endif
// Must match default value of `active`
bool _visible = false;
bool _parent_visible = true;
MeshState _mesh_state = MESH_NEVER_UPDATED;
uint8_t _transition_mask = 0;
// The block was edited, which requires its LOD counterparts to be recomputed
bool _needs_lodding = false;
// Indicates if this block is different from the time it was loaded (should be saved)
bool _modified = false;
};
#endif // VOXEL_BLOCK_H

View File

@ -1,7 +1,6 @@
#include "voxel_data_map.h"
#include "../constants/cube_tables.h"
#include "../util/macros.h"
#include "voxel_block.h"
#include <limits>
VoxelDataMap::VoxelDataMap() :

View File

@ -1,336 +0,0 @@
#include "voxel_map.h"
#include "../constants/cube_tables.h"
#include "../util/macros.h"
#include "voxel_block.h"
#include <limits>
VoxelMap::VoxelMap() :
_last_accessed_block(nullptr) {
// TODO Make it configurable in editor (with all necessary notifications and updatings!)
set_block_size_pow2(VoxelConstants::DEFAULT_BLOCK_SIZE_PO2);
_default_voxel.fill(0);
_default_voxel[VoxelBuffer::CHANNEL_SDF] = 255;
}
VoxelMap::~VoxelMap() {
PRINT_VERBOSE("Destroying VoxelMap");
clear();
}
void VoxelMap::create(unsigned int block_size_po2, int lod_index) {
clear();
set_block_size_pow2(block_size_po2);
set_lod_index(lod_index);
}
void VoxelMap::set_block_size_pow2(unsigned int p) {
ERR_FAIL_COND_MSG(p < 1, "Block size is too small");
ERR_FAIL_COND_MSG(p > 8, "Block size is too big");
_block_size_pow2 = p;
_block_size = 1 << _block_size_pow2;
_block_size_mask = _block_size - 1;
}
void VoxelMap::set_lod_index(int lod_index) {
ERR_FAIL_COND_MSG(lod_index < 0, "LOD index can't be negative");
ERR_FAIL_COND_MSG(lod_index >= 32, "LOD index is too big");
_lod_index = lod_index;
}
unsigned int VoxelMap::get_lod_index() const {
return _lod_index;
}
int VoxelMap::get_voxel(Vector3i pos, unsigned int c) const {
Vector3i bpos = voxel_to_block(pos);
const VoxelBlock *block = get_block(bpos);
if (block == nullptr) {
return _default_voxel[c];
}
RWLockRead lock(block->voxels->get_lock());
return block->voxels->get_voxel(to_local(pos), c);
}
VoxelBlock *VoxelMap::create_default_block(Vector3i bpos) {
Ref<VoxelBuffer> buffer(memnew(VoxelBuffer));
buffer->create(_block_size, _block_size, _block_size);
buffer->set_default_values(_default_voxel);
VoxelBlock *block = VoxelBlock::create(bpos, buffer, _block_size, _lod_index);
set_block(bpos, block);
return block;
}
VoxelBlock *VoxelMap::get_or_create_block_at_voxel_pos(Vector3i pos) {
Vector3i bpos = voxel_to_block(pos);
VoxelBlock *block = get_block(bpos);
if (block == nullptr) {
block = create_default_block(bpos);
}
return block;
}
void VoxelMap::set_voxel(int value, Vector3i pos, unsigned int c) {
VoxelBlock *block = get_or_create_block_at_voxel_pos(pos);
// TODO If it turns out to be a problem, use CoW
RWLockWrite lock(block->voxels->get_lock());
block->voxels->set_voxel(value, to_local(pos), c);
}
float VoxelMap::get_voxel_f(Vector3i pos, unsigned int c) const {
Vector3i bpos = voxel_to_block(pos);
const VoxelBlock *block = get_block(bpos);
if (block == nullptr) {
return _default_voxel[c];
}
Vector3i lpos = to_local(pos);
RWLockRead lock(block->voxels->get_lock());
return block->voxels->get_voxel_f(lpos.x, lpos.y, lpos.z, c);
}
void VoxelMap::set_voxel_f(real_t value, Vector3i pos, unsigned int c) {
VoxelBlock *block = get_or_create_block_at_voxel_pos(pos);
Vector3i lpos = to_local(pos);
RWLockWrite lock(block->voxels->get_lock());
block->voxels->set_voxel_f(value, lpos.x, lpos.y, lpos.z, 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->position == bpos) {
return _last_accessed_block;
}
unsigned int *iptr = _blocks_map.getptr(bpos);
if (iptr != nullptr) {
const unsigned int i = *iptr;
#ifdef DEBUG_ENABLED
CRASH_COND(i >= _blocks.size());
#endif
VoxelBlock *block = _blocks[i];
CRASH_COND(block == nullptr); // The map should not contain null blocks
_last_accessed_block = block;
return _last_accessed_block;
}
return nullptr;
}
const VoxelBlock *VoxelMap::get_block(Vector3i bpos) const {
if (_last_accessed_block != nullptr && _last_accessed_block->position == bpos) {
return _last_accessed_block;
}
const unsigned int *iptr = _blocks_map.getptr(bpos);
if (iptr != nullptr) {
const unsigned int i = *iptr;
#ifdef DEBUG_ENABLED
CRASH_COND(i >= _blocks.size());
#endif
// TODO This function can't cache _last_accessed_block, because it's const, so repeated accesses are hashing again...
const VoxelBlock *block = _blocks[i];
CRASH_COND(block == nullptr); // The map should not contain null blocks
return block;
}
return nullptr;
}
void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) {
ERR_FAIL_COND(block == nullptr);
CRASH_COND(bpos != block->position);
if (_last_accessed_block == nullptr || _last_accessed_block->position == bpos) {
_last_accessed_block = block;
}
#ifdef DEBUG_ENABLED
CRASH_COND(_blocks_map.has(bpos));
#endif
unsigned int i = _blocks.size();
_blocks.push_back(block);
_blocks_map.set(bpos, i);
}
void VoxelMap::remove_block_internal(Vector3i bpos, unsigned int index) {
// This function assumes the block is already freed
_blocks_map.erase(bpos);
VoxelBlock *moved_block = _blocks.back();
#ifdef DEBUG_ENABLED
CRASH_COND(index >= _blocks.size());
#endif
_blocks[index] = moved_block;
_blocks.pop_back();
if (index < _blocks.size()) {
unsigned int *moved_block_index = _blocks_map.getptr(moved_block->position);
CRASH_COND(moved_block_index == nullptr);
*moved_block_index = index;
}
}
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 == nullptr) {
block = VoxelBlock::create(bpos, *buffer, _block_size, _lod_index);
set_block(bpos, block);
} else {
block->voxels = buffer;
}
return block;
}
bool VoxelMap::has_block(Vector3i pos) const {
return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos);
}
bool VoxelMap::is_block_surrounded(Vector3i pos) const {
// TODO If that check proves to be too expensive with all blocks we deal with, cache it in VoxelBlocks
for (unsigned int i = 0; i < Cube::MOORE_NEIGHBORING_3D_COUNT; ++i) {
Vector3i bpos = pos + Cube::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 channels_mask) {
const Vector3i max_pos = min_pos + dst_buffer.get_size();
const Vector3i min_block_pos = voxel_to_block(min_pos);
const Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1, 1, 1)) + Vector3i(1, 1, 1);
const Vector3i block_size_v(_block_size, _block_size, _block_size);
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) {
for (unsigned int channel = 0; channel < VoxelBuffer::MAX_CHANNELS; ++channel) {
if (((1 << channel) & channels_mask) == 0) {
continue;
}
const VoxelBlock *block = get_block(bpos);
if (block != nullptr) {
const VoxelBuffer &src_buffer = **block->voxels;
dst_buffer.set_channel_depth(channel, src_buffer.get_channel_depth(channel));
const Vector3i offset = block_to_voxel(bpos);
RWLockRead lock(src_buffer.get_lock());
// 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 {
// For now, inexistent blocks default to hardcoded defaults, corresponding to "empty space".
// If we want to change this, we may have to add an API for that.
const Vector3i offset = block_to_voxel(bpos);
dst_buffer.fill_area(
_default_voxel[channel],
offset - min_pos,
offset - min_pos + block_size_v,
channel);
}
}
}
}
}
}
void VoxelMap::paste(Vector3i min_pos, VoxelBuffer &src_buffer, unsigned int channels_mask, uint64_t mask_value,
bool create_new_blocks) {
const Vector3i max_pos = min_pos + src_buffer.get_size();
const Vector3i min_block_pos = voxel_to_block(min_pos);
const Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1, 1, 1)) + Vector3i(1, 1, 1);
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) {
for (unsigned int channel = 0; channel < VoxelBuffer::MAX_CHANNELS; ++channel) {
if (((1 << channel) & channels_mask) == 0) {
continue;
}
VoxelBlock *block = get_block(bpos);
if (block == nullptr) {
if (create_new_blocks) {
block = create_default_block(bpos);
} else {
continue;
}
}
const Vector3i offset = block_to_voxel(bpos);
VoxelBuffer &dst_buffer = **block->voxels;
RWLockWrite lock(dst_buffer.get_lock());
if (mask_value != std::numeric_limits<uint64_t>::max()) {
dst_buffer.read_write_action(Rect3i(offset - min_pos, src_buffer.get_size()), channel,
[&src_buffer, mask_value, offset, channel](const Vector3i pos, uint64_t dst_v) {
const uint64_t src_v = src_buffer.get_voxel(pos + offset, channel);
if (src_v == mask_value) {
return dst_v;
}
return src_v;
});
} else {
dst_buffer.copy_from(src_buffer,
offset - min_pos,
offset - max_pos,
min_pos - offset,
channel);
}
}
}
}
}
}
void VoxelMap::clear() {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
VoxelBlock *block = *it;
if (block == nullptr) {
ERR_PRINT("Unexpected nullptr in VoxelMap::clear()");
} else {
memdelete(block);
}
}
_blocks.clear();
_blocks_map.clear();
_last_accessed_block = nullptr;
}
int VoxelMap::get_block_count() const {
#ifdef DEBUG_ENABLED
const unsigned int blocks_map_size = _blocks_map.size();
CRASH_COND(_blocks.size() != blocks_map_size);
#endif
return _blocks.size();
}
bool VoxelMap::is_area_fully_loaded(const Rect3i voxels_box) const {
Rect3i block_box = voxels_box.downscaled(get_block_size());
return block_box.all_cells_match([this](Vector3i pos) {
return has_block(pos);
});
}

View File

@ -1,146 +0,0 @@
#ifndef VOXEL_MAP_H
#define VOXEL_MAP_H
#include "../util/fixed_array.h"
#include "voxel_block.h"
#include <core/hash_map.h>
#include <scene/main/node.h>
// Infinite voxel storage by means of octants like Gridmap, within a constant LOD.
// Convenience functions to access VoxelBuffers internally will lock them to protect against multithreaded access.
// However, the map itself is not thread-safe.
class VoxelMap {
public:
// Converts voxel coodinates into block coordinates.
// Don't use division because it introduces an offset in negative coordinates.
static _FORCE_INLINE_ Vector3i voxel_to_block_b(Vector3i pos, int block_size_pow2) {
return pos >> block_size_pow2;
}
_FORCE_INLINE_ Vector3i voxel_to_block(Vector3i pos) const {
return voxel_to_block_b(pos, _block_size_pow2);
}
_FORCE_INLINE_ Vector3i to_local(Vector3i pos) const {
return Vector3i(
pos.x & _block_size_mask,
pos.y & _block_size_mask,
pos.z & _block_size_mask);
}
// Converts block coodinates into voxel coordinates
_FORCE_INLINE_ Vector3i block_to_voxel(Vector3i bpos) const {
return bpos * _block_size;
}
VoxelMap();
~VoxelMap();
void create(unsigned int block_size_po2, int lod_index);
_FORCE_INLINE_ unsigned int get_block_size() const { return _block_size; }
_FORCE_INLINE_ unsigned int get_block_size_pow2() const { return _block_size_pow2; }
_FORCE_INLINE_ unsigned int get_block_size_mask() const { return _block_size_mask; }
void set_lod_index(int lod_index);
unsigned int get_lod_index() const;
int get_voxel(Vector3i pos, unsigned int c = 0) const;
void set_voxel(int value, Vector3i pos, unsigned int c = 0);
float get_voxel_f(Vector3i pos, unsigned int c = VoxelBuffer::CHANNEL_SDF) const;
void set_voxel_f(real_t value, Vector3i pos, unsigned int c = VoxelBuffer::CHANNEL_SDF);
void set_default_voxel(int value, unsigned int channel = 0);
int get_default_voxel(unsigned int channel = 0);
// 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 channels_mask);
void paste(Vector3i min_pos, VoxelBuffer &src_buffer, unsigned int channels_mask, uint64_t mask_value,
bool create_new_blocks);
// Moves the given buffer into a block of the map. The buffer is referenced, no copy is made.
VoxelBlock *set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
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->position == bpos) {
_last_accessed_block = nullptr;
}
unsigned int *iptr = _blocks_map.getptr(bpos);
if (iptr != nullptr) {
const unsigned int i = *iptr;
#ifdef DEBUG_ENABLED
CRASH_COND(i >= _blocks.size());
#endif
VoxelBlock *block = _blocks[i];
ERR_FAIL_COND(block == nullptr);
pre_delete(block);
memdelete(block);
remove_block_internal(bpos, i);
}
}
VoxelBlock *get_block(Vector3i bpos);
const VoxelBlock *get_block(Vector3i bpos) const;
bool has_block(Vector3i pos) const;
bool is_block_surrounded(Vector3i pos) const;
void clear();
int get_block_count() const;
// TODO Rename for_each_block
template <typename Op_T>
inline void for_all_blocks(Op_T op) {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
op(*it);
}
}
// TODO Rename for_each_block
template <typename Op_T>
inline void for_all_blocks(Op_T op) const {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
op(*it);
}
}
bool is_area_fully_loaded(const Rect3i voxels_box) const;
private:
void set_block(Vector3i bpos, VoxelBlock *block);
VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
VoxelBlock *create_default_block(Vector3i bpos);
void remove_block_internal(Vector3i bpos, unsigned int index);
void set_block_size_pow2(unsigned int p);
private:
// Voxel values that will be returned if access is out of map bounds
FixedArray<uint64_t, VoxelBuffer::MAX_CHANNELS> _default_voxel;
// Blocks stored with a spatial hash in all 3D directions.
// RELATIONSHIP = 2 because it delivers better performance with this kind of key and hash (less collisions).
HashMap<Vector3i, unsigned int, Vector3iHasher, HashMapComparatorDefault<Vector3i>, 3, 2> _blocks_map;
std::vector<VoxelBlock *> _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.
mutable VoxelBlock *_last_accessed_block;
unsigned int _block_size;
unsigned int _block_size_pow2;
unsigned int _block_size_mask;
unsigned int _lod_index = 0;
};
#endif // VOXEL_MAP_H

View File

@ -8,8 +8,6 @@
#include "../util/macros.h"
#include "../util/profiling.h"
#include "../util/profiling_clock.h"
#include "voxel_block.h"
#include "voxel_map.h"
#include <core/core_string_names.h>
#include <core/engine.h>