Allow to use block size 32 for mesh blocks while using 16 for data blocks

Still hardcoded but a config should be added
This commit is contained in:
Marc Gilleron 2021-04-03 20:39:37 +01:00
parent 24428d5af9
commit 758e3bd227
28 changed files with 2358 additions and 537 deletions

View File

@ -9,6 +9,11 @@ namespace VoxelConstants {
static const float MINIMUM_LOD_DISTANCE = 16.f;
static const float MAXIMUM_LOD_DISTANCE = 128.f;
static const unsigned int MIN_BLOCK_SIZE = 16;
static const unsigned int MAX_BLOCK_SIZE = 32;
static const unsigned int MAX_BLOCK_COUNT_PER_REQUEST = 4 * 4 * 4;
static const unsigned int MAX_LOD = 32;
static const unsigned int MAX_VOLUME_EXTENT = 0x1fffffff;
static const unsigned int MAX_VOLUME_SIZE = 2 * MAX_VOLUME_EXTENT; // 1,073,741,822 voxels

View File

@ -1,9 +1,9 @@
#include "voxel_tool_lod_terrain.h"
#include "../terrain/voxel_data_map.h"
#include "../terrain/voxel_lod_terrain.h"
#include "../terrain/voxel_map.h"
#include "../util/voxel_raycast.h"
VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelMap &map) :
VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelDataMap &map) :
_terrain(terrain), _map(&map) {
ERR_FAIL_COND(terrain == nullptr);
// At the moment, only LOD0 is supported.
@ -85,7 +85,7 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
// TODO Implement broad-phase on blocks to minimize locking and increase performance
struct RaycastPredicate {
const VoxelMap &map;
const VoxelDataMap &map;
bool operator()(Vector3i pos) {
// This is not particularly optimized, but runs fast enough for player raycasts
@ -126,7 +126,7 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
if (_raycast_binary_search_iterations > 0) {
// This is not particularly optimized, but runs fast enough for player raycasts
struct VolumeSampler {
const VoxelMap &map;
const VoxelDataMap &map;
inline float operator()(const Vector3i &pos) {
return map.get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);

View File

@ -4,13 +4,13 @@
#include "voxel_tool.h"
class VoxelLodTerrain;
class VoxelMap;
class VoxelDataMap;
class VoxelToolLodTerrain : public VoxelTool {
GDCLASS(VoxelToolLodTerrain, VoxelTool)
public:
VoxelToolLodTerrain() {}
VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelMap &map);
VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelDataMap &map);
bool is_area_editable(const Rect3i &box) const override;
Ref<VoxelRaycastResult> raycast(Vector3 pos, Vector3 dir, float max_distance, uint32_t collision_mask) override;
@ -29,7 +29,7 @@ private:
static void _bind_methods();
VoxelLodTerrain *_terrain = nullptr;
VoxelMap *_map = nullptr;
VoxelDataMap *_map = nullptr;
int _raycast_binary_search_iterations = 0;
};

View File

@ -163,9 +163,14 @@ void VoxelServer::set_volume_transform(uint32_t volume_id, Transform t) {
volume.transform = t;
}
void VoxelServer::set_volume_block_size(uint32_t volume_id, uint32_t block_size) {
void VoxelServer::set_volume_render_block_size(uint32_t volume_id, uint32_t block_size) {
Volume &volume = _world.volumes.get(volume_id);
volume.block_size = block_size;
volume.render_block_size = block_size;
}
void VoxelServer::set_volume_data_block_size(uint32_t volume_id, uint32_t block_size) {
Volume &volume = _world.volumes.get(volume_id);
volume.data_block_size = block_size;
}
void VoxelServer::set_volume_stream(uint32_t volume_id, Ref<VoxelStream> stream) {
@ -220,10 +225,10 @@ static inline Vector3i get_block_center(Vector3i pos, int bs, int lod) {
}
void VoxelServer::init_priority_dependency(
VoxelServer::PriorityDependency &dep, Vector3i block_position, uint8_t lod, const Volume &volume) {
VoxelServer::PriorityDependency &dep, Vector3i block_position, uint8_t lod, const Volume &volume, int block_size) {
const Vector3i voxel_pos = get_block_center(block_position, volume.block_size, lod);
const float block_radius = (volume.block_size << lod) / 2;
const Vector3i voxel_pos = get_block_center(block_position, block_size, lod);
const float block_radius = (block_size << lod) / 2;
dep.shared = _world.shared_priority_dependency;
dep.world_position = volume.transform.xform(voxel_pos.to_vec3());
const float transformed_block_radius =
@ -241,7 +246,7 @@ void VoxelServer::init_priority_dependency(
// This does not depend on viewer's view distance, but on LOD precision instead.
dep.drop_distance_squared =
squared(2.f * transformed_block_radius *
get_octree_lod_block_region_extent(volume.octree_lod_distance, volume.block_size));
get_octree_lod_block_region_extent(volume.octree_lod_distance, block_size));
break;
default:
@ -257,11 +262,12 @@ void VoxelServer::request_block_mesh(uint32_t volume_id, BlockMeshInput &input)
BlockMeshRequest *r = memnew(BlockMeshRequest);
r->volume_id = volume_id;
r->blocks = input.blocks;
r->blocks_count = input.blocks_count;
r->position = input.position;
r->lod = input.lod;
r->meshing_dependency = volume.meshing_dependency;
init_priority_dependency(r->priority_dependency, input.position, input.lod, volume);
init_priority_dependency(r->priority_dependency, input.position, input.lod, volume, volume.render_block_size);
// We'll allocate this quite often. If it becomes a problem, it should be easy to pool.
_meshing_thread_pool.enqueue(r);
@ -277,11 +283,11 @@ void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_LOAD;
r->block_size = volume.block_size;
r->block_size = volume.data_block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = request_instances;
init_priority_dependency(r->priority_dependency, block_pos, lod, volume);
init_priority_dependency(r->priority_dependency, block_pos, lod, volume, volume.data_block_size);
_streaming_thread_pool.enqueue(r);
@ -293,10 +299,10 @@ void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.block_size = volume.block_size;
r.block_size = volume.data_block_size;
r.stream_dependency = volume.stream_dependency;
init_priority_dependency(r.priority_dependency, block_pos, lod, volume);
init_priority_dependency(r.priority_dependency, block_pos, lod, volume, volume.data_block_size);
BlockGenerateRequest *rp = memnew(BlockGenerateRequest(r));
_generation_thread_pool.enqueue(rp);
@ -314,7 +320,7 @@ void VoxelServer::request_voxel_block_save(uint32_t volume_id, Ref<VoxelBuffer>
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_SAVE;
r->block_size = volume.block_size;
r->block_size = volume.data_block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = false;
r->request_voxels = true;
@ -337,7 +343,7 @@ void VoxelServer::request_instance_block_save(uint32_t volume_id, std::unique_pt
r->position = block_pos;
r->lod = lod;
r->type = BlockDataRequest::TYPE_SAVE;
r->block_size = volume.block_size;
r->block_size = volume.data_block_size;
r->stream_dependency = volume.stream_dependency;
r->request_instances = true;
r->request_voxels = false;
@ -775,11 +781,15 @@ bool VoxelServer::BlockGenerateRequest::is_cancelled() {
//----------------------------------------------------------------------------------------------------------------------
static void copy_block_and_neighbors(const FixedArray<Ref<VoxelBuffer>, Cube::MOORE_AREA_3D_COUNT> &moore_blocks,
VoxelBuffer &dst, int min_padding, int max_padding, int channels_mask) {
// Takes a list of blocks and interprets it as a cube of blocks centered around the area we want to create a mesh from.
// Voxels from central blocks are copied, and part of side blocks are also copied so we get a temporary buffer
// which includes enough neighbors for the mesher to avoid doing bound checks.
static void copy_block_and_neighbors(ArraySlice<Ref<VoxelBuffer> > blocks, VoxelBuffer &dst,
int min_padding, int max_padding, int channels_mask) {
VOXEL_PROFILE_SCOPE();
// Extract wanted channels in a list
FixedArray<uint8_t, VoxelBuffer::MAX_CHANNELS> channels;
unsigned int channels_count = 0;
for (unsigned int i = 0; i < VoxelBuffer::MAX_CHANNELS; ++i) {
@ -789,36 +799,64 @@ static void copy_block_and_neighbors(const FixedArray<Ref<VoxelBuffer>, Cube::MO
}
}
Ref<VoxelBuffer> central_buffer = moore_blocks[Cube::MOORE_AREA_3D_CENTRAL_INDEX];
CRASH_COND_MSG(central_buffer.is_null(), "Central buffer must be valid");
const int block_size = central_buffer->get_size().x;
const unsigned int padded_block_size = block_size + min_padding + max_padding;
// Determine size of the cube of blocks
int edge_size;
int mesh_block_size_factor;
switch (blocks.size()) {
case 3 * 3 * 3:
edge_size = 3;
mesh_block_size_factor = 1;
break;
case 4 * 4 * 4:
edge_size = 4;
mesh_block_size_factor = 2;
break;
default:
ERR_FAIL_MSG("Unsupported block count");
}
dst.create(padded_block_size, padded_block_size, padded_block_size);
// Pick anchor block, usually within the central part of the cube (that block must be valid)
const unsigned int anchor_buffer_index = edge_size * edge_size + edge_size + 1;
Ref<VoxelBuffer> central_buffer = blocks[anchor_buffer_index];
ERR_FAIL_COND_MSG(central_buffer.is_null(), "Central buffer must be valid");
ERR_FAIL_COND_MSG(central_buffer->get_size().all_members_equal() == false, "Central buffer must be cubic");
const int data_block_size = central_buffer->get_size().x;
const int mesh_block_size = data_block_size * mesh_block_size_factor;
const int padded_mesh_block_size = mesh_block_size + min_padding + max_padding;
dst.create(padded_mesh_block_size, padded_mesh_block_size, padded_mesh_block_size);
for (unsigned int ci = 0; ci < channels.size(); ++ci) {
dst.set_channel_depth(ci, central_buffer->get_channel_depth(ci));
}
const Vector3i min_pos = -Vector3i(min_padding);
const Vector3i max_pos = Vector3i(block_size + max_padding);
const Vector3i max_pos = Vector3i(mesh_block_size + max_padding);
for (unsigned int i = 0; i < Cube::MOORE_AREA_3D_COUNT; ++i) {
const Vector3i offset = block_size * Cube::g_ordered_moore_area_3d[i];
Ref<VoxelBuffer> src = moore_blocks[i];
// Using ZXY as convention to reconstruct positions with thread locking consistency
unsigned int i = 0;
for (int z = -1; z < edge_size - 1; ++z) {
for (int x = -1; x < edge_size - 1; ++x) {
for (int y = -1; y < edge_size - 1; ++y) {
const Vector3i offset = data_block_size * Vector3i(x, y, z);
Ref<VoxelBuffer> src = blocks[i];
++i;
if (src.is_null()) {
continue;
}
if (src.is_null()) {
continue;
}
const Vector3i src_min = min_pos - offset;
const Vector3i src_max = max_pos - offset;
const Vector3i dst_min = offset - min_pos;
const Vector3i src_min = min_pos - offset;
const Vector3i src_max = max_pos - offset;
const Vector3i dst_min = offset - min_pos;
{
RWLockRead read(src->get_lock());
for (unsigned int ci = 0; ci < channels.size(); ++ci) {
dst.copy_from(**src, src_min, src_max, dst_min, ci);
{
RWLockRead read(src->get_lock());
for (unsigned int ci = 0; ci < channels.size(); ++ci) {
dst.copy_from(**src, src_min, src_max, dst_min, channels[ci]);
}
}
}
}
}
@ -836,7 +874,8 @@ void VoxelServer::BlockMeshRequest::run(VoxelTaskContext ctx) {
// TODO Cache?
Ref<VoxelBuffer> voxels;
voxels.instance();
copy_block_and_neighbors(blocks, **voxels, min_padding, max_padding, mesher->get_used_channels_mask());
copy_block_and_neighbors(to_slice(blocks, blocks_count),
**voxels, min_padding, max_padding, mesher->get_used_channels_mask());
VoxelMesher::Input input = { **voxels, lod };

View File

@ -46,7 +46,8 @@ public:
struct BlockMeshInput {
// Moore area ordered by forward XYZ iteration
FixedArray<Ref<VoxelBuffer>, Cube::MOORE_AREA_3D_COUNT> blocks;
FixedArray<Ref<VoxelBuffer>, VoxelConstants::MAX_BLOCK_COUNT_PER_REQUEST> blocks;
unsigned int blocks_count = 0;
Vector3i position;
uint8_t lod = 0;
};
@ -84,7 +85,8 @@ public:
// TODO Rename functions to C convention
uint32_t add_volume(ReceptionBuffers *buffers, VolumeType type);
void set_volume_transform(uint32_t volume_id, Transform t);
void set_volume_block_size(uint32_t volume_id, uint32_t block_size);
void set_volume_render_block_size(uint32_t volume_id, uint32_t block_size);
void set_volume_data_block_size(uint32_t volume_id, uint32_t block_size);
void set_volume_stream(uint32_t volume_id, Ref<VoxelStream> stream);
void set_volume_generator(uint32_t volume_id, Ref<VoxelGenerator> generator);
void set_volume_mesher(uint32_t volume_id, Ref<VoxelMesher> mesher);
@ -204,7 +206,8 @@ private:
Ref<VoxelStream> stream;
Ref<VoxelGenerator> generator;
Ref<VoxelMesher> mesher;
uint32_t block_size = 16;
uint32_t render_block_size = 16;
uint32_t data_block_size = 16;
float octree_lod_distance = 0;
std::shared_ptr<StreamingDependency> stream_dependency;
std::shared_ptr<MeshingDependency> meshing_dependency;
@ -234,7 +237,8 @@ private:
float drop_distance_squared;
};
void init_priority_dependency(PriorityDependency &dep, Vector3i block_position, uint8_t lod, const Volume &volume);
void init_priority_dependency(PriorityDependency &dep, Vector3i block_position, uint8_t lod, const Volume &volume,
int block_size);
static int get_priority(const PriorityDependency &dep, uint8_t lod_index, float *out_closest_distance_sq);
class BlockDataRequest : public IVoxelTask {
@ -288,10 +292,11 @@ private:
int get_priority() override;
bool is_cancelled() override;
FixedArray<Ref<VoxelBuffer>, Cube::MOORE_AREA_3D_COUNT> blocks;
FixedArray<Ref<VoxelBuffer>, VoxelConstants::MAX_BLOCK_COUNT_PER_REQUEST> blocks;
Vector3i position;
uint32_t volume_id;
uint8_t lod;
uint8_t blocks_count;
bool has_run = false;
bool too_far = false;
PriorityDependency priority_dependency;

View File

@ -198,6 +198,9 @@ public:
// This returns that scale for a given depth configuration.
static float get_sdf_quantization_scale(Depth d);
// TODO Switch to using GPU format inorm16 for these conversions
// The current ones seem to work but aren't really correct
static inline float u8_to_norm(uint8_t v) {
return (static_cast<float>(v) - 0x7f) * VoxelConstants::INV_0x7f;
}

View File

@ -9,6 +9,8 @@ namespace {
const uint32_t TRAILING_MAGIC = 0x900df00d;
}
// TODO Unify with functions from VoxelBuffer?
inline uint8_t norm_to_u8(float x) {
return clamp(static_cast<int>(128.f * x + 128.f), 0, 0xff);
}

View File

@ -8,11 +8,6 @@
namespace {
const float MAX_DENSITY = 1.f;
const char *DENSITY_HINT_STRING = "0.0, 1.0, 0.01";
thread_local std::vector<Vector3> g_vertex_cache;
thread_local std::vector<Vector3> g_normal_cache;
thread_local std::vector<float> g_noise_cache;
} // namespace
static inline Vector3 normalized(Vector3 pos, float &length) {
@ -33,7 +28,9 @@ void VoxelInstanceGenerator::generate_transforms(
int layer_id,
Array surface_arrays,
const Transform &block_local_transform,
UpMode up_mode) {
UpMode up_mode,
uint8_t octant_mask,
float block_size) {
VOXEL_PROFILE_SCOPE();
@ -70,6 +67,10 @@ void VoxelInstanceGenerator::generate_transforms(
// TODO This part might be moved to the meshing thread if it turns out to be too heavy
static thread_local std::vector<Vector3> g_vertex_cache;
static thread_local std::vector<Vector3> g_normal_cache;
static thread_local std::vector<float> g_noise_cache;
std::vector<Vector3> &vertex_cache = g_vertex_cache;
std::vector<Vector3> &normal_cache = g_normal_cache;
@ -83,6 +84,7 @@ void VoxelInstanceGenerator::generate_transforms(
PoolVector3Array::Read vertices_r = vertices.read();
PoolVector3Array::Read normals_r = normals.read();
// Generate base positions
switch (_emit_mode) {
case EMIT_FROM_VERTICES: {
// Density is interpreted differently here,
@ -147,8 +149,25 @@ void VoxelInstanceGenerator::generate_transforms(
}
}
// Filter out by octants
// This is done so some octants can be filled with user-edited data instead,
// because mesh size may not necessarily match data block size
if ((octant_mask & 0xff) != 0xff) {
const float h = block_size / 2.f;
for (unsigned int i = 0; i < vertex_cache.size(); ++i) {
const Vector3 &pos = vertex_cache[i];
const uint8_t octant_index = get_octant_index(pos, h);
if ((octant_mask & (1 << octant_index)) == 0) {
unordered_remove(vertex_cache, i);
unordered_remove(normal_cache, i);
--i;
}
}
}
std::vector<float> &noise_cache = g_noise_cache;
// Filter out by noise
if (_noise.is_valid()) {
noise_cache.clear();
@ -199,7 +218,7 @@ void VoxelInstanceGenerator::generate_transforms(
const float min_height = _min_height;
const float max_height = _max_height;
// Calculate orientations
// Calculate orientations and scales
for (size_t vertex_index = 0; vertex_index < vertex_cache.size(); ++vertex_index) {
Transform t;
t.origin = vertex_cache[vertex_index];

View File

@ -58,7 +58,9 @@ public:
int layer_id,
Array surface_arrays,
const Transform &block_local_transform,
UpMode up_mode);
UpMode up_mode,
uint8_t octant_mask,
float block_size);
void set_density(float d);
float get_density() const;
@ -107,6 +109,10 @@ public:
void set_noise_on_scale(float amount);
float get_noise_on_scale() const;
static inline int get_octant_index(const Vector3 pos, float half_block_size) {
return (pos.x > half_block_size) | ((pos.y > half_block_size) << 1) | ((pos.z > half_block_size) << 2);
}
private:
void _on_noise_changed();

View File

@ -1,6 +1,7 @@
#include "voxel_instancer.h"
#include "../../edition/voxel_tool.h"
#include "../../util/funcs.h"
#include "../../util/godot/funcs.h"
#include "../../util/macros.h"
#include "../../util/profiling.h"
#include "../voxel_lod_terrain.h"
@ -20,8 +21,12 @@ public:
set_mode(RigidBody::MODE_STATIC);
}
void set_block_index(int block_index) {
_block_index = block_index;
void set_data_block_position(Vector3i data_block_position) {
_data_block_position = data_block_position;
}
void set_render_block_index(unsigned int render_block_index) {
_render_block_index = render_block_index;
}
void set_instance_index(int instance_index) {
@ -49,7 +54,7 @@ protected:
// The user could queue_free() that node in game,
// so we have to notify the instancer to remove the multimesh instance and pointer
if (_parent != nullptr) {
_parent->on_body_removed(_block_index, _instance_index);
_parent->on_body_removed(_data_block_position, _render_block_index, _instance_index);
_parent = nullptr;
}
break;
@ -58,7 +63,8 @@ protected:
private:
VoxelInstancer *_parent = nullptr;
int _block_index = -1;
Vector3i _data_block_position;
unsigned int _render_block_index;
int _instance_index = -1;
};
@ -162,7 +168,7 @@ void VoxelInstancer::_notification(int p_what) {
}
const Transform parent_transform = get_global_transform();
const int base_block_size_po2 = _parent->get_block_size_pow2();
const int base_block_size_po2 = _parent->get_mesh_block_size_pow2();
//print_line(String("IP: {0}").format(varray(parent_transform.origin)));
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
@ -217,12 +223,12 @@ void VoxelInstancer::process_mesh_lods() {
const Vector3 cam_pos_local = (gtrans.affine_inverse() * camera->get_global_transform()).origin;
ERR_FAIL_COND(_parent == nullptr);
const int block_size = _parent->get_block_size();
const int block_size = _parent->get_mesh_block_size();
// Hardcoded LOD thresholds for now.
// Can't really use pixel density because view distances are controlled by the main surface LOD octree
{
const int block_region_extent = _parent->get_block_region_extent();
const int block_region_extent = _parent->get_mesh_block_region_extent();
FixedArray<float, 4> coeffs;
coeffs[0] = 0;
@ -389,25 +395,76 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
}
}
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
ERR_FAIL_COND(render_to_data_factor <= 0 || render_to_data_factor > 2);
struct L {
// Does not return a bool so it can be used in bit-shifting operations without a compiler warning.
// Can be treated like a bool too.
static inline uint8_t has_edited_block(const Lod &lod, Vector3i pos) {
return lod.modified_blocks.has(pos) ||
lod.loaded_instances_data.find(pos) != lod.loaded_instances_data.end();
}
static inline void extract_octant_transforms(
const Block &render_block, std::vector<Transform> &dst, uint8_t octant_mask, int render_block_size) {
if (!render_block.multimesh_instance.is_valid()) {
return;
}
Ref<MultiMesh> multimesh = render_block.multimesh_instance.get_multimesh();
ERR_FAIL_COND(multimesh.is_null());
const int instance_count = get_visible_instance_count(**multimesh);
const float h = render_block_size / 2;
for (int i = 0; i < instance_count; ++i) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform t = multimesh->get_instance_transform(i);
const uint8_t octant_index = VoxelInstanceGenerator::get_octant_index(t.origin, h);
if ((octant_mask & (1 << octant_index)) != 0) {
dst.push_back(t);
}
}
}
};
// Update existing blocks
for (size_t block_index = 0; block_index < _blocks.size(); ++block_index) {
Block *block = _blocks[block_index];
CRASH_COND(block == nullptr);
if (block->layer_id != layer_id) {
continue;
}
const int lod_index = block->lod_index;
const Lod &lod = _lods[lod_index];
if (lod.modified_blocks.has(block->grid_position) ||
lod.loaded_instances_data.find(block->grid_position) != lod.loaded_instances_data.end()) {
// Was authored, no regen on this
continue;
uint8_t octant_mask = 0xff;
if (render_to_data_factor == 1) {
if (L::has_edited_block(lod, block->grid_position)) {
// Was edited, no regen on this
continue;
}
} else if (render_to_data_factor == 2) {
// The rendering block corresponds to 8 smaller data blocks
octant_mask = 0;
const Vector3i data_pos0 = block->grid_position * render_to_data_factor;
octant_mask |= L::has_edited_block(lod, Vector3i(data_pos0.x, data_pos0.y, data_pos0.z));
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x + 1, data_pos0.y, data_pos0.z)) << 1);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x, data_pos0.y + 1, data_pos0.z)) << 2);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x + 1, data_pos0.y + 1, data_pos0.z)) << 3);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x, data_pos0.y, data_pos0.z + 1)) << 4);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x + 1, data_pos0.y, data_pos0.z + 1)) << 5);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x, data_pos0.y + 1, data_pos0.z + 1)) << 6);
octant_mask |= (L::has_edited_block(lod, Vector3i(data_pos0.x + 1, data_pos0.y + 1, data_pos0.z + 1)) << 7);
if (octant_mask == 0) {
// All data blocks were edited, no regen on the whole render block
continue;
}
}
_transform_cache.clear();
Array surface_arrays = _parent->get_block_surface(block->grid_position, lod_index);
Array surface_arrays = _parent->get_mesh_block_surface(block->grid_position, lod_index);
const int lod_block_size = _parent->get_block_size() << lod_index;
const int lod_block_size = _parent->get_mesh_block_size() << lod_index;
const Transform block_local_transform =
Transform(Basis(), (block->grid_position * lod_block_size).to_vec3());
const Transform block_transform = parent_transform * block_local_transform;
@ -419,7 +476,16 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
layer_id,
surface_arrays,
block_local_transform,
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode));
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode),
octant_mask,
lod_block_size);
if (render_to_data_factor == 2 && octant_mask != 0xff) {
// Complete transforms with edited ones
L::extract_octant_transforms(*block, _transform_cache, ~octant_mask, _parent->get_mesh_block_size());
// TODO What if these blocks had loaded data which wasn't yet uploaded for render?
// We may setup a local transform list as well since it's expensive to get it from VisualServer
}
update_block_from_transforms(block_index, to_slice_const(_transform_cache),
block->grid_position, layer, *item, layer_id, world, block_transform);
@ -577,7 +643,7 @@ void VoxelInstancer::remove_block(unsigned int block_index) {
}
}
void VoxelInstancer::on_block_data_loaded(Vector3i grid_position, unsigned int lod_index,
void VoxelInstancer::on_data_block_loaded(Vector3i grid_position, unsigned int lod_index,
std::unique_ptr<VoxelInstanceBlockData> instances) {
ERR_FAIL_COND(lod_index >= _lods.size());
@ -585,29 +651,14 @@ void VoxelInstancer::on_block_data_loaded(Vector3i grid_position, unsigned int l
lod.loaded_instances_data.insert(std::make_pair(grid_position, std::move(instances)));
}
void VoxelInstancer::on_block_enter(Vector3i grid_position, unsigned int lod_index, Array surface_arrays) {
void VoxelInstancer::on_mesh_block_enter(Vector3i render_grid_position, unsigned int lod_index, Array surface_arrays) {
if (lod_index >= _lods.size()) {
return;
}
Lod &lod = _lods[lod_index];
auto it = lod.loaded_instances_data.find(grid_position);
const VoxelInstanceBlockData *instances_data = nullptr;
if (it != lod.loaded_instances_data.end()) {
instances_data = it->second.get();
ERR_FAIL_COND(instances_data == nullptr);
// We could do this:
// lod.loaded_instances_data.erase(grid_position);
// but we'd loose the information that this block was edited, so keeping it for now
}
create_blocks(instances_data, grid_position, lod_index, surface_arrays);
create_render_blocks(render_grid_position, lod_index, surface_arrays);
}
void VoxelInstancer::on_block_exit(Vector3i grid_position, unsigned int lod_index) {
void VoxelInstancer::on_mesh_block_exit(Vector3i render_grid_position, unsigned int lod_index) {
if (lod_index >= _lods.size()) {
return;
}
@ -616,21 +667,33 @@ void VoxelInstancer::on_block_exit(Vector3i grid_position, unsigned int lod_inde
Lod &lod = _lods[lod_index];
// If we loaded data there but it was never used, we'll unload it either way
lod.loaded_instances_data.erase(grid_position);
// Remove data blocks
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const Vector3i data_min_pos = render_grid_position * render_to_data_factor;
const Vector3i data_max_pos = data_min_pos + Vector3i(render_to_data_factor);
Vector3i data_grid_pos;
for (data_grid_pos.z = data_min_pos.z; data_grid_pos.z < data_max_pos.z; ++data_grid_pos.z) {
for (data_grid_pos.y = data_min_pos.y; data_grid_pos.y < data_max_pos.y; ++data_grid_pos.y) {
for (data_grid_pos.x = data_min_pos.x; data_grid_pos.x < data_max_pos.x; ++data_grid_pos.x) {
// If we loaded data there but it was never used, we'll unload it either way
lod.loaded_instances_data.erase(data_grid_pos);
if (lod.modified_blocks.has(grid_position)) {
save_block(grid_position, lod_index);
lod.modified_blocks.erase(grid_position);
if (lod.modified_blocks.has(data_grid_pos)) {
save_block(data_grid_pos, lod_index);
lod.modified_blocks.erase(data_grid_pos);
}
}
}
}
// Remove render blocks
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
Layer *layer = get_layer(layer_id);
CRASH_COND(layer == nullptr);
const unsigned int *block_index_ptr = layer->blocks.getptr(grid_position);
const unsigned int *block_index_ptr = layer->blocks.getptr(render_grid_position);
if (block_index_ptr != nullptr) {
remove_block(*block_index_ptr);
}
@ -721,9 +784,12 @@ void VoxelInstancer::update_block_from_transforms(int block_index, ArraySlice<co
if (collision_shapes.size() > 0) {
VOXEL_PROFILE_SCOPE();
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
// Add new bodies
for (unsigned int instance_index = 0; instance_index < transforms.size(); ++instance_index) {
const Transform body_transform = block_transform * transforms[instance_index];
const Transform &local_transform = transforms[instance_index];
const Transform body_transform = block_transform * local_transform;
VoxelInstancerRigidBody *body;
@ -734,7 +800,8 @@ void VoxelInstancer::update_block_from_transforms(int block_index, ArraySlice<co
body = memnew(VoxelInstancerRigidBody);
body->attach(this);
body->set_instance_index(instance_index);
body->set_block_index(block_index);
body->set_render_block_index(block_index);
body->set_data_block_position(Vector3i::from_floored(body_transform.origin) >> data_block_size_po2);
for (int i = 0; i < collision_shapes.size(); ++i) {
const VoxelInstanceLibraryItem::CollisionShapeInfo &shape_info = collision_shapes[i];
@ -761,6 +828,16 @@ void VoxelInstancer::update_block_from_transforms(int block_index, ArraySlice<co
}
}
static const VoxelInstanceBlockData::LayerData *find_layer_data(const VoxelInstanceBlockData &instances_data, int id) {
for (size_t i = 0; i < instances_data.layers.size(); ++i) {
const VoxelInstanceBlockData::LayerData &layer = instances_data.layers[i];
if (layer.id == id) {
return &layer;
}
}
return nullptr;
}
static bool contains_layer_id(const VoxelInstanceBlockData &instances_data, int id) {
for (auto it = instances_data.layers.begin(); it != instances_data.layers.end(); ++it) {
const VoxelInstanceBlockData::LayerData &layer = *it;
@ -771,143 +848,135 @@ static bool contains_layer_id(const VoxelInstanceBlockData &instances_data, int
return false;
}
void VoxelInstancer::create_blocks(const VoxelInstanceBlockData *instances_data_ptr, Vector3i grid_position,
int lod_index, Array surface_arrays) {
void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod_index, Array surface_arrays) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_library.is_null());
// TODO Query one or multiple data blocks if any
Lod &lod = _lods[lod_index];
const Transform parent_transform = get_global_transform();
Ref<World> world_ref = get_world();
ERR_FAIL_COND(world_ref.is_null());
World *world = *world_ref;
const int lod_block_size = _parent->get_block_size() << lod_index;
const Transform block_local_transform = Transform(Basis(), (grid_position * lod_block_size).to_vec3());
const int lod_block_size = _parent->get_mesh_block_size() << lod_index;
const Transform block_local_transform = Transform(Basis(), (render_grid_position * lod_block_size).to_vec3());
const Transform block_transform = parent_transform * block_local_transform;
// Load from data if any
if (instances_data_ptr != nullptr) {
VOXEL_PROFILE_SCOPE();
const VoxelInstanceBlockData &instances_data = *instances_data_ptr;
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const Vector3i data_min_pos = render_grid_position * render_to_data_factor;
const Vector3i data_max_pos = data_min_pos + Vector3i(render_to_data_factor);
for (auto layer_it = instances_data.layers.begin(); layer_it != instances_data.layers.end(); ++layer_it) {
const VoxelInstanceBlockData::LayerData &layer_data = *layer_it;
const int lod_render_block_size = _parent->get_mesh_block_size() << lod_index;
const VoxelInstanceLibraryItem *item = _library->get_item(layer_data.id);
if (item == nullptr) {
ERR_PRINT(String("Could not find associated layer ID {0} from loaded instances data")
.format(varray(layer_data.id)));
continue;
}
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
Layer *layer = get_layer(layer_data.id);
CRASH_COND(layer == nullptr);
Layer *layer = get_layer(layer_id);
CRASH_COND(layer == nullptr);
if (!item->is_persistent()) {
// That layer is no longer persistent so we'll have to ignore authored data...
WARN_PRINT(String("Layer ID={0} received loaded data but is no longer persistent. "
"Loaded data will be ignored.")
.format(varray(layer_data.id)));
continue;
}
const unsigned int *block_index_ptr = layer->blocks.getptr(grid_position);
if (block_index_ptr != nullptr) {
// The block was already made?
continue;
}
// TODO Don't create blocks if there are no transforms?
static_assert(sizeof(VoxelInstanceBlockData::InstanceData) == sizeof(Transform),
"Assuming instance data only contains a transform for now");
ArraySlice<const Transform> transforms(
reinterpret_cast<const Transform *>(layer_data.instances.data()), layer_data.instances.size());
update_block_from_transforms(
-1, transforms, grid_position, layer, item, layer_data.id, world, block_transform);
}
}
// Generate other blocks
{
VOXEL_PROFILE_SCOPE();
if (surface_arrays.size() == 0) {
return;
const unsigned int *render_block_index_ptr = layer->blocks.getptr(render_grid_position);
if (render_block_index_ptr != nullptr) {
// The block was already made?
continue;
}
PoolVector3Array vertices = surface_arrays[ArrayMesh::ARRAY_VERTEX];
if (vertices.size() == 0) {
return;
_transform_cache.clear();
uint8_t gen_octant_mask = 0xff;
// Fill in edited data
unsigned int octant_index = 0;
Vector3i data_grid_pos;
for (data_grid_pos.z = data_min_pos.z; data_grid_pos.z < data_max_pos.z; ++data_grid_pos.z) {
for (data_grid_pos.y = data_min_pos.y; data_grid_pos.y < data_max_pos.y; ++data_grid_pos.y) {
for (data_grid_pos.x = data_min_pos.x; data_grid_pos.x < data_max_pos.x; ++data_grid_pos.x) {
auto instances_data_it = lod.loaded_instances_data.find(data_grid_pos);
if (instances_data_it != lod.loaded_instances_data.end()) {
// This area has user-edited instances
const VoxelInstanceBlockData *instances_data = instances_data_it->second.get();
CRASH_COND(instances_data == nullptr);
const VoxelInstanceBlockData::LayerData *layer_data =
find_layer_data(*instances_data, layer_id);
if (layer_data == nullptr) {
continue;
}
for (auto it = layer_data->instances.begin(); it != layer_data->instances.end(); ++it) {
const VoxelInstanceBlockData::InstanceData &d = *it;
_transform_cache.push_back(d.transform);
}
// Unset bit for this octant so it won't be generated
gen_octant_mask &= ~(1 << octant_index);
}
++octant_index;
}
}
}
PoolVector3Array normals = surface_arrays[ArrayMesh::ARRAY_NORMAL];
ERR_FAIL_COND(normals.size() == 0);
const VoxelInstanceLibraryItem *item = _library->get_item(layer_id);
CRASH_COND(item == nullptr);
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
// Generate the rest
if (gen_octant_mask != 0 && surface_arrays.size() != 0 && item->get_generator().is_valid()) {
PoolVector3Array vertices = surface_arrays[ArrayMesh::ARRAY_VERTEX];
const VoxelInstanceLibraryItem *item = _library->get_item(layer_id);
CRASH_COND(item == nullptr);
if (item->get_generator().is_null()) {
continue;
if (vertices.size() != 0) {
PoolVector3Array normals = surface_arrays[ArrayMesh::ARRAY_NORMAL];
ERR_FAIL_COND(normals.size() == 0);
VOXEL_PROFILE_SCOPE();
static thread_local std::vector<Transform> s_generated_transforms;
s_generated_transforms.clear();
item->get_generator()->generate_transforms(
s_generated_transforms,
render_grid_position,
lod_index,
layer_id,
surface_arrays,
block_local_transform,
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode),
gen_octant_mask,
lod_render_block_size);
for (auto it = s_generated_transforms.begin(); it != s_generated_transforms.end(); ++it) {
_transform_cache.push_back(*it);
}
}
Layer *layer = get_layer(layer_id);
CRASH_COND(layer == nullptr);
const unsigned int *block_index_ptr = layer->blocks.getptr(grid_position);
if (block_index_ptr != nullptr) {
// The block was already made?
continue;
}
if (item->is_persistent() && instances_data_ptr != nullptr &&
contains_layer_id(*instances_data_ptr, layer_id)) {
// Don't generate, it received modified data
continue;
}
_transform_cache.clear();
item->get_generator()->generate_transforms(
_transform_cache,
grid_position,
lod_index,
layer_id,
surface_arrays,
block_local_transform,
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode));
update_block_from_transforms(-1, to_slice_const(_transform_cache),
grid_position, layer, item, layer_id, world, block_transform);
}
update_block_from_transforms(-1, to_slice_const(_transform_cache),
render_grid_position, layer, item, layer_id, world, block_transform);
}
}
// This API can be confusing so I made a wrapper
static inline int get_visible_instance_count(Ref<MultiMesh> mm) {
int visible_count = mm->get_visible_instance_count();
if (visible_count == -1) {
visible_count = mm->get_instance_count();
}
return visible_count;
}
void VoxelInstancer::save_block(Vector3i grid_pos, int lod_index) const {
void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_library.is_null());
PRINT_VERBOSE(String("Requesting save of instance block {0} lod {1}")
.format(varray(grid_pos.to_vec3(), lod_index)));
.format(varray(data_grid_pos.to_vec3(), lod_index)));
const Lod &lod = _lods[lod_index];
std::unique_ptr<VoxelInstanceBlockData> data = std::make_unique<VoxelInstanceBlockData>();
const int block_size = _parent->get_block_size() << lod_index;
data->position_range = block_size;
const int data_block_size = _parent->get_data_block_size() << lod_index;
data->position_range = data_block_size;
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const int half_render_block_size = _parent->get_mesh_block_size() / 2;
const Vector3i render_block_pos = data_grid_pos.floordiv(render_to_data_factor);
const int octant_index = (data_grid_pos.x & 1) | ((data_grid_pos.y & 1) << 1) | ((data_grid_pos.z & 1) << 1);
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
@ -923,15 +992,15 @@ void VoxelInstancer::save_block(Vector3i grid_pos, int lod_index) const {
ERR_FAIL_COND(layer_id < 0);
const unsigned int *block_index_ptr = layer->blocks.getptr(grid_pos);
const unsigned int *render_block_index_ptr = layer->blocks.getptr(render_block_pos);
if (block_index_ptr != nullptr) {
const unsigned int block_index = *block_index_ptr;
if (render_block_index_ptr != nullptr) {
const unsigned int render_block_index = *render_block_index_ptr;
#ifdef DEBUG_ENABLED
CRASH_COND(block_index >= _blocks.size());
CRASH_COND(render_block_index >= _blocks.size());
#endif
Block *block = _blocks[block_index];
Block *render_block = _blocks[render_block_index];
data->layers.push_back(VoxelInstanceBlockData::LayerData());
VoxelInstanceBlockData::LayerData &layer_data = data->layers.back();
@ -947,37 +1016,59 @@ void VoxelInstancer::save_block(Vector3i grid_pos, int lod_index) const {
layer_data.scale_max = 10.f;
}
if (block->multimesh_instance.is_valid()) {
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
if (render_block->multimesh_instance.is_valid()) {
Ref<MultiMesh> multimesh = render_block->multimesh_instance.get_multimesh();
CRASH_COND(multimesh.is_null());
const int instance_count = get_visible_instance_count(multimesh);
VOXEL_PROFILE_SCOPE();
layer_data.instances.resize(instance_count);
layer_data.instances.clear();
const int instance_count = get_visible_instance_count(**multimesh);
// TODO Optimization: it would be nice to get the whole array at once
for (int i = 0; i < instance_count; ++i) {
VOXEL_PROFILE_SCOPE();
layer_data.instances[i].transform = multimesh->get_instance_transform(i);
if (render_to_data_factor == 1) {
layer_data.instances.resize(instance_count);
// TODO Optimization: it would be nice to get the whole array at once
for (int i = 0; i < instance_count; ++i) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
layer_data.instances[i].transform = multimesh->get_instance_transform(i);
}
} else if (render_to_data_factor == 2) {
for (int i = 0; i < instance_count; ++i) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform t = multimesh->get_instance_transform(i);
const int instance_octant_index =
VoxelInstanceGenerator::get_octant_index(t.origin, half_render_block_size);
if (instance_octant_index == octant_index) {
VoxelInstanceBlockData::InstanceData d;
d.transform = t;
layer_data.instances.push_back(d);
}
}
} else {
ERR_FAIL_MSG("Unsupported block size");
}
}
}
}
const int volume_id = _parent->get_volume_id();
VoxelServer::get_singleton()->request_instance_block_save(volume_id, std::move(data), grid_pos, lod_index);
VoxelServer::get_singleton()->request_instance_block_save(volume_id, std::move(data), data_grid_pos, lod_index);
}
void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_parent == nullptr);
const int block_size = _parent->get_block_size();
const int render_block_size = _parent->get_mesh_block_size();
const int data_block_size = _parent->get_data_block_size();
Ref<VoxelTool> voxel_tool = _parent->get_voxel_tool();
voxel_tool->set_channel(VoxelBuffer::CHANNEL_SDF);
const Transform parent_transform = get_global_transform();
const int base_block_size_po2 = _parent->get_block_size_pow2();
const int base_block_size_po2 = _parent->get_mesh_block_size_pow2();
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
Lod &lod = _lods[lod_index];
@ -986,15 +1077,16 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
continue;
}
const Rect3i blocks_box = p_voxel_box.downscaled(block_size << lod_index);
const Rect3i render_blocks_box = p_voxel_box.downscaled(render_block_size << lod_index);
const Rect3i data_blocks_box = p_voxel_box.downscaled(data_block_size << lod_index);
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const Layer *layer = get_layer(*it);
const std::vector<Block *> &blocks = _blocks;
const int block_size_po2 = base_block_size_po2 + layer->lod_index;
blocks_box.for_each_cell([layer, &blocks, voxel_tool, p_voxel_box, parent_transform, block_size_po2, &lod](
Vector3i block_pos) {
render_blocks_box.for_each_cell([layer, &blocks, voxel_tool, p_voxel_box, parent_transform, block_size_po2, &lod, data_blocks_box](
Vector3i block_pos) {
const unsigned int *iptr = layer->blocks.getptr(block_pos);
if (iptr == nullptr) {
// No instancing block here
@ -1010,7 +1102,7 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
ERR_FAIL_COND(multimesh.is_null());
int initial_instance_count = get_visible_instance_count(multimesh);
const int initial_instance_count = get_visible_instance_count(**multimesh);
int instance_count = initial_instance_count;
const Transform block_global_transform = Transform(parent_transform.basis,
@ -1021,6 +1113,7 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
// - We probably have to sync with its thread in MT mode
// - A hashmap RID lookup is performed to check `RID_Owner::id_map`
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform mm_transform = multimesh->get_instance_transform(instance_index);
const Vector3i voxel_pos(mm_transform.origin + block_global_transform.origin);
@ -1037,6 +1130,7 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
// Remove the MultiMesh instance
const int last_instance_index = --instance_count;
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform last_trans = multimesh->get_instance_transform(last_instance_index);
multimesh->set_instance_transform(instance_index, last_trans);
@ -1070,7 +1164,9 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
// All instances have to be frozen as edited.
// Because even if none of them were removed or added, the ground on which they can spawn has changed,
// and at the moment we don't want unexpected instances to generate when loading back this area.
lod.modified_blocks.set(block_pos, true);
data_blocks_box.for_each_cell([&lod](Vector3i data_block_pos) {
lod.modified_blocks.set(data_block_pos, true);
});
if (instance_count < initial_instance_count) {
// According to the docs, set_instance_count() resets the array so we only hide them instead
@ -1095,9 +1191,8 @@ void VoxelInstancer::on_area_edited(Rect3i p_voxel_box) {
}
// This is called if a user destroys or unparents the body node while it's still attached to the ground
void VoxelInstancer::on_body_removed(unsigned int block_index, int instance_index) {
ERR_FAIL_INDEX(block_index, _blocks.size());
Block *block = _blocks[block_index];
void VoxelInstancer::on_body_removed(Vector3i data_block_position, unsigned int render_block_index, int instance_index) {
Block *block = _blocks[render_block_index];
CRASH_COND(block == nullptr);
ERR_FAIL_INDEX(instance_index, block->bodies.size());
@ -1107,10 +1202,11 @@ void VoxelInstancer::on_body_removed(unsigned int block_index, int instance_inde
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
ERR_FAIL_COND(multimesh.is_null());
int visible_count = get_visible_instance_count(multimesh);
int visible_count = get_visible_instance_count(**multimesh);
ERR_FAIL_COND(instance_index >= visible_count);
--visible_count;
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform last_trans = multimesh->get_instance_transform(visible_count);
multimesh->set_instance_transform(instance_index, last_trans);
multimesh->set_visible_instance_count(visible_count);
@ -1126,11 +1222,11 @@ void VoxelInstancer::on_body_removed(unsigned int block_index, int instance_inde
}
block->bodies.resize(body_count);
// Mark block as modified
// Mark data block as modified
const Layer *layer = get_layer(block->layer_id);
CRASH_COND(layer == nullptr);
Lod &lod = _lods[layer->lod_index];
lod.modified_blocks.set(block->grid_position, true);
lod.modified_blocks.set(data_block_position, true);
}
int VoxelInstancer::debug_get_block_count() const {

View File

@ -53,12 +53,12 @@ public:
// Event handlers
void on_block_data_loaded(Vector3i grid_position, unsigned int lod_index,
void on_data_block_loaded(Vector3i grid_position, unsigned int lod_index,
std::unique_ptr<VoxelInstanceBlockData> instances);
void on_block_enter(Vector3i grid_position, unsigned int lod_index, Array surface_arrays);
void on_block_exit(Vector3i grid_position, unsigned int lod_index);
void on_mesh_block_enter(Vector3i render_grid_position, unsigned int lod_index, Array surface_arrays);
void on_mesh_block_exit(Vector3i render_grid_position, unsigned int lod_index);
void on_area_edited(Rect3i p_voxel_box);
void on_body_removed(unsigned int block_index, int instance_index);
void on_body_removed(Vector3i data_block_position, unsigned int render_block_index, int instance_index);
// Debug
@ -84,14 +84,13 @@ private:
void clear_blocks_in_layer(int layer_id);
void clear_layers();
void update_visibility();
void save_block(Vector3i grid_pos, int lod_index) const;
void save_block(Vector3i data_grid_pos, int lod_index) const;
Layer *get_layer(int id);
const Layer *get_layer_const(int id) const;
void regenerate_layer(uint16_t layer_id, bool regenerate_blocks);
void update_layer_meshes(int layer_id);
void create_blocks(const VoxelInstanceBlockData *instances_data, Vector3i grid_position, int lod_index,
Array surface_arrays);
void create_render_blocks(Vector3i grid_position, int lod_index, Array surface_arrays);
void update_block_from_transforms(int block_index, ArraySlice<const Transform> transforms,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItem *item, uint16_t layer_id,
@ -101,19 +100,24 @@ private:
static void _bind_methods();
// TODO Rename RenderBlock?
struct Block {
uint16_t layer_id;
uint8_t current_mesh_lod = 0;
uint8_t lod_index;
// Position in mesh block coordinate system
Vector3i grid_position;
DirectMultiMeshInstance multimesh_instance;
// For physics we use nodes because it's easier to manage.
// Such instances may be less numerous.
// If the item associated to this block has no collisions, this will be empty.
Vector<VoxelInstancerRigidBody *> bodies;
};
struct Layer {
unsigned int lod_index;
// Blocks indexed by grid position.
// Keys follow the mesh block coordinate system.
HashMap<Vector3i, unsigned int, Vector3iHasher> blocks;
};
@ -134,12 +138,14 @@ private:
struct Lod {
std::vector<int> layers;
// Blocks that have have unsaved changes
// Blocks that have have unsaved changes.
// Keys follows the data block coordinate system.
HashMap<Vector3i, bool, Vector3iHasher> modified_blocks;
// This is a temporary place to store loaded instances data while it's not visible yet.
// These instances are user-authored ones. If a block does not have an entry there,
// it will get generated instances.
// Keys follows the data block coordinate system.
// Can't use `HashMap` because it lacks move semantics.
std::unordered_map<Vector3i, std::unique_ptr<VoxelInstanceBlockData> > loaded_instances_data;

View File

@ -1,61 +1,12 @@
#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>
// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
//
static Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfaces) {
VOXEL_PROFILE_SCOPE();
PoolVector<Vector3> face_points;
int face_points_size = 0;
//find the correct size for face_points
for (int i = 0; i < surfaces.size(); i++) {
const Array &surface_arrays = surfaces[i];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
face_points_size += indices.size();
}
face_points.resize(face_points_size);
//copy the points into it
int face_points_offset = 0;
for (int i = 0; i < surfaces.size(); i++) {
const Array &surface_arrays = surfaces[i];
PoolVector<Vector3> positions = surface_arrays[Mesh::ARRAY_VERTEX];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(positions.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ConcavePolygonShape>());
int face_points_count = face_points_offset + indices.size();
{
PoolVector<Vector3>::Write w = face_points.write();
PoolVector<int>::Read index_r = indices.read();
PoolVector<Vector3>::Read position_r = positions.read();
for (int p = face_points_offset; p < face_points_count; ++p) {
w[p] = position_r[index_r[p - face_points_offset]];
}
}
face_points_offset += indices.size();
}
Ref<ConcavePolygonShape> shape = memnew(ConcavePolygonShape);
shape->set_faces(face_points);
return shape;
}
// Helper
VoxelBlock *VoxelBlock::create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int size, unsigned int p_lod_index) {
const int bs = size;

View File

@ -0,0 +1,49 @@
#ifndef VOXEL_DATA_BLOCK_H
#define VOXEL_DATA_BLOCK_H
#include "../storage/voxel_buffer.h"
#include "../util/macros.h"
// Stores loaded voxel data for a chunk of the volume. Mesh and colliders are stored separately.
class VoxelDataBlock {
public:
Ref<VoxelBuffer> voxels;
const Vector3i position;
const unsigned int lod_index = 0;
static VoxelDataBlock *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);
return memnew(VoxelDataBlock(bpos, buffer, p_lod_index));
}
void 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;
}
inline bool is_modified() const { return _modified; }
void set_needs_lodding(bool need_lodding) {
_needs_lodding = need_lodding;
}
inline bool get_needs_lodding() const { return _needs_lodding; }
private:
VoxelDataBlock(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int p_lod_index) :
voxels(buffer), position(bpos), lod_index(p_lod_index) {}
// 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_DATA_BLOCK_H

336
terrain/voxel_data_map.cpp Normal file
View File

@ -0,0 +1,336 @@
#include "voxel_data_map.h"
#include "../constants/cube_tables.h"
#include "../util/macros.h"
#include "voxel_block.h"
#include <limits>
VoxelDataMap::VoxelDataMap() :
_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;
}
VoxelDataMap::~VoxelDataMap() {
PRINT_VERBOSE("Destroying VoxelMap");
clear();
}
void VoxelDataMap::create(unsigned int block_size_po2, int lod_index) {
clear();
set_block_size_pow2(block_size_po2);
set_lod_index(lod_index);
}
void VoxelDataMap::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 VoxelDataMap::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 VoxelDataMap::get_lod_index() const {
return _lod_index;
}
int VoxelDataMap::get_voxel(Vector3i pos, unsigned int c) const {
Vector3i bpos = voxel_to_block(pos);
const VoxelDataBlock *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);
}
VoxelDataBlock *VoxelDataMap::create_default_block(Vector3i bpos) {
Ref<VoxelBuffer> buffer(memnew(VoxelBuffer));
buffer->create(_block_size, _block_size, _block_size);
buffer->set_default_values(_default_voxel);
VoxelDataBlock *block = VoxelDataBlock::create(bpos, buffer, _block_size, _lod_index);
set_block(bpos, block);
return block;
}
VoxelDataBlock *VoxelDataMap::get_or_create_block_at_voxel_pos(Vector3i pos) {
Vector3i bpos = voxel_to_block(pos);
VoxelDataBlock *block = get_block(bpos);
if (block == nullptr) {
block = create_default_block(bpos);
}
return block;
}
void VoxelDataMap::set_voxel(int value, Vector3i pos, unsigned int c) {
VoxelDataBlock *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 VoxelDataMap::get_voxel_f(Vector3i pos, unsigned int c) const {
Vector3i bpos = voxel_to_block(pos);
const VoxelDataBlock *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 VoxelDataMap::set_voxel_f(real_t value, Vector3i pos, unsigned int c) {
VoxelDataBlock *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 VoxelDataMap::set_default_voxel(int value, unsigned int channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
_default_voxel[channel] = value;
}
int VoxelDataMap::get_default_voxel(unsigned int channel) {
ERR_FAIL_INDEX_V(channel, VoxelBuffer::MAX_CHANNELS, 0);
return _default_voxel[channel];
}
VoxelDataBlock *VoxelDataMap::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
VoxelDataBlock *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 VoxelDataBlock *VoxelDataMap::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 VoxelDataBlock *block = _blocks[i];
CRASH_COND(block == nullptr); // The map should not contain null blocks
return block;
}
return nullptr;
}
void VoxelDataMap::set_block(Vector3i bpos, VoxelDataBlock *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 VoxelDataMap::remove_block_internal(Vector3i bpos, unsigned int index) {
// This function assumes the block is already freed
_blocks_map.erase(bpos);
VoxelDataBlock *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;
}
}
VoxelDataBlock *VoxelDataMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
ERR_FAIL_COND_V(buffer.is_null(), nullptr);
VoxelDataBlock *block = get_block(bpos);
if (block == nullptr) {
block = VoxelDataBlock::create(bpos, *buffer, _block_size, _lod_index);
set_block(bpos, block);
} else {
block->voxels = buffer;
}
return block;
}
bool VoxelDataMap::has_block(Vector3i pos) const {
return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos);
}
bool VoxelDataMap::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 VoxelDataMap::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 VoxelDataBlock *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 VoxelDataMap::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;
}
VoxelDataBlock *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 VoxelDataMap::clear() {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
VoxelDataBlock *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 VoxelDataMap::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 VoxelDataMap::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);
});
}

146
terrain/voxel_data_map.h Normal file
View File

@ -0,0 +1,146 @@
#ifndef VOXEL_DATA_MAP_H
#define VOXEL_DATA_MAP_H
#include "../util/fixed_array.h"
#include "voxel_data_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 VoxelDataMap {
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;
}
VoxelDataMap();
~VoxelDataMap();
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.
VoxelDataBlock *set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
struct NoAction {
inline void operator()(VoxelDataBlock *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
VoxelDataBlock *block = _blocks[i];
ERR_FAIL_COND(block == nullptr);
pre_delete(block);
memdelete(block);
remove_block_internal(bpos, i);
}
}
VoxelDataBlock *get_block(Vector3i bpos);
const VoxelDataBlock *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, VoxelDataBlock *block);
VoxelDataBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
VoxelDataBlock *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<VoxelDataBlock *> _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 VoxelDataBlock *_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

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
#include "../server/voxel_server.h"
#include "lod_octree.h"
#include "voxel_map.h"
#include "voxel_data_map.h"
#include "voxel_mesh_map.h"
#include "voxel_node.h"
#include <core/set.h>
@ -55,12 +56,17 @@ public:
void set_collision_lod_count(int lod_count);
int get_collision_lod_count() const;
int get_block_region_extent() const;
Vector3 voxel_to_block_position(Vector3 vpos, int lod_index) const;
int get_data_block_region_extent() const;
int get_mesh_block_region_extent() const;
unsigned int get_block_size_pow2() const;
void set_block_size_po2(unsigned int p_block_size_po2);
unsigned int get_block_size() const;
Vector3 voxel_to_data_block_position(Vector3 vpos, int lod_index) const;
unsigned int get_data_block_size_pow2() const;
unsigned int get_data_block_size() const;
void set_data_block_size_po2(unsigned int p_block_size_po2);
unsigned int get_mesh_block_size_pow2() const;
unsigned int get_mesh_block_size() const;
// These must be called after an edit
void post_edit_area(Rect3i p_box);
@ -117,8 +123,8 @@ public:
// Debugging
Array debug_raycast_block(Vector3 world_origin, Vector3 world_direction) const;
Dictionary debug_get_block_info(Vector3 fbpos, int lod_index) const;
Array debug_raycast_mesh_block(Vector3 world_origin, Vector3 world_direction) const;
Dictionary debug_get_mesh_block_info(Vector3 fbpos, int lod_index) const;
Array debug_get_octree_positions() const;
Array debug_get_octrees_detailed() const;
@ -137,7 +143,7 @@ public:
void set_instancer(VoxelInstancer *instancer);
uint32_t get_volume_id() const { return _volume_id; }
Array get_block_surface(Vector3i block_pos, int lod_index) const;
Array get_mesh_block_surface(Vector3i block_pos, int lod_index) const;
Vector<Vector3i> get_meshed_block_positions_at_lod(int lod_index) const;
protected:
@ -147,7 +153,14 @@ protected:
void _process(float delta);
private:
void immerge_block(Vector3i block_pos, int lod_index);
void unload_data_block(Vector3i block_pos, int lod_index);
void unload_mesh_block(Vector3i block_pos, int lod_index);
static inline bool check_block_sizes(int data_block_size, int mesh_block_size) {
return (data_block_size == 16 || data_block_size == 32) &&
(mesh_block_size == 16 || mesh_block_size == 32) &&
mesh_block_size >= data_block_size;
}
void start_updater();
void stop_updater();
@ -156,13 +169,13 @@ private:
void reset_maps();
Vector3 get_local_viewer_pos() const;
void try_schedule_loading_with_neighbors(const Vector3i &p_bpos, int lod_index);
bool is_block_surrounded(const Vector3i &p_bpos, int lod_index, const VoxelMap &map) const;
bool check_block_loaded_and_updated(const Vector3i &p_bpos, int lod_index);
bool check_block_mesh_updated(VoxelBlock *block);
void try_schedule_loading_with_neighbors(const Vector3i &p_data_block_pos, int lod_index);
bool is_block_surrounded(const Vector3i &p_bpos, int lod_index, const VoxelDataMap &map) const;
bool check_block_loaded_and_meshed(const Vector3i &p_mesh_block_pos, int lod_index);
bool check_block_mesh_updated(VoxelMeshBlock *block);
void _set_lod_count(int p_lod_count);
void _set_block_size_po2(int p_block_size_po2);
void set_block_active(VoxelBlock &block, bool active);
void set_mesh_block_active(VoxelMeshBlock &block, bool active);
void _on_stream_params_changed();
@ -172,7 +185,7 @@ private:
void process_deferred_collision_updates(uint32_t timeout_msec);
void process_fading_blocks(float delta);
void add_transition_update(VoxelBlock *block);
void add_transition_update(VoxelMeshBlock *block);
void add_transition_updates_around(Vector3i block_pos, int lod_index);
void process_transition_updates();
uint8_t get_transition_mask(Vector3i block_pos, int lod_index) const;
@ -181,7 +194,8 @@ private:
void _b_set_voxel_bounds(AABB aabb);
AABB _b_get_voxel_bounds() const;
Array _b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const;
int _b_debug_get_block_count() const;
int _b_debug_get_mesh_block_count() const;
int _b_debug_get_data_block_count() const;
Error _b_debug_dump_as_scene(String fpath) const;
Dictionary _b_get_statistics() const;
@ -218,7 +232,7 @@ private:
ProcessMode _process_mode = PROCESS_MODE_IDLE;
// Only populated and then cleared inside _process, so lifetime of pointers should be valid
std::vector<VoxelBlock *> _blocks_pending_transition_update;
std::vector<VoxelMeshBlock *> _blocks_pending_transition_update;
Ref<Material> _material;
std::vector<Ref<ShaderMaterial> > _shader_material_pool;
@ -231,23 +245,23 @@ private:
// Each LOD works in a set of coordinates spanning 2x more voxels the higher their index is
struct Lod {
VoxelMap map;
VoxelDataMap data_map;
Set<Vector3i> loading_blocks;
std::vector<Vector3i> blocks_pending_update;
// Blocks that were edited and need their LOD counterparts to be updated
std::vector<Vector3i> blocks_pending_lodding;
// These are relative to this LOD, in block coordinates
Vector3i last_viewer_block_pos;
int last_view_distance_blocks = 0;
Vector3i last_viewer_data_block_pos;
int last_view_distance_data_blocks = 0;
VoxelMeshMap mesh_map;
std::vector<Vector3i> blocks_pending_update;
std::vector<Vector3i> deferred_collision_updates;
Map<Vector3i, VoxelMeshBlock *> fading_blocks;
Vector3i last_viewer_mesh_block_pos;
int last_view_distance_mesh_blocks = 0;
// Members for memory caching
std::vector<Vector3i> blocks_to_load;
Map<Vector3i, VoxelBlock *> fading_blocks;
};
FixedArray<Lod, VoxelConstants::MAX_LOD> _lods;

View File

@ -0,0 +1,340 @@
#include "voxel_mesh_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>
VoxelMeshBlock *VoxelMeshBlock::create(Vector3i bpos, unsigned int size, unsigned int p_lod_index) {
VoxelMeshBlock *block = memnew(VoxelMeshBlock);
block->position = bpos;
block->lod_index = p_lod_index;
block->_position_in_voxels = bpos * (size << p_lod_index);
#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;
}
VoxelMeshBlock::VoxelMeshBlock() {
}
VoxelMeshBlock::~VoxelMeshBlock() {
}
void VoxelMeshBlock::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 VoxelMeshBlock::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> VoxelMeshBlock::get_mesh() const {
if (_mesh_instance.is_valid()) {
return _mesh_instance.get_mesh();
}
return Ref<Mesh>();
}
void VoxelMeshBlock::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 VoxelMeshBlock::has_mesh() const {
return _mesh_instance.get_mesh().is_valid();
}
void VoxelMeshBlock::drop_mesh() {
if (_mesh_instance.is_valid()) {
_mesh_instance.destroy();
}
}
void VoxelMeshBlock::set_mesh_state(MeshState ms) {
_mesh_state = ms;
}
VoxelMeshBlock::MeshState VoxelMeshBlock::get_mesh_state() const {
return _mesh_state;
}
void VoxelMeshBlock::set_visible(bool visible) {
if (_visible == visible) {
return;
}
_visible = visible;
_set_visible(_visible && _parent_visible);
}
bool VoxelMeshBlock::is_visible() const {
return _visible;
}
void VoxelMeshBlock::_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 VoxelMeshBlock::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 VoxelMeshBlock::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 VoxelMeshBlock::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 VoxelMeshBlock::set_parent_visible(bool parent_visible) {
if (_parent_visible && parent_visible) {
return;
}
_parent_visible = parent_visible;
_set_visible(_visible && _parent_visible);
}
void VoxelMeshBlock::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 VoxelMeshBlock::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 VoxelMeshBlock::drop_collision() {
if (_static_body.is_valid()) {
_static_body.destroy();
}
}
// Returns `true` when finished
bool VoxelMeshBlock::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;
}

138
terrain/voxel_mesh_block.h Normal file
View File

@ -0,0 +1,138 @@
#ifndef VOXEL_MESH_BLOCK_H
#define VOXEL_MESH_BLOCK_H
#include "../constants/cube_tables.h"
#include "../util/godot/direct_mesh_instance.h"
#include "../util/godot/direct_static_body.h"
#include "voxel_viewer_ref_count.h"
class Spatial;
// Stores mesh and collider for one chunk of the rendered volume.
// It doesn't store voxel data, because it may be using different block size, or different data structure.
class VoxelMeshBlock {
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
};
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 VoxelMeshBlock *create(Vector3i bpos, unsigned int size, unsigned int p_lod_index);
~VoxelMeshBlock();
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; }
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:
VoxelMeshBlock();
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;
};
#endif // VOXEL_MESH_BLOCK_H

164
terrain/voxel_mesh_map.cpp Normal file
View File

@ -0,0 +1,164 @@
#include "voxel_mesh_map.h"
#include "../constants/cube_tables.h"
#include "../constants/voxel_constants.h"
#include "../util/macros.h"
#include <limits>
VoxelMeshMap::VoxelMeshMap() :
_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);
}
VoxelMeshMap::~VoxelMeshMap() {
PRINT_VERBOSE("Destroying VoxelMeshMap");
clear();
}
void VoxelMeshMap::create(unsigned int block_size_po2, int lod_index) {
clear();
set_block_size_pow2(block_size_po2);
set_lod_index(lod_index);
}
void VoxelMeshMap::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 VoxelMeshMap::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 VoxelMeshMap::get_lod_index() const {
return _lod_index;
}
// VoxelMeshBlock *VoxelMeshMap::create_default_block(Vector3i bpos) {
// VoxelMeshBlock *block = VoxelMeshBlock::create(bpos, _block_size, _lod_index);
// set_block(bpos, block);
// return block;
// }
// VoxelMeshBlock *VoxelMeshMap::get_or_create_block_at_voxel_pos(Vector3i pos) {
// Vector3i bpos = voxel_to_block(pos);
// VoxelMeshBlock *block = get_block(bpos);
// if (block == nullptr) {
// block = create_default_block(bpos);
// }
// return block;
// }
VoxelMeshBlock *VoxelMeshMap::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
VoxelMeshBlock *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 VoxelMeshBlock *VoxelMeshMap::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 VoxelMeshBlock *block = _blocks[i];
CRASH_COND(block == nullptr); // The map should not contain null blocks
return block;
}
return nullptr;
}
void VoxelMeshMap::set_block(Vector3i bpos, VoxelMeshBlock *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 VoxelMeshMap::remove_block_internal(Vector3i bpos, unsigned int index) {
// This function assumes the block is already freed
_blocks_map.erase(bpos);
VoxelMeshBlock *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;
}
}
bool VoxelMeshMap::has_block(Vector3i pos) const {
return /*(_last_accessed_block != nullptr && _last_accessed_block->pos == pos) ||*/ _blocks_map.has(pos);
}
bool VoxelMeshMap::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 VoxelMeshMap::clear() {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
VoxelMeshBlock *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 VoxelMeshMap::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();
}

119
terrain/voxel_mesh_map.h Normal file
View File

@ -0,0 +1,119 @@
#ifndef VOXEL_MESH_MAP_H
#define VOXEL_MESH_MAP_H
#include "voxel_mesh_block.h"
#include <vector>
// Stores meshes and colliders in an infinite sparse grid of chunks (aka blocks).
class VoxelMeshMap {
public:
// Converts voxel coodinates into block coordinates.
// Don't use division because it introduces an offset in negative coordinates.
static inline Vector3i voxel_to_block_b(Vector3i pos, int block_size_pow2) {
return pos >> block_size_pow2;
}
inline Vector3i voxel_to_block(Vector3i pos) const {
return voxel_to_block_b(pos, _block_size_pow2);
}
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
inline Vector3i block_to_voxel(Vector3i bpos) const {
return bpos * _block_size;
}
VoxelMeshMap();
~VoxelMeshMap();
void create(unsigned int block_size_po2, int lod_index);
inline unsigned int get_block_size() const { return _block_size; }
inline unsigned int get_block_size_pow2() const { return _block_size_pow2; }
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;
struct NoAction {
inline void operator()(VoxelMeshBlock *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
VoxelMeshBlock *block = _blocks[i];
ERR_FAIL_COND(block == nullptr);
pre_delete(block);
memdelete(block);
remove_block_internal(bpos, i);
}
}
VoxelMeshBlock *get_block(Vector3i bpos);
const VoxelMeshBlock *get_block(Vector3i bpos) const;
void set_block(Vector3i bpos, VoxelMeshBlock *block);
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);
}
}
private:
//VoxelMeshBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
VoxelMeshBlock *create_default_block(Vector3i bpos);
void remove_block_internal(Vector3i bpos, unsigned int index);
void set_block_size_pow2(unsigned int p);
private:
// 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<VoxelMeshBlock *> _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 VoxelMeshBlock *_last_accessed_block;
unsigned int _block_size;
unsigned int _block_size_pow2;
unsigned int _block_size_mask;
unsigned int _lod_index = 0;
};
#endif // VOXEL_MESH_BLOCK_MAP_H

