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:
parent
c5972a420e
commit
c5b0ac43fb
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user