Semi-async do_sphere() implementation. May change though.

It is a lot better than the fully synchronous do_sphere(),
but it would be a lot better if we could move everything async
This commit is contained in:
Marc Gilleron 2021-10-04 19:20:36 +01:00
parent c5972a420e
commit c5b0ac43fb
10 changed files with 385 additions and 118 deletions

View File

@ -9,6 +9,76 @@
class VoxelBuffer;
namespace VoxelToolOps {
template <typename Op, typename Shape>
struct SdfOperation16bit {
Op op;
Shape shape;
inline uint16_t operator()(Vector3i pos, uint16_t sdf) const {
return norm_to_u16(op(u16_to_norm(sdf), shape(pos.to_vec3())));
}
};
struct SdfUnion {
inline float operator()(float a, float b) const {
return sdf_union(a, b);
}
};
struct SdfSubtract {
inline float operator()(float a, float b) const {
return sdf_subtract(a, b);
}
};
struct SdfSet {
inline float operator()(float a, float b) const {
return b;
}
};
struct SdfSphere {
Vector3 center;
float radius;
float scale;
inline float operator()(Vector3 pos) const {
return scale * sdf_sphere(pos, center, radius);
}
};
struct TextureParams {
float opacity = 1.f;
float sharpness = 2.f;
unsigned int index = 0;
};
struct TextureBlendSphereOp {
Vector3 center;
float radius;
float radius_squared;
TextureParams tp;
TextureBlendSphereOp(Vector3 p_center, float p_radius, TextureParams p_tp) {
center = p_center;
radius = p_radius;
radius_squared = p_radius * p_radius;
tp = p_tp;
}
inline void operator()(Vector3i pos, uint16_t &indices, uint16_t &weights) const {
const float distance_squared = pos.to_vec3().distance_squared_to(center);
if (distance_squared < radius_squared) {
const float distance_from_radius = radius - Math::sqrt(distance_squared);
const float target_weight = tp.opacity * clamp(tp.sharpness * (distance_from_radius / radius), 0.f, 1.f);
blend_texture_packed_u16(tp.index, target_weight, indices, weights);
}
}
};
}; // namespace VoxelToolOps
// TODO Need to review VoxelTool to account for transformed volumes
// High-level generic voxel edition utility.
@ -150,74 +220,8 @@ protected:
float _sdf_scale = 1.f;
Mode _mode = MODE_ADD;
struct TextureParams {
float opacity = 1.f;
float sharpness = 2.f;
unsigned int index = 0;
};
struct TextureBlendSphereOp {
Vector3 center;
float radius;
float radius_squared;
TextureParams tp;
TextureBlendSphereOp(Vector3 p_center, float p_radius, TextureParams p_tp) {
center = p_center;
radius = p_radius;
radius_squared = p_radius * p_radius;
tp = p_tp;
}
inline void operator()(Vector3i pos, uint16_t &indices, uint16_t &weights) const {
const float distance_squared = pos.to_vec3().distance_squared_to(center);
if (distance_squared < radius_squared) {
const float distance_from_radius = radius - Math::sqrt(distance_squared);
const float target_weight = tp.opacity * clamp(tp.sharpness * (distance_from_radius / radius), 0.f, 1.f);
blend_texture_packed_u16(tp.index, target_weight, indices, weights);
}
}
};
template <typename Op, typename Shape>
struct SdfOperation16bit {
Op op;
Shape shape;
inline uint16_t operator()(Vector3i pos, uint16_t sdf) const {
return norm_to_u16(op(u16_to_norm(sdf), shape(pos.to_vec3())));
}
};
struct SdfUnion {
inline float operator()(float a, float b) const {
return sdf_union(a, b);
}
};
struct SdfSubtract {
inline float operator()(float a, float b) const {
return sdf_subtract(a, b);
}
};
struct SdfSet {
inline float operator()(float a, float b) const {
return b;
}
};
struct SdfSphere {
Vector3 center;
float radius;
float scale;
inline float operator()(Vector3 pos) const {
return scale * sdf_sphere(pos, center, radius);
}
};
// Used on smooth terrain
TextureParams _texture_params;
VoxelToolOps::TextureParams _texture_params;
};
VARIANT_ENUM_CAST(VoxelTool::Mode)

