Removed VoxelMap and VoxelBlock, superseded by their specialized versions
This commit is contained in:
parent
63d9361a87
commit
96bcc44d00
@ -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))));
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -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() :
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user