Migrate VoxelTerrain to use the new VoxelData. UNTESTED
parent
7a8e605430
commit
afd81e4a24
|
@ -2,6 +2,7 @@
|
|||
#include "../meshers/blocky/voxel_mesher_blocky.h"
|
||||
#include "../meshers/cubes/voxel_mesher_cubes.h"
|
||||
#include "../storage/voxel_buffer_gd.h"
|
||||
#include "../storage/voxel_data.h"
|
||||
#include "../storage/voxel_metadata_variant.h"
|
||||
#include "../terrain/fixed_lod/voxel_terrain.h"
|
||||
#include "../util/godot/ref_counted.h"
|
||||
|
@ -21,38 +22,45 @@ VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain) {
|
|||
bool VoxelToolTerrain::is_area_editable(const Box3i &box) const {
|
||||
ERR_FAIL_COND_V(_terrain == nullptr, false);
|
||||
// TODO Take volume bounds into account
|
||||
return _terrain->get_storage().is_area_fully_loaded(box);
|
||||
return _terrain->get_storage().is_area_loaded(box);
|
||||
}
|
||||
|
||||
Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(
|
||||
Vector3 p_pos, Vector3 p_dir, float p_max_distance, uint32_t p_collision_mask) {
|
||||
// TODO Implement broad-phase on blocks to minimize locking and increase performance
|
||||
|
||||
// TODO Optimization: voxel raycast uses `get_voxel` which is the slowest, but could be made faster.
|
||||
// See `VoxelToolLodTerrain` for information about how to implement improvements.
|
||||
|
||||
struct RaycastPredicateColor {
|
||||
const VoxelDataMap ↦
|
||||
const VoxelData &data;
|
||||
|
||||
bool operator()(const VoxelRaycastState &rs) const {
|
||||
const uint64_t v = map.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_COLOR);
|
||||
VoxelSingleValue defval;
|
||||
defval.i = 0;
|
||||
const uint64_t v = data.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_COLOR, defval).i;
|
||||
return v != 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct RaycastPredicateSDF {
|
||||
const VoxelDataMap ↦
|
||||
const VoxelData &data;
|
||||
|
||||
bool operator()(const VoxelRaycastState &rs) const {
|
||||
const float v = map.get_voxel_f(rs.hit_position, VoxelBufferInternal::CHANNEL_SDF);
|
||||
const float v = data.get_voxel_f(rs.hit_position, VoxelBufferInternal::CHANNEL_SDF);
|
||||
return v < 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct RaycastPredicateBlocky {
|
||||
const VoxelDataMap ↦
|
||||
const VoxelData &data;
|
||||
const VoxelBlockyLibrary &library;
|
||||
const uint32_t collision_mask;
|
||||
|
||||
bool operator()(const VoxelRaycastState &rs) const {
|
||||
const int v = map.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_TYPE);
|
||||
VoxelSingleValue defval;
|
||||
defval.i = 0;
|
||||
const int v = data.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_TYPE, defval).i;
|
||||
|
||||
if (library.has_voxel(v) == false) {
|
||||
return false;
|
||||
|
@ -178,9 +186,9 @@ void VoxelToolTerrain::do_sphere(Vector3 center, float radius) {
|
|||
return;
|
||||
}
|
||||
|
||||
VoxelDataMap &data = _terrain->get_storage();
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
|
||||
op.blocks.reference_area(data, op.box);
|
||||
data.get_blocks_grid(op.blocks, op.box, 0);
|
||||
op();
|
||||
|
||||
_post_edit(op.box);
|
||||
|
@ -209,9 +217,9 @@ void VoxelToolTerrain::do_hemisphere(Vector3 center, float radius, Vector3 flat_
|
|||
return;
|
||||
}
|
||||
|
||||
VoxelDataMap &data = _terrain->get_storage();
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
|
||||
op.blocks.reference_area(data, op.box);
|
||||
data.get_blocks_grid(op.blocks, op.box, 0);
|
||||
op();
|
||||
|
||||
_post_edit(op.box);
|
||||
|
@ -219,7 +227,9 @@ void VoxelToolTerrain::do_hemisphere(Vector3 center, float radius, Vector3 flat_
|
|||
|
||||
uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) const {
|
||||
ERR_FAIL_COND_V(_terrain == nullptr, 0);
|
||||
return _terrain->get_storage().get_voxel(pos, _channel);
|
||||
VoxelSingleValue defval;
|
||||
defval.i = 0;
|
||||
return _terrain->get_storage().get_voxel(pos, _channel, defval).i;
|
||||
}
|
||||
|
||||
float VoxelToolTerrain::_get_voxel_f(Vector3i pos) const {
|
||||
|
@ -229,12 +239,12 @@ float VoxelToolTerrain::_get_voxel_f(Vector3i pos) const {
|
|||
|
||||
void VoxelToolTerrain::_set_voxel(Vector3i pos, uint64_t v) {
|
||||
ERR_FAIL_COND(_terrain == nullptr);
|
||||
_terrain->get_storage().set_voxel(v, pos, _channel);
|
||||
_terrain->get_storage().try_set_voxel(v, pos, _channel);
|
||||
}
|
||||
|
||||
void VoxelToolTerrain::_set_voxel_f(Vector3i pos, float v) {
|
||||
ERR_FAIL_COND(_terrain == nullptr);
|
||||
_terrain->get_storage().set_voxel_f(v, pos, _channel);
|
||||
_terrain->get_storage().try_set_voxel_f(v, pos, _channel);
|
||||
}
|
||||
|
||||
void VoxelToolTerrain::_post_edit(const Box3i &box) {
|
||||
|
@ -244,40 +254,25 @@ void VoxelToolTerrain::_post_edit(const Box3i &box) {
|
|||
|
||||
void VoxelToolTerrain::set_voxel_metadata(Vector3i pos, Variant meta) {
|
||||
ERR_FAIL_COND(_terrain == nullptr);
|
||||
VoxelDataMap &map = _terrain->get_storage();
|
||||
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
||||
ERR_FAIL_COND_MSG(block == nullptr, "Area not editable");
|
||||
// TODO In this situation, the generator would need to be invoked to fill in the blank
|
||||
ERR_FAIL_COND_MSG(!block->has_voxels(), "Area not cached");
|
||||
RWLockWrite lock(block->get_voxels().get_lock());
|
||||
VoxelMetadata *meta_storage = block->get_voxels().get_or_create_voxel_metadata(map.to_local(pos));
|
||||
ERR_FAIL_COND(meta_storage == nullptr);
|
||||
gd::set_as_variant(*meta_storage, meta);
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
data.set_voxel_metadata(pos, meta);
|
||||
}
|
||||
|
||||
Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) const {
|
||||
ERR_FAIL_COND_V(_terrain == nullptr, Variant());
|
||||
VoxelDataMap &map = _terrain->get_storage();
|
||||
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
||||
ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable");
|
||||
// TODO In this situation, the generator would need to be invoked to fill in the blank
|
||||
ERR_FAIL_COND_V_MSG(!block->has_voxels(), Variant(), "Area not cached");
|
||||
RWLockRead lock(block->get_voxels().get_lock());
|
||||
const VoxelMetadata *meta = block->get_voxels_const().get_voxel_metadata(map.to_local(pos));
|
||||
if (meta == nullptr) {
|
||||
return Variant();
|
||||
}
|
||||
return gd::get_as_variant(*meta);
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
return data.get_voxel_metadata(pos);
|
||||
}
|
||||
|
||||
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
||||
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelData &data, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
||||
int voxel_count, int batch_count, void *callback_data, bool (*callback)(void *, Vector3i, int64_t)) {
|
||||
ERR_FAIL_COND(batch_count <= 0);
|
||||
ERR_FAIL_COND(voxel_count < 0);
|
||||
ERR_FAIL_COND(!math::is_valid_size(voxel_box.size));
|
||||
ERR_FAIL_COND(callback == nullptr);
|
||||
|
||||
const Box3i block_box = voxel_box.downscaled(map.get_block_size());
|
||||
const unsigned int block_size = data.get_block_size();
|
||||
const Box3i block_box = voxel_box.downscaled(block_size);
|
||||
|
||||
const int block_count = voxel_count / batch_count;
|
||||
//const int bs_mask = map.get_block_size_mask();
|
||||
|
@ -290,7 +285,7 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
|
|||
static thread_local std::vector<Pick> picks;
|
||||
picks.reserve(batch_count);
|
||||
|
||||
const float block_volume = map.get_block_size() * map.get_block_size() * map.get_block_size();
|
||||
const float block_volume = math::cubed(block_size);
|
||||
CRASH_COND(block_volume < 0.1f);
|
||||
|
||||
struct L {
|
||||
|
@ -309,15 +304,15 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
|
|||
for (int bi = 0; bi < block_count; ++bi) {
|
||||
const Vector3i block_pos = block_box.pos + L::urand_vec3i(block_box.size);
|
||||
|
||||
const Vector3i block_origin = map.block_to_voxel(block_pos);
|
||||
const Vector3i block_origin = data.block_to_voxel(block_pos);
|
||||
|
||||
VoxelDataBlock *block = map.get_block(block_pos);
|
||||
std::shared_ptr<VoxelBufferInternal> voxels_ptr = data.try_get_block_voxels(block_pos);
|
||||
|
||||
if (block != nullptr && block->has_voxels()) {
|
||||
if (voxels_ptr != nullptr) {
|
||||
// Doing ONLY reads here.
|
||||
{
|
||||
RWLockRead lock(block->get_voxels().get_lock());
|
||||
const VoxelBufferInternal &voxels = block->get_voxels_const();
|
||||
RWLockRead lock(voxels_ptr->get_lock());
|
||||
const VoxelBufferInternal &voxels = *voxels_ptr;
|
||||
|
||||
if (voxels.get_channel_compression(channel) == VoxelBufferInternal::COMPRESSION_UNIFORM) {
|
||||
const uint64_t v = voxels.get_voxel(0, 0, 0, channel);
|
||||
|
@ -330,7 +325,7 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
|
|||
}
|
||||
}
|
||||
|
||||
Box3i block_voxel_box(block_origin, Vector3iUtil::create(map.get_block_size()));
|
||||
const Box3i block_voxel_box(block_origin, Vector3iUtil::create(block_size));
|
||||
Box3i local_voxel_box = voxel_box.clipped(block_voxel_box);
|
||||
local_voxel_box.pos -= block_origin;
|
||||
const float volume_ratio = Vector3iUtil::get_volume(local_voxel_box.size) / block_volume;
|
||||
|
@ -398,11 +393,11 @@ void VoxelToolTerrain::run_blocky_random_tick(
|
|||
CallbackData cb_self{ callback };
|
||||
|
||||
const VoxelBlockyLibrary &lib = **get_voxel_library(*_terrain);
|
||||
VoxelDataMap &map = _terrain->get_storage();
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
const Box3i voxel_box(math::floor_to_int(voxel_area.position), math::floor_to_int(voxel_area.size));
|
||||
|
||||
run_blocky_random_tick_static(
|
||||
map, voxel_box, lib, voxel_count, batch_count, &cb_self, [](void *self, Vector3i pos, int64_t val) {
|
||||
data, voxel_box, lib, voxel_count, batch_count, &cb_self, [](void *self, Vector3i pos, int64_t val) {
|
||||
const Variant vpos = pos;
|
||||
const Variant vv = val;
|
||||
const Variant *args[2];
|
||||
|
@ -430,20 +425,20 @@ void VoxelToolTerrain::for_each_voxel_metadata_in_area(AABB voxel_area, const Ca
|
|||
|
||||
const Box3i data_block_box = voxel_box.downscaled(_terrain->get_data_block_size());
|
||||
|
||||
VoxelDataMap &map = _terrain->get_storage();
|
||||
VoxelData &data = _terrain->get_storage();
|
||||
|
||||
data_block_box.for_each_cell([&map, &callback, voxel_box](Vector3i block_pos) {
|
||||
VoxelDataBlock *block = map.get_block(block_pos);
|
||||
data_block_box.for_each_cell([&data, &callback, voxel_box](Vector3i block_pos) {
|
||||
std::shared_ptr<VoxelBufferInternal> voxels_ptr = data.try_get_block_voxels(block_pos);
|
||||
|
||||
if (block == nullptr || !block->has_voxels()) {
|
||||
if (voxels_ptr == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector3i block_origin = block_pos * map.get_block_size();
|
||||
const Vector3i block_origin = block_pos * data.get_block_size();
|
||||
const Box3i rel_voxel_box(voxel_box.pos - block_origin, voxel_box.size);
|
||||
// TODO Worth it locking blocks for metadata?
|
||||
|
||||
block->get_voxels().for_each_voxel_metadata_in_area(
|
||||
voxels_ptr->for_each_voxel_metadata_in_area(
|
||||
rel_voxel_box, [&callback, block_origin](Vector3i rel_pos, const VoxelMetadata &meta) {
|
||||
Variant v = gd::get_as_variant(meta);
|
||||
const Variant key = rel_pos + block_origin;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace zylann::voxel {
|
|||
|
||||
class VoxelTerrain;
|
||||
class VoxelBlockyLibrary;
|
||||
class VoxelDataMap;
|
||||
class VoxelData;
|
||||
|
||||
class VoxelToolTerrain : public VoxelTool {
|
||||
GDCLASS(VoxelToolTerrain, VoxelTool)
|
||||
|
@ -37,7 +37,7 @@ public:
|
|||
|
||||
// For easier unit testing (the regular one needs a terrain setup etc, harder to test atm)
|
||||
// The `_static` suffix is because it otherwise conflicts with the non-static method when registering the class
|
||||
static void run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
||||
static void run_blocky_random_tick_static(VoxelData &data, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
||||
int voxel_count, int batch_count, void *callback_data, bool (*callback)(void *, Vector3i, int64_t));
|
||||
|
||||
void for_each_voxel_metadata_in_area(AABB voxel_area, const Callable &callback);
|
||||
|
|
|
@ -2,9 +2,61 @@
|
|||
#include "../util/dstack.h"
|
||||
#include "../util/math/conv.h"
|
||||
#include "voxel_data_grid.h"
|
||||
#include "voxel_metadata_variant.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
namespace {
|
||||
struct BeforeUnloadSaveAction {
|
||||
std::vector<VoxelData::BlockToSave> *to_save;
|
||||
Vector3i position;
|
||||
unsigned int lod_index;
|
||||
|
||||
inline void operator()(VoxelDataBlock &block) {
|
||||
if (block.is_modified()) {
|
||||
// If a modified block has no voxels, it is equivalent to removing the block from the stream
|
||||
VoxelData::BlockToSave b;
|
||||
b.position = position;
|
||||
b.lod_index = lod_index;
|
||||
if (block.has_voxels()) {
|
||||
// No copy is necessary because the block will be removed anyways
|
||||
b.voxels = block.get_voxels_shared();
|
||||
}
|
||||
to_save->push_back(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ScheduleSaveAction {
|
||||
std::vector<VoxelData::BlockToSave> &blocks_to_save;
|
||||
uint8_t lod_index;
|
||||
bool with_copy;
|
||||
|
||||
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
|
||||
if (block.is_modified()) {
|
||||
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
||||
VoxelData::BlockToSave b;
|
||||
// If a modified block has no voxels, it is equivalent to removing the block from the stream
|
||||
if (block.has_voxels()) {
|
||||
if (with_copy) {
|
||||
RWLockRead lock(block.get_voxels().get_lock());
|
||||
b.voxels = make_shared_instance<VoxelBufferInternal>();
|
||||
block.get_voxels_const().duplicate_to(*b.voxels, true);
|
||||
} else {
|
||||
b.voxels = block.get_voxels_shared();
|
||||
}
|
||||
}
|
||||
b.position = bpos;
|
||||
b.lod_index = lod_index;
|
||||
blocks_to_save.push_back(b);
|
||||
block.set_modified(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
} //namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
VoxelData::VoxelData() {}
|
||||
VoxelData::~VoxelData() {}
|
||||
|
||||
|
@ -385,8 +437,7 @@ void VoxelData::mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod
|
|||
|
||||
//RWLockWrite wlock(block->get_voxels_shared()->get_lock());
|
||||
block->set_modified(true);
|
||||
|
||||
// TODO call `set_edited(true)` as well? Apparently it wasn't needed so far, but it's a bit confusing
|
||||
block->set_edited(true);
|
||||
|
||||
// TODO That boolean is also modified by the threaded update task (always set to false)
|
||||
if (!block->get_needs_lodding()) {
|
||||
|
@ -401,8 +452,8 @@ void VoxelData::mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod
|
|||
}
|
||||
}
|
||||
|
||||
bool VoxelData::try_set_block_buffer(
|
||||
Vector3i block_position, unsigned int lod_index, std::shared_ptr<VoxelBufferInternal> buffer, bool edited) {
|
||||
VoxelDataBlock *VoxelData::try_set_block_buffer(Vector3i block_position, unsigned int lod_index,
|
||||
std::shared_ptr<VoxelBufferInternal> buffer, bool edited, bool overwrite) {
|
||||
Lod &data_lod = _lods[lod_index];
|
||||
|
||||
if (buffer->get_size() != Vector3iUtil::create(get_block_size())) {
|
||||
|
@ -414,11 +465,11 @@ bool VoxelData::try_set_block_buffer(
|
|||
// Store buffer
|
||||
RWLockWrite wlock(data_lod.map_lock);
|
||||
// TODO Expose `overwrite` as parameter?
|
||||
VoxelDataBlock *block = data_lod.map.set_block_buffer(block_position, buffer, false);
|
||||
CRASH_COND(block == nullptr);
|
||||
VoxelDataBlock *block = data_lod.map.set_block_buffer(block_position, buffer, overwrite);
|
||||
ZN_ASSERT(block != nullptr);
|
||||
block->set_edited(edited);
|
||||
|
||||
return true;
|
||||
return block;
|
||||
}
|
||||
|
||||
void VoxelData::set_empty_block_buffer(Vector3i block_position, unsigned int lod_index) {
|
||||
|
@ -435,6 +486,24 @@ bool VoxelData::has_block(Vector3i bpos, unsigned int lod_index) const {
|
|||
return data_lod.map.has_block(bpos);
|
||||
}
|
||||
|
||||
// VoxelDataBlock *VoxelData::get_block(Vector3i bpos) {
|
||||
// Lod &data_lod = _lods[0];
|
||||
// RWLockRead rlock(data_lod.map_lock);
|
||||
// return data_lod.map.get_block(bpos);
|
||||
// }
|
||||
|
||||
bool VoxelData::has_all_blocks_in_area(Box3i data_blocks_box) const {
|
||||
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
|
||||
data_blocks_box = data_blocks_box.clipped(bounds_in_blocks);
|
||||
|
||||
const Lod &data_lod = _lods[0];
|
||||
RWLockRead rlock(data_lod.map_lock);
|
||||
|
||||
return data_blocks_box.all_cells_match([&data_lod](Vector3i bpos) { //
|
||||
return data_lod.map.has_block(bpos);
|
||||
});
|
||||
}
|
||||
|
||||
unsigned int VoxelData::get_block_count() const {
|
||||
unsigned int sum = 0;
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
|
@ -585,6 +654,64 @@ void VoxelData::update_lods(Span<const Vector3i> modified_lod0_blocks, std::vect
|
|||
// }
|
||||
}
|
||||
|
||||
void VoxelData::unload_blocks(Box3i bbox, unsigned int lod_index, std::vector<BlockToSave> *to_save) {
|
||||
Lod &lod = _lods[lod_index];
|
||||
RWLockWrite wlock(lod.map_lock);
|
||||
if (to_save == nullptr) {
|
||||
bbox.for_each_cell_zxy([&lod](Vector3i bpos) { //
|
||||
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
|
||||
});
|
||||
} else {
|
||||
bbox.for_each_cell_zxy([&lod, lod_index, to_save](Vector3i bpos) {
|
||||
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, lod_index });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelData::unload_blocks(Span<const Vector3i> positions, std::vector<BlockToSave> *to_save) {
|
||||
Lod &lod = _lods[0];
|
||||
RWLockWrite wlock(lod.map_lock);
|
||||
if (to_save == nullptr) {
|
||||
for (Vector3i bpos : positions) {
|
||||
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
|
||||
}
|
||||
} else {
|
||||
for (Vector3i bpos : positions) {
|
||||
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelData::consume_block_modifications(Vector3i bpos, VoxelData::BlockToSave &out_to_save) {
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
VoxelDataBlock *block = lod.map.get_block(bpos);
|
||||
if (block == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (block->is_modified()) {
|
||||
if (block->has_voxels()) {
|
||||
RWLockRead lock(block->get_voxels().get_lock());
|
||||
out_to_save.voxels = make_shared_instance<VoxelBufferInternal>();
|
||||
block->get_voxels_const().duplicate_to(*out_to_save.voxels, true);
|
||||
}
|
||||
out_to_save.position = bpos;
|
||||
out_to_save.lod_index = 0;
|
||||
block->set_modified(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void VoxelData::consume_all_modifications(std::vector<BlockToSave> &to_save, bool with_copy) {
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
Lod &lod = _lods[lod_index];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
lod.map.for_each_block(ScheduleSaveAction{ to_save, uint8_t(lod_index), with_copy });
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelData::get_missing_blocks(
|
||||
Span<const Vector3i> block_positions, unsigned int lod_index, std::vector<Vector3i> &out_missing) const {
|
||||
const Lod &lod = _lods[lod_index];
|
||||
|
@ -600,7 +727,7 @@ void VoxelData::get_missing_blocks(
|
|||
Box3i p_blocks_box, unsigned int lod_index, std::vector<Vector3i> &out_missing) const {
|
||||
const Lod &data_lod = _lods[lod_index];
|
||||
|
||||
const Box3i bounds_in_blocks = _bounds_in_voxels.downscaled(get_block_size());
|
||||
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
|
||||
const Box3i blocks_box = p_blocks_box.clipped(bounds_in_blocks);
|
||||
|
||||
RWLockRead rlock(data_lod.map_lock);
|
||||
|
@ -612,27 +739,25 @@ void VoxelData::get_missing_blocks(
|
|||
});
|
||||
}
|
||||
|
||||
unsigned int VoxelData::get_blocks_with_voxel_data(
|
||||
void VoxelData::get_blocks_with_voxel_data(
|
||||
Box3i p_blocks_box, unsigned int lod_index, Span<std::shared_ptr<VoxelBufferInternal>> out_blocks) const {
|
||||
ZN_ASSERT(int64_t(out_blocks.size()) >= Vector3iUtil::get_volume(p_blocks_box.size));
|
||||
|
||||
const Lod &data_lod = _lods[lod_index];
|
||||
RWLockRead rlock(data_lod.map_lock);
|
||||
|
||||
unsigned int count = 0;
|
||||
unsigned int index = 0;
|
||||
|
||||
// Iteration order matters for thread access.
|
||||
p_blocks_box.for_each_cell_zxy([&count, &data_lod, &out_blocks](Vector3i data_block_pos) {
|
||||
p_blocks_box.for_each_cell_zxy([&index, &data_lod, &out_blocks](Vector3i data_block_pos) {
|
||||
const VoxelDataBlock *nblock = data_lod.map.get_block(data_block_pos);
|
||||
// The block can actually be null on some occasions. Not sure yet if it's that bad
|
||||
//CRASH_COND(nblock == nullptr);
|
||||
if (nblock != nullptr && nblock->has_voxels()) {
|
||||
out_blocks[count] = nblock->get_voxels_shared();
|
||||
out_blocks[index] = nblock->get_voxels_shared();
|
||||
}
|
||||
++count;
|
||||
++index;
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void VoxelData::get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsigned int lod_index) const {
|
||||
|
@ -641,4 +766,93 @@ void VoxelData::get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsign
|
|||
grid.reference_area(data_lod.map, box_in_voxels);
|
||||
}
|
||||
|
||||
void VoxelData::view_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
|
||||
std::vector<Vector3i> &found_blocks_positions, std::vector<VoxelDataBlock *> &found_blocks) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
|
||||
blocks_box = blocks_box.clipped(bounds_in_blocks);
|
||||
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
|
||||
blocks_box.for_each_cell_zxy([&lod, &found_blocks_positions, &found_blocks, &missing_blocks](Vector3i bpos) {
|
||||
VoxelDataBlock *block = lod.map.get_block(bpos);
|
||||
if (block != nullptr) {
|
||||
block->viewers.add();
|
||||
found_blocks.push_back(block);
|
||||
found_blocks_positions.push_back(bpos);
|
||||
} else {
|
||||
missing_blocks.push_back(bpos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VoxelData::unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
|
||||
std::vector<Vector3i> &found_blocks, std::vector<BlockToSave> *to_save) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
|
||||
blocks_box = blocks_box.clipped(bounds_in_blocks);
|
||||
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
|
||||
blocks_box.for_each_cell_zxy([&lod, &missing_blocks, &found_blocks, to_save](Vector3i bpos) {
|
||||
VoxelDataBlock *block = lod.map.get_block(bpos);
|
||||
if (block != nullptr) {
|
||||
block->viewers.remove();
|
||||
if (block->viewers.get() == 0) {
|
||||
if (to_save == nullptr) {
|
||||
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
|
||||
} else {
|
||||
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, 0 });
|
||||
}
|
||||
}
|
||||
found_blocks.push_back(bpos);
|
||||
} else {
|
||||
missing_blocks.push_back(bpos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<VoxelBufferInternal> VoxelData::try_get_block_voxels(Vector3i bpos) {
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
VoxelDataBlock *block = lod.map.get_block(bpos);
|
||||
if (block == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (block->has_voxels()) {
|
||||
return block->get_voxels_shared();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VoxelData::set_voxel_metadata(Vector3i pos, Variant meta) {
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
VoxelDataBlock *block = lod.map.get_block(lod.map.voxel_to_block(pos));
|
||||
ZN_ASSERT_RETURN_MSG(block != nullptr, "Area not editable");
|
||||
// TODO Ability to have metadata in areas where voxels have not been allocated?
|
||||
// Otherwise we have to generate the block, because that's where it is stored at the moment.
|
||||
ZN_ASSERT_RETURN_MSG(block->has_voxels(), "Area not cached");
|
||||
RWLockWrite lock(block->get_voxels().get_lock());
|
||||
VoxelMetadata *meta_storage = block->get_voxels().get_or_create_voxel_metadata(lod.map.to_local(pos));
|
||||
ZN_ASSERT_RETURN(meta_storage != nullptr);
|
||||
gd::set_as_variant(*meta_storage, meta);
|
||||
}
|
||||
|
||||
Variant VoxelData::get_voxel_metadata(Vector3i pos) {
|
||||
Lod &lod = _lods[0];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
VoxelDataBlock *block = lod.map.get_block(lod.map.voxel_to_block(pos));
|
||||
ZN_ASSERT_RETURN_V_MSG(block != nullptr, Variant(), "Area not editable");
|
||||
ZN_ASSERT_RETURN_V_MSG(block->has_voxels(), Variant(), "Area not cached");
|
||||
RWLockRead lock(block->get_voxels().get_lock());
|
||||
const VoxelMetadata *meta = block->get_voxels_const().get_voxel_metadata(lod.map.to_local(pos));
|
||||
if (meta == nullptr) {
|
||||
return Variant();
|
||||
}
|
||||
return gd::get_as_variant(*meta);
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
|
|
@ -36,6 +36,10 @@ public:
|
|||
return _lods[0].map.voxel_to_block(pos);
|
||||
}
|
||||
|
||||
inline Vector3i block_to_voxel(Vector3i pos) const {
|
||||
return _lods[0].map.block_to_voxel(pos);
|
||||
}
|
||||
|
||||
void set_lod_count(unsigned int p_lod_count);
|
||||
|
||||
// Clears voxel data. Keeps modifiers, generator and settings.
|
||||
|
@ -82,7 +86,7 @@ public:
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Queries.
|
||||
// Voxel queries.
|
||||
// When not specified, the used LOD index is 0.
|
||||
|
||||
VoxelSingleValue get_voxel(Vector3i pos, unsigned int channel_index, VoxelSingleValue defval) const;
|
||||
|
@ -102,6 +106,8 @@ public:
|
|||
void paste(Vector3i min_pos, const VoxelBufferInternal &src_buffer, unsigned int channels_mask, bool use_mask,
|
||||
uint64_t mask_value, bool create_new_blocks);
|
||||
|
||||
// Tests if the given area is loaded at LOD0.
|
||||
// This is necessary for editing destructively.
|
||||
bool is_area_loaded(const Box3i p_voxels_box) const;
|
||||
|
||||
// Executes a read+write operation on all voxels in the given area, on a specific channel.
|
||||
|
@ -118,6 +124,7 @@ public:
|
|||
Ref<VoxelGenerator> generator = _generator;
|
||||
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
|
||||
{
|
||||
// New blocks can be created in the map so we have to lock for writing
|
||||
RWLockWrite wlock(data_lod0.map_lock);
|
||||
data_lod0.map.write_box(
|
||||
voxel_box, channel_index, action, [&generator](VoxelBufferInternal &voxels, Vector3i pos) {
|
||||
|
@ -144,6 +151,7 @@ public:
|
|||
Ref<VoxelGenerator> generator = _generator;
|
||||
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
|
||||
{
|
||||
// New blocks can be created in the map so we have to lock for writing
|
||||
RWLockWrite wlock(data_lod0.map_lock);
|
||||
data_lod0.map.write_box_2(voxel_box, channel1_index, channel2_index, action,
|
||||
[&generator](VoxelBufferInternal &voxels, Vector3i pos) {
|
||||
|
@ -165,6 +173,7 @@ public:
|
|||
|
||||
// Clears voxel data from blocks that are pure results of generators and modifiers.
|
||||
// WARNING: this does not check if the area is editable.
|
||||
// TODO Rename `clear_cached_voxel_data_in_area`
|
||||
void clear_cached_blocks_in_voxel_area(Box3i p_voxel_box);
|
||||
|
||||
// Flags all blocks in the given area as modified at LOD0.
|
||||
|
@ -172,14 +181,17 @@ public:
|
|||
// Optionally, returns a list of affected block positions which did not require LOD updates before.
|
||||
void mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod0_new_blocks_to_lod);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Block-aware API
|
||||
|
||||
// Sets voxel data at a block position. Also sets wether this is edited data (otherwise it is cached generator
|
||||
// results).
|
||||
// If the block has different size than expected, returns false and doesn't set the data.
|
||||
// If the block already exists, it will not be overwritten, but still returns true.
|
||||
// Otherwise, returns true.
|
||||
// TODO Might need to expose a parameter for the overwriting behavior.
|
||||
bool try_set_block_buffer(
|
||||
Vector3i block_position, unsigned int lod_index, std::shared_ptr<VoxelBufferInternal> buffer, bool edited);
|
||||
// If the block has different size than expected, returns nullptr and doesn't set the data.
|
||||
// If the block already exists, it will be overwritten if `overwrite` is `true`.
|
||||
// The block is returned if it exists.
|
||||
// WARNING: the returned pointer may only be used if you know that this block won't get removed by another thread.
|
||||
VoxelDataBlock *try_set_block_buffer(Vector3i block_position, unsigned int lod_index,
|
||||
std::shared_ptr<VoxelBufferInternal> buffer, bool edited, bool overwrite);
|
||||
|
||||
// Sets empty voxel data at a block position. It means this block is known to have no edits and no cached generator
|
||||
// data.
|
||||
|
@ -187,18 +199,18 @@ public:
|
|||
// TODO Might need to expose a parameter for the overwriting behavior.
|
||||
void set_empty_block_buffer(Vector3i block_position, unsigned int lod_index);
|
||||
|
||||
// void op(Vector3i bpos, VoxelDataBlock &block)
|
||||
// void op(Vector3i bpos, const VoxelDataBlock &block)
|
||||
template <typename F>
|
||||
void for_each_block(F op) {
|
||||
void for_each_block(F op) const {
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
|
||||
Lod &lod = _lods[lod_index];
|
||||
const Lod &lod = _lods[lod_index];
|
||||
RWLockRead rlock(lod.map_lock);
|
||||
lod.map.for_each_block(op);
|
||||
}
|
||||
}
|
||||
|
||||
// void op(Vector3i bpos, VoxelDataBlock &block)
|
||||
// void op(Vector3i bpos, const VoxelDataBlock &block)
|
||||
template <typename F>
|
||||
void for_each_block_at_lod(F op, unsigned int lod_index) const {
|
||||
const Lod &lod = _lods[lod_index];
|
||||
|
@ -210,6 +222,14 @@ public:
|
|||
// This is mainly used for debugging so it isn't optimal, don't use this if you plan to query many blocks.
|
||||
bool has_block(Vector3i bpos, unsigned int lod_index) const;
|
||||
|
||||
// Gets a block directly from LOD0.
|
||||
// WARNING: the returned pointer may only be used if you know that this block won't get removed by another thread.
|
||||
// Prefer using other safe functions if possible.
|
||||
//VoxelDataBlock *get_block(Vector3i bpos);
|
||||
|
||||
// Tests if all blocks in a LOD0 area are loaded. If any isn't, returns false. Otherwise, returns true.
|
||||
bool has_all_blocks_in_area(Box3i data_blocks_box) const;
|
||||
|
||||
// Gets the total amount of allocated blocks. This includes blocks having no voxel data.
|
||||
unsigned int get_block_count() const;
|
||||
|
||||
|
@ -222,20 +242,33 @@ public:
|
|||
// Optionally, returns a list of affected block positions.
|
||||
void update_lods(Span<const Vector3i> modified_lod0_blocks, std::vector<BlockLocation> *out_updated_blocks);
|
||||
|
||||
// void action(VoxelDataBlock &block, Vector3i bpos)
|
||||
template <typename F>
|
||||
void unload_blocks(Box3i bbox, unsigned int lod_index, F action) {
|
||||
Lod &lod = _lods[lod_index];
|
||||
RWLockWrite wlock(lod.map_lock);
|
||||
bbox.for_each_cell_zxy([&lod, &action](Vector3i bpos) {
|
||||
lod.map.remove_block(bpos, [&action, bpos](VoxelDataBlock &block) { action(block, bpos); });
|
||||
});
|
||||
}
|
||||
struct BlockToSave {
|
||||
std::shared_ptr<VoxelBufferInternal> voxels;
|
||||
Vector3i position;
|
||||
uint32_t lod_index;
|
||||
};
|
||||
|
||||
// Unloads data blocks in the specified area. If some of them were modified and `to_save` is not null, their data
|
||||
// will be returned for the caller to save.
|
||||
void unload_blocks(Box3i bbox, unsigned int lod_index, std::vector<BlockToSave> *to_save);
|
||||
|
||||
// Unloads data blocks at specified positions of LOD0. If some of them were modified and `to_save` is not null,
|
||||
// their data will be returned for the caller to save.
|
||||
void unload_blocks(Span<const Vector3i> positions, std::vector<BlockToSave> *to_save);
|
||||
|
||||
// If the block at the specified LOD0 position exists and is modified, marks it as non-modified and returns a copy
|
||||
// of its data to save. Returns true if there is something to save.
|
||||
bool consume_block_modifications(Vector3i bpos, BlockToSave &out_to_save);
|
||||
|
||||
// Marks all modified blocks as unmodified and returns their data to save. if `with_copy` is true, the returned data
|
||||
// will be a copy, otherwise it will reference voxel data. Prefer using references when about to quit for example.
|
||||
void consume_all_modifications(std::vector<BlockToSave> &to_save, bool with_copy);
|
||||
|
||||
// Gets missing blocks out of the given block positions.
|
||||
// WARNING: positions outside bounds will be considered missing too.
|
||||
// TODO Don't consider positions outside bounds to be missing? This is only a byproduct of migrating old code.
|
||||
// It doesnt check this because the code using this function already does it (a bit more efficiently, but still).
|
||||
// TODO Don't consider positions outside bounds to be missing? This is only a byproduct of migrating old
|
||||
// code. It doesnt check this because the code using this function already does it (a bit more efficiently,
|
||||
// but still).
|
||||
void get_missing_blocks(
|
||||
Span<const Vector3i> block_positions, unsigned int lod_index, std::vector<Vector3i> &out_missing) const;
|
||||
|
||||
|
@ -243,11 +276,37 @@ public:
|
|||
// If the area intersects the outside of the bounds, it will be clipped.
|
||||
void get_missing_blocks(Box3i p_blocks_box, unsigned int lod_index, std::vector<Vector3i> &out_missing) const;
|
||||
|
||||
unsigned int get_blocks_with_voxel_data(
|
||||
// Gets blocks with voxel data in the given area in block coordinates.
|
||||
// Voxel data references are returned in an array big enough to contain a grid of the size of the area.
|
||||
// Blocks found will be placed at an index computed as if the array was a flat grid (ZXY).
|
||||
// Entries without voxel data will be left to null.
|
||||
void get_blocks_with_voxel_data(
|
||||
Box3i p_blocks_box, unsigned int lod_index, Span<std::shared_ptr<VoxelBufferInternal>> out_blocks) const;
|
||||
|
||||
void get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsigned int lod_index) const;
|
||||
|
||||
std::shared_ptr<VoxelBufferInternal> try_get_block_voxels(Vector3i bpos);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Reference-counted API (LOD0 only)
|
||||
|
||||
void view_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
|
||||
std::vector<Vector3i> &found_blocks_positions, std::vector<VoxelDataBlock *> &found_blocks);
|
||||
|
||||
// void unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
|
||||
// std::vector<Vector3i> &blocks_without_viewers_positions,
|
||||
// std::vector<VoxelDataBlock *> &blocks_without_viewers);
|
||||
|
||||
void unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks, std::vector<Vector3i> &found_blocks,
|
||||
std::vector<BlockToSave> *to_save);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Metadata queries.
|
||||
// Only at LOD0.
|
||||
|
||||
void set_voxel_metadata(Vector3i pos, Variant meta);
|
||||
Variant get_voxel_metadata(Vector3i pos);
|
||||
|
||||
private:
|
||||
void reset_maps_no_settings_lock();
|
||||
|
||||
|
@ -288,19 +347,21 @@ private:
|
|||
//
|
||||
// TODO Optimize: (low priority) this takes more than 5Kb in the object, even when not using LODs.
|
||||
// Each LOD contains an RWLock, which is 242 bytes, so *24 it adds up quickly.
|
||||
// A solution would be to allocate LODs dynamically in the constructor (the potential presence of LODs doesnt need
|
||||
// to change after being constructed, there is no use case for that so far)
|
||||
// A solution would be to allocate LODs dynamically in the constructor (the potential presence of LODs doesnt
|
||||
// need to change after being constructed, there is no use case for that so far)
|
||||
FixedArray<Lod, constants::MAX_LOD> _lods;
|
||||
|
||||
// Area within which voxels can exist.
|
||||
// Note, these bounds might not be exactly represented. Volumes are chunk-based, so the result may be
|
||||
// approximated to the closest chunk.
|
||||
Box3i _bounds_in_voxels;
|
||||
|
||||
uint8_t _lod_count = 1;
|
||||
|
||||
// If enabled, some data blocks can have the "not loaded" and "loaded" status. Which means we can't assume what they
|
||||
// contain, until we load them from the stream.
|
||||
// If disabled, all edits are loaded in memory, and we know if a block isn't stored, it means we can use the
|
||||
// generator and modifiers to obtain its data.
|
||||
// This mostly changes how this class is used, streaming itself is not directly implemented in this class.
|
||||
// If enabled, some data blocks can have the "not loaded" and "loaded" status. Which means we can't assume what
|
||||
// they contain, until we load them from the stream. If disabled, all edits are loaded in memory, and we know if
|
||||
// a block isn't stored, it means we can use the generator and modifiers to obtain its data. This mostly changes
|
||||
// how this class is used, streaming itself is not directly implemented in this class.
|
||||
bool _streaming_enabled = true;
|
||||
|
||||
// Procedural generation stack
|
||||
|
@ -317,7 +378,8 @@ private:
|
|||
// It is not a RWLock because it may be locked for VERY short periods of time (just reading small values).
|
||||
// In comparison, RWLock uses a `shared_timed_mutex` under the hood, and locking that for reading locks a
|
||||
// mutex internally either way.
|
||||
// There are times where locking can take longer, but it only happens rarely, when changing LOD count for example.
|
||||
// There are times where locking can take longer, but it only happens rarely, when changing LOD count for
|
||||
// example.
|
||||
Mutex _settings_mutex;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "voxel_box_mover.h"
|
||||
#include "../../meshers/blocky/voxel_mesher_blocky.h"
|
||||
#include "../../meshers/cubes/voxel_mesher_cubes.h"
|
||||
#include "../../storage/voxel_data.h"
|
||||
#include "../../util/godot/ref_counted.h"
|
||||
#include "voxel_terrain.h"
|
||||
|
||||
|
@ -183,7 +184,7 @@ static bool intersects(Span<const AABB> aabbs, const AABB &box) {
|
|||
|
||||
static void collect_boxes(
|
||||
VoxelTerrain &p_terrain, AABB query_box, uint32_t collision_nask, std::vector<AABB> &potential_boxes) {
|
||||
const VoxelDataMap &voxels = p_terrain.get_storage();
|
||||
const VoxelData &voxels = p_terrain.get_storage();
|
||||
|
||||
const int min_x = int(Math::floor(query_box.position.x));
|
||||
const int min_y = int(Math::floor(query_box.position.y));
|
||||
|
@ -204,11 +205,14 @@ static void collect_boxes(
|
|||
ERR_FAIL_COND_MSG(library_ref.is_null(), "VoxelMesherBlocky has no library assigned");
|
||||
VoxelBlockyLibrary &library = **library_ref;
|
||||
const int channel = VoxelBufferInternal::CHANNEL_TYPE;
|
||||
VoxelSingleValue defval;
|
||||
defval.i = 0;
|
||||
|
||||
// TODO Optimization: read the whole box of voxels at once, querying individually is slower
|
||||
for (i.z = min_z; i.z < max_z; ++i.z) {
|
||||
for (i.y = min_y; i.y < max_y; ++i.y) {
|
||||
for (i.x = min_x; i.x < max_x; ++i.x) {
|
||||
const int type_id = voxels.get_voxel(i, channel);
|
||||
const int type_id = voxels.get_voxel(i, channel, defval).i;
|
||||
|
||||
if (library.has_voxel(type_id)) {
|
||||
const VoxelBlockyModel &voxel_type = library.get_voxel_const(type_id);
|
||||
|
@ -231,11 +235,14 @@ static void collect_boxes(
|
|||
|
||||
} else if (try_get_as(p_terrain.get_mesher(), mesher_cubes)) {
|
||||
const int channel = VoxelBufferInternal::CHANNEL_COLOR;
|
||||
VoxelSingleValue defval;
|
||||
defval.i = 0;
|
||||
|
||||
// TODO Optimization: read the whole box of voxels at once, querying individually is slower
|
||||
for (i.z = min_z; i.z < max_z; ++i.z) {
|
||||
for (i.y = min_y; i.y < max_y; ++i.y) {
|
||||
for (i.x = min_x; i.x < max_x; ++i.x) {
|
||||
const int color_data = voxels.get_voxel(i, channel);
|
||||
const int color_data = voxels.get_voxel(i, channel, defval).i;
|
||||
if (color_data != 0) {
|
||||
potential_boxes.push_back(AABB(i, Vector3(1, 1, 1)));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "../../engine/voxel_engine_updater.h"
|
||||
#include "../../meshers/blocky/voxel_mesher_blocky.h"
|
||||
#include "../../storage/voxel_buffer_gd.h"
|
||||
#include "../../storage/voxel_data.h"
|
||||
#include "../../util/container_funcs.h"
|
||||
#include "../../util/macros.h"
|
||||
#include "../../util/math/conv.h"
|
||||
|
@ -36,9 +37,11 @@ VoxelTerrain::VoxelTerrain() {
|
|||
|
||||
set_notify_transform(true);
|
||||
|
||||
_data = make_shared_instance<VoxelData>();
|
||||
|
||||
// TODO Should it actually be finite for better discovery?
|
||||
// Infinite by default
|
||||
_bounds_in_voxels = Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT));
|
||||
_data->set_bounds(Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)));
|
||||
|
||||
_streaming_dependency = make_shared_instance<StreamingDependency>();
|
||||
_meshing_dependency = make_shared_instance<MeshingDependency>();
|
||||
|
@ -106,18 +109,18 @@ Ref<Material> VoxelTerrain::get_material_override() const {
|
|||
}
|
||||
|
||||
void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
|
||||
if (p_stream == _stream) {
|
||||
if (p_stream == get_stream()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_stream = p_stream;
|
||||
_data->set_stream(p_stream);
|
||||
|
||||
StreamingDependency::reset(_streaming_dependency, _stream, _generator);
|
||||
StreamingDependency::reset(_streaming_dependency, p_stream, get_generator());
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (_stream.is_valid()) {
|
||||
if (p_stream.is_valid()) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
Ref<Script> script = _stream->get_script();
|
||||
Ref<Script> script = p_stream->get_script();
|
||||
if (script.is_valid()) {
|
||||
// Safety check. It's too easy to break threads by making a script reload.
|
||||
// You can turn it back on, but be careful.
|
||||
|
@ -132,23 +135,23 @@ void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
|
|||
}
|
||||
|
||||
Ref<VoxelStream> VoxelTerrain::get_stream() const {
|
||||
return _stream;
|
||||
return _data->get_stream();
|
||||
}
|
||||
|
||||
void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
|
||||
if (p_generator == _generator) {
|
||||
if (p_generator == get_generator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_generator = p_generator;
|
||||
_data->set_generator(p_generator);
|
||||
|
||||
MeshingDependency::reset(_meshing_dependency, _mesher, p_generator);
|
||||
StreamingDependency::reset(_streaming_dependency, _stream, p_generator);
|
||||
StreamingDependency::reset(_streaming_dependency, get_stream(), p_generator);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (_generator.is_valid()) {
|
||||
if (p_generator.is_valid()) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
Ref<Script> script = _generator->get_script();
|
||||
Ref<Script> script = p_generator->get_script();
|
||||
if (script.is_valid()) {
|
||||
// Safety check. It's too easy to break threads by making a script reload.
|
||||
// You can turn it back on, but be careful.
|
||||
|
@ -163,7 +166,7 @@ void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
|
|||
}
|
||||
|
||||
Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
|
||||
return _generator;
|
||||
return _data->get_generator();
|
||||
}
|
||||
|
||||
/*void VoxelTerrain::set_data_block_size_po2(unsigned int p_block_size_po2) {
|
||||
|
@ -183,12 +186,12 @@ Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
|
|||
_on_stream_params_changed();
|
||||
}*/
|
||||
|
||||
void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
|
||||
_data_map.create(0);
|
||||
}
|
||||
// void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
|
||||
// _data_map.create(0);
|
||||
// }
|
||||
|
||||
unsigned int VoxelTerrain::get_data_block_size_pow2() const {
|
||||
return _data_map.get_block_size_pow2();
|
||||
return _data->get_block_size_po2();
|
||||
}
|
||||
|
||||
unsigned int VoxelTerrain::get_mesh_block_size_pow2() const {
|
||||
|
@ -250,10 +253,10 @@ void VoxelTerrain::_on_stream_params_changed() {
|
|||
stop_streamer();
|
||||
stop_updater();
|
||||
|
||||
if (_stream.is_valid()) {
|
||||
const int stream_block_size_po2 = _stream->get_block_size_po2();
|
||||
_set_block_size_po2(stream_block_size_po2);
|
||||
}
|
||||
// if (_stream.is_valid()) {
|
||||
// const int stream_block_size_po2 = _stream->get_block_size_po2();
|
||||
// _set_block_size_po2(stream_block_size_po2);
|
||||
// }
|
||||
|
||||
// VoxelEngine::get_singleton().set_volume_data_block_size(_volume_id, 1 << get_data_block_size_pow2());
|
||||
// VoxelEngine::get_singleton().set_volume_render_block_size(_volume_id, 1 << get_mesh_block_size_pow2());
|
||||
|
@ -261,7 +264,7 @@ void VoxelTerrain::_on_stream_params_changed() {
|
|||
// The whole map might change, so regenerate it
|
||||
reset_map();
|
||||
|
||||
if ((_stream.is_valid() || _generator.is_valid()) &&
|
||||
if ((get_stream().is_valid() || get_generator().is_valid()) &&
|
||||
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
|
||||
start_streamer();
|
||||
start_updater();
|
||||
|
@ -288,7 +291,7 @@ void VoxelTerrain::set_mesher(Ref<VoxelMesher> mesher) {
|
|||
|
||||
_mesher = mesher;
|
||||
|
||||
MeshingDependency::reset(_meshing_dependency, _mesher, _generator);
|
||||
MeshingDependency::reset(_meshing_dependency, _mesher, get_generator());
|
||||
|
||||
stop_updater();
|
||||
|
||||
|
@ -302,7 +305,7 @@ void VoxelTerrain::set_mesher(Ref<VoxelMesher> mesher) {
|
|||
}
|
||||
|
||||
void VoxelTerrain::get_viewers_in_area(std::vector<int> &out_viewer_ids, Box3i voxel_box) const {
|
||||
const Box3i block_box = voxel_box.downscaled(_data_map.get_block_size());
|
||||
const Box3i block_box = voxel_box.downscaled(get_data_block_size());
|
||||
|
||||
for (auto it = _paired_viewers.begin(); it != _paired_viewers.end(); ++it) {
|
||||
const PairedViewer &viewer = *it;
|
||||
|
@ -405,7 +408,8 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
|
|||
}
|
||||
|
||||
const int render_to_data_factor = get_mesh_block_size() / get_data_block_size();
|
||||
const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
|
||||
|
||||
/*const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
|
||||
// Pad by 1 because meshing needs neighbors
|
||||
const Box3i data_box =
|
||||
Box3i(mesh_block.position * render_to_data_factor, Vector3iUtil::create(render_to_data_factor))
|
||||
|
@ -418,7 +422,15 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
|
|||
// Check if we have the data
|
||||
const bool data_available = data_box.all_cells_match([this](Vector3i bpos) { //
|
||||
return _data_map.has_block(bpos);
|
||||
});
|
||||
});*/
|
||||
|
||||
const Box3i data_box =
|
||||
Box3i(mesh_block.position * render_to_data_factor, Vector3iUtil::create(render_to_data_factor)).padded(1);
|
||||
|
||||
// If we get an empty box at this point, something is wrong with the caller
|
||||
ZN_ASSERT_RETURN(!data_box.is_empty());
|
||||
|
||||
const bool data_available = _data->has_all_blocks_in_area(data_box);
|
||||
|
||||
if (data_available) {
|
||||
// Regardless of if the updater is updating the block already,
|
||||
|
@ -428,7 +440,7 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
|
|||
}
|
||||
}
|
||||
|
||||
void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification) {
|
||||
/*void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification) {
|
||||
VoxelDataBlock *block = _data_map.get_block(bpos);
|
||||
|
||||
if (block == nullptr) {
|
||||
|
@ -469,7 +481,7 @@ void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool requi
|
|||
// TODO viewers with varying flags during the game is not supported at the moment.
|
||||
// They have to be re-created, which may cause world re-load...
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
|
||||
if (mesh_flag == false && collision_flag == false) {
|
||||
|
@ -503,7 +515,7 @@ void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision
|
|||
// They have to be re-created, which may cause world re-load...
|
||||
}
|
||||
|
||||
void VoxelTerrain::unview_data_block(Vector3i bpos) {
|
||||
/*void VoxelTerrain::unview_data_block(Vector3i bpos) {
|
||||
VoxelDataBlock *block = _data_map.get_block(bpos);
|
||||
|
||||
if (block == nullptr) {
|
||||
|
@ -540,7 +552,7 @@ void VoxelTerrain::unview_data_block(Vector3i bpos) {
|
|||
unload_data_block(bpos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
|
||||
VoxelMeshBlockVT *block = _mesh_map.get_block(bpos);
|
||||
|
@ -569,35 +581,7 @@ void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collisi
|
|||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct ScheduleSaveAction {
|
||||
std::vector<VoxelTerrain::BlockToSave> &blocks_to_save;
|
||||
bool with_copy;
|
||||
|
||||
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
|
||||
// TODO Don't ask for save if the stream doesn't support it!
|
||||
if (block.is_modified()) {
|
||||
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
||||
VoxelTerrain::BlockToSave b;
|
||||
// If a modified block has no voxels, it is equivalent to removing the block from the stream
|
||||
if (block.has_voxels()) {
|
||||
if (with_copy) {
|
||||
RWLockRead lock(block.get_voxels().get_lock());
|
||||
b.voxels = make_shared_instance<VoxelBufferInternal>();
|
||||
block.get_voxels_const().duplicate_to(*b.voxels, true);
|
||||
} else {
|
||||
b.voxels = block.get_voxels_shared();
|
||||
}
|
||||
}
|
||||
b.position = bpos;
|
||||
blocks_to_save.push_back(b);
|
||||
block.set_modified(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void VoxelTerrain::unload_data_block(Vector3i bpos) {
|
||||
/*void VoxelTerrain::unload_data_block(Vector3i bpos) {
|
||||
const bool save = _stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor);
|
||||
|
||||
_data_map.remove_block(bpos, [this, save, bpos](VoxelDataBlock &block) {
|
||||
|
@ -612,7 +596,7 @@ void VoxelTerrain::unload_data_block(Vector3i bpos) {
|
|||
|
||||
// Blocks in the update queue will be cancelled in _process,
|
||||
// because it's too expensive to linear-search all blocks for each block
|
||||
}
|
||||
}*/
|
||||
|
||||
void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
|
||||
std::vector<Vector3i> &blocks_pending_update = _blocks_pending_update;
|
||||
|
@ -631,10 +615,14 @@ void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
|
|||
}
|
||||
|
||||
void VoxelTerrain::save_all_modified_blocks(bool with_copy) {
|
||||
// That may cause a stutter, so should be used when the player won't notice
|
||||
_data_map.for_each_block(ScheduleSaveAction{ _blocks_to_save, with_copy });
|
||||
ZN_PROFILE_SCOPE();
|
||||
Ref<VoxelStream> stream = get_stream();
|
||||
ERR_FAIL_COND_MSG(stream.is_null(), "Attempting to save modified blocks, but there is no stream to save them to.");
|
||||
|
||||
if (_stream.is_valid() && _instancer != nullptr && _stream->supports_instance_blocks()) {
|
||||
// That may cause a stutter, so should be used when the player won't notice
|
||||
_data->consume_all_modifications(_blocks_to_save, with_copy);
|
||||
|
||||
if (stream.is_valid() && _instancer != nullptr && stream->supports_instance_blocks()) {
|
||||
_instancer->save_all_modified_blocks();
|
||||
}
|
||||
|
||||
|
@ -714,7 +702,7 @@ void VoxelTerrain::start_updater() {
|
|||
|
||||
void VoxelTerrain::stop_updater() {
|
||||
// Invalidate pending tasks
|
||||
MeshingDependency::reset(_meshing_dependency, _mesher, _generator);
|
||||
MeshingDependency::reset(_meshing_dependency, _mesher, get_generator());
|
||||
|
||||
// VoxelEngine::get_singleton().set_volume_mesher(_volume_id, Ref<VoxelMesher>());
|
||||
|
||||
|
@ -738,7 +726,7 @@ void VoxelTerrain::remesh_all_blocks() {
|
|||
|
||||
// At the moment, this function is for client-side use case in multiplayer scenarios
|
||||
void VoxelTerrain::generate_block_async(Vector3i block_position) {
|
||||
if (_data_map.has_block(block_position)) {
|
||||
if (_data->has_block(block_position, 0)) {
|
||||
// Already exists
|
||||
return;
|
||||
}
|
||||
|
@ -752,7 +740,7 @@ void VoxelTerrain::generate_block_async(Vector3i block_position) {
|
|||
// }
|
||||
|
||||
LoadingBlock new_loading_block;
|
||||
const Box3i block_box(_data_map.block_to_voxel(block_position), Vector3iUtil::create(_data_map.get_block_size()));
|
||||
const Box3i block_box(_data->block_to_voxel(block_position), Vector3iUtil::create(_data->get_block_size()));
|
||||
for (size_t i = 0; i < _paired_viewers.size(); ++i) {
|
||||
const PairedViewer &viewer = _paired_viewers[i];
|
||||
if (viewer.state.data_box.intersects(block_box)) {
|
||||
|
@ -777,7 +765,7 @@ void VoxelTerrain::start_streamer() {
|
|||
|
||||
void VoxelTerrain::stop_streamer() {
|
||||
// Invalidate pending tasks
|
||||
StreamingDependency::reset(_streaming_dependency, _stream, _generator);
|
||||
StreamingDependency::reset(_streaming_dependency, get_stream(), get_generator());
|
||||
// VoxelEngine::get_singleton().set_volume_stream(_volume_id, Ref<VoxelStream>());
|
||||
// VoxelEngine::get_singleton().set_volume_generator(_volume_id, Ref<VoxelGenerator>());
|
||||
_loading_blocks.clear();
|
||||
|
@ -787,10 +775,10 @@ void VoxelTerrain::stop_streamer() {
|
|||
void VoxelTerrain::reset_map() {
|
||||
// Discard everything, to reload it all
|
||||
|
||||
_data_map.for_each_block([this](const Vector3i &bpos, VoxelDataBlock &block) { //
|
||||
emit_data_block_unloaded(block, bpos);
|
||||
_data->for_each_block([this](const Vector3i &bpos, const VoxelDataBlock &block) { //
|
||||
emit_data_block_unloaded(bpos);
|
||||
});
|
||||
_data_map.create(0);
|
||||
_data->reset_maps();
|
||||
|
||||
_mesh_map.clear();
|
||||
|
||||
|
@ -821,10 +809,12 @@ void VoxelTerrain::try_schedule_mesh_update_from_data(const Box3i &box_in_voxels
|
|||
}
|
||||
|
||||
void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
|
||||
box_in_voxels.clip(_bounds_in_voxels);
|
||||
_data->mark_area_modified(box_in_voxels, nullptr);
|
||||
|
||||
box_in_voxels.clip(_data->get_bounds());
|
||||
|
||||
// Mark data as modified
|
||||
const Box3i data_box = box_in_voxels.downscaled(get_data_block_size());
|
||||
/*const Box3i data_box = box_in_voxels.downscaled(get_data_block_size());
|
||||
data_box.for_each_cell([this](Vector3i pos) {
|
||||
VoxelDataBlock *block = _data_map.get_block(pos);
|
||||
// The edit can happen next to a boundary
|
||||
|
@ -832,7 +822,7 @@ void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
|
|||
block->set_modified(true);
|
||||
block->set_edited(true);
|
||||
}
|
||||
});
|
||||
});*/
|
||||
|
||||
if (_area_edit_notification_enabled) {
|
||||
GDVIRTUAL_CALL(_on_area_edited, box_in_voxels.pos, box_in_voxels.size);
|
||||
|
@ -1002,17 +992,17 @@ void VoxelTerrain::send_block_data_requests() {
|
|||
// Blocks to load
|
||||
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
|
||||
const Vector3i block_pos = _blocks_pending_load[i];
|
||||
// TODO Batch request
|
||||
// TODO Optimization: Batch request
|
||||
request_block_load(_volume_id, _streaming_dependency, get_data_block_size(), block_pos, shared_viewers_data,
|
||||
volume_transform, _instancer != nullptr);
|
||||
}
|
||||
|
||||
// Blocks to save
|
||||
if (_stream.is_valid()) {
|
||||
if (get_stream().is_valid()) {
|
||||
for (unsigned int i = 0; i < _blocks_to_save.size(); ++i) {
|
||||
ZN_PRINT_VERBOSE(format("Requesting save of block {}", _blocks_to_save[i].position));
|
||||
const BlockToSave b = _blocks_to_save[i];
|
||||
// TODO Batch request
|
||||
const VoxelData::BlockToSave b = _blocks_to_save[i];
|
||||
// TODO Optimization: Batch request
|
||||
request_voxel_block_save(_volume_id, b.voxels, b.position, _streaming_dependency, get_data_block_size());
|
||||
}
|
||||
} else {
|
||||
|
@ -1026,7 +1016,7 @@ void VoxelTerrain::send_block_data_requests() {
|
|||
_blocks_to_save.clear();
|
||||
}
|
||||
|
||||
void VoxelTerrain::emit_data_block_loaded(const VoxelDataBlock &block, Vector3i bpos) {
|
||||
void VoxelTerrain::emit_data_block_loaded(Vector3i bpos) {
|
||||
// Not sure about exposing buffers directly... some stuff on them is useful to obtain directly,
|
||||
// but also it allows scripters to mess with voxels in a way they should not.
|
||||
// Example: modifying voxels without locking them first, while another thread may be reading them at the same
|
||||
|
@ -1039,9 +1029,7 @@ void VoxelTerrain::emit_data_block_loaded(const VoxelDataBlock &block, Vector3i
|
|||
emit_signal(VoxelStringNames::get_singleton().block_loaded, bpos);
|
||||
}
|
||||
|
||||
void VoxelTerrain::emit_data_block_unloaded(const VoxelDataBlock &block, Vector3i bpos) {
|
||||
// const Variant vbuffer = block->voxels;
|
||||
// const Variant *args[2] = { &vpos, &vbuffer };
|
||||
void VoxelTerrain::emit_data_block_unloaded(Vector3i bpos) {
|
||||
emit_signal(VoxelStringNames::get_singleton().block_unloaded, bpos);
|
||||
}
|
||||
|
||||
|
@ -1057,7 +1045,7 @@ bool VoxelTerrain::try_get_paired_viewer_index(uint32_t id, size_t &out_i) const
|
|||
}
|
||||
|
||||
// TODO It is unclear yet if this API will stay. I have a feeling it might consume a lot of CPU
|
||||
void VoxelTerrain::notify_data_block_enter(VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id) {
|
||||
void VoxelTerrain::notify_data_block_enter(const VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id) {
|
||||
if (!VoxelEngine::get_singleton().viewer_exists(viewer_id)) {
|
||||
// The viewer might have been removed between the moment we requested the block and the moment we finished
|
||||
// loading it
|
||||
|
@ -1113,8 +1101,10 @@ void VoxelTerrain::process_viewers() {
|
|||
// TODO There is probably a better way to do this
|
||||
const float view_distance_scale = world_to_local_transform.basis.xform(Vector3(1, 0, 0)).length();
|
||||
|
||||
const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
|
||||
const Box3i bounds_in_mesh_blocks = _bounds_in_voxels.downscaled(get_mesh_block_size());
|
||||
const Box3i bounds_in_voxels = _data->get_bounds();
|
||||
|
||||
const Box3i bounds_in_data_blocks = bounds_in_voxels.downscaled(get_data_block_size());
|
||||
const Box3i bounds_in_mesh_blocks = bounds_in_voxels.downscaled(get_mesh_block_size());
|
||||
|
||||
struct UpdatePairedViewer {
|
||||
VoxelTerrain &self;
|
||||
|
@ -1186,7 +1176,8 @@ void VoxelTerrain::process_viewers() {
|
|||
VoxelEngine::get_singleton().for_each_viewer(u);
|
||||
}
|
||||
|
||||
const bool can_load_blocks = (_automatic_loading_enabled && (_stream.is_valid() || _generator.is_valid())) &&
|
||||
const bool can_load_blocks =
|
||||
(_automatic_loading_enabled && (get_stream().is_valid() || get_generator().is_valid())) &&
|
||||
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor);
|
||||
|
||||
// Find out which blocks need to appear and which need to be unloaded
|
||||
|
@ -1201,28 +1192,7 @@ void VoxelTerrain::process_viewers() {
|
|||
const Box3i &prev_data_box = viewer.prev_state.data_box;
|
||||
|
||||
if (prev_data_box != new_data_box) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
const bool require_notifications = _block_enter_notification_enabled &&
|
||||
VoxelEngine::get_singleton().is_viewer_requiring_data_block_notifications(viewer.id);
|
||||
|
||||
// Unview blocks that just fell out of range
|
||||
prev_data_box.difference(new_data_box, [this](Box3i out_of_range_box) {
|
||||
out_of_range_box.for_each_cell([this](Vector3i bpos) { //
|
||||
unview_data_block(bpos);
|
||||
});
|
||||
});
|
||||
|
||||
// View blocks that just entered the range
|
||||
if (can_load_blocks) {
|
||||
new_data_box.difference(
|
||||
prev_data_box, [this, &viewer, require_notifications](Box3i box_to_load) {
|
||||
box_to_load.for_each_cell([this, &viewer, require_notifications](Vector3i bpos) {
|
||||
// Load or update block
|
||||
view_data_block(bpos, viewer.id, require_notifications);
|
||||
});
|
||||
});
|
||||
}
|
||||
process_viewer_data_box_change(viewer.id, prev_data_box, new_data_box, can_load_blocks);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1303,6 +1273,140 @@ void VoxelTerrain::process_viewers() {
|
|||
_stats.time_request_blocks_to_load = profiling_clock.restart();
|
||||
}
|
||||
|
||||
void VoxelTerrain::process_viewer_data_box_change(
|
||||
uint32_t viewer_id, Box3i prev_data_box, Box3i new_data_box, bool can_load_blocks) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
/*
|
||||
// Unview blocks that just fell out of range
|
||||
prev_data_box.difference(new_data_box, [this](Box3i out_of_range_box) {
|
||||
out_of_range_box.for_each_cell([this](Vector3i bpos) { //
|
||||
unview_data_block(bpos);
|
||||
});
|
||||
});
|
||||
|
||||
// View blocks that just entered the range
|
||||
if (can_load_blocks) {
|
||||
new_data_box.difference(
|
||||
prev_data_box, [this, &viewer, require_notifications](Box3i box_to_load) {
|
||||
box_to_load.for_each_cell([this, &viewer, require_notifications](Vector3i bpos) {
|
||||
// Load or update block
|
||||
view_data_block(bpos, viewer.id, require_notifications);
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
static thread_local std::vector<Vector3i> tls_missing_blocks;
|
||||
static thread_local std::vector<Vector3i> tls_found_blocks_positions;
|
||||
|
||||
// Unview blocks that just fell out of range
|
||||
{
|
||||
const bool may_save =
|
||||
get_stream().is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor);
|
||||
|
||||
tls_missing_blocks.clear();
|
||||
tls_found_blocks_positions.clear();
|
||||
|
||||
// Decrement refcounts from loaded blocks, and unload them
|
||||
prev_data_box.difference(new_data_box, [this, may_save](Box3i out_of_range_box) {
|
||||
_data->unview_area(out_of_range_box, tls_missing_blocks, tls_found_blocks_positions, &_blocks_to_save);
|
||||
});
|
||||
|
||||
// Remove loading blocks (those were loaded and had their refcount reach zero)
|
||||
for (const Vector3i bpos : tls_found_blocks_positions) {
|
||||
emit_data_block_unloaded(bpos);
|
||||
_loading_blocks.erase(bpos);
|
||||
}
|
||||
|
||||
// Remove refcount from loading blocks, and cancel loading if it reaches zero
|
||||
for (const Vector3i bpos : tls_missing_blocks) {
|
||||
auto loading_block_it = _loading_blocks.find(bpos);
|
||||
if (loading_block_it == _loading_blocks.end()) {
|
||||
ZN_PRINT_VERBOSE("Request to unview a loading block that was never requested");
|
||||
// Not expected, but fine I guess
|
||||
return;
|
||||
}
|
||||
|
||||
LoadingBlock &loading_block = loading_block_it->second;
|
||||
loading_block.viewers.remove();
|
||||
|
||||
if (loading_block.viewers.get() == 0) {
|
||||
// No longer want to load it
|
||||
_loading_blocks.erase(loading_block_it);
|
||||
|
||||
// TODO Do we really need that vector after all?
|
||||
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
|
||||
if (_blocks_pending_load[i] == bpos) {
|
||||
_blocks_pending_load[i] = _blocks_pending_load.back();
|
||||
_blocks_pending_load.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// View blocks coming into range
|
||||
if (can_load_blocks) {
|
||||
const bool require_notifications = _block_enter_notification_enabled &&
|
||||
VoxelEngine::get_singleton().is_viewer_requiring_data_block_notifications(viewer_id);
|
||||
|
||||
static thread_local std::vector<VoxelDataBlock *> tls_found_blocks;
|
||||
|
||||
tls_missing_blocks.clear();
|
||||
tls_found_blocks.clear();
|
||||
tls_found_blocks_positions.clear();
|
||||
|
||||
new_data_box.difference(prev_data_box, [this](Box3i box_to_load) {
|
||||
_data->view_area(box_to_load, tls_missing_blocks, tls_found_blocks_positions, tls_found_blocks);
|
||||
});
|
||||
|
||||
// Schedule loading of missing blocks
|
||||
for (const Vector3i missing_bpos : tls_missing_blocks) {
|
||||
auto loading_block_it = _loading_blocks.find(missing_bpos);
|
||||
|
||||
if (loading_block_it == _loading_blocks.end()) {
|
||||
// First viewer to request it
|
||||
LoadingBlock new_loading_block;
|
||||
new_loading_block.viewers.add();
|
||||
|
||||
if (require_notifications) {
|
||||
new_loading_block.viewers_to_notify.push_back(viewer_id);
|
||||
}
|
||||
|
||||
// Schedule a loading request
|
||||
_loading_blocks.insert({ missing_bpos, new_loading_block });
|
||||
_blocks_pending_load.push_back(missing_bpos);
|
||||
|
||||
} else {
|
||||
// More viewers
|
||||
LoadingBlock &loading_block = loading_block_it->second;
|
||||
loading_block.viewers.add();
|
||||
|
||||
if (require_notifications) {
|
||||
loading_block.viewers_to_notify.push_back(viewer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (require_notifications) {
|
||||
// Notifications for blocks that were already loaded
|
||||
// Note, we carry pointers to blocks up to here because we assume it won't get removed or invalidated by a
|
||||
// different thread. The only place where we remove blocks is in the current thread.
|
||||
for (unsigned int i = 0; i < tls_found_blocks.size(); ++i) {
|
||||
const Vector3i bpos = tls_found_blocks_positions[i];
|
||||
const VoxelDataBlock *block = tls_found_blocks[i];
|
||||
ZN_ASSERT(block != nullptr);
|
||||
notify_data_block_enter(*block, bpos, viewer_id);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO viewers with varying flags during the game is not supported at the moment.
|
||||
// They have to be re-created, which may cause world re-load...
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
|
@ -1352,7 +1456,7 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
|
|||
|
||||
CRASH_COND(ob.voxels == nullptr);
|
||||
|
||||
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.get_block_size());
|
||||
/*const Vector3i expected_block_size = Vector3iUtil::create(_data->get_block_size());
|
||||
if (ob.voxels->get_size() != expected_block_size) {
|
||||
// Voxel block size is incorrect, drop it
|
||||
ERR_PRINT(String("Block size obtained from stream is different from expected size. "
|
||||
|
@ -1360,22 +1464,25 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
|
|||
.format(varray(expected_block_size, ob.voxels->get_size())));
|
||||
++_stats.dropped_block_loads;
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Create or update block data
|
||||
VoxelDataBlock *block = _data_map.get_block(block_pos);
|
||||
const bool was_not_loaded = block == nullptr;
|
||||
block = _data_map.set_block_buffer(block_pos, ob.voxels, true);
|
||||
CRASH_COND(block == nullptr);
|
||||
|
||||
block->set_edited(ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED);
|
||||
const bool was_not_loaded = _data->has_block(block_pos, 0);
|
||||
VoxelDataBlock *block = _data->try_set_block_buffer(
|
||||
block_pos, 0, ob.voxels, ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED, true);
|
||||
if (block == nullptr) {
|
||||
// An error occurred
|
||||
ERR_PRINT("Error occured when applying loaded data block.");
|
||||
++_stats.dropped_block_loads;
|
||||
return;
|
||||
}
|
||||
|
||||
if (was_not_loaded) {
|
||||
// Set viewers count that are currently expecting the block
|
||||
block->viewers = loading_block.viewers;
|
||||
}
|
||||
|
||||
emit_data_block_loaded(*block, block_pos);
|
||||
emit_data_block_loaded(block_pos);
|
||||
|
||||
for (unsigned int i = 0; i < loading_block.viewers_to_notify.size(); ++i) {
|
||||
const uint32_t viewer_id = loading_block.viewers_to_notify[i];
|
||||
|
@ -1386,7 +1493,7 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
|
|||
{
|
||||
ZN_PROFILE_SCOPE();
|
||||
try_schedule_mesh_update_from_data(
|
||||
Box3i(_data_map.block_to_voxel(block_pos), Vector3iUtil::create(get_data_block_size())));
|
||||
Box3i(_data->block_to_voxel(block_pos), Vector3iUtil::create(get_data_block_size())));
|
||||
}
|
||||
|
||||
// We might have requested some blocks again (if we got a dropped one while we still need them)
|
||||
|
@ -1406,7 +1513,7 @@ bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBu
|
|||
ZN_PROFILE_SCOPE();
|
||||
ERR_FAIL_COND_V(voxel_data == nullptr, false);
|
||||
|
||||
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.get_block_size());
|
||||
const Vector3i expected_block_size = Vector3iUtil::create(_data->get_block_size());
|
||||
ERR_FAIL_COND_V_MSG(voxel_data->get_size() != expected_block_size, false,
|
||||
String("Block size is different from expected size. "
|
||||
"Expected {0}, got {1}")
|
||||
|
@ -1432,20 +1539,21 @@ bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBu
|
|||
_loading_blocks.erase(position);
|
||||
|
||||
// Create or update block data
|
||||
VoxelDataBlock *block = _data_map.set_block_buffer(position, voxel_data, true);
|
||||
// TODO How to set the `edited` flag? Does it matter in use cases for this function?
|
||||
const bool edited = true;
|
||||
VoxelDataBlock *block = _data->try_set_block_buffer(position, 0, voxel_data, edited, true);
|
||||
CRASH_COND(block == nullptr);
|
||||
block->viewers = refcount;
|
||||
// TODO How to set the `edited` flag? Does it matter in use cases for this function?
|
||||
|
||||
// The block itself might not be suitable for meshing yet, but blocks surrounding it might be now
|
||||
try_schedule_mesh_update_from_data(
|
||||
Box3i(_data_map.block_to_voxel(position), Vector3iUtil::create(get_data_block_size())));
|
||||
Box3i(_data->block_to_voxel(position), Vector3iUtil::create(get_data_block_size())));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VoxelTerrain::has_data_block(Vector3i position) const {
|
||||
return _data_map.has_block(position);
|
||||
return _data->has_block(position, 0);
|
||||
}
|
||||
|
||||
void VoxelTerrain::process_meshing() {
|
||||
|
@ -1481,8 +1589,7 @@ void VoxelTerrain::process_meshing() {
|
|||
// We must have picked up a valid data block
|
||||
{
|
||||
const Vector3i anchor_pos = data_box.pos + Vector3i(1, 1, 1);
|
||||
const VoxelDataBlock *data_block = _data_map.get_block(anchor_pos);
|
||||
ERR_CONTINUE(data_block == nullptr);
|
||||
ERR_CONTINUE(!_data->has_block(anchor_pos, 0));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1497,14 +1604,16 @@ void VoxelTerrain::process_meshing() {
|
|||
task->collision_hint = _generate_collisions;
|
||||
|
||||
// This iteration order is specifically chosen to match VoxelEngine and threaded access
|
||||
task->blocks_count = 0;
|
||||
_data->get_blocks_with_voxel_data(data_box, 0, to_span(task->blocks));
|
||||
task->blocks_count = Vector3iUtil::get_volume(data_box.size);
|
||||
/*task->blocks_count = 0;
|
||||
data_box.for_each_cell_zxy([this, task](Vector3i data_block_pos) {
|
||||
VoxelDataBlock *data_block = _data_map.get_block(data_block_pos);
|
||||
if (data_block != nullptr && data_block->has_voxels()) {
|
||||
task->blocks[task->blocks_count] = data_block->get_voxels_shared();
|
||||
}
|
||||
++task->blocks_count;
|
||||
});
|
||||
});*/
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
{
|
||||
|
@ -1659,11 +1768,13 @@ bool VoxelTerrain::is_stream_running_in_editor() const {
|
|||
}
|
||||
|
||||
void VoxelTerrain::set_bounds(Box3i box) {
|
||||
_bounds_in_voxels =
|
||||
Box3i bounds_in_voxels =
|
||||
box.clipped(Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)));
|
||||
|
||||
// Round to block size
|
||||
_bounds_in_voxels = _bounds_in_voxels.snapped(get_data_block_size());
|
||||
bounds_in_voxels = bounds_in_voxels.snapped(get_data_block_size());
|
||||
|
||||
_data->set_bounds(bounds_in_voxels);
|
||||
|
||||
const unsigned int largest_dimension =
|
||||
static_cast<unsigned int>(math::max(math::max(box.size.x, box.size.y), box.size.z));
|
||||
|
@ -1678,15 +1789,15 @@ void VoxelTerrain::set_bounds(Box3i box) {
|
|||
}
|
||||
|
||||
Box3i VoxelTerrain::get_bounds() const {
|
||||
return _bounds_in_voxels;
|
||||
return _data->get_bounds();
|
||||
}
|
||||
|
||||
Vector3i VoxelTerrain::_b_voxel_to_data_block(Vector3 pos) const {
|
||||
return _data_map.voxel_to_block(math::floor_to_int(pos));
|
||||
return _data->voxel_to_block(math::floor_to_int(pos));
|
||||
}
|
||||
|
||||
Vector3i VoxelTerrain::_b_data_block_to_voxel(Vector3i pos) const {
|
||||
return _data_map.block_to_voxel(pos);
|
||||
return _data->block_to_voxel(pos);
|
||||
}
|
||||
|
||||
void VoxelTerrain::_b_save_modified_blocks() {
|
||||
|
@ -1695,14 +1806,18 @@ void VoxelTerrain::_b_save_modified_blocks() {
|
|||
|
||||
// Explicitely ask to save a block if it was modified
|
||||
void VoxelTerrain::_b_save_block(Vector3i p_block_pos) {
|
||||
VoxelDataBlock *block = _data_map.get_block(p_block_pos);
|
||||
VoxelData::BlockToSave to_save;
|
||||
if (_data->consume_block_modifications(p_block_pos, to_save)) {
|
||||
_blocks_to_save.push_back(to_save);
|
||||
}
|
||||
/*VoxelDataBlock *block = _data->get_block(p_block_pos);
|
||||
ERR_FAIL_COND(block == nullptr);
|
||||
|
||||
if (!block->is_modified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleSaveAction{ _blocks_to_save, true }(p_block_pos, *block);
|
||||
ScheduleSaveAction{ _blocks_to_save, true }(p_block_pos, *block);*/
|
||||
}
|
||||
|
||||
void VoxelTerrain::_b_set_bounds(AABB aabb) {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#ifndef VOXEL_TERRAIN_H
|
||||
#define VOXEL_TERRAIN_H
|
||||
|
||||
#include "../../constants/voxel_constants.h"
|
||||
#include "../../engine/meshing_dependency.h"
|
||||
#include "../../storage/voxel_data_map.h"
|
||||
#include "../../storage/voxel_data.h"
|
||||
#include "../../util/godot/memory.h"
|
||||
#include "../../util/math/box3i.h"
|
||||
#include "../voxel_data_block_enter_info.h"
|
||||
#include "../voxel_mesh_map.h"
|
||||
#include "../voxel_node.h"
|
||||
|
@ -80,11 +82,13 @@ public:
|
|||
void set_material_override(Ref<Material> material);
|
||||
Ref<Material> get_material_override() const;
|
||||
|
||||
VoxelDataMap &get_storage() {
|
||||
return _data_map;
|
||||
VoxelData &get_storage() const {
|
||||
ZN_ASSERT(_data != nullptr);
|
||||
return *_data;
|
||||
}
|
||||
const VoxelDataMap &get_storage() const {
|
||||
return _data_map;
|
||||
|
||||
std::shared_ptr<VoxelData> get_storage_shared() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
Ref<VoxelTool> get_voxel_tool() override;
|
||||
|
@ -122,10 +126,10 @@ public:
|
|||
|
||||
const Stats &get_stats() const;
|
||||
|
||||
struct BlockToSave {
|
||||
std::shared_ptr<VoxelBufferInternal> voxels;
|
||||
Vector3i position;
|
||||
};
|
||||
// struct BlockToSave {
|
||||
// std::shared_ptr<VoxelBufferInternal> voxels;
|
||||
// Vector3i position;
|
||||
// };
|
||||
|
||||
// Internal
|
||||
|
||||
|
@ -149,13 +153,15 @@ protected:
|
|||
private:
|
||||
void _process();
|
||||
void process_viewers();
|
||||
void process_viewer_data_box_change(
|
||||
uint32_t viewer_id, Box3i prev_data_box, Box3i new_data_box, bool can_load_blocks);
|
||||
//void process_received_data_blocks();
|
||||
void process_meshing();
|
||||
void apply_mesh_update(const VoxelEngine::BlockMeshOutput &ob);
|
||||
void apply_data_block_response(VoxelEngine::BlockDataOutput &ob);
|
||||
|
||||
void _on_stream_params_changed();
|
||||
void _set_block_size_po2(int p_block_size_po2);
|
||||
// void _set_block_size_po2(int p_block_size_po2);
|
||||
//void make_all_view_dirty();
|
||||
void start_updater();
|
||||
void stop_updater();
|
||||
|
@ -163,11 +169,11 @@ private:
|
|||
void stop_streamer();
|
||||
void reset_map();
|
||||
|
||||
void view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification);
|
||||
// void view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification);
|
||||
void view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag);
|
||||
void unview_data_block(Vector3i bpos);
|
||||
// void unview_data_block(Vector3i bpos);
|
||||
void unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag);
|
||||
void unload_data_block(Vector3i bpos);
|
||||
// void unload_data_block(Vector3i bpos);
|
||||
void unload_mesh_block(Vector3i bpos);
|
||||
//void make_data_block_dirty(Vector3i bpos);
|
||||
void try_schedule_mesh_update(VoxelMeshBlockVT &block);
|
||||
|
@ -177,12 +183,12 @@ private:
|
|||
void get_viewer_pos_and_direction(Vector3 &out_pos, Vector3 &out_direction) const;
|
||||
void send_block_data_requests();
|
||||
|
||||
void emit_data_block_loaded(const VoxelDataBlock &block, Vector3i bpos);
|
||||
void emit_data_block_unloaded(const VoxelDataBlock &block, Vector3i bpos);
|
||||
void emit_data_block_loaded(Vector3i bpos);
|
||||
void emit_data_block_unloaded(Vector3i bpos);
|
||||
|
||||
bool try_get_paired_viewer_index(uint32_t id, size_t &out_i) const;
|
||||
|
||||
void notify_data_block_enter(VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id);
|
||||
void notify_data_block_enter(const VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id);
|
||||
|
||||
void get_viewers_in_area(std::vector<int> &out_viewer_ids, Box3i voxel_box) const;
|
||||
|
||||
|
@ -227,17 +233,13 @@ private:
|
|||
|
||||
std::vector<PairedViewer> _paired_viewers;
|
||||
|
||||
// Voxel storage
|
||||
VoxelDataMap _data_map;
|
||||
// Voxel storage. Using a shared_ptr so threaded tasks can use it safely.
|
||||
std::shared_ptr<VoxelData> _data;
|
||||
|
||||
// Mesh storage
|
||||
VoxelMeshMap<VoxelMeshBlockVT> _mesh_map;
|
||||
uint32_t _mesh_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
|
||||
|
||||
// Area within which voxels can exist.
|
||||
// Note, these bounds might not be exactly represented. This volume is chunk-based, so the result will be
|
||||
// approximated to the closest chunk.
|
||||
Box3i _bounds_in_voxels;
|
||||
|
||||
unsigned int _max_view_distance_voxels = 128;
|
||||
|
||||
// TODO Terrains only need to handle the visible portion of voxels, which reduces the bounds blocks to handle.
|
||||
|
@ -259,11 +261,11 @@ private:
|
|||
std::vector<Vector3i> _blocks_pending_update;
|
||||
// Blocks that should be saved on the next process call.
|
||||
// The order in that list does not matter.
|
||||
std::vector<BlockToSave> _blocks_to_save;
|
||||
std::vector<VoxelData::BlockToSave> _blocks_to_save;
|
||||
|
||||
Ref<VoxelStream> _stream;
|
||||
// Ref<VoxelStream> _stream;
|
||||
Ref<VoxelMesher> _mesher;
|
||||
Ref<VoxelGenerator> _generator;
|
||||
// Ref<VoxelGenerator> _generator;
|
||||
|
||||
// Data stored with a shared pointer so it can be sent to asynchronous tasks
|
||||
std::shared_ptr<StreamingDependency> _streaming_dependency;
|
||||
|
|
|
@ -50,15 +50,15 @@ struct BeforeUnloadMeshAction {
|
|||
}
|
||||
};
|
||||
|
||||
struct ScheduleSaveAction {
|
||||
std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save;
|
||||
/*struct ScheduleSaveAction {
|
||||
std::vector<VoxelData::BlockToSave> &blocks_to_save;
|
||||
|
||||
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
|
||||
// Save if modified
|
||||
// TODO Don't ask for save if the stream doesn't support it!
|
||||
if (block.is_modified()) {
|
||||
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
||||
VoxelLodTerrainUpdateData::BlockToSave b;
|
||||
VoxelData::BlockToSave b;
|
||||
|
||||
// If a modified block has no voxels, it is equivalent to removing the block from the stream
|
||||
if (block.has_voxels()) {
|
||||
|
@ -70,12 +70,12 @@ struct ScheduleSaveAction {
|
|||
}
|
||||
|
||||
b.position = bpos;
|
||||
b.lod = block.get_lod_index();
|
||||
b.lod_index = block.get_lod_index();
|
||||
blocks_to_save.push_back(b);
|
||||
block.set_modified(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
};*/
|
||||
|
||||
static inline uint64_t get_ticks_msec() {
|
||||
return Time::get_singleton()->get_ticks_msec();
|
||||
|
@ -1566,8 +1566,8 @@ void VoxelLodTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob
|
|||
}
|
||||
|
||||
if (ob.voxels != nullptr) {
|
||||
if (!_data->try_set_block_buffer(
|
||||
ob.position, ob.lod, ob.voxels, ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED)) {
|
||||
if (_data->try_set_block_buffer(ob.position, ob.lod, ob.voxels,
|
||||
ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED, false) == nullptr) {
|
||||
++_stats.dropped_block_loads;
|
||||
return;
|
||||
}
|
||||
|
@ -2116,12 +2116,13 @@ void VoxelLodTerrain::save_all_modified_blocks(bool with_copy) {
|
|||
|
||||
VoxelLodTerrainUpdateTask::flush_pending_lod_edits(_update_data->state, *_data, get_mesh_block_size());
|
||||
|
||||
std::vector<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save;
|
||||
std::vector<VoxelData::BlockToSave> blocks_to_save;
|
||||
|
||||
Ref<VoxelStream> stream = get_stream();
|
||||
if (stream.is_valid()) {
|
||||
// That may cause a stutter, so should be used when the player won't notice
|
||||
_data->for_each_block(ScheduleSaveAction{ blocks_to_save });
|
||||
_data->consume_all_modifications(blocks_to_save, with_copy);
|
||||
|
||||
/*for (unsigned int i = 0; i < _data->lod_count; ++i) {
|
||||
VoxelDataLodMap::Lod &data_lod = _data->lods[i];
|
||||
RWLockRead rlock(data_lod.map_lock);
|
||||
|
|
|
@ -36,11 +36,11 @@ struct VoxelLodTerrainUpdateData {
|
|||
uint8_t lod;
|
||||
};
|
||||
|
||||
struct BlockToSave {
|
||||
std::shared_ptr<VoxelBufferInternal> voxels;
|
||||
Vector3i position;
|
||||
uint8_t lod;
|
||||
};
|
||||
// struct BlockToSave {
|
||||
// std::shared_ptr<VoxelBufferInternal> voxels;
|
||||
// Vector3i position;
|
||||
// uint8_t lod;
|
||||
// };
|
||||
|
||||
// These values don't change during the update task.
|
||||
struct Settings {
|
||||
|
|
|
@ -209,7 +209,7 @@ void VoxelLodTerrainUpdateTask::flush_pending_lod_edits(
|
|||
*/
|
||||
}
|
||||
|
||||
struct BeforeUnloadDataAction {
|
||||
/*struct BeforeUnloadDataAction {
|
||||
std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save;
|
||||
const Vector3i bpos;
|
||||
bool save;
|
||||
|
@ -230,7 +230,7 @@ struct BeforeUnloadDataAction {
|
|||
blocks_to_save.push_back(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
};*/
|
||||
|
||||
/*static void unload_data_block_no_lock(VoxelLodTerrainUpdateData::Lod &lod, VoxelDataLodMap::Lod &data_lod,
|
||||
Vector3i block_pos, std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save, bool can_save) {
|
||||
|
@ -252,7 +252,7 @@ struct BeforeUnloadDataAction {
|
|||
}*/
|
||||
|
||||
static void process_unload_data_blocks_sliding_box(VoxelLodTerrainUpdateData::State &state, VoxelData &data,
|
||||
Vector3 p_viewer_pos, std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save, bool can_save,
|
||||
Vector3 p_viewer_pos, std::vector<VoxelData::BlockToSave> &blocks_to_save, bool can_save,
|
||||
const VoxelLodTerrainUpdateData::Settings &settings) {
|
||||
ZN_PROFILE_SCOPE_NAMED("Sliding box data unload");
|
||||
// TODO Could it actually be enough to have a rolling update on all blocks?
|
||||
|
@ -310,10 +310,7 @@ static void process_unload_data_blocks_sliding_box(VoxelLodTerrainUpdateData::St
|
|||
prev_box.difference_to_vec(new_box, tls_to_remove);
|
||||
|
||||
for (const Box3i bbox : tls_to_remove) {
|
||||
data.unload_blocks(bbox, lod_index, //
|
||||
[&blocks_to_save, can_save](VoxelDataBlock &block, Vector3i bpos) {
|
||||
BeforeUnloadDataAction{ blocks_to_save, bpos, can_save }(block);
|
||||
});
|
||||
data.unload_blocks(bbox, lod_index, &blocks_to_save);
|
||||
}
|
||||
|
||||
/*prev_box.difference(new_box, [&lod, &data_lod, &blocks_to_save, can_save](Box3i out_of_range_box) {
|
||||
|
@ -1268,14 +1265,13 @@ static void request_voxel_block_save(uint32_t volume_id, std::shared_ptr<VoxelBu
|
|||
}
|
||||
|
||||
void VoxelLodTerrainUpdateTask::send_block_save_requests(uint32_t volume_id,
|
||||
Span<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save,
|
||||
std::shared_ptr<StreamingDependency> &stream_dependency, unsigned int data_block_size,
|
||||
BufferedTaskScheduler &task_scheduler) {
|
||||
Span<VoxelData::BlockToSave> blocks_to_save, std::shared_ptr<StreamingDependency> &stream_dependency,
|
||||
unsigned int data_block_size, BufferedTaskScheduler &task_scheduler) {
|
||||
for (unsigned int i = 0; i < blocks_to_save.size(); ++i) {
|
||||
VoxelLodTerrainUpdateData::BlockToSave &b = blocks_to_save[i];
|
||||
ZN_PRINT_VERBOSE(format("Requesting save of block {} lod {}", b.position, b.lod));
|
||||
VoxelData::BlockToSave &b = blocks_to_save[i];
|
||||
ZN_PRINT_VERBOSE(format("Requesting save of block {} lod {}", b.position, b.lod_index));
|
||||
request_voxel_block_save(
|
||||
volume_id, b.voxels, b.position, b.lod, stream_dependency, data_block_size, task_scheduler);
|
||||
volume_id, b.voxels, b.position, b.lod_index, stream_dependency, data_block_size, task_scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1342,7 +1338,8 @@ static void send_mesh_requests(uint32_t volume_id, VoxelLodTerrainUpdateData::St
|
|||
// Iteration order matters for thread access.
|
||||
// The array also implicitely encodes block position due to the convention being used,
|
||||
// so there is no need to also include positions in the request
|
||||
task->blocks_count = data.get_blocks_with_voxel_data(data_box, lod_index, to_span(task->blocks));
|
||||
data.get_blocks_with_voxel_data(data_box, lod_index, to_span(task->blocks));
|
||||
task->blocks_count = Vector3iUtil::get_volume(data_box.size);
|
||||
|
||||
/*const VoxelDataLodMap::Lod &data_lod = data.lods[lod_index];
|
||||
RWLockRead rlock(data_lod.map_lock);
|
||||
|
@ -1600,7 +1597,7 @@ void VoxelLodTerrainUpdateTask::run(ThreadedTaskContext ctx) {
|
|||
// Other mesh updates
|
||||
process_changed_generated_areas(state, settings, lod_count);
|
||||
|
||||
static thread_local std::vector<VoxelLodTerrainUpdateData::BlockToSave> data_blocks_to_save;
|
||||
static thread_local std::vector<VoxelData::BlockToSave> data_blocks_to_save;
|
||||
static thread_local std::vector<VoxelLodTerrainUpdateData::BlockLocation> data_blocks_to_load;
|
||||
data_blocks_to_load.clear();
|
||||
|
||||
|
|
|
@ -62,8 +62,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static void send_block_save_requests(uint32_t volume_id,
|
||||
Span<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save,
|
||||
static void send_block_save_requests(uint32_t volume_id, Span<VoxelData::BlockToSave> blocks_to_save,
|
||||
std::shared_ptr<StreamingDependency> &stream_dependency, unsigned int data_block_size,
|
||||
BufferedTaskScheduler &task_scheduler);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class VoxelDataBlockEnterInfo : public Object {
|
|||
public:
|
||||
int network_peer_id = -1;
|
||||
Vector3i block_position;
|
||||
VoxelDataBlock *voxel_block = nullptr;
|
||||
const VoxelDataBlock *voxel_block = nullptr;
|
||||
|
||||
private:
|
||||
int _b_get_network_peer_id() const;
|
||||
|
|
Loading…
Reference in New Issue