View File

@ -27,10 +27,10 @@ void VoxelToolBuffer::do_sphere(Vector3 center, float radius) {
Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
box.clip(Box3i(Vector3i(), _buffer->get_size()));
_buffer->get_buffer().write_box_2_template<TextureBlendSphereOp, uint16_t, uint16_t>(box,
_buffer->get_buffer().write_box_2_template<VoxelToolOps::TextureBlendSphereOp, uint16_t, uint16_t>(box,
VoxelBufferInternal::CHANNEL_INDICES,
VoxelBufferInternal::CHANNEL_WEIGHTS,
TextureBlendSphereOp(center, radius, _texture_params),
VoxelToolOps::TextureBlendSphereOp(center, radius, _texture_params),
Vector3i());
_post_edit(box);

View File

@ -161,6 +161,60 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
return res;
}
namespace {
struct DoSphere {
Vector3 center;
float radius;
VoxelTool::Mode mode;
VoxelLodTerrain *terrain;
float sdf_scale;
Box3i box;
VoxelToolOps::TextureParams texture_params;
void operator()() {
using namespace VoxelToolOps;
switch (mode) {
case VoxelTool::MODE_ADD: {
// TODO Support other depths, format should be accessible from the volume
SdfOperation16bit<SdfUnion, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = sdf_scale;
terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
case VoxelTool::MODE_REMOVE: {
SdfOperation16bit<SdfSubtract, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = sdf_scale;
terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
case VoxelTool::MODE_SET: {
SdfOperation16bit<SdfSet, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = sdf_scale;
terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
case VoxelTool::MODE_TEXTURE_PAINT: {
terrain->write_box_2(box, VoxelBufferInternal::CHANNEL_INDICES, VoxelBufferInternal::CHANNEL_WEIGHTS,
TextureBlendSphereOp{ center, radius, texture_params });
} break;
default:
ERR_PRINT("Unknown mode");
break;
}
}
};
} // namespace
void VoxelToolLodTerrain::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_terrain == nullptr);
@ -171,41 +225,43 @@ void VoxelToolLodTerrain::do_sphere(Vector3 center, float radius) {
return;
}
switch (_mode) {
case MODE_ADD: {
// TODO Support other depths, format should be accessible from the volume
SdfOperation16bit<SdfUnion, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
DoSphere op;
op.box = box;
op.center = center;
op.mode = get_mode();
op.radius = radius;
op.sdf_scale = get_sdf_scale();
op.terrain = _terrain;
op.texture_params = _texture_params;
op();
}
case MODE_REMOVE: {
SdfOperation16bit<SdfSubtract, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
void VoxelToolLodTerrain::do_sphere_async(Vector3 center, float radius) {
ERR_FAIL_COND(_terrain == nullptr);
const Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
case MODE_SET: {
SdfOperation16bit<SdfSet, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBufferInternal::CHANNEL_SDF, op);
} break;
DoSphere op;
op.box = box;
op.center = center;
op.mode = get_mode();
op.radius = radius;
op.sdf_scale = get_sdf_scale();
op.terrain = _terrain;
op.texture_params = _texture_params;
case MODE_TEXTURE_PAINT: {
_terrain->write_box_2(box, VoxelBufferInternal::CHANNEL_INDICES, VoxelBufferInternal::CHANNEL_WEIGHTS,
TextureBlendSphereOp{ center, radius, _texture_params });
} break;
struct Task : IVoxelTimeSpreadTask {
DoSphere op;
void run() override {
op();
}
};
default:
ERR_PRINT("Unknown mode");
break;
}
// TODO How do I use unique_ptr with Godot's memnew/memdelete instead?
// (without having to mention it everywhere I pass this around)
std::unique_ptr<Task> task = std::make_unique<Task>();
task->op = op;
_terrain->push_async_edit(std::move(task), op.box);
}
void VoxelToolLodTerrain::copy(Vector3i pos, Ref<VoxelBuffer> dst, uint8_t channels_mask) const {
@ -611,4 +667,5 @@ void VoxelToolLodTerrain::_bind_methods() {
&VoxelToolLodTerrain::get_voxel_f_interpolated);
ClassDB::bind_method(D_METHOD("separate_floating_chunks", "box", "parent_node"),
&VoxelToolLodTerrain::separate_floating_chunks);
ClassDB::bind_method(D_METHOD("do_sphere_async", "center", "radius"), &VoxelToolLodTerrain::do_sphere_async);
}

View File

@ -19,6 +19,7 @@ public:
void set_raycast_binary_search_iterations(int iterations);
void do_sphere(Vector3 center, float radius) override;
void do_sphere_async(Vector3 center, float radius);
void copy(Vector3i pos, Ref<VoxelBuffer> dst, uint8_t channels_mask) const override;

View File

@ -170,7 +170,7 @@ void VoxelToolTerrain::do_sphere(Vector3 center, float radius) {
}
_terrain->get_storage().write_box_2(box, VoxelBuffer::CHANNEL_INDICES, VoxelBuffer::CHANNEL_WEIGHTS,
TextureBlendSphereOp{ center, radius, _texture_params });
VoxelToolOps::TextureBlendSphereOp{ center, radius, _texture_params });
_post_edit(box);
}

View File

@ -361,6 +361,7 @@ void VoxelServer::request_block_mesh(uint32_t volume_id, const BlockMeshInput &i
void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int lod, bool request_instances) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream_dependency == nullptr);
ERR_FAIL_COND(volume.data_block_size > 255);
if (volume.stream_dependency->stream.is_valid()) {
BlockDataRequest *r = memnew(BlockDataRequest);
@ -393,6 +394,26 @@ void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int
}
}
void VoxelServer::request_block_generate(uint32_t volume_id, Vector3i block_pos, int lod,
std::shared_ptr<VoxelAsyncDependencyTracker> tracker) {
//
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream_dependency->generator.is_null());
BlockGenerateRequest *r = memnew(BlockGenerateRequest);
r->volume_id = volume_id;
r->position = block_pos;
r->lod = lod;
r->block_size = volume.data_block_size;
r->stream_dependency = volume.stream_dependency;
r->tracker = tracker;
r->drop_beyond_max_distance = false;
init_priority_dependency(r->priority_dependency, block_pos, lod, volume, volume.data_block_size);
_general_thread_pool.enqueue(r);
}
void VoxelServer::request_all_stream_blocks(uint32_t volume_id) {
PRINT_VERBOSE(String("Request all blocks for volume {0}").format(varray(volume_id)));
const Volume &volume = _world.volumes.get(volume_id);
@ -814,6 +835,7 @@ void VoxelServer::BlockDataRequest::apply_result() {
o.lod = lod;
o.dropped = !has_run;
o.max_lod_hint = max_lod_hint;
o.initial_load = false;
switch (type) {
case BlockDataRequest::TYPE_SAVE:
@ -887,6 +909,7 @@ void VoxelServer::AllBlocksDataRequest::apply_result() {
o.lod = rb.lod;
o.dropped = false;
o.max_lod_hint = false;
o.initial_load = true;
++dst_i;
}
@ -939,7 +962,7 @@ void VoxelServer::BlockGenerateRequest::run(VoxelTaskContext ctx) {
int VoxelServer::BlockGenerateRequest::get_priority() {
float closest_viewer_distance_sq;
const int p = VoxelServer::get_priority(priority_dependency, lod, &closest_viewer_distance_sq);
too_far = closest_viewer_distance_sq > priority_dependency.drop_distance_squared;
too_far = drop_beyond_max_distance && closest_viewer_distance_sq > priority_dependency.drop_distance_squared;
return p;
}
@ -950,6 +973,8 @@ bool VoxelServer::BlockGenerateRequest::is_cancelled() {
void VoxelServer::BlockGenerateRequest::apply_result() {
Volume *volume = VoxelServer::get_singleton()->_world.volumes.try_get(volume_id);
bool aborted = true;
if (volume != nullptr) {
// TODO Comparing pointer may not be guaranteed
// The request response must match the dependency it would have been requested with.
@ -962,13 +987,26 @@ void VoxelServer::BlockGenerateRequest::apply_result() {
o.dropped = !has_run;
o.type = BlockDataOutput::TYPE_LOAD;
o.max_lod_hint = max_lod_hint;
o.initial_load = false;
volume->reception_buffers->data_output.push_back(std::move(o));
aborted = !has_run;
}
} else {
// This can happen if the user removes the volume while requests are still about to return
PRINT_VERBOSE("Gemerated data request response came back but volume wasn't found");
}
if (tracker != nullptr) {
if (aborted) {
tracker->abort();
} else {
// TODO Problem: the task is actually not complete yet!
// We only posted the result into a reception queue, it still needs to be dequeued by the terrain.
tracker->post_complete();
}
}
}
//----------------------------------------------------------------------------------------------------------------------

View File

@ -31,6 +31,45 @@ private:
std::queue<IVoxelTimeSpreadTask *> _tasks;
};
class VoxelAsyncDependencyTracker {
public:
// Creates a tracker which will track `initial_count` tasks.
// The tracker may be passed by shared pointer to each of these tasks so they can notify completion.
VoxelAsyncDependencyTracker(int initial_count) :
_count(initial_count), _aborted(false) {
}
// Call this when one of the tracked dependencies is complete
void post_complete() {
ERR_FAIL_COND_MSG(_count == 0, "post() called more times than expected");
--_count;
}
// Call this when one of the tracked dependencies is aborted
void abort() {
_aborted = true;
}
// Returns `true` if any of the tracked tasks was aborted.
// It usually means the task depending on this tracker may be aborted as well.
bool is_aborted() const {
return _aborted;
}
// Returns `true` when all the tracked tasks have completed
bool is_complete() const {
return _count == 0;
}
int get_remaining_count() const {
return _count;
}
private:
std::atomic_int _count;
std::atomic_bool _aborted;
};
class VoxelNode;
// TODO Don't inherit Object. Instead have a Godot wrapper, there is very little use for Object stuff
@ -65,6 +104,8 @@ public:
uint8_t lod;
bool dropped;
bool max_lod_hint;
// Blocks with this flag set should not be ignored
bool initial_load;
};
struct BlockMeshInput {
@ -119,6 +160,8 @@ public:
void request_block_mesh(uint32_t volume_id, const BlockMeshInput &input);
// TODO Add parameter to skip stream loading
void request_block_load(uint32_t volume_id, Vector3i block_pos, int lod, bool request_instances);
void request_block_generate(uint32_t volume_id, Vector3i block_pos, int lod,
std::shared_ptr<VoxelAsyncDependencyTracker> tracker);
void request_all_stream_blocks(uint32_t volume_id);
void request_voxel_block_save(uint32_t volume_id, std::shared_ptr<VoxelBufferInternal> voxels, Vector3i block_pos,
int lod);
@ -336,8 +379,10 @@ private:
bool has_run = false;
bool too_far = false;
bool max_lod_hint = false;
bool drop_beyond_max_distance = true;
PriorityDependency priority_dependency;
std::shared_ptr<StreamingDependency> stream_dependency;
std::shared_ptr<VoxelAsyncDependencyTracker> tracker;
};
class BlockMeshRequest : public IVoxelTask {

View File

@ -202,6 +202,7 @@ VoxelLodTerrain::VoxelLodTerrain() {
VoxelLodTerrain::~VoxelLodTerrain() {
PRINT_VERBOSE("Destroy VoxelLodTerrain");
abort_async_edits();
VoxelServer::get_singleton()->remove_volume(_volume_id);
// Instancer can take care of itself
}
@ -368,6 +369,8 @@ void VoxelLodTerrain::_on_stream_params_changed() {
void VoxelLodTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
mesh_block_size = clamp(mesh_block_size, get_data_block_size(), VoxelConstants::MAX_BLOCK_SIZE);
// Only these sizes are allowed at the moment. This stuff is still not supported in a generic way yet,
// some code still exploits the fact it's a multiple of data block size, for performance
unsigned int po2;
switch (mesh_block_size) {
case 16:
@ -467,6 +470,45 @@ void VoxelLodTerrain::set_mesh_block_active(VoxelMeshBlock &block, bool active)
}
}
std::shared_ptr<VoxelAsyncDependencyTracker> VoxelLodTerrain::preload_box_async(Box3i voxel_box) {
ERR_FAIL_COND_V_MSG(_full_load_mode == false, nullptr, "This function can only be used in full load mode");
const uint32_t volume_id = _volume_id;
struct TaskArguments {
Vector3i block_pos;
unsigned int lod_index;
};
std::vector<TaskArguments> todo;
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
Lod &lod = _lods[lod_index];
const Box3i block_box = voxel_box.downscaled(get_data_block_size() << lod_index);
PRINT_VERBOSE(String("Preloading box {0} at lod {1}")
.format(varray(block_box.to_string(), lod_index)));
block_box.for_each_cell([&lod, lod_index, &todo](Vector3i block_pos) {
if (!lod.data_map.has_block(block_pos) && !lod.has_loading_block(block_pos)) {
todo.push_back({ block_pos, lod_index });
lod.loading_blocks.insert(block_pos);
}
});
}
std::shared_ptr<VoxelAsyncDependencyTracker> tracker = gd_make_shared<VoxelAsyncDependencyTracker>(todo.size());
PRINT_VERBOSE(String("Preloading box {0} with {1} tasks")
.format(varray(voxel_box.to_string(), tracker->get_remaining_count())));
for (unsigned int i = 0; i < todo.size(); ++i) {
const TaskArguments args = todo[i];
VoxelServer::get_singleton()->request_block_generate(volume_id, args.block_pos, args.lod_index, tracker);
}
return tracker;
}
inline int get_octree_size_po2(const VoxelLodTerrain &self) {
return self.get_mesh_block_size_pow2() + self.get_lod_count() - 1;
}
@ -495,6 +537,15 @@ VoxelSingleValue VoxelLodTerrain::get_voxel(Vector3i pos, unsigned int channel,
if (_generator.is_valid()) {
return _generator->generate_single(pos, channel);
}
} else {
const Vector3i rpos = lod0.data_map.to_local(pos);
VoxelSingleValue v;
if (channel == VoxelBufferInternal::CHANNEL_SDF) {
v.f = block->get_voxels_const().get_voxel_f(rpos.x, rpos.y, rpos.z, channel);
} else {
v.i = block->get_voxels_const().get_voxel(rpos, channel);
}
return v;
}
return defval;
@ -590,6 +641,14 @@ void VoxelLodTerrain::post_edit_area(Box3i p_box) {
}
}
void VoxelLodTerrain::push_async_edit(std::unique_ptr<IVoxelTimeSpreadTask> task, Box3i box) {
AsyncEdit edit;
edit.task = std::move(task);
edit.dependency_tracker = preload_box_async(box);
ERR_FAIL_COND(edit.dependency_tracker == nullptr);
_async_edits.push(std::move(edit));
}
Ref<VoxelTool> VoxelLodTerrain::get_voxel_tool() {
VoxelToolLodTerrain *vt = memnew(VoxelToolLodTerrain(this));
// Set to most commonly used channel on this kind of terrain
@ -744,6 +803,8 @@ void VoxelLodTerrain::reset_maps() {
lod.deferred_collision_updates.clear();
}
abort_async_edits();
// Reset previous state caches to force rebuilding the view area
_last_octree_region_box = Box3i();
_lod_octrees.clear();
@ -1590,6 +1651,9 @@ void VoxelLodTerrain::_process(float delta) {
// It should only happen on first load, though.
{
VOXEL_PROFILE_SCOPE_NAMED("Data loading responses");
// if (_reception_buffers.data_output.size() > 0) {
// print_line(String("Received {0} data blocks").format(varray(_reception_buffers.data_output.size())));
// }
for (size_t reception_index = 0; reception_index < _reception_buffers.data_output.size(); ++reception_index) {
VOXEL_PROFILE_SCOPE();
@ -1612,10 +1676,14 @@ void VoxelLodTerrain::_process(float delta) {
Lod &lod = _lods[ob.lod];
{
// Initial load will be true when we requested data without specifying specific positions,
// so we wouldn't know which ones to expect. This is the case of full load mode.
if (!ob.initial_load) {
std::unordered_set<Vector3i>::iterator it = lod.loading_blocks.find(ob.position);
if (it == lod.loading_blocks.end()) {
// That block was not requested, or is no longer needed. drop it...
PRINT_VERBOSE(String("Ignoring block {0} lod {1}, it was not in loading blocks")
.format(varray(ob.position.to_vec3(), ob.lod)));
++_stats.dropped_block_loads;
continue;
}
@ -1635,16 +1703,18 @@ void VoxelLodTerrain::_process(float delta) {
continue;
}
if (ob.voxels->get_size() != Vector3i(lod.data_map.get_block_size())) {
// Voxel block size is incorrect, drop it
ERR_PRINT("Block size obtained from stream is different from expected size");
++_stats.dropped_block_loads;
continue;
}
if (ob.voxels != nullptr) {
if (ob.voxels->get_size() != Vector3i(lod.data_map.get_block_size())) {
// Voxel block size is incorrect, drop it
ERR_PRINT("Block size obtained from stream is different from expected size");
++_stats.dropped_block_loads;
continue;
}
// Store buffer
VoxelDataBlock *block = lod.data_map.set_block_buffer(ob.position, ob.voxels);
CRASH_COND(block == nullptr);
// Store buffer
VoxelDataBlock *block = lod.data_map.set_block_buffer(ob.position, ob.voxels);
CRASH_COND(block == nullptr);
}
if (_instancer != nullptr && ob.instances != nullptr) {
VoxelServer::BlockDataOutput &wob = _reception_buffers.data_output[reception_index];
@ -1655,6 +1725,9 @@ void VoxelLodTerrain::_process(float delta) {
_reception_buffers.data_output.clear();
}
// Process async edits after receiving data blocks because they may need them
process_async_edits();
process_fading_blocks(delta);
_stats.time_process_load_responses = profiling_clock.restart();
@ -1888,6 +1961,35 @@ void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec)
}
}
void VoxelLodTerrain::process_async_edits() {
VOXEL_PROFILE_SCOPE();
while (_async_edits.size() > 0) {
AsyncEdit &edit = _async_edits.front();
CRASH_COND(edit.dependency_tracker == nullptr);
CRASH_COND(edit.task == nullptr);
if (edit.dependency_tracker->is_aborted()) {
_async_edits.pop();
} else if (edit.dependency_tracker->is_complete()) {
edit.task->run();
_async_edits.pop();
} else {
// print_line(String("Async edit waiting for {0} tasks")
// .format(varray(edit.dependency_tracker->get_remaining_count())));
break;
}
}
}
void VoxelLodTerrain::abort_async_edits() {
while (_async_edits.size() > 0) {
_async_edits.pop();
}
}
void VoxelLodTerrain::process_fading_blocks(float delta) {
VOXEL_PROFILE_SCOPE();
@ -2048,6 +2150,7 @@ void VoxelLodTerrain::unload_data_block(Vector3i block_pos, int lod_index) {
lod.data_map.remove_block(block_pos, BeforeUnloadDataAction{ _blocks_to_save, _stream.is_valid() });
//print_line(String("Unloading data block {0} lod {1}").format(varray(block_pos.to_vec3(), lod_index)));
lod.loading_blocks.erase(block_pos);
// if (_instancer != nullptr) {
@ -2573,19 +2676,19 @@ void VoxelLodTerrain::update_gizmos() {
const Transform t = parent_transform * local_transform;
// Squaring because lower lod indexes are more interesting to see, so we give them more contrast.
// Also this might be better with sRGB?
float g = squared(max(1.f - float(lod_index) / lod_count_f, 0.f));
const float g = squared(max(1.f - float(lod_index) / lod_count_f, 0.f));
dr.draw_box_mm(t, Color8(255, uint8_t(g * 254.f), 0, 255));
});
}
}
// Edited blocks
if (_show_edited_lod0_blocks) {
const Lod &lod0 = _lods[0];
const int data_block_size = lod0.data_map.get_block_size();
if (_show_edited_blocks && _edited_blocks_gizmos_lod_index < _lod_count) {
const Lod &lod = _lods[_edited_blocks_gizmos_lod_index];
const int data_block_size = get_data_block_size() << _edited_blocks_gizmos_lod_index;
const Basis basis(Basis().scaled(Vector3(data_block_size, data_block_size, data_block_size)));
lod0.data_map.for_all_blocks([&dr, parent_transform, data_block_size, basis](const VoxelDataBlock *block) {
lod.data_map.for_all_blocks([&dr, parent_transform, data_block_size, basis](const VoxelDataBlock *block) {
const Transform local_transform(basis, (block->position * data_block_size).to_vec3());
const Transform t = parent_transform * local_transform;
const Color8 c = Color8(block->is_modified() ? 255 : 0, 255, 0, 255);

View File

@ -126,6 +126,11 @@ public:
// These must be called after an edit
void post_edit_area(Box3i p_box);
// TODO This still sucks atm cuz the edit will still run on the main thread
void push_async_edit(std::unique_ptr<IVoxelTimeSpreadTask> task, Box3i box);
void process_async_edits();
void abort_async_edits();
void set_voxel_bounds(Box3i p_box);
inline Box3i get_voxel_bounds() const { return _bounds_in_voxels; }
@ -231,6 +236,7 @@ private:
void _set_lod_count(int p_lod_count);
void _set_block_size_po2(int p_block_size_po2);
void set_mesh_block_active(VoxelMeshBlock &block, bool active);
std::shared_ptr<VoxelAsyncDependencyTracker> preload_box_async(Box3i voxel_box);
void _on_stream_params_changed();
@ -303,6 +309,13 @@ private:
VoxelInstancer *_instancer = nullptr;
struct AsyncEdit {
std::unique_ptr<IVoxelTimeSpreadTask> task;
std::shared_ptr<VoxelAsyncDependencyTracker> dependency_tracker;
};
std::queue<AsyncEdit> _async_edits;
// Each LOD works in a set of coordinates spanning 2x more voxels the higher their index is
struct Lod {
VoxelDataMap data_map;
@ -342,7 +355,8 @@ private:
bool _show_octree_bounds_gizmos = true;
bool _show_volume_bounds_gizmos = true;
bool _show_octree_node_gizmos = false;
bool _show_edited_lod0_blocks = false;
bool _show_edited_blocks = false;
unsigned int _edited_blocks_gizmos_lod_index = 0;
VoxelDebug::DebugRenderer _debug_renderer;
#endif

View File

@ -43,4 +43,9 @@ inline std::shared_ptr<T> gd_make_shared() {
return std::shared_ptr<T>(memnew(T), memdelete<T>);
}
template <typename T, typename Arg_T>
inline std::shared_ptr<T> gd_make_shared(Arg_T arg) {
return std::shared_ptr<T>(memnew(T(arg)), memdelete<T>);
}
#endif // VOXEL_UTILITY_GODOT_FUNCS_H