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:
parent
24428d5af9
commit
758e3bd227
@ -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
|
||||
|
@ -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 ↦
|
||||
const VoxelDataMap ↦
|
||||
|
||||
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 ↦
|
||||
const VoxelDataMap ↦
|
||||
|
||||
inline float operator()(const Vector3i &pos) {
|
||||
return map.get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
49
terrain/voxel_data_block.h
Normal file
49
terrain/voxel_data_block.h
Normal 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
336
terrain/voxel_data_map.cpp
Normal 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
146
terrain/voxel_data_map.h
Normal 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
@ -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;
|
||||
|
340
terrain/voxel_mesh_block.cpp
Normal file
340
terrain/voxel_mesh_block.cpp
Normal 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
138
terrain/voxel_mesh_block.h
Normal 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
164
terrain/voxel_mesh_map.cpp
Normal 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
119
terrain/voxel_mesh_map.h
Normal 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
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user