View File

@ -163,7 +163,8 @@ void VoxelTerrain::_on_stream_params_changed() {
_set_block_size_po2(stream_block_size_po2);
}
VoxelServer::get_singleton()->set_volume_block_size(_volume_id, 1 << get_block_size_pow2());
VoxelServer::get_singleton()->set_volume_data_block_size(_volume_id, 1 << get_block_size_pow2());
VoxelServer::get_singleton()->set_volume_render_block_size(_volume_id, 1 << get_block_size_pow2());
// The whole map might change, so regenerate it
reset_map();
@ -1174,15 +1175,22 @@ void VoxelTerrain::_process() {
VoxelServer::BlockMeshInput mesh_request;
mesh_request.position = block_pos;
mesh_request.lod = 0;
for (unsigned int i = 0; i < Cube::MOORE_AREA_3D_COUNT; ++i) {
const Vector3i npos = block_pos + Cube::g_ordered_moore_area_3d[i];
VoxelBlock *nblock = _map.get_block(npos);
// The block can actually be null on some occasions. Not sure yet if it's that bad
//CRASH_COND(nblock == nullptr);
if (nblock == nullptr) {
continue;
mesh_request.blocks_count = 3 * 3 * 3;
// This iteration order is specifically chosen to match VoxelServer and threaded access
unsigned int i = 0;
for (int z = -1; z < 2; ++z) {
for (int x = -1; x < 2; ++x) {
for (int y = -1; y < 2; ++y) {
const Vector3i npos = block_pos + Vector3i(x, y, z);
VoxelBlock *nblock = _map.get_block(npos);
// The block can actually be null on some occasions. Not sure yet if it's that bad
//CRASH_COND(nblock == nullptr);
if (nblock != nullptr) {
mesh_request.blocks[i] = nblock->voxels;
}
++i;
}
}
mesh_request.blocks[i] = nblock->voxels;
}
VoxelServer::get_singleton()->request_block_mesh(_volume_id, mesh_request);

View File

@ -121,4 +121,10 @@ ArraySlice<const T> to_slice_const(const std::vector<T> &vec) {
return ArraySlice<const T>(vec.data(), 0, vec.size());
}
template <typename T, unsigned int N>
ArraySlice<T> to_slice(FixedArray<T, N> &a, unsigned int count) {
CRASH_COND(count > a.size());
return ArraySlice<T>(a.data(), count);
}
#endif // ARRAY_SLICE_H

View File

@ -1,7 +1,10 @@
#include "funcs.h"
#include "../profiling.h"
#include <core/engine.h>
#include <scene/resources/concave_polygon_shape.h>
#include <scene/resources/mesh.h>
#include <scene/resources/multimesh.h>
bool is_surface_triangulated(Array surface) {
PoolVector3Array positions = surface[Mesh::ARRAY_VERTEX];
@ -53,3 +56,61 @@ bool try_call_script(
return true;
}
// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
//
Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfaces) {
VOXEL_PROFILE_SCOPE();
PoolVector<Vector3> face_points;
int face_points_size = 0;
//find the correct size for face_points
for (int i = 0; i < surfaces.size(); i++) {
const Array &surface_arrays = surfaces[i];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
face_points_size += indices.size();
}
face_points.resize(face_points_size);
//copy the points into it
int face_points_offset = 0;
for (int i = 0; i < surfaces.size(); i++) {
const Array &surface_arrays = surfaces[i];
PoolVector<Vector3> positions = surface_arrays[Mesh::ARRAY_VERTEX];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(positions.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ConcavePolygonShape>());
int face_points_count = face_points_offset + indices.size();
{
PoolVector<Vector3>::Write w = face_points.write();
PoolVector<int>::Read index_r = indices.read();
PoolVector<Vector3>::Read position_r = positions.read();
for (int p = face_points_offset; p < face_points_count; ++p) {
w[p] = position_r[index_r[p - face_points_offset]];
}
}
face_points_offset += indices.size();
}
Ref<ConcavePolygonShape> shape = memnew(ConcavePolygonShape);
shape->set_faces(face_points);
return shape;
}
int get_visible_instance_count(const MultiMesh &mm) {
int visible_count = mm.get_visible_instance_count();
if (visible_count == -1) {
visible_count = mm.get_instance_count();
}
return visible_count;
}

View File

@ -5,6 +5,8 @@
#include <core/variant.h>
class Mesh;
class ConcavePolygonShape;
class MultiMesh;
bool is_surface_triangulated(Array surface);
bool is_mesh_empty(Ref<Mesh> mesh_ref);
@ -18,4 +20,9 @@ inline bool try_call_script(
return try_call_script(obj, method_name, args, 3, out_ret);
}
Ref<ConcavePolygonShape> create_concave_polygon_shape(Vector<Array> surfaces);
// This API can be confusing so I made a wrapper
int get_visible_instance_count(const MultiMesh &mm);
#endif // VOXEL_UTILITY_GODOT_FUNCS_H

View File

@ -131,6 +131,10 @@ inline int udiv(int x, int d) {
}
}
inline int ceildiv(int x, int d) {
return -udiv(-x, d);
}
// TODO Rename `wrapi`
// `Math::wrapi` with zero min
inline int wrap(int x, int d) {

View File

@ -115,6 +115,19 @@ public:
}
}
template <typename A>
inline void for_each_cell_zxy(A a) const {
Vector3i max = pos + size;
Vector3i p;
for (p.z = pos.z; p.z < max.z; ++p.z) {
for (p.x = pos.x; p.x < max.x; ++p.x) {
for (p.y = pos.y; p.y < max.y; ++p.y) {
a(p);
}
}
}
}
template <typename A>
inline bool all_cells_match(A a) const {
Vector3i max = pos + size;
@ -188,6 +201,53 @@ public:
}
}
template <typename F>
void for_inner_outline(F f) const {
// o-------o
// /| /|
// / | / |
// o--+----o |
// | o----|--o
// | / | / y z
// |/ |/ |/
// o-------o o---x
Vector3i min_pos = pos;
Vector3i max_pos = pos + size;
// Top and bottom
for (int z = min_pos.z; z < max_pos.z; ++z) {
for (int x = min_pos.x; x < max_pos.x; ++x) {
f(Vector3i(x, min_pos.y, z));
f(Vector3i(x, max_pos.y - 1, z));
}
}
// Exclude top and bottom cells from the sides we'll iterate next
++min_pos.y;
--max_pos.y;
// Z sides
for (int x = min_pos.x; x < max_pos.x; ++x) {
for (int y = min_pos.y; y < max_pos.y; ++y) {
f(Vector3i(x, y, min_pos.z));
f(Vector3i(x, y, max_pos.z - 1));
}
}
// Exclude cells belonging to edges of Z sides we did before
++min_pos.z;
--max_pos.z;
// X sides
for (int z = min_pos.z; z < max_pos.z; ++z) {
for (int y = min_pos.y; y < max_pos.y; ++y) {
f(Vector3i(min_pos.x, y, z));
f(Vector3i(max_pos.x - 1, y, z));
}
}
}
inline Rect3i padded(int m) const {
return Rect3i(
pos.x - m,
@ -198,14 +258,25 @@ public:
size.z + 2 * m);
}
// Converts the rectangle into a coordinate system of higher step size,
// rounding outwards of the area covered by the original rectangle if divided coordinates have remainders.
inline Rect3i downscaled(int step_size) const {
Rect3i o;
o.pos = pos.floordiv(step_size);
// TODO Is that ceildiv?
Vector3i max_pos = (pos + size - Vector3i(1)).floordiv(step_size);
o.size = max_pos - o.pos + Vector3i(1);
return o;
}
// Converts the rectangle into a coordinate system of higher step size,
// rounding inwards of the area covered by the original rectangle if divided coordinates have remainders.
// This is such that the result is included in the original rectangle (assuming a common coordinate system).
// The result can be an empty rectangle.
inline Rect3i downscaled_inner(int step_size) const {
return Rect3i::from_min_max(pos.ceildiv(step_size), (pos + size).floordiv(step_size));
}
static inline void clip_range(int &pos, int &size, int lim_pos, int lim_size) {
int max_pos = pos + size;
int lim_max_pos = lim_pos + lim_size;

View File

@ -157,6 +157,10 @@ struct Vector3i {
return Vector3i(::udiv(x, d), ::udiv(y, d), ::udiv(z, d));
}
inline Vector3i ceildiv(const int d) const {
return Vector3i(::ceildiv(x, d), ::ceildiv(y, d), ::ceildiv(z, d));
}
inline Vector3i wrap(const Vector3i d) const {
return Vector3i(::wrap(x, d.x), ::wrap(y, d.y), ::wrap(z, d.z));
}