2016-05-10 01:59:54 +02:00
|
|
|
#include "voxel_terrain.h"
|
2021-02-21 23:58:00 +00:00
|
|
|
#include "../constants/voxel_constants.h"
|
|
|
|
#include "../constants/voxel_string_names.h"
|
2020-01-26 20:29:44 +00:00
|
|
|
#include "../edition/voxel_tool_terrain.h"
|
2020-08-25 23:00:38 +01:00
|
|
|
#include "../server/voxel_server.h"
|
2022-01-09 04:53:33 +00:00
|
|
|
#include "../server/voxel_server_updater.h"
|
2022-02-03 00:02:10 +00:00
|
|
|
#include "../storage/voxel_buffer.h"
|
2021-02-17 20:34:35 +00:00
|
|
|
#include "../util/funcs.h"
|
2020-07-08 20:48:52 +01:00
|
|
|
#include "../util/macros.h"
|
2020-08-30 03:59:02 +01:00
|
|
|
#include "../util/profiling.h"
|
2019-06-25 20:51:35 +01:00
|
|
|
#include "../util/profiling_clock.h"
|
2022-01-31 21:23:39 +00:00
|
|
|
#include "voxel_data_block_enter_info.h"
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
#include <core/config/engine.h>
|
2019-08-24 01:44:27 +01:00
|
|
|
#include <core/core_string_names.h>
|
2021-12-13 21:38:10 +00:00
|
|
|
#include <scene/3d/mesh_instance_3d.h>
|
2017-08-20 15:17:54 +02:00
|
|
|
|
2022-01-09 22:13:10 +00:00
|
|
|
namespace zylann::voxel {
|
2022-01-03 23:14:18 +00:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
VoxelTerrain::VoxelTerrain() {
|
2019-08-24 01:44:27 +01:00
|
|
|
// Note: don't do anything heavy in the constructor.
|
|
|
|
// Godot may create and destroy dozens of instances of all node types on startup,
|
|
|
|
// due to how ClassDB gets its default values.
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2020-10-25 00:55:57 +01:00
|
|
|
set_notify_transform(true);
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
// TODO Should it actually be finite for better discovery?
|
2020-09-13 22:36:28 +01:00
|
|
|
// Infinite by default
|
2022-01-09 03:06:58 +00:00
|
|
|
_bounds_in_voxels = Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT));
|
2020-09-13 22:36:28 +01:00
|
|
|
|
2022-01-09 22:13:10 +00:00
|
|
|
struct ApplyMeshUpdateTask : public ITimeSpreadTask {
|
2021-09-25 00:02:41 +01:00
|
|
|
void run() override {
|
|
|
|
if (!VoxelServer::get_singleton()->is_volume_valid(volume_id)) {
|
|
|
|
// The node can have been destroyed while this task was still pending
|
|
|
|
PRINT_VERBOSE("Cancelling ApplyMeshUpdateTask, volume_id is invalid");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self->apply_mesh_update(data);
|
|
|
|
}
|
|
|
|
uint32_t volume_id = 0;
|
|
|
|
VoxelTerrain *self = nullptr;
|
|
|
|
VoxelServer::BlockMeshOutput data;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mesh updates are spread over frames by scheduling them in a task runner of VoxelServer,
|
|
|
|
// but instead of using a reception buffer we use a callback,
|
|
|
|
// because this kind of task scheduling would otherwise delay the update by 1 frame
|
2021-10-13 20:28:20 +01:00
|
|
|
VoxelServer::VolumeCallbacks callbacks;
|
|
|
|
callbacks.data = this;
|
|
|
|
callbacks.mesh_output_callback = [](void *cb_data, const VoxelServer::BlockMeshOutput &ob) {
|
2021-09-25 00:02:41 +01:00
|
|
|
VoxelTerrain *self = reinterpret_cast<VoxelTerrain *>(cb_data);
|
|
|
|
ApplyMeshUpdateTask *task = memnew(ApplyMeshUpdateTask);
|
|
|
|
task->volume_id = self->_volume_id;
|
|
|
|
task->self = self;
|
|
|
|
task->data = ob;
|
|
|
|
VoxelServer::get_singleton()->push_time_spread_task(task);
|
|
|
|
};
|
2021-10-13 20:28:20 +01:00
|
|
|
callbacks.data_output_callback = [](void *cb_data, VoxelServer::BlockDataOutput &ob) {
|
|
|
|
VoxelTerrain *self = reinterpret_cast<VoxelTerrain *>(cb_data);
|
|
|
|
self->apply_data_block_response(ob);
|
|
|
|
};
|
2021-09-25 00:02:41 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
_volume_id = VoxelServer::get_singleton()->add_volume(callbacks, VoxelServer::VOLUME_SPARSE_GRID);
|
2020-08-25 23:00:38 +01:00
|
|
|
|
2021-12-16 01:30:31 +00:00
|
|
|
// TODO Can't setup a default mesher anymore due to a Godot 4 warning...
|
2020-12-18 21:01:50 +00:00
|
|
|
// For ease of use in editor
|
2021-12-16 01:30:31 +00:00
|
|
|
// Ref<VoxelMesherBlocky> default_mesher;
|
|
|
|
// default_mesher.instantiate();
|
|
|
|
// _mesher = default_mesher;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-03-28 00:56:01 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
VoxelTerrain::~VoxelTerrain() {
|
2020-07-08 20:48:52 +01:00
|
|
|
PRINT_VERBOSE("Destroying VoxelTerrain");
|
2020-08-25 23:00:38 +01:00
|
|
|
VoxelServer::get_singleton()->remove_volume(_volume_id);
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
// TODO See if there is a way to specify materials in voxels directly?
|
|
|
|
|
|
|
|
bool VoxelTerrain::_set(const StringName &p_name, const Variant &p_value) {
|
|
|
|
if (p_name.operator String().begins_with("material/")) {
|
2019-06-18 14:24:56 +09:00
|
|
|
unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false);
|
2017-08-15 02:24:52 +02:00
|
|
|
set_material(idx, p_value);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::_get(const StringName &p_name, Variant &r_ret) const {
|
|
|
|
if (p_name.operator String().begins_with("material/")) {
|
2019-06-18 14:24:56 +09:00
|
|
|
unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false);
|
2017-08-15 02:24:52 +02:00
|
|
|
r_ret = get_material(idx);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::_get_property_list(List<PropertyInfo> *p_list) const {
|
2021-08-01 15:40:41 +01:00
|
|
|
// Need to add a group here because otherwise it appears under the last group declared in `_bind_methods`
|
|
|
|
p_list->push_back(PropertyInfo(Variant::NIL, "Materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
|
2022-01-16 16:04:25 +00:00
|
|
|
|
|
|
|
const String material_type_hint = ShaderMaterial::get_class_static() + "," + BaseMaterial3D::get_class_static();
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
for (unsigned int i = 0; i < VoxelMesherBlocky::MAX_MATERIALS; ++i) {
|
2022-01-16 16:04:25 +00:00
|
|
|
p_list->push_back(
|
|
|
|
PropertyInfo(Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, material_type_hint));
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
|
|
|
|
if (p_stream == _stream) {
|
|
|
|
return;
|
|
|
|
}
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
_stream = p_stream;
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
2019-08-24 01:44:27 +01:00
|
|
|
if (_stream.is_valid()) {
|
2020-08-14 20:33:09 +01:00
|
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
2021-01-17 17:18:05 +00:00
|
|
|
Ref<Script> script = _stream->get_script();
|
|
|
|
if (script.is_valid()) {
|
2020-08-14 20:33:09 +01:00
|
|
|
// Safety check. It's too easy to break threads by making a script reload.
|
|
|
|
// You can turn it back on, but be careful.
|
|
|
|
_run_stream_in_editor = false;
|
2021-12-13 21:38:10 +00:00
|
|
|
notify_property_list_changed();
|
2020-08-14 20:33:09 +01:00
|
|
|
}
|
|
|
|
}
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
2020-08-14 20:33:09 +01:00
|
|
|
#endif
|
2019-08-24 01:44:27 +01:00
|
|
|
|
|
|
|
_on_stream_params_changed();
|
2017-01-02 02:15:57 +01:00
|
|
|
}
|
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
Ref<VoxelStream> VoxelTerrain::get_stream() const {
|
|
|
|
return _stream;
|
2017-01-02 02:15:57 +01:00
|
|
|
}
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
|
|
|
|
if (p_generator == _generator) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_generator = p_generator;
|
|
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
if (_generator.is_valid()) {
|
|
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
|
Ref<Script> script = _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.
|
|
|
|
_run_stream_in_editor = false;
|
2021-12-13 21:38:10 +00:00
|
|
|
notify_property_list_changed();
|
2021-01-17 17:18:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
_on_stream_params_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
|
|
|
|
return _generator;
|
|
|
|
}
|
|
|
|
|
2021-10-03 01:28:29 +01:00
|
|
|
/*void VoxelTerrain::set_data_block_size_po2(unsigned int p_block_size_po2) {
|
2019-08-24 01:44:27 +01:00
|
|
|
ERR_FAIL_COND(p_block_size_po2 < 1);
|
|
|
|
ERR_FAIL_COND(p_block_size_po2 > 32);
|
|
|
|
|
|
|
|
unsigned int block_size_po2 = p_block_size_po2;
|
2021-02-01 22:25:25 +00:00
|
|
|
if (_stream.is_valid()) {
|
|
|
|
block_size_po2 = _stream->get_block_size_po2();
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
if (block_size_po2 == get_data_block_size_pow2()) {
|
2019-08-24 01:44:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
_set_block_size_po2(block_size_po2);
|
2020-08-14 20:33:09 +01:00
|
|
|
_on_stream_params_changed();
|
2021-10-03 01:28:29 +01:00
|
|
|
}*/
|
2019-08-24 01:44:27 +01:00
|
|
|
|
|
|
|
void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
|
2021-04-13 23:48:35 +01:00
|
|
|
_data_map.create(p_block_size_po2, 0);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
unsigned int VoxelTerrain::get_data_block_size_pow2() const {
|
|
|
|
return _data_map.get_block_size_pow2();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int VoxelTerrain::get_mesh_block_size_pow2() const {
|
|
|
|
return _mesh_map.get_block_size_pow2();
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-15 20:00:41 +01:00
|
|
|
void VoxelTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
|
2022-01-09 03:06:58 +00:00
|
|
|
mesh_block_size = math::clamp(mesh_block_size, get_data_block_size(), constants::MAX_BLOCK_SIZE);
|
2021-04-15 20:00:41 +01:00
|
|
|
|
|
|
|
unsigned int po2;
|
|
|
|
switch (mesh_block_size) {
|
|
|
|
case 16:
|
|
|
|
po2 = 4;
|
|
|
|
break;
|
|
|
|
case 32:
|
|
|
|
po2 = 5;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
mesh_block_size = 16;
|
|
|
|
po2 = 4;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (mesh_block_size == get_mesh_block_size()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unload all mesh blocks regardless of refcount
|
|
|
|
_mesh_map.create(po2, 0);
|
|
|
|
|
|
|
|
// Make paired viewers re-view the new meshable area
|
|
|
|
for (unsigned int i = 0; i < _paired_viewers.size(); ++i) {
|
|
|
|
PairedViewer &viewer = _paired_viewers[i];
|
|
|
|
// Resetting both because it's a re-initialization.
|
|
|
|
// We could also be doing that before or after their are shifted.
|
2021-05-31 17:10:54 +01:00
|
|
|
viewer.state.mesh_box = Box3i();
|
|
|
|
viewer.prev_state.mesh_box = Box3i();
|
2021-04-15 20:00:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
VoxelServer::get_singleton()->set_volume_render_block_size(_volume_id, mesh_block_size);
|
|
|
|
|
|
|
|
// No update on bounds because we can support a mismatch, as long as it is a multiple of data block size
|
|
|
|
//set_bounds(_bounds_in_voxels);
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
void VoxelTerrain::restart_stream() {
|
|
|
|
_on_stream_params_changed();
|
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelTerrain::_on_stream_params_changed() {
|
|
|
|
stop_streamer();
|
|
|
|
stop_updater();
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
if (_stream.is_valid()) {
|
|
|
|
const int stream_block_size_po2 = _stream->get_block_size_po2();
|
2019-08-24 01:44:27 +01:00
|
|
|
_set_block_size_po2(stream_block_size_po2);
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelServer::get_singleton()->set_volume_data_block_size(_volume_id, 1 << get_data_block_size_pow2());
|
|
|
|
VoxelServer::get_singleton()->set_volume_render_block_size(_volume_id, 1 << get_mesh_block_size_pow2());
|
2020-08-25 23:00:38 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
// The whole map might change, so regenerate it
|
|
|
|
reset_map();
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
if ((_stream.is_valid() || _generator.is_valid()) &&
|
|
|
|
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
|
2019-08-24 01:44:27 +01:00
|
|
|
start_streamer();
|
|
|
|
start_updater();
|
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
update_configuration_warnings();
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-12-16 00:11:11 +00:00
|
|
|
void VoxelTerrain::_on_gi_mode_changed() {
|
|
|
|
const GIMode gi_mode = get_gi_mode();
|
|
|
|
_mesh_map.for_all_blocks([gi_mode](VoxelMeshBlock *block) { //
|
|
|
|
block->set_gi_mode(DirectMeshInstance::GIMode(gi_mode));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
Ref<VoxelMesher> VoxelTerrain::get_mesher() const {
|
|
|
|
return _mesher;
|
2017-03-26 20:07:01 +02:00
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
void VoxelTerrain::set_mesher(Ref<VoxelMesher> mesher) {
|
|
|
|
if (mesher == _mesher) {
|
2019-08-24 01:44:27 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
_mesher = mesher;
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
stop_updater();
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
if (_mesher.is_valid()) {
|
|
|
|
start_updater();
|
|
|
|
// Voxel appearance might completely change
|
|
|
|
remesh_all_blocks();
|
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
update_configuration_warnings();
|
2020-12-18 21:01:50 +00:00
|
|
|
}
|
|
|
|
|
2022-01-09 23:23:59 +00:00
|
|
|
Ref<VoxelBlockyLibrary> VoxelTerrain::get_voxel_library() const {
|
2020-12-18 21:01:50 +00:00
|
|
|
Ref<VoxelMesherBlocky> blocky_mesher = _mesher;
|
|
|
|
if (blocky_mesher.is_valid()) {
|
|
|
|
return blocky_mesher->get_library();
|
|
|
|
}
|
2022-01-09 23:23:59 +00:00
|
|
|
return Ref<VoxelBlockyLibrary>();
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
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());
|
|
|
|
|
|
|
|
for (auto it = _paired_viewers.begin(); it != _paired_viewers.end(); ++it) {
|
|
|
|
const PairedViewer &viewer = *it;
|
|
|
|
|
|
|
|
if (viewer.state.data_box.intersects(block_box)) {
|
|
|
|
out_viewer_ids.push_back(viewer.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-26 18:09:37 +02:00
|
|
|
void VoxelTerrain::set_generate_collisions(bool enabled) {
|
|
|
|
_generate_collisions = enabled;
|
|
|
|
}
|
|
|
|
|
2021-05-09 20:49:45 +01:00
|
|
|
void VoxelTerrain::set_collision_layer(int layer) {
|
|
|
|
_collision_layer = layer;
|
2021-12-16 22:17:52 +00:00
|
|
|
_mesh_map.for_all_blocks([layer](VoxelMeshBlock *block) { //
|
|
|
|
block->set_collision_layer(layer);
|
|
|
|
});
|
2021-05-09 20:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int VoxelTerrain::get_collision_layer() const {
|
|
|
|
return _collision_layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::set_collision_mask(int mask) {
|
|
|
|
_collision_mask = mask;
|
2021-12-16 22:17:52 +00:00
|
|
|
_mesh_map.for_all_blocks([mask](VoxelMeshBlock *block) { //
|
|
|
|
block->set_collision_mask(mask);
|
|
|
|
});
|
2021-05-09 20:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int VoxelTerrain::get_collision_mask() const {
|
|
|
|
return _collision_mask;
|
|
|
|
}
|
|
|
|
|
2021-07-10 22:14:17 +01:00
|
|
|
void VoxelTerrain::set_collision_margin(float margin) {
|
|
|
|
_collision_margin = margin;
|
2021-12-16 22:17:52 +00:00
|
|
|
_mesh_map.for_all_blocks([margin](VoxelMeshBlock *block) { //
|
|
|
|
block->set_collision_margin(margin);
|
|
|
|
});
|
2021-07-10 22:14:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
float VoxelTerrain::get_collision_margin() const {
|
|
|
|
return _collision_margin;
|
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
unsigned int VoxelTerrain::get_max_view_distance() const {
|
2021-04-13 23:48:35 +01:00
|
|
|
return _max_view_distance_voxels;
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
void VoxelTerrain::set_max_view_distance(unsigned int distance_in_voxels) {
|
2020-02-07 04:52:06 +01:00
|
|
|
ERR_FAIL_COND(distance_in_voxels < 0);
|
2021-04-13 23:48:35 +01:00
|
|
|
_max_view_distance_voxels = distance_in_voxels;
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
void VoxelTerrain::set_block_enter_notification_enabled(bool enable) {
|
|
|
|
_block_enter_notification_enabled = enable;
|
|
|
|
|
|
|
|
if (enable == false) {
|
|
|
|
const Vector3i *key = nullptr;
|
|
|
|
while ((key = _loading_blocks.next(key))) {
|
|
|
|
LoadingBlock *lb = _loading_blocks.getptr(*key);
|
|
|
|
CRASH_COND(lb == nullptr);
|
|
|
|
lb->viewers_to_notify.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::is_block_enter_notification_enabled() const {
|
|
|
|
return _block_enter_notification_enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::set_area_edit_notification_enabled(bool enable) {
|
|
|
|
_area_edit_notification_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::is_area_edit_notification_enabled() const {
|
|
|
|
return _area_edit_notification_enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::set_automatic_loading_enabled(bool enable) {
|
|
|
|
_automatic_loading_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::is_automatic_loading_enabled() const {
|
|
|
|
return _automatic_loading_enabled;
|
|
|
|
}
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
void VoxelTerrain::set_material(unsigned int id, Ref<Material> material) {
|
2017-08-15 02:24:52 +02:00
|
|
|
// TODO Update existing block surfaces
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND(id < 0 || id >= VoxelMesherBlocky::MAX_MATERIALS);
|
2018-09-25 00:54:07 +01:00
|
|
|
_materials[id] = material;
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
Ref<Material> VoxelTerrain::get_material(unsigned int id) const {
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(id < 0 || id >= VoxelMesherBlocky::MAX_MATERIALS, Ref<Material>());
|
2018-09-25 00:54:07 +01:00
|
|
|
return _materials[id];
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlock *mesh_block) {
|
|
|
|
CRASH_COND(mesh_block == nullptr);
|
|
|
|
|
|
|
|
if (mesh_block->get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_NOT_SENT) {
|
|
|
|
// Already in the list
|
|
|
|
return;
|
|
|
|
}
|
2021-04-15 20:16:27 +01:00
|
|
|
if (mesh_block->mesh_viewers.get() == 0 && mesh_block->collision_viewers.get() == 0) {
|
2021-04-13 23:48:35 +01:00
|
|
|
// No viewers want mesh on this block (why even call this function then?)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int render_to_data_factor = get_mesh_block_size() / get_data_block_size();
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
|
2021-04-13 23:48:35 +01:00
|
|
|
// Pad by 1 because meshing needs neighbors
|
2021-12-13 21:38:10 +00:00
|
|
|
const Box3i data_box =
|
|
|
|
Box3i(mesh_block->position * render_to_data_factor, Vector3iUtil::create(render_to_data_factor))
|
|
|
|
.padded(1)
|
|
|
|
.clipped(bounds_in_data_blocks);
|
2021-04-13 23:48:35 +01:00
|
|
|
|
2021-06-13 19:00:47 +01:00
|
|
|
// If we get an empty box at this point, something is wrong with the caller
|
|
|
|
ERR_FAIL_COND(data_box.is_empty());
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Check if we have the data
|
2021-12-13 21:38:10 +00:00
|
|
|
const bool data_available = data_box.all_cells_match([this](Vector3i bpos) { return _data_map.has_block(bpos); });
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
if (data_available) {
|
2020-09-06 19:01:12 +01:00
|
|
|
// Regardless of if the updater is updating the block already,
|
2021-04-13 23:48:35 +01:00
|
|
|
// the block could have been modified again so we schedule another update
|
|
|
|
mesh_block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
|
|
|
_blocks_pending_update.push_back(mesh_block->position);
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification) {
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelDataBlock *block = _data_map.get_block(bpos);
|
2019-04-29 21:57:39 +01:00
|
|
|
|
2019-08-25 15:14:10 +01:00
|
|
|
if (block == nullptr) {
|
2020-09-06 19:01:12 +01:00
|
|
|
// The block isn't loaded
|
|
|
|
LoadingBlock *loading_block = _loading_blocks.getptr(bpos);
|
|
|
|
|
|
|
|
if (loading_block == nullptr) {
|
|
|
|
// First viewer to request it
|
2020-09-09 18:58:58 +01:00
|
|
|
LoadingBlock new_loading_block;
|
2021-04-15 20:16:27 +01:00
|
|
|
new_loading_block.viewers.add();
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
if (require_notification) {
|
|
|
|
new_loading_block.viewers_to_notify.push_back(viewer_id);
|
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
// Schedule a loading request
|
2020-09-09 18:58:58 +01:00
|
|
|
_loading_blocks.set(bpos, new_loading_block);
|
2018-09-25 00:54:07 +01:00
|
|
|
_blocks_pending_load.push_back(bpos);
|
2020-09-06 19:01:12 +01:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// More viewers
|
2021-04-15 20:16:27 +01:00
|
|
|
loading_block->viewers.add();
|
2022-01-31 21:23:39 +00:00
|
|
|
|
|
|
|
if (require_notification) {
|
|
|
|
loading_block->viewers_to_notify.push_back(viewer_id);
|
|
|
|
}
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
} else {
|
|
|
|
// The block is loaded
|
2021-04-15 20:16:27 +01:00
|
|
|
block->viewers.add();
|
2019-08-25 15:14:10 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
if (require_notification) {
|
|
|
|
notify_data_block_enter(*block, viewer_id);
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// 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...
|
|
|
|
}
|
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
|
|
|
|
if (mesh_flag == false && collision_flag == false) {
|
|
|
|
// Why even call the function?
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VoxelMeshBlock *block = _mesh_map.get_block(bpos);
|
|
|
|
|
|
|
|
if (block == nullptr) {
|
|
|
|
// Create if not found
|
|
|
|
block = VoxelMeshBlock::create(bpos, get_mesh_block_size(), 0);
|
2021-12-13 21:38:10 +00:00
|
|
|
block->set_world(get_world_3d());
|
2021-04-13 23:48:35 +01:00
|
|
|
_mesh_map.set_block(bpos, block);
|
|
|
|
}
|
|
|
|
|
2021-04-15 20:16:27 +01:00
|
|
|
if (mesh_flag) {
|
|
|
|
block->mesh_viewers.add();
|
|
|
|
}
|
|
|
|
if (collision_flag) {
|
|
|
|
block->collision_viewers.add();
|
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
|
2021-04-15 20:00:41 +01:00
|
|
|
// This is needed in case a viewer wants to view meshes in places data blocks are already present.
|
|
|
|
// Before that, meshes were updated only when a data block was loaded or modified,
|
|
|
|
// so changing block size or viewer flags did not make meshes appear.
|
|
|
|
try_schedule_mesh_update(block);
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// 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...
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::unview_data_block(Vector3i bpos) {
|
|
|
|
VoxelDataBlock *block = _data_map.get_block(bpos);
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
if (block == nullptr) {
|
|
|
|
// The block isn't loaded
|
|
|
|
LoadingBlock *loading_block = _loading_blocks.getptr(bpos);
|
2020-09-06 23:57:41 +01:00
|
|
|
if (loading_block == nullptr) {
|
|
|
|
PRINT_VERBOSE("Request to unview a loading block that was never requested");
|
|
|
|
// Not expected, but fine I guess
|
|
|
|
return;
|
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-15 20:16:27 +01:00
|
|
|
loading_block->viewers.remove();
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-15 20:16:27 +01:00
|
|
|
if (loading_block->viewers.get() == 0) {
|
2020-09-06 19:01:12 +01:00
|
|
|
// No longer want to load it
|
|
|
|
_loading_blocks.erase(bpos);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// The block is loaded
|
2021-04-15 20:16:27 +01:00
|
|
|
block->viewers.remove();
|
|
|
|
if (block->viewers.get() == 0) {
|
2021-04-13 23:48:35 +01:00
|
|
|
// The block itself is no longer wanted
|
|
|
|
unload_data_block(bpos);
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
|
|
|
|
VoxelMeshBlock *block = _mesh_map.get_block(bpos);
|
|
|
|
// Mesh blocks are created on first view call,
|
|
|
|
// so that would mean we unview one without viewing it in the first place
|
|
|
|
ERR_FAIL_COND(block == nullptr);
|
|
|
|
|
|
|
|
if (mesh_flag) {
|
2021-04-15 20:16:27 +01:00
|
|
|
block->mesh_viewers.remove();
|
|
|
|
if (block->mesh_viewers.get() == 0) {
|
2021-04-13 23:48:35 +01:00
|
|
|
// Mesh no longer required
|
|
|
|
block->drop_mesh();
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (collision_flag) {
|
2021-04-15 20:16:27 +01:00
|
|
|
block->collision_viewers.remove();
|
|
|
|
if (block->collision_viewers.get() == 0) {
|
2021-04-13 23:48:35 +01:00
|
|
|
// Collision no longer required
|
|
|
|
block->drop_collision();
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
|
2021-12-16 22:17:20 +00:00
|
|
|
if (block->mesh_viewers.get() == 0 && block->collision_viewers.get() == 0) {
|
2021-04-13 23:48:35 +01:00
|
|
|
unload_mesh_block(bpos);
|
|
|
|
}
|
2017-03-28 00:56:01 +02:00
|
|
|
}
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
namespace {
|
|
|
|
struct ScheduleSaveAction {
|
2020-08-26 19:49:30 +01:00
|
|
|
std::vector<VoxelTerrain::BlockToSave> &blocks_to_save;
|
2019-05-28 00:40:09 +01:00
|
|
|
bool with_copy;
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void operator()(VoxelDataBlock *block) {
|
2020-08-29 22:09:54 +01:00
|
|
|
// TODO Don't ask for save if the stream doesn't support it!
|
2019-09-06 23:24:56 +01:00
|
|
|
if (block->is_modified()) {
|
2019-08-16 20:56:07 +01:00
|
|
|
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
2020-08-26 19:49:30 +01:00
|
|
|
VoxelTerrain::BlockToSave b;
|
2020-08-29 22:09:54 +01:00
|
|
|
if (with_copy) {
|
2021-09-26 04:14:50 +01:00
|
|
|
RWLockRead lock(block->get_voxels().get_lock());
|
|
|
|
b.voxels = gd_make_shared<VoxelBufferInternal>();
|
|
|
|
block->get_voxels_const().duplicate_to(*b.voxels, true);
|
2020-08-29 22:09:54 +01:00
|
|
|
} else {
|
2021-09-26 04:14:50 +01:00
|
|
|
b.voxels = block->get_voxels_shared();
|
2020-08-29 22:09:54 +01:00
|
|
|
}
|
2019-05-28 00:40:09 +01:00
|
|
|
b.position = block->position;
|
|
|
|
blocks_to_save.push_back(b);
|
2019-09-06 23:24:56 +01:00
|
|
|
block->set_modified(false);
|
2019-05-28 00:40:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::unload_data_block(Vector3i bpos) {
|
2022-01-31 21:23:39 +00:00
|
|
|
const bool save = _stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor);
|
|
|
|
|
|
|
|
_data_map.remove_block(bpos, [this, bpos, save](VoxelDataBlock *block) {
|
2021-04-13 23:48:35 +01:00
|
|
|
emit_data_block_unloaded(block);
|
2022-01-31 21:23:39 +00:00
|
|
|
if (save) {
|
|
|
|
// Note: no need to copy the block because it gets removed from the map anyways
|
|
|
|
ScheduleSaveAction{ _blocks_to_save, false }(block);
|
|
|
|
}
|
2020-08-10 20:57:03 +01:00
|
|
|
});
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2019-08-25 15:14:10 +01:00
|
|
|
_loading_blocks.erase(bpos);
|
|
|
|
|
2017-08-28 01:47:38 +02:00
|
|
|
// Blocks in the update queue will be cancelled in _process,
|
|
|
|
// because it's too expensive to linear-search all blocks for each block
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
|
2021-12-16 22:59:31 +00:00
|
|
|
std::vector<Vector3i> &blocks_pending_update = _blocks_pending_update;
|
|
|
|
|
|
|
|
_mesh_map.remove_block(bpos, [&blocks_pending_update](const VoxelMeshBlock *block) {
|
|
|
|
if (block->get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_NOT_SENT) {
|
|
|
|
// That block was in the list of blocks to update later in the process loop, we'll need to unregister it.
|
|
|
|
// We expect that block to be in that list. If it isn't, something wrong happened with its state.
|
|
|
|
ERR_FAIL_COND(!unordered_remove_value(blocks_pending_update, block->position));
|
|
|
|
}
|
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
void VoxelTerrain::save_all_modified_blocks(bool with_copy) {
|
|
|
|
// That may cause a stutter, so should be used when the player won't notice
|
2021-04-13 23:48:35 +01:00
|
|
|
_data_map.for_all_blocks(ScheduleSaveAction{ _blocks_to_save, with_copy });
|
2019-06-02 01:59:39 +01:00
|
|
|
// And flush immediately
|
|
|
|
send_block_data_requests();
|
2019-05-28 00:40:09 +01:00
|
|
|
}
|
|
|
|
|
2020-12-25 17:08:40 +00:00
|
|
|
const VoxelTerrain::Stats &VoxelTerrain::get_stats() const {
|
|
|
|
return _stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dictionary VoxelTerrain::_b_get_statistics() const {
|
2018-09-25 00:54:07 +01:00
|
|
|
Dictionary d;
|
2020-08-10 20:57:03 +01:00
|
|
|
|
2018-09-29 18:59:19 +01:00
|
|
|
// Breakdown of time spent in _process
|
|
|
|
d["time_detect_required_blocks"] = _stats.time_detect_required_blocks;
|
2019-08-25 18:47:43 +01:00
|
|
|
d["time_request_blocks_to_load"] = _stats.time_request_blocks_to_load;
|
2018-09-29 18:59:19 +01:00
|
|
|
d["time_process_load_responses"] = _stats.time_process_load_responses;
|
2019-08-25 18:47:43 +01:00
|
|
|
d["time_request_blocks_to_update"] = _stats.time_request_blocks_to_update;
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2019-08-25 18:47:43 +01:00
|
|
|
d["dropped_block_loads"] = _stats.dropped_block_loads;
|
|
|
|
d["dropped_block_meshs"] = _stats.dropped_block_meshs;
|
|
|
|
d["updated_blocks"] = _stats.updated_blocks;
|
2019-08-25 17:23:25 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
return d;
|
2017-03-29 22:36:42 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelTerrain::start_updater() {
|
2020-12-18 21:01:50 +00:00
|
|
|
Ref<VoxelMesherBlocky> blocky_mesher = _mesher;
|
|
|
|
if (blocky_mesher.is_valid()) {
|
2022-01-09 23:23:59 +00:00
|
|
|
Ref<VoxelBlockyLibrary> library = blocky_mesher->get_library();
|
2020-12-18 21:01:50 +00:00
|
|
|
if (library.is_valid()) {
|
|
|
|
// TODO Any way to execute this function just after the TRES resource loader has finished to load?
|
2022-01-09 23:23:59 +00:00
|
|
|
// VoxelBlockyLibrary should be baked ahead of time, like MeshLibrary
|
2020-12-18 21:01:50 +00:00
|
|
|
library->bake();
|
|
|
|
}
|
2020-02-11 21:20:05 +00:00
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_mesher(_volume_id, _mesher);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::stop_updater() {
|
|
|
|
struct ResetMeshStateAction {
|
2021-04-13 23:48:35 +01:00
|
|
|
void operator()(VoxelMeshBlock *block) {
|
|
|
|
if (block->get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_SENT) {
|
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-25 23:00:38 +01:00
|
|
|
VoxelServer::get_singleton()->invalidate_volume_mesh_requests(_volume_id);
|
2020-12-18 21:01:50 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_mesher(_volume_id, Ref<VoxelMesher>());
|
2019-08-24 01:44:27 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
// TODO We can still receive a few mesh delayed mesh updates after this. Is it a problem?
|
|
|
|
//_reception_buffers.mesh_output.clear();
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
_blocks_pending_update.clear();
|
|
|
|
|
|
|
|
ResetMeshStateAction a;
|
2021-04-13 23:48:35 +01:00
|
|
|
_mesh_map.for_all_blocks(a);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
void VoxelTerrain::remesh_all_blocks() {
|
2021-12-16 22:17:52 +00:00
|
|
|
_mesh_map.for_all_blocks([this](VoxelMeshBlock *block) { //
|
|
|
|
try_schedule_mesh_update(block);
|
|
|
|
});
|
2020-12-18 21:01:50 +00:00
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
// 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)) {
|
|
|
|
// Already exists
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_loading_blocks.has(block_position)) {
|
|
|
|
// Already loading
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (require_notification) {
|
|
|
|
// new_loading_block.viewers_to_notify.push_back(viewer_id);
|
|
|
|
// }
|
|
|
|
|
|
|
|
LoadingBlock new_loading_block;
|
|
|
|
const Box3i block_box(_data_map.block_to_voxel(block_position), Vector3iUtil::create(_data_map.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)) {
|
|
|
|
new_loading_block.viewers.add();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_loading_block.viewers.get() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Schedule a loading request
|
|
|
|
// TODO This could also end up loading from stream
|
|
|
|
_loading_blocks.set(block_position, new_loading_block);
|
|
|
|
_blocks_pending_load.push_back(block_position);
|
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelTerrain::start_streamer() {
|
2020-08-25 23:00:38 +01:00
|
|
|
VoxelServer::get_singleton()->set_volume_stream(_volume_id, _stream);
|
2021-01-17 17:18:05 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_generator(_volume_id, _generator);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::stop_streamer() {
|
2020-08-25 23:00:38 +01:00
|
|
|
VoxelServer::get_singleton()->set_volume_stream(_volume_id, Ref<VoxelStream>());
|
2021-01-17 17:18:05 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_generator(_volume_id, Ref<VoxelGenerator>());
|
2019-08-25 15:14:10 +01:00
|
|
|
_loading_blocks.clear();
|
2019-08-24 01:44:27 +01:00
|
|
|
_blocks_pending_load.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::reset_map() {
|
2020-09-06 19:01:12 +01:00
|
|
|
// Discard everything, to reload it all
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
_data_map.for_all_blocks([this](VoxelDataBlock *block) { emit_data_block_unloaded(block); });
|
2021-04-13 23:48:35 +01:00
|
|
|
_data_map.create(get_data_block_size_pow2(), 0);
|
|
|
|
|
|
|
|
_mesh_map.create(get_mesh_block_size_pow2(), 0);
|
2020-08-14 20:33:09 +01:00
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
_loading_blocks.clear();
|
|
|
|
_blocks_pending_load.clear();
|
|
|
|
_blocks_pending_update.clear();
|
|
|
|
_blocks_to_save.clear();
|
|
|
|
|
|
|
|
// No need to care about refcounts, we drop everything anyways. Will pair it back on next process.
|
|
|
|
_paired_viewers.clear();
|
2019-04-28 01:32:23 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::post_edit_voxel(Vector3i pos) {
|
2021-05-31 17:10:54 +01:00
|
|
|
post_edit_area(Box3i(pos, Vector3i(1, 1, 1)));
|
2017-03-29 22:36:42 +02:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:10:54 +01:00
|
|
|
void VoxelTerrain::try_schedule_mesh_update_from_data(const Box3i &box_in_voxels) {
|
2021-09-09 18:54:15 +01:00
|
|
|
// We pad by 1 because neighbor blocks might be affected visually (for example, baked ambient occlusion)
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i mesh_box = box_in_voxels.padded(1).downscaled(get_mesh_block_size());
|
2021-04-13 23:48:35 +01:00
|
|
|
mesh_box.for_each_cell([this](Vector3i pos) {
|
|
|
|
VoxelMeshBlock *block = _mesh_map.get_block(pos);
|
|
|
|
// There isn't necessarily a mesh block, if the edit happens in a boundary,
|
|
|
|
// or if it is done next to a viewer that doesn't need meshes
|
|
|
|
if (block != nullptr) {
|
|
|
|
try_schedule_mesh_update(block);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:10:54 +01:00
|
|
|
void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
|
2021-04-13 23:48:35 +01:00
|
|
|
box_in_voxels.clip(_bounds_in_voxels);
|
2020-09-13 22:36:28 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Mark data as modified
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i data_box = box_in_voxels.downscaled(get_data_block_size());
|
2021-04-13 23:48:35 +01:00
|
|
|
data_box.for_each_cell([this](Vector3i pos) {
|
|
|
|
VoxelDataBlock *block = _data_map.get_block(pos);
|
|
|
|
// The edit can happen next to a boundary
|
|
|
|
if (block != nullptr) {
|
|
|
|
block->set_modified(true);
|
2022-01-31 21:23:39 +00:00
|
|
|
block->set_edited(true);
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
if (_area_edit_notification_enabled) {
|
|
|
|
GDVIRTUAL_CALL(_on_area_edited, box_in_voxels.pos, box_in_voxels.size);
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
try_schedule_mesh_update_from_data(box_in_voxels);
|
2018-10-01 20:48:47 +01:00
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelTerrain::_notification(int p_what) {
|
2019-08-25 01:11:38 +01:00
|
|
|
struct SetWorldAction {
|
2021-12-13 21:38:10 +00:00
|
|
|
World3D *world;
|
|
|
|
SetWorldAction(World3D *w) : world(w) {}
|
2021-04-13 23:48:35 +01:00
|
|
|
void operator()(VoxelMeshBlock *block) {
|
2019-08-25 01:11:38 +01:00
|
|
|
block->set_world(world);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-08-31 21:46:19 +01:00
|
|
|
struct SetParentVisibilityAction {
|
2019-08-24 01:44:27 +01:00
|
|
|
bool visible;
|
2021-12-13 21:38:10 +00:00
|
|
|
SetParentVisibilityAction(bool v) : visible(v) {}
|
2021-04-13 23:48:35 +01:00
|
|
|
void operator()(VoxelMeshBlock *block) {
|
2019-08-31 21:46:19 +01:00
|
|
|
block->set_parent_visible(visible);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
};
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-01-01 04:40:16 +01:00
|
|
|
switch (p_what) {
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_ENTER_TREE:
|
|
|
|
set_process(true);
|
|
|
|
break;
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_PROCESS:
|
2020-08-25 23:00:38 +01:00
|
|
|
// Can't do that in enter tree because Godot is "still setting up children".
|
|
|
|
// Can't do that in ready either because Godot says node state is locked.
|
2020-08-31 21:51:30 +01:00
|
|
|
// This hack is quite miserable.
|
2020-08-25 23:00:38 +01:00
|
|
|
VoxelServerUpdater::ensure_existence(get_tree());
|
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
_process();
|
2017-08-13 01:19:39 +02:00
|
|
|
break;
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_EXIT_TREE:
|
|
|
|
break;
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
case NOTIFICATION_ENTER_WORLD:
|
2021-12-13 21:38:10 +00:00
|
|
|
_mesh_map.for_all_blocks(SetWorldAction(*get_world_3d()));
|
2021-04-13 23:48:35 +01:00
|
|
|
break;
|
2017-08-28 02:32:23 +02:00
|
|
|
|
|
|
|
case NOTIFICATION_EXIT_WORLD:
|
2021-04-13 23:48:35 +01:00
|
|
|
_mesh_map.for_all_blocks(SetWorldAction(nullptr));
|
2017-08-28 02:32:23 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case NOTIFICATION_VISIBILITY_CHANGED:
|
2021-04-13 23:48:35 +01:00
|
|
|
_mesh_map.for_all_blocks(SetParentVisibilityAction(is_visible()));
|
2017-08-28 02:32:23 +02:00
|
|
|
break;
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2020-10-25 00:55:57 +01:00
|
|
|
case NOTIFICATION_TRANSFORM_CHANGED: {
|
2021-12-13 21:38:10 +00:00
|
|
|
const Transform3D transform = get_global_transform();
|
2020-10-25 00:55:57 +01:00
|
|
|
VoxelServer::get_singleton()->set_volume_transform(_volume_id, transform);
|
|
|
|
|
|
|
|
if (!is_inside_tree()) {
|
|
|
|
// The transform and other properties can be set by the scene loader,
|
|
|
|
// before we enter the tree
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-12-16 22:17:52 +00:00
|
|
|
_mesh_map.for_all_blocks([&transform](VoxelMeshBlock *block) { //
|
|
|
|
block->set_parent_transform(transform);
|
|
|
|
});
|
2020-10-25 00:55:57 +01:00
|
|
|
|
|
|
|
} break;
|
2017-08-20 18:37:08 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
default:
|
|
|
|
break;
|
2017-01-01 04:40:16 +01:00
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 01:59:39 +01:00
|
|
|
void VoxelTerrain::send_block_data_requests() {
|
2020-08-30 03:59:02 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2020-08-25 23:00:38 +01:00
|
|
|
// Blocks to load
|
2020-09-09 18:58:58 +01:00
|
|
|
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
|
2020-08-25 23:00:38 +01:00
|
|
|
const Vector3i block_pos = _blocks_pending_load[i];
|
|
|
|
// TODO Batch request
|
2021-02-07 17:22:50 +00:00
|
|
|
VoxelServer::get_singleton()->request_block_load(_volume_id, block_pos, 0, false);
|
2019-06-02 01:59:39 +01:00
|
|
|
}
|
|
|
|
|
2020-08-25 23:00:38 +01:00
|
|
|
// Blocks to save
|
2021-05-08 17:56:43 +01:00
|
|
|
if (_stream.is_valid()) {
|
|
|
|
for (unsigned int i = 0; i < _blocks_to_save.size(); ++i) {
|
2021-12-13 21:38:10 +00:00
|
|
|
PRINT_VERBOSE(String("Requesting save of block {0}").format(varray(_blocks_to_save[i].position)));
|
2021-05-08 17:56:43 +01:00
|
|
|
const BlockToSave b = _blocks_to_save[i];
|
|
|
|
// TODO Batch request
|
|
|
|
VoxelServer::get_singleton()->request_voxel_block_save(_volume_id, b.voxels, b.position, 0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (_blocks_to_save.size() > 0) {
|
|
|
|
PRINT_VERBOSE(String("Not saving {0} blocks because no stream is assigned")
|
2021-06-13 18:13:11 +01:00
|
|
|
.format(varray(SIZE_T_TO_VARIANT(_blocks_to_save.size()))));
|
2021-05-08 17:56:43 +01:00
|
|
|
}
|
2019-06-02 01:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//print_line(String("Sending {0} block requests").format(varray(input.blocks_to_emerge.size())));
|
|
|
|
_blocks_pending_load.clear();
|
|
|
|
_blocks_to_save.clear();
|
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::emit_data_block_loaded(const VoxelDataBlock *block) {
|
2021-12-13 21:38:10 +00:00
|
|
|
const Variant vpos = block->position;
|
|
|
|
// Not sure about exposDing buffers directly... some stuff on them is useful to obtain directly,
|
2021-06-16 23:29:59 +01:00
|
|
|
// but also it allows scripters to mess with voxels in a way they should not.
|
2021-12-16 22:17:52 +00:00
|
|
|
// Example: modifying voxels without locking them first, while another thread may be reading them at the same
|
|
|
|
// time. The same thing could happen the other way around (threaded task modifying voxels while you try to read
|
|
|
|
// them). It isn't planned to expose VoxelBuffer locks because there are too many of them, it may likely shift
|
|
|
|
// to another system in the future, and might even be changed to no longer inherit Reference. So unless this is
|
|
|
|
// absolutely necessary, buffers aren't exposed. Workaround: use VoxelTool
|
2021-06-16 23:29:59 +01:00
|
|
|
//const Variant vbuffer = block->voxels;
|
|
|
|
//const Variant *args[2] = { &vpos, &vbuffer };
|
|
|
|
const Variant *args[1] = { &vpos };
|
|
|
|
emit_signal(VoxelStringNames::get_singleton()->block_loaded, args, 1);
|
2020-08-10 20:57:03 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
void VoxelTerrain::emit_data_block_unloaded(const VoxelDataBlock *block) {
|
2021-12-13 21:38:10 +00:00
|
|
|
const Variant vpos = block->position;
|
2021-06-16 23:29:59 +01:00
|
|
|
// const Variant vbuffer = block->voxels;
|
|
|
|
// const Variant *args[2] = { &vpos, &vbuffer };
|
|
|
|
const Variant *args[1] = { &vpos };
|
|
|
|
emit_signal(VoxelStringNames::get_singleton()->block_unloaded, args, 1);
|
2020-08-10 20:57:03 +01:00
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
bool VoxelTerrain::try_get_paired_viewer_index(uint32_t id, size_t &out_i) const {
|
|
|
|
for (size_t i = 0; i < _paired_viewers.size(); ++i) {
|
|
|
|
const PairedViewer &p = _paired_viewers[i];
|
|
|
|
if (p.id == id) {
|
|
|
|
out_i = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
// 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, uint32_t viewer_id) {
|
|
|
|
if (!VoxelServer::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
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_data_block_enter_info_obj == nullptr) {
|
|
|
|
_data_block_enter_info_obj = gd_make_unique<VoxelDataBlockEnterInfo>();
|
|
|
|
}
|
|
|
|
_data_block_enter_info_obj->network_peer_id = VoxelServer::get_singleton()->get_viewer_network_peer_id(viewer_id);
|
|
|
|
_data_block_enter_info_obj->voxel_block = █
|
|
|
|
|
|
|
|
if (!GDVIRTUAL_CALL(_on_data_block_enter, _data_block_enter_info_obj.get())) {
|
|
|
|
WARN_PRINT_ONCE("VoxelTerrain::notify_data_block_enter is unimplemented!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
void VoxelTerrain::_process() {
|
2020-08-30 03:59:02 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-09-09 18:54:15 +01:00
|
|
|
process_viewers();
|
2021-10-13 20:28:20 +01:00
|
|
|
//process_received_data_blocks();
|
2021-09-09 18:54:15 +01:00
|
|
|
process_meshing();
|
|
|
|
}
|
2020-08-30 03:59:02 +01:00
|
|
|
|
2021-09-09 18:54:15 +01:00
|
|
|
void VoxelTerrain::process_viewers() {
|
2022-01-09 22:13:10 +00:00
|
|
|
ProfilingClock profiling_clock;
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Ordered by ascending index in paired viewers list
|
2020-09-06 19:01:12 +01:00
|
|
|
std::vector<size_t> unpaired_viewer_indexes;
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
// Update viewers
|
2020-09-06 19:01:12 +01:00
|
|
|
{
|
|
|
|
// Our node doesn't have bounds yet, so for now viewers are always paired.
|
2020-10-25 00:55:57 +01:00
|
|
|
// TODO Update: the node has bounds now, need to change this
|
2020-09-06 19:01:12 +01:00
|
|
|
|
|
|
|
// Destroyed viewers
|
|
|
|
for (size_t i = 0; i < _paired_viewers.size(); ++i) {
|
|
|
|
PairedViewer &p = _paired_viewers[i];
|
|
|
|
if (!VoxelServer::get_singleton()->viewer_exists(p.id)) {
|
2020-09-06 19:29:56 +01:00
|
|
|
PRINT_VERBOSE("Detected destroyed viewer in VoxelTerrain");
|
2021-04-13 23:48:35 +01:00
|
|
|
// Interpret removal as nullified view distance so the same code handling loading of blocks
|
|
|
|
// will be used to unload those viewed by this viewer.
|
|
|
|
// We'll actually remove unpaired viewers in a second pass.
|
|
|
|
p.state.view_distance_voxels = 0;
|
2020-09-06 19:01:12 +01:00
|
|
|
unpaired_viewer_indexes.push_back(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
const Transform3D local_to_world_transform = get_global_transform();
|
|
|
|
const Transform3D world_to_local_transform = local_to_world_transform.affine_inverse();
|
2020-10-25 00:55:57 +01:00
|
|
|
|
|
|
|
// Note, this does not support non-uniform scaling
|
|
|
|
// 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();
|
|
|
|
|
2021-06-13 19:00:47 +01:00
|
|
|
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());
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
struct UpdatePairedViewer {
|
|
|
|
VoxelTerrain &self;
|
2021-06-13 19:00:47 +01:00
|
|
|
const Box3i bounds_in_data_blocks;
|
|
|
|
const Box3i bounds_in_mesh_blocks;
|
2021-12-13 21:38:10 +00:00
|
|
|
const Transform3D world_to_local_transform;
|
2021-04-13 23:48:35 +01:00
|
|
|
const float view_distance_scale;
|
|
|
|
|
|
|
|
inline void operator()(const VoxelServer::Viewer &viewer, uint32_t viewer_id) {
|
|
|
|
size_t paired_viewer_index;
|
|
|
|
if (!self.try_get_paired_viewer_index(viewer_id, paired_viewer_index)) {
|
|
|
|
PairedViewer p;
|
|
|
|
p.id = viewer_id;
|
|
|
|
paired_viewer_index = self._paired_viewers.size();
|
|
|
|
self._paired_viewers.push_back(p);
|
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
PairedViewer &paired_viewer = self._paired_viewers[paired_viewer_index];
|
|
|
|
paired_viewer.prev_state = paired_viewer.state;
|
|
|
|
PairedViewer::State &state = paired_viewer.state;
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
const unsigned int view_distance_voxels =
|
|
|
|
static_cast<unsigned int>(static_cast<float>(viewer.view_distance) * view_distance_scale);
|
|
|
|
const Vector3 local_position = world_to_local_transform.xform(viewer.world_position);
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2022-01-03 23:14:18 +00:00
|
|
|
state.view_distance_voxels = math::min(view_distance_voxels, self._max_view_distance_voxels);
|
2021-12-13 21:38:10 +00:00
|
|
|
state.local_position_voxels = Vector3iUtil::from_floored(local_position);
|
2021-04-13 23:48:35 +01:00
|
|
|
state.requires_collisions = VoxelServer::get_singleton()->is_viewer_requiring_collisions(viewer_id);
|
|
|
|
state.requires_meshes = VoxelServer::get_singleton()->is_viewer_requiring_visuals(viewer_id);
|
2020-10-25 00:55:57 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Update data and mesh view boxes
|
|
|
|
|
|
|
|
const int data_block_size = self.get_data_block_size();
|
|
|
|
const int mesh_block_size = self.get_mesh_block_size();
|
|
|
|
|
|
|
|
int view_distance_data_blocks;
|
|
|
|
Vector3i data_block_pos;
|
|
|
|
|
|
|
|
if (state.requires_meshes || state.requires_collisions) {
|
2022-01-03 23:14:18 +00:00
|
|
|
const int view_distance_mesh_blocks = math::ceildiv(state.view_distance_voxels, mesh_block_size);
|
2021-04-13 23:48:35 +01:00
|
|
|
const int render_to_data_factor = (mesh_block_size / data_block_size);
|
2021-12-13 21:38:10 +00:00
|
|
|
const Vector3i mesh_block_pos =
|
|
|
|
Vector3iUtil::floordiv(state.local_position_voxels, mesh_block_size);
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
// Adding one block of padding because meshing requires neighbors
|
|
|
|
view_distance_data_blocks = view_distance_mesh_blocks * render_to_data_factor + 1;
|
2021-06-13 19:00:47 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
data_block_pos = mesh_block_pos * render_to_data_factor;
|
2021-12-13 21:38:10 +00:00
|
|
|
state.mesh_box =
|
|
|
|
Box3i::from_center_extents(mesh_block_pos, Vector3iUtil::create(view_distance_mesh_blocks))
|
|
|
|
.clipped(bounds_in_mesh_blocks);
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
} else {
|
2022-01-03 23:14:18 +00:00
|
|
|
view_distance_data_blocks = math::ceildiv(state.view_distance_voxels, data_block_size);
|
2021-06-13 19:00:47 +01:00
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
data_block_pos = Vector3iUtil::floordiv(state.local_position_voxels, data_block_size);
|
2021-05-31 17:10:54 +01:00
|
|
|
state.mesh_box = Box3i();
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
state.data_box =
|
|
|
|
Box3i::from_center_extents(data_block_pos, Vector3iUtil::create(view_distance_data_blocks))
|
|
|
|
.clipped(bounds_in_data_blocks);
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// New viewers and updates
|
2021-12-13 21:38:10 +00:00
|
|
|
UpdatePairedViewer u{ *this, bounds_in_data_blocks, bounds_in_mesh_blocks, world_to_local_transform,
|
|
|
|
view_distance_scale };
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelServer::get_singleton()->for_each_viewer(u);
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
const bool can_load_blocks = (_automatic_loading_enabled && (_stream.is_valid() || _generator.is_valid())) &&
|
2021-12-13 21:38:10 +00:00
|
|
|
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor);
|
2020-09-08 21:34:35 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Find out which blocks need to appear and which need to be unloaded
|
2022-01-31 21:23:39 +00:00
|
|
|
{
|
2020-08-30 03:59:02 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
for (size_t i = 0; i < _paired_viewers.size(); ++i) {
|
|
|
|
const PairedViewer &viewer = _paired_viewers[i];
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
{
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i &new_data_box = viewer.state.data_box;
|
|
|
|
const Box3i &prev_data_box = viewer.prev_state.data_box;
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
if (prev_data_box != new_data_box) {
|
2021-04-15 20:00:41 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
const bool require_notifications = _block_enter_notification_enabled &&
|
|
|
|
VoxelServer::get_singleton()->is_viewer_requiring_data_block_notifications(viewer.id);
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Unview blocks that just fell out of range
|
2021-05-31 17:10:54 +01:00
|
|
|
prev_data_box.difference(new_data_box, [this, &viewer](Box3i out_of_range_box) {
|
2022-01-31 21:23:39 +00:00
|
|
|
out_of_range_box.for_each_cell([this, &viewer](Vector3i bpos) { //
|
|
|
|
unview_data_block(bpos);
|
|
|
|
});
|
2020-09-06 19:01:12 +01:00
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
// View blocks that just entered the range
|
2022-01-31 21:23:39 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
{
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i &new_mesh_box = viewer.state.mesh_box;
|
|
|
|
const Box3i &prev_mesh_box = viewer.prev_state.mesh_box;
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
if (prev_mesh_box != new_mesh_box) {
|
2021-04-15 20:00:41 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Unview blocks that just fell out of range
|
2021-05-31 17:10:54 +01:00
|
|
|
prev_mesh_box.difference(new_mesh_box, [this, &viewer](Box3i out_of_range_box) {
|
2021-04-13 23:48:35 +01:00
|
|
|
out_of_range_box.for_each_cell([this, &viewer](Vector3i bpos) {
|
2021-12-13 21:38:10 +00:00
|
|
|
unview_mesh_block(
|
|
|
|
bpos, viewer.prev_state.requires_meshes, viewer.prev_state.requires_collisions);
|
2021-04-13 23:48:35 +01:00
|
|
|
});
|
2020-09-06 19:01:12 +01:00
|
|
|
});
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// View blocks that just entered the range
|
2021-05-31 17:10:54 +01:00
|
|
|
new_mesh_box.difference(prev_mesh_box, [this, &viewer](Box3i box_to_load) {
|
2021-04-13 23:48:35 +01:00
|
|
|
box_to_load.for_each_cell([this, &viewer](Vector3i bpos) {
|
|
|
|
// Load or update block
|
2021-12-13 21:38:10 +00:00
|
|
|
view_mesh_block(bpos, viewer.state.requires_meshes, viewer.state.requires_collisions);
|
2021-04-13 23:48:35 +01:00
|
|
|
});
|
2020-09-06 19:01:12 +01:00
|
|
|
});
|
|
|
|
}
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
// Blocks that remained within range of the viewer may need some changes too if viewer flags were
|
|
|
|
// modified. This operates on a DISTINCT set of blocks than the one above.
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
if (viewer.state.requires_collisions != viewer.prev_state.requires_collisions) {
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i box = new_mesh_box.clipped(prev_mesh_box);
|
2021-04-13 23:48:35 +01:00
|
|
|
if (viewer.state.requires_collisions) {
|
2022-01-31 21:23:39 +00:00
|
|
|
box.for_each_cell([this](Vector3i bpos) { //
|
|
|
|
view_mesh_block(bpos, false, true);
|
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
} else {
|
2022-01-31 21:23:39 +00:00
|
|
|
box.for_each_cell([this](Vector3i bpos) { //
|
|
|
|
unview_mesh_block(bpos, false, true);
|
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (viewer.state.requires_meshes != viewer.prev_state.requires_meshes) {
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i box = new_mesh_box.clipped(prev_mesh_box);
|
2021-04-13 23:48:35 +01:00
|
|
|
if (viewer.state.requires_meshes) {
|
2022-01-31 21:23:39 +00:00
|
|
|
box.for_each_cell([this](Vector3i bpos) { //
|
|
|
|
view_mesh_block(bpos, true, false);
|
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
|
|
|
|
} else {
|
2022-01-31 21:23:39 +00:00
|
|
|
box.for_each_cell([this](Vector3i bpos) { //
|
|
|
|
unview_mesh_block(bpos, true, false);
|
|
|
|
});
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-28 01:47:38 +02:00
|
|
|
}
|
|
|
|
|
2019-06-25 20:51:35 +01:00
|
|
|
_stats.time_detect_required_blocks = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// We no longer need unpaired viewers.
|
2020-09-06 19:01:12 +01:00
|
|
|
for (size_t i = 0; i < unpaired_viewer_indexes.size(); ++i) {
|
2020-09-06 19:29:56 +01:00
|
|
|
PRINT_VERBOSE("Unpairing viewer from VoxelTerrain");
|
2021-04-13 23:48:35 +01:00
|
|
|
// Iterating backward so indexes of paired viewers will not change because of the removal
|
|
|
|
const size_t vi = unpaired_viewer_indexes[unpaired_viewer_indexes.size() - i - 1];
|
2020-09-06 19:01:12 +01:00
|
|
|
_paired_viewers[vi] = _paired_viewers.back();
|
|
|
|
_paired_viewers.pop_back();
|
|
|
|
}
|
2017-03-28 00:56:01 +02:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
// It's possible the user didn't set a stream yet, or it is turned off
|
2022-01-31 21:23:39 +00:00
|
|
|
if (can_load_blocks) {
|
2020-05-07 21:13:47 +01:00
|
|
|
send_block_data_requests();
|
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-08-25 18:47:43 +01:00
|
|
|
_stats.time_request_blocks_to_load = profiling_clock.restart();
|
2021-09-09 18:54:15 +01:00
|
|
|
}
|
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
void VoxelTerrain::apply_data_block_response(VoxelServer::BlockDataOutput &ob) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-09-09 18:54:15 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
//print_line(String("Receiving {0} blocks").format(varray(output.emerged_blocks.size())));
|
2020-08-25 23:00:38 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
if (ob.type == VoxelServer::BlockDataOutput::TYPE_SAVED) {
|
2021-10-13 20:28:20 +01:00
|
|
|
if (ob.dropped) {
|
2021-12-13 21:38:10 +00:00
|
|
|
ERR_PRINT(String("Could not save block {0}").format(varray(ob.position)));
|
2021-10-13 20:28:20 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
CRASH_COND(ob.type != VoxelServer::BlockDataOutput::TYPE_LOADED &&
|
|
|
|
ob.type != VoxelServer::BlockDataOutput::TYPE_GENERATED);
|
2019-08-25 15:14:10 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
const Vector3i block_pos = ob.position;
|
2019-08-25 15:14:10 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
if (ob.dropped) {
|
|
|
|
// That block was cancelled by the server, but we are still expecting it.
|
|
|
|
// We'll have to request it again.
|
|
|
|
PRINT_VERBOSE(String("Received a block loading drop while we were still expecting it: "
|
|
|
|
"lod{0} ({1}, {2}, {3}), re-requesting it")
|
|
|
|
.format(varray(ob.lod, ob.position.x, ob.position.y, ob.position.z)));
|
|
|
|
++_stats.dropped_block_loads;
|
2020-09-06 23:57:41 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
_blocks_pending_load.push_back(ob.position);
|
|
|
|
return;
|
|
|
|
}
|
2020-08-25 23:00:38 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
LoadingBlock loading_block;
|
|
|
|
{
|
|
|
|
LoadingBlock *loading_block_ptr = _loading_blocks.getptr(block_pos);
|
|
|
|
|
|
|
|
if (loading_block_ptr == nullptr) {
|
|
|
|
// That block was not requested or is no longer needed, drop it.
|
|
|
|
++_stats.dropped_block_loads;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using move semantics because it can contain an allocated vector
|
|
|
|
loading_block = std::move(*loading_block_ptr);
|
|
|
|
}
|
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
// Now we got the block. If we still have to drop it, the cause will be an error.
|
|
|
|
_loading_blocks.erase(block_pos);
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
CRASH_COND(ob.voxels == nullptr);
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.get_block_size());
|
2021-10-13 20:28:20 +01:00
|
|
|
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. "
|
|
|
|
"Expected {0}, got {1}")
|
2021-12-13 21:38:10 +00:00
|
|
|
.format(varray(expected_block_size, ob.voxels->get_size())));
|
2021-10-13 20:28:20 +01:00
|
|
|
++_stats.dropped_block_loads;
|
|
|
|
return;
|
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
// 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);
|
2021-04-13 23:48:35 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
block->set_edited(ob.type == VoxelServer::BlockDataOutput::TYPE_LOADED);
|
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
if (was_not_loaded) {
|
|
|
|
// Set viewers count that are currently expecting the block
|
|
|
|
block->viewers = loading_block.viewers;
|
|
|
|
}
|
2020-08-25 23:00:38 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
emit_data_block_loaded(block);
|
2020-09-06 23:57:41 +01:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
for (unsigned int i = 0; i < loading_block.viewers_to_notify.size(); ++i) {
|
|
|
|
const uint32_t viewer_id = loading_block.viewers_to_notify[i];
|
|
|
|
notify_data_block_enter(*block, viewer_id);
|
|
|
|
}
|
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
// The block itself might not be suitable for meshing yet, but blocks surrounding it might be now
|
|
|
|
{
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
try_schedule_mesh_update_from_data(
|
2021-12-13 21:38:10 +00:00
|
|
|
Box3i(_data_map.block_to_voxel(block_pos), Vector3iUtil::create(get_data_block_size())));
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2021-10-13 20:28:20 +01:00
|
|
|
// We might have requested some blocks again (if we got a dropped one while we still need them)
|
2022-01-31 21:23:39 +00:00
|
|
|
// if (stream_enabled) {
|
|
|
|
// send_block_data_requests();
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets voxel data of a block, discarding existing data if any.
|
|
|
|
// If the given block coordinates are not inside any viewer's area, this function won't do anything and return false.
|
|
|
|
// If a block is already loading or generating at this position, it will be cancelled.
|
|
|
|
bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBufferInternal> &voxel_data) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
ERR_FAIL_COND_V(voxel_data == nullptr, false);
|
|
|
|
|
|
|
|
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.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}")
|
|
|
|
.format(varray(expected_block_size, voxel_data->get_size())));
|
|
|
|
|
|
|
|
// Setup viewers count intersecting with this block
|
2022-01-31 21:29:08 +00:00
|
|
|
RefCount refcount;
|
2022-01-31 21:23:39 +00:00
|
|
|
for (unsigned int i = 0; i < _paired_viewers.size(); ++i) {
|
|
|
|
const PairedViewer &viewer = _paired_viewers[i];
|
|
|
|
if (viewer.state.data_box.contains(position)) {
|
|
|
|
refcount.add();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (refcount.get() == 0) {
|
|
|
|
// Actually, this block is not even in range. So we may ignore it.
|
|
|
|
// If we don't want this behavior, we could introduce a fake viewer that adds a reference to all blocks in this
|
|
|
|
// volume as long as it is enabled?
|
|
|
|
return false;
|
2021-10-13 20:28:20 +01:00
|
|
|
}
|
2022-01-31 21:23:39 +00:00
|
|
|
|
|
|
|
// Cancel loading version if any
|
|
|
|
_loading_blocks.erase(position);
|
|
|
|
|
|
|
|
// Create or update block data
|
|
|
|
VoxelDataBlock *block = _data_map.set_block_buffer(position, voxel_data);
|
|
|
|
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())));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::has_block(Vector3i position) const {
|
|
|
|
return _data_map.has_block(position);
|
2021-09-09 18:54:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::process_meshing() {
|
2022-01-09 22:13:10 +00:00
|
|
|
ProfilingClock profiling_clock;
|
2021-09-09 18:54:15 +01:00
|
|
|
|
|
|
|
_stats.dropped_block_meshs = 0;
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Send mesh updates
|
|
|
|
{
|
2020-08-30 03:59:02 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
//const int used_channels_mask = get_used_channels_mask();
|
2021-04-13 23:48:35 +01:00
|
|
|
const int mesh_to_data_factor = get_mesh_block_size() / get_data_block_size();
|
2021-01-17 17:18:05 +00:00
|
|
|
|
2020-09-09 18:58:58 +01:00
|
|
|
for (size_t bi = 0; bi < _blocks_pending_update.size(); ++bi) {
|
2021-04-13 23:48:35 +01:00
|
|
|
const Vector3i mesh_block_pos = _blocks_pending_update[bi];
|
2017-03-29 22:36:42 +02:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelMeshBlock *mesh_block = _mesh_map.get_block(mesh_block_pos);
|
2019-08-25 15:14:10 +01:00
|
|
|
|
|
|
|
// If we got here, it must have been because of scheduling an update
|
2021-04-13 23:48:35 +01:00
|
|
|
ERR_CONTINUE(mesh_block == nullptr);
|
|
|
|
ERR_CONTINUE(mesh_block->get_mesh_state() != VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
2019-04-28 01:32:23 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
// Pad by 1 because meshing requires neighbors
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i data_box =
|
2021-12-13 21:38:10 +00:00
|
|
|
Box3i(mesh_block_pos * mesh_to_data_factor, Vector3iUtil::create(mesh_to_data_factor)).padded(1);
|
2021-04-13 23:48:35 +01:00
|
|
|
|
2021-06-13 19:00:47 +01:00
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelServer::BlockMeshInput mesh_request;
|
|
|
|
mesh_request.render_block_position = mesh_block_pos;
|
|
|
|
mesh_request.lod = 0;
|
|
|
|
//mesh_request.data_blocks_count = data_box.size.volume();
|
|
|
|
|
|
|
|
// This iteration order is specifically chosen to match VoxelServer and threaded access
|
|
|
|
data_box.for_each_cell_zxy([this, &mesh_request](Vector3i data_block_pos) {
|
|
|
|
VoxelDataBlock *data_block = _data_map.get_block(data_block_pos);
|
|
|
|
if (data_block != nullptr) {
|
2021-09-26 04:14:50 +01:00
|
|
|
mesh_request.data_blocks[mesh_request.data_blocks_count] = data_block->get_voxels_shared();
|
2021-04-13 23:48:35 +01:00
|
|
|
}
|
|
|
|
++mesh_request.data_blocks_count;
|
|
|
|
});
|
|
|
|
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
{
|
|
|
|
unsigned int count = 0;
|
|
|
|
for (unsigned int i = 0; i < mesh_request.data_blocks_count; ++i) {
|
2021-09-26 04:14:50 +01:00
|
|
|
if (mesh_request.data_blocks[i] != nullptr) {
|
2021-04-13 23:48:35 +01:00
|
|
|
++count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Blocks that were in the list must have been scheduled because we have data for them!
|
|
|
|
ERR_CONTINUE(count == 0);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//print_line(String("DDD request {0}").format(varray(mesh_request.render_block_position.to_vec3())));
|
2020-09-09 18:58:58 +01:00
|
|
|
VoxelServer::get_singleton()->request_block_mesh(_volume_id, mesh_request);
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
mesh_block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_SENT);
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-04-01 20:10:38 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
_blocks_pending_update.clear();
|
|
|
|
}
|
2017-01-05 02:39:40 +01:00
|
|
|
|
2019-08-25 18:47:43 +01:00
|
|
|
_stats.time_request_blocks_to_update = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
//print_line(String("d:") + String::num(_dirty_blocks.size()) + String(", q:") +
|
|
|
|
//String::num(_block_update_queue.size()));
|
2021-09-25 00:02:41 +01:00
|
|
|
}
|
2020-08-30 03:59:02 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
void VoxelTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
//print_line(String("DDD receive {0}").format(varray(ob.position.to_vec3())));
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
VoxelMeshBlock *block = _mesh_map.get_block(ob.position);
|
|
|
|
if (block == nullptr) {
|
|
|
|
//print_line("- no longer loaded");
|
|
|
|
// That block is no longer loaded, drop the result
|
|
|
|
++_stats.dropped_block_meshs;
|
|
|
|
return;
|
|
|
|
}
|
2019-08-25 22:59:55 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
if (ob.type == VoxelServer::BlockMeshOutput::TYPE_DROPPED) {
|
|
|
|
// That block is loaded, but its meshing request was dropped.
|
|
|
|
// TODO Not sure what to do in this case, the code sending update queries has to be tweaked
|
|
|
|
PRINT_VERBOSE("Received a block mesh drop while we were still expecting it");
|
|
|
|
++_stats.dropped_block_meshs;
|
|
|
|
return;
|
|
|
|
}
|
2019-08-26 14:00:48 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
Ref<ArrayMesh> mesh;
|
2021-12-13 21:38:10 +00:00
|
|
|
mesh.instantiate();
|
2017-04-06 23:44:11 +02:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
Vector<Array> collidable_surfaces; //need to put both blocky and smooth surfaces into one list
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
int surface_index = 0;
|
|
|
|
for (int i = 0; i < ob.surfaces.surfaces.size(); ++i) {
|
|
|
|
Array surface = ob.surfaces.surfaces[i];
|
2021-12-13 21:38:10 +00:00
|
|
|
if (surface.is_empty()) {
|
2021-09-25 00:02:41 +01:00
|
|
|
continue;
|
|
|
|
}
|
2020-09-06 19:01:12 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
|
|
|
if (!is_surface_triangulated(surface)) {
|
|
|
|
continue;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-04-06 23:44:11 +02:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
collidable_surfaces.push_back(surface);
|
2020-12-25 17:08:40 +00:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
mesh->add_surface_from_arrays(
|
2021-12-13 21:38:10 +00:00
|
|
|
ob.surfaces.primitive_type, surface, Array(), Dictionary(), ob.surfaces.mesh_flags);
|
2021-09-25 00:02:41 +01:00
|
|
|
mesh->surface_set_material(surface_index, _materials[i]);
|
|
|
|
++surface_index;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
if (is_mesh_empty(mesh)) {
|
|
|
|
mesh = Ref<Mesh>();
|
|
|
|
collidable_surfaces.clear();
|
|
|
|
}
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2021-09-25 00:02:41 +01:00
|
|
|
const bool gen_collisions = _generate_collisions && block->collision_viewers.get() > 0;
|
|
|
|
|
2021-12-16 00:11:11 +00:00
|
|
|
block->set_mesh(mesh, DirectMeshInstance::GIMode(get_gi_mode()));
|
2021-09-25 00:02:41 +01:00
|
|
|
if (gen_collisions) {
|
2021-12-13 21:38:10 +00:00
|
|
|
block->set_collision_mesh(
|
|
|
|
collidable_surfaces, get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
|
2021-09-25 00:02:41 +01:00
|
|
|
block->set_collision_layer(_collision_layer);
|
|
|
|
block->set_collision_mask(_collision_mask);
|
|
|
|
}
|
|
|
|
block->set_visible(true);
|
|
|
|
block->set_parent_visible(is_visible());
|
|
|
|
block->set_parent_transform(get_global_transform());
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
|
|
|
|
2019-09-08 19:42:25 +01:00
|
|
|
Ref<VoxelTool> VoxelTerrain::get_voxel_tool() {
|
2020-11-21 18:28:06 +00:00
|
|
|
Ref<VoxelTool> vt = memnew(VoxelToolTerrain(this));
|
2021-01-17 17:18:05 +00:00
|
|
|
const int used_channels_mask = get_used_channels_mask();
|
|
|
|
// Auto-pick first used channel
|
2021-09-26 04:14:50 +01:00
|
|
|
for (int channel = 0; channel < VoxelBufferInternal::MAX_CHANNELS; ++channel) {
|
2021-01-17 17:18:05 +00:00
|
|
|
if ((used_channels_mask & (1 << channel)) != 0) {
|
|
|
|
vt->set_channel(channel);
|
2021-07-10 20:27:55 +01:00
|
|
|
break;
|
2020-02-15 03:12:13 +08:00
|
|
|
}
|
2020-02-13 00:48:18 +08:00
|
|
|
}
|
|
|
|
return vt;
|
2018-10-01 20:48:47 +01:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
void VoxelTerrain::set_run_stream_in_editor(bool enable) {
|
|
|
|
if (enable == _run_stream_in_editor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_run_stream_in_editor = enable;
|
|
|
|
|
|
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
|
if (_run_stream_in_editor) {
|
|
|
|
_on_stream_params_changed();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// This is expected to block the main thread until the streaming thread is done.
|
|
|
|
stop_streamer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::is_stream_running_in_editor() const {
|
|
|
|
return _run_stream_in_editor;
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:10:54 +01:00
|
|
|
void VoxelTerrain::set_bounds(Box3i box) {
|
2022-01-09 03:06:58 +00:00
|
|
|
_bounds_in_voxels =
|
|
|
|
box.clipped(Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)));
|
2020-10-22 22:43:31 +01:00
|
|
|
|
|
|
|
// Round to block size
|
2021-04-13 23:48:35 +01:00
|
|
|
_bounds_in_voxels = _bounds_in_voxels.snapped(get_data_block_size());
|
2020-10-22 22:43:31 +01:00
|
|
|
|
2022-01-03 23:14:18 +00:00
|
|
|
const unsigned int largest_dimension =
|
|
|
|
static_cast<unsigned int>(math::max(math::max(box.size.x, box.size.y), box.size.z));
|
2020-09-13 22:36:28 +01:00
|
|
|
if (largest_dimension > MAX_VIEW_DISTANCE_FOR_LARGE_VOLUME) {
|
|
|
|
// Cap view distance to make sure you don't accidentally blow up memory when changing parameters
|
2021-04-13 23:48:35 +01:00
|
|
|
if (_max_view_distance_voxels > MAX_VIEW_DISTANCE_FOR_LARGE_VOLUME) {
|
2022-01-03 23:14:18 +00:00
|
|
|
_max_view_distance_voxels = math::min(_max_view_distance_voxels, MAX_VIEW_DISTANCE_FOR_LARGE_VOLUME);
|
2021-12-13 21:38:10 +00:00
|
|
|
notify_property_list_changed();
|
2020-09-13 22:36:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO Editor gizmo bounds
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:10:54 +01:00
|
|
|
Box3i VoxelTerrain::get_bounds() const {
|
2020-09-13 22:36:28 +01:00
|
|
|
return _bounds_in_voxels;
|
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
Vector3i VoxelTerrain::_b_voxel_to_data_block(Vector3 pos) const {
|
|
|
|
return _data_map.voxel_to_block(Vector3iUtil::from_floored(pos));
|
2017-08-20 15:17:54 +02:00
|
|
|
}
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
Vector3i VoxelTerrain::_b_data_block_to_voxel(Vector3i pos) const {
|
|
|
|
return _data_map.block_to_voxel(pos);
|
2017-08-20 15:17:54 +02:00
|
|
|
}
|
|
|
|
|
2020-07-25 16:29:16 +01:00
|
|
|
void VoxelTerrain::_b_save_modified_blocks() {
|
|
|
|
save_all_modified_blocks(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Explicitely ask to save a block if it was modified
|
2021-12-13 21:38:10 +00:00
|
|
|
void VoxelTerrain::_b_save_block(Vector3i p_block_pos) {
|
|
|
|
const Vector3i block_pos(Vector3iUtil::from_floored(p_block_pos));
|
2020-07-25 16:29:16 +01:00
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
VoxelDataBlock *block = _data_map.get_block(block_pos);
|
2020-07-25 16:29:16 +01:00
|
|
|
ERR_FAIL_COND(block == nullptr);
|
|
|
|
|
|
|
|
if (!block->is_modified()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-25 21:57:36 +01:00
|
|
|
ScheduleSaveAction{ _blocks_to_save, true }(block);
|
2020-07-25 16:29:16 +01:00
|
|
|
}
|
|
|
|
|
2020-09-13 22:36:28 +01:00
|
|
|
void VoxelTerrain::_b_set_bounds(AABB aabb) {
|
2022-01-03 23:14:18 +00:00
|
|
|
ERR_FAIL_COND(!math::is_valid_size(aabb.size));
|
2021-12-13 21:38:10 +00:00
|
|
|
set_bounds(Box3i(Vector3iUtil::from_rounded(aabb.position), Vector3iUtil::from_rounded(aabb.size)));
|
2020-09-13 22:36:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
AABB VoxelTerrain::_b_get_bounds() const {
|
2021-05-31 17:10:54 +01:00
|
|
|
const Box3i b = get_bounds();
|
2021-12-13 21:38:10 +00:00
|
|
|
return AABB(b.pos, b.size);
|
2020-09-13 22:36:28 +01:00
|
|
|
}
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
bool VoxelTerrain::_b_try_set_block_data(Vector3i position, Ref<VoxelBuffer> voxel_data) {
|
|
|
|
ERR_FAIL_COND_V(voxel_data.is_null(), false);
|
2022-02-05 17:12:31 +00:00
|
|
|
std::shared_ptr<VoxelBufferInternal> buffer = voxel_data->get_buffer_shared();
|
|
|
|
return try_set_block_data(position, buffer);
|
2022-01-31 21:23:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PackedInt32Array VoxelTerrain::_b_get_viewer_network_peer_ids_in_area(Vector3i area_origin, Vector3i area_size) const {
|
|
|
|
static thread_local std::vector<int> s_ids;
|
|
|
|
std::vector<int> &viewer_ids = s_ids;
|
|
|
|
viewer_ids.clear();
|
|
|
|
get_viewers_in_area(viewer_ids, Box3i(area_origin, area_size));
|
|
|
|
|
|
|
|
PackedInt32Array peer_ids;
|
|
|
|
peer_ids.resize(viewer_ids.size());
|
|
|
|
for (size_t i = 0; i < viewer_ids.size(); ++i) {
|
|
|
|
const int peer_id = VoxelServer::get_singleton()->get_viewer_network_peer_id(viewer_ids[i]);
|
|
|
|
peer_ids.write[i] = peer_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
return peer_ids;
|
|
|
|
}
|
|
|
|
|
2020-07-25 16:29:16 +01:00
|
|
|
void VoxelTerrain::_bind_methods() {
|
2019-07-04 00:58:39 +09:00
|
|
|
ClassDB::bind_method(D_METHOD("set_material", "id", "material"), &VoxelTerrain::set_material);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_material", "id"), &VoxelTerrain::get_material);
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_max_view_distance", "distance_in_voxels"), &VoxelTerrain::set_max_view_distance);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_max_view_distance"), &VoxelTerrain::get_max_view_distance);
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("set_block_enter_notification_enabled", "enabled"),
|
|
|
|
&VoxelTerrain::set_block_enter_notification_enabled);
|
|
|
|
ClassDB::bind_method(
|
|
|
|
D_METHOD("is_block_enter_notification_enabled"), &VoxelTerrain::is_block_enter_notification_enabled);
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("set_area_edit_notification_enabled", "enabled"),
|
|
|
|
&VoxelTerrain::set_area_edit_notification_enabled);
|
|
|
|
ClassDB::bind_method(
|
|
|
|
D_METHOD("is_area_edit_notification_enabled"), &VoxelTerrain::is_area_edit_notification_enabled);
|
|
|
|
|
2017-03-26 18:09:37 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelTerrain::get_generate_collisions);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelTerrain::set_generate_collisions);
|
|
|
|
|
2021-05-09 20:49:45 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_layer"), &VoxelTerrain::get_collision_layer);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &VoxelTerrain::set_collision_layer);
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_mask"), &VoxelTerrain::get_collision_mask);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &VoxelTerrain::set_collision_mask);
|
|
|
|
|
2021-07-10 22:14:17 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_margin"), &VoxelTerrain::get_collision_margin);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_margin", "margin"), &VoxelTerrain::set_collision_margin);
|
|
|
|
|
2021-04-13 23:48:35 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("voxel_to_data_block", "voxel_pos"), &VoxelTerrain::_b_voxel_to_data_block);
|
|
|
|
ClassDB::bind_method(D_METHOD("data_block_to_voxel", "block_pos"), &VoxelTerrain::_b_data_block_to_voxel);
|
2017-03-26 20:07:01 +02:00
|
|
|
|
2021-06-16 23:30:09 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_data_block_size"), &VoxelTerrain::get_data_block_size);
|
|
|
|
|
2021-04-15 20:00:41 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_mesh_block_size"), &VoxelTerrain::get_mesh_block_size);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_mesh_block_size", "size"), &VoxelTerrain::set_mesh_block_size);
|
|
|
|
|
2020-12-25 17:08:40 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelTerrain::_b_get_statistics);
|
2019-09-08 19:42:25 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_voxel_tool"), &VoxelTerrain::get_voxel_tool);
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2020-07-25 16:29:16 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelTerrain::_b_save_modified_blocks);
|
2020-09-13 22:36:28 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("save_block", "position"), &VoxelTerrain::_b_save_block);
|
2020-07-25 16:29:16 +01:00
|
|
|
|
2020-09-13 22:36:28 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_run_stream_in_editor", "enable"), &VoxelTerrain::set_run_stream_in_editor);
|
2020-08-14 20:33:09 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("is_stream_running_in_editor"), &VoxelTerrain::is_stream_running_in_editor);
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
ClassDB::bind_method(
|
|
|
|
D_METHOD("set_automatic_loading_enabled", "enable"), &VoxelTerrain::set_automatic_loading_enabled);
|
|
|
|
ClassDB::bind_method(D_METHOD("is_automatic_loading_enabled"), &VoxelTerrain::is_automatic_loading_enabled);
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
// TODO Rename `_voxel_bounds`
|
2020-09-13 22:36:28 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_bounds"), &VoxelTerrain::_b_set_bounds);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_bounds"), &VoxelTerrain::_b_get_bounds);
|
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("try_set_block_data", "position", "voxels", "replace_if_exists"),
|
|
|
|
&VoxelTerrain::_b_try_set_block_data);
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_viewer_network_peer_ids_in_area", "area_origin", "area_size"),
|
|
|
|
&VoxelTerrain::_b_get_viewer_network_peer_ids_in_area);
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("has_block", "block_position"), &VoxelTerrain::has_block);
|
|
|
|
|
|
|
|
GDVIRTUAL_BIND(_on_data_block_enter, "info");
|
|
|
|
GDVIRTUAL_BIND(_on_area_edited, "area_origin", "area_size");
|
2019-08-24 01:44:27 +01:00
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Bounds", "");
|
|
|
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::AABB, "bounds"), "set_bounds", "get_bounds");
|
2020-09-06 19:01:12 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_view_distance"), "set_max_view_distance", "get_max_view_distance");
|
2021-03-27 16:38:52 +00:00
|
|
|
|
|
|
|
ADD_GROUP("Collisions", "");
|
|
|
|
|
2021-12-13 21:38:10 +00:00
|
|
|
ADD_PROPERTY(
|
|
|
|
PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer",
|
|
|
|
"get_collision_layer");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask",
|
|
|
|
"get_collision_mask");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_margin"), "set_collision_margin", "get_collision_margin");
|
2021-03-27 16:38:52 +00:00
|
|
|
|
2022-01-31 21:23:39 +00:00
|
|
|
ADD_GROUP("Networking", "");
|
|
|
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "block_enter_notification_enabled"),
|
|
|
|
"set_block_enter_notification_enabled", "is_block_enter_notification_enabled");
|
|
|
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "area_edit_notification_enabled"), "set_area_edit_notification_enabled",
|
|
|
|
"is_area_edit_notification_enabled");
|
|
|
|
|
|
|
|
// This may be set to false in multiplayer designs where the server is the one sending the blocks
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "automatic_loading_enabled"), "set_automatic_loading_enabled",
|
|
|
|
"is_automatic_loading_enabled");
|
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Advanced", "");
|
|
|
|
|
|
|
|
// TODO Should probably be in the parent class?
|
2021-12-13 21:38:10 +00:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "run_stream_in_editor"), "set_run_stream_in_editor",
|
|
|
|
"is_stream_running_in_editor");
|
2021-04-15 20:00:41 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_block_size"), "set_mesh_block_size", "get_mesh_block_size");
|
2020-08-10 20:57:03 +01:00
|
|
|
|
2020-08-29 22:09:54 +01:00
|
|
|
// TODO Add back access to block, but with an API securing multithreaded access
|
2021-12-13 21:38:10 +00:00
|
|
|
ADD_SIGNAL(MethodInfo(VoxelStringNames::get_singleton()->block_loaded, PropertyInfo(Variant::VECTOR3, "position")));
|
|
|
|
ADD_SIGNAL(
|
|
|
|
MethodInfo(VoxelStringNames::get_singleton()->block_unloaded, PropertyInfo(Variant::VECTOR3, "position")));
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|
2022-01-09 22:13:10 +00:00
|
|
|
|
|
|
|
} // namespace zylann::voxel
|