2019-05-04 00:02:10 +01:00
|
|
|
#include "voxel_lod_terrain.h"
|
2021-02-21 23:58:00 +00:00
|
|
|
#include "../constants/voxel_string_names.h"
|
2020-01-26 20:29:44 +00:00
|
|
|
#include "../edition/voxel_tool_lod_terrain.h"
|
2020-12-18 21:01:50 +00:00
|
|
|
#include "../meshers/transvoxel/voxel_mesher_transvoxel.h"
|
2020-10-25 21:21:37 +00:00
|
|
|
#include "../server/voxel_server.h"
|
2021-02-17 20:34:35 +00:00
|
|
|
#include "../util/funcs.h"
|
|
|
|
#include "../util/godot/funcs.h"
|
2020-07-08 20:48:52 +01:00
|
|
|
#include "../util/macros.h"
|
2021-02-17 20:34:35 +00:00
|
|
|
#include "../util/math/rect3i.h"
|
2020-08-29 23:20:51 +01:00
|
|
|
#include "../util/profiling.h"
|
2019-05-05 01:09:12 +01:00
|
|
|
#include "../util/profiling_clock.h"
|
2021-02-07 21:40:55 +00:00
|
|
|
#include "instancing/voxel_instancer.h"
|
2020-01-17 20:43:28 +00:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
#include <core/core_string_names.h>
|
2019-05-04 00:02:10 +01:00
|
|
|
#include <core/engine.h>
|
2020-11-21 18:31:28 +00:00
|
|
|
#include <scene/3d/mesh_instance.h>
|
|
|
|
#include <scene/resources/packed_scene.h>
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
Ref<ArrayMesh> build_mesh(const Vector<Array> surfaces, Mesh::PrimitiveType primitive, int compression_flags,
|
2020-05-07 13:55:16 -07:00
|
|
|
Ref<Material> material) {
|
2019-12-31 16:48:46 +00:00
|
|
|
|
|
|
|
Ref<ArrayMesh> mesh;
|
|
|
|
mesh.instance();
|
|
|
|
|
|
|
|
unsigned int surface_index = 0;
|
|
|
|
for (int i = 0; i < surfaces.size(); ++i) {
|
|
|
|
Array surface = surfaces[i];
|
2020-10-25 21:21:37 +00:00
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
if (surface.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
|
|
|
if (!is_surface_triangulated(surface)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
mesh->add_surface_from_arrays(primitive, surface, Array(), compression_flags);
|
|
|
|
mesh->surface_set_material(surface_index, material);
|
|
|
|
// No multi-material supported yet
|
|
|
|
++surface_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_mesh_empty(mesh)) {
|
|
|
|
mesh = Ref<Mesh>();
|
|
|
|
}
|
|
|
|
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
// To use on loaded blocks
|
2021-04-03 20:39:37 +01:00
|
|
|
static inline void schedule_mesh_update(VoxelMeshBlock *block, std::vector<Vector3i> &blocks_pending_update) {
|
|
|
|
if (block->get_mesh_state() != VoxelMeshBlock::MESH_UPDATE_NOT_SENT) {
|
2021-03-02 22:49:42 +00:00
|
|
|
if (block->active) {
|
2020-12-18 21:01:50 +00:00
|
|
|
// Schedule an update
|
2021-04-03 20:39:37 +01:00
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
2020-12-18 21:01:50 +00:00
|
|
|
blocks_pending_update.push_back(block->position);
|
|
|
|
} else {
|
|
|
|
// Just mark it as needing update, so the visibility system will schedule its update when needed
|
2021-04-03 20:39:37 +01:00
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_NEED_UPDATE);
|
2020-12-18 21:01:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
struct BeforeUnloadDataAction {
|
2021-01-22 18:31:46 +00:00
|
|
|
std::vector<VoxelLodTerrain::BlockToSave> &blocks_to_save;
|
2021-01-23 23:01:02 +00:00
|
|
|
bool save;
|
2020-12-29 22:10:31 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void operator()(VoxelDataBlock *block) {
|
2020-12-29 22:10:31 +00:00
|
|
|
// Save if modified
|
|
|
|
// TODO Don't ask for save if the stream doesn't support it!
|
2021-01-23 23:01:02 +00:00
|
|
|
if (save && block->is_modified()) {
|
2020-12-29 22:10:31 +00:00
|
|
|
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
|
|
|
VoxelLodTerrain::BlockToSave b;
|
|
|
|
// We don't copy since the block will be unloaded anyways
|
|
|
|
b.voxels = block->voxels;
|
|
|
|
b.position = block->position;
|
|
|
|
b.lod = block->lod_index;
|
|
|
|
blocks_to_save.push_back(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
struct BeforeUnloadMeshAction {
|
|
|
|
std::vector<Ref<ShaderMaterial> > &shader_material_pool;
|
|
|
|
|
|
|
|
void operator()(VoxelMeshBlock *block) {
|
|
|
|
// Recycle material
|
|
|
|
Ref<ShaderMaterial> sm = block->get_shader_material();
|
|
|
|
if (sm.is_valid()) {
|
|
|
|
shader_material_pool.push_back(sm);
|
|
|
|
block->set_shader_material(Ref<ShaderMaterial>());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-12-29 22:10:31 +00:00
|
|
|
struct ScheduleSaveAction {
|
2021-01-22 18:31:46 +00:00
|
|
|
std::vector<VoxelLodTerrain::BlockToSave> &blocks_to_save;
|
2020-12-29 22:10:31 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void operator()(VoxelDataBlock *block) {
|
2020-12-29 22:10:31 +00:00
|
|
|
// Save if modified
|
|
|
|
// TODO Don't ask for save if the stream doesn't support it!
|
|
|
|
if (block->is_modified()) {
|
|
|
|
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
|
|
|
VoxelLodTerrain::BlockToSave b;
|
|
|
|
|
|
|
|
RWLockRead lock(block->voxels->get_lock());
|
|
|
|
b.voxels = block->voxels->duplicate(true);
|
|
|
|
|
|
|
|
b.position = block->position;
|
|
|
|
b.lod = block->lod_index;
|
|
|
|
blocks_to_save.push_back(b);
|
|
|
|
block->set_modified(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
static inline uint64_t get_ticks_msec() {
|
|
|
|
return OS::get_singleton()->get_ticks_msec();
|
|
|
|
}
|
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
} // namespace
|
|
|
|
|
2020-01-07 20:32:36 +00:00
|
|
|
VoxelLodTerrain::VoxelLodTerrain() {
|
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.
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-07-08 20:48:52 +01:00
|
|
|
PRINT_VERBOSE("Construct VoxelLodTerrain");
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
set_notify_transform(true);
|
|
|
|
|
2020-10-30 19:02:00 +00:00
|
|
|
// Doing this to setup the defaults
|
|
|
|
set_process_mode(_process_mode);
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
// Infinite by default
|
|
|
|
_bounds_in_voxels = Rect3i::from_center_extents(Vector3i(0), Vector3i(VoxelConstants::MAX_VOLUME_EXTENT));
|
|
|
|
|
2020-10-20 23:58:55 +01:00
|
|
|
_volume_id = VoxelServer::get_singleton()->add_volume(&_reception_buffers, VoxelServer::VOLUME_SPARSE_OCTREE);
|
2021-03-27 16:38:20 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_octree_lod_distance(_volume_id, get_lod_distance());
|
2020-08-31 21:51:30 +01:00
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
// TODO Being able to set a LOD smaller than the stream is probably a bad idea,
|
|
|
|
// Because it prevents edits from propagating up to the last one, they will be left out of sync
|
2019-08-29 22:55:02 +01:00
|
|
|
set_lod_count(4);
|
2021-03-27 16:38:20 +00:00
|
|
|
|
|
|
|
set_lod_distance(48.f);
|
2020-12-18 21:01:50 +00:00
|
|
|
|
|
|
|
// For ease of use in editor
|
|
|
|
Ref<VoxelMesherTransvoxel> default_mesher;
|
|
|
|
default_mesher.instance();
|
|
|
|
_mesher = default_mesher;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
VoxelLodTerrain::~VoxelLodTerrain() {
|
2020-07-08 20:48:52 +01:00
|
|
|
PRINT_VERBOSE("Destroy VoxelLodTerrain");
|
2020-08-31 21:51:30 +01:00
|
|
|
VoxelServer::get_singleton()->remove_volume(_volume_id);
|
2020-12-29 22:25:22 +00:00
|
|
|
// Instancer can take care of itself
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Ref<Material> VoxelLodTerrain::get_material() const {
|
|
|
|
return _material;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::set_material(Ref<Material> p_material) {
|
|
|
|
_material = p_material;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
unsigned int VoxelLodTerrain::get_data_block_size() const {
|
|
|
|
return _lods[0].data_map.get_block_size();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int VoxelLodTerrain::get_data_block_size_pow2() const {
|
|
|
|
return _lods[0].data_map.get_block_size_pow2();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int VoxelLodTerrain::get_mesh_block_size_pow2() const {
|
|
|
|
return _lods[0].mesh_map.get_block_size_pow2();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
unsigned int VoxelLodTerrain::get_mesh_block_size() const {
|
|
|
|
return _lods[0].mesh_map.get_block_size();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
void VoxelLodTerrain::set_stream(Ref<VoxelStream> p_stream) {
|
2019-08-24 01:44:27 +01:00
|
|
|
if (p_stream == _stream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_stream = p_stream;
|
|
|
|
|
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;
|
|
|
|
_change_notify();
|
|
|
|
}
|
|
|
|
}
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
2020-08-14 20:33:09 +01:00
|
|
|
#endif
|
2019-08-24 01:44:27 +01:00
|
|
|
|
|
|
|
_on_stream_params_changed();
|
|
|
|
}
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
Ref<VoxelStream> VoxelLodTerrain::get_stream() const {
|
|
|
|
return _stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::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;
|
|
|
|
_change_notify();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
_on_stream_params_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ref<VoxelGenerator> VoxelLodTerrain::get_generator() const {
|
|
|
|
return _generator;
|
2020-12-18 21:01:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::set_mesher(Ref<VoxelMesher> p_mesher) {
|
|
|
|
if (_mesher == p_mesher) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stop_updater();
|
|
|
|
|
|
|
|
_mesher = p_mesher;
|
|
|
|
|
|
|
|
if (_mesher.is_valid()) {
|
|
|
|
start_updater();
|
|
|
|
remesh_all_blocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
update_configuration_warning();
|
|
|
|
}
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
Ref<VoxelMesher> VoxelLodTerrain::get_mesher() const {
|
|
|
|
return _mesher;
|
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::_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-02-07 17:04:22 +00:00
|
|
|
// TODO We have to figure out streams that have a LOD requirement
|
|
|
|
// const int stream_lod_count = _stream->get_lod_count();
|
|
|
|
// _set_lod_count(min(stream_lod_count, get_lod_count()));
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelServer::get_singleton()->set_volume_data_block_size(_volume_id, get_data_block_size());
|
|
|
|
VoxelServer::get_singleton()->set_volume_render_block_size(_volume_id, get_mesh_block_size());
|
2020-08-31 21:51:30 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
reset_maps();
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// The whole map might change, so make all area dirty
|
2019-09-03 22:54:40 +01:00
|
|
|
for (int i = 0; i < get_lod_count(); ++i) {
|
|
|
|
Lod &lod = _lods[i];
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.last_view_distance_data_blocks = 0;
|
|
|
|
lod.last_view_distance_mesh_blocks = 0;
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
2020-02-13 00:48:18 +08:00
|
|
|
|
|
|
|
update_configuration_warning();
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void VoxelLodTerrain::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);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
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
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
if (block_size_po2 == get_data_block_size_pow2()) {
|
2019-08-24 01:44:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
_on_stream_params_changed();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 23:51:11 +01:00
|
|
|
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);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset mesh maps
|
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
if (_instancer != nullptr) {
|
|
|
|
// Unload instances
|
|
|
|
VoxelInstancer *instancer = _instancer;
|
|
|
|
lod.mesh_map.for_all_blocks([lod_index, instancer](VoxelMeshBlock *block) {
|
|
|
|
instancer->on_mesh_block_exit(block->position, lod_index);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Unload mesh blocks
|
|
|
|
lod.mesh_map.for_all_blocks(BeforeUnloadMeshAction{ _shader_material_pool });
|
|
|
|
lod.mesh_map.create(po2, lod_index);
|
|
|
|
// Reset view distance cache so they will be re-entered
|
|
|
|
lod.last_view_distance_mesh_blocks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset LOD octrees
|
|
|
|
LodOctree::NoDestroyAction nda;
|
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
|
|
|
OctreeItem &item = E->value();
|
|
|
|
item.octree.create_from_lod_count(get_mesh_block_size(), _lod_count, nda);
|
|
|
|
}
|
|
|
|
|
|
|
|
VoxelServer::get_singleton()->set_volume_render_block_size(_volume_id, mesh_block_size);
|
2021-04-05 03:47:25 +01:00
|
|
|
|
|
|
|
// Update voxel bounds because block size change can affect octree size
|
|
|
|
set_voxel_bounds(_bounds_in_voxels);
|
2021-04-03 23:51:11 +01:00
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::_set_block_size_po2(int p_block_size_po2) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[0].data_map.create(p_block_size_po2, 0);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void VoxelLodTerrain::set_mesh_block_active(VoxelMeshBlock &block, bool active) {
|
2021-03-02 22:49:42 +00:00
|
|
|
if (block.active == active) {
|
|
|
|
return;
|
|
|
|
}
|
2021-03-12 23:35:51 +00:00
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
block.active = active;
|
2021-03-12 23:35:51 +00:00
|
|
|
|
|
|
|
if (_lod_fade_duration == 0.f) {
|
|
|
|
block.set_visible(active);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock::FadingState fading_state;
|
2021-03-12 23:35:51 +00:00
|
|
|
// Initial progress has to be set too because it sometimes happens that a LOD must appear before its parent
|
|
|
|
// finished fading in. So the parent will have to fade out from solid with the same duration.
|
|
|
|
float initial_progress;
|
2021-03-02 22:49:42 +00:00
|
|
|
if (active) {
|
|
|
|
block.set_visible(true);
|
2021-04-03 20:39:37 +01:00
|
|
|
fading_state = VoxelMeshBlock::FADING_IN;
|
2021-03-12 23:35:51 +00:00
|
|
|
initial_progress = 0.f;
|
2021-03-02 22:49:42 +00:00
|
|
|
} else {
|
2021-04-03 20:39:37 +01:00
|
|
|
fading_state = VoxelMeshBlock::FADING_OUT;
|
2021-03-12 23:35:51 +00:00
|
|
|
initial_progress = 1.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (block.fading_state != fading_state) {
|
2021-04-03 20:39:37 +01:00
|
|
|
if (block.fading_state == VoxelMeshBlock::FADING_NONE) {
|
2021-03-12 23:35:51 +00:00
|
|
|
Lod &lod = _lods[block.lod_index];
|
|
|
|
// Must not have duplicates
|
|
|
|
ERR_FAIL_COND(lod.fading_blocks.has(block.position));
|
|
|
|
lod.fading_blocks.insert(block.position, &block);
|
2021-03-02 22:49:42 +00:00
|
|
|
}
|
2021-03-12 23:35:51 +00:00
|
|
|
block.fading_state = fading_state;
|
|
|
|
block.fading_progress = initial_progress;
|
2021-03-02 22:49:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
// Marks intersecting blocks in the area as modified, updates LODs and schedules remeshing.
|
|
|
|
// The provided box must be at LOD0 coordinates.
|
|
|
|
void VoxelLodTerrain::post_edit_area(Rect3i p_box) {
|
2020-10-25 21:21:37 +00:00
|
|
|
const Rect3i box = p_box.padded(1);
|
2021-04-03 20:39:37 +01:00
|
|
|
const Rect3i bbox = box.downscaled(get_data_block_size());
|
2019-09-03 22:54:40 +01:00
|
|
|
|
|
|
|
bbox.for_each_cell([this](Vector3i block_pos_lod0) {
|
|
|
|
post_edit_block_lod0(block_pos_lod0);
|
|
|
|
});
|
2020-12-30 20:11:46 +00:00
|
|
|
|
|
|
|
if (_instancer != nullptr) {
|
|
|
|
_instancer->on_area_edited(p_box);
|
|
|
|
}
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::post_edit_block_lod0(Vector3i block_pos_lod0) {
|
|
|
|
Lod &lod0 = _lods[0];
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelDataBlock *block = lod0.data_map.get_block(block_pos_lod0);
|
2019-09-03 22:54:40 +01:00
|
|
|
ERR_FAIL_COND(block == nullptr);
|
|
|
|
|
2019-09-07 21:19:12 +01:00
|
|
|
block->set_modified(true);
|
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
if (!block->get_needs_lodding()) {
|
|
|
|
block->set_needs_lodding(true);
|
|
|
|
lod0.blocks_pending_lodding.push_back(block_pos_lod0);
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
Ref<VoxelTool> VoxelLodTerrain::get_voxel_tool() {
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelToolLodTerrain *vt = memnew(VoxelToolLodTerrain(this, _lods[0].data_map));
|
2019-09-06 23:24:56 +01:00
|
|
|
// Set to most commonly used channel on this kind of terrain
|
2020-01-02 21:03:44 +00:00
|
|
|
vt->set_channel(VoxelBuffer::CHANNEL_SDF);
|
2019-09-06 23:24:56 +01:00
|
|
|
return Ref<VoxelTool>(vt);
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
int VoxelLodTerrain::get_view_distance() const {
|
2019-09-02 21:32:05 +01:00
|
|
|
return _view_distance_voxels;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2020-10-29 22:58:49 +00:00
|
|
|
// TODO Needs to be clamped dynamically, to avoid the user accidentally setting blowing up memory.
|
|
|
|
// It used to be clamped to a hardcoded value, but now it may depend on LOD count and boundaries
|
2019-09-03 22:56:57 +01:00
|
|
|
void VoxelLodTerrain::set_view_distance(int p_distance_in_voxels) {
|
|
|
|
ERR_FAIL_COND(p_distance_in_voxels <= 0);
|
2019-08-29 22:55:02 +01:00
|
|
|
// Note: this is a hint distance, the terrain will attempt to have this radius filled with loaded voxels.
|
|
|
|
// It is possible for blocks to still load beyond that distance.
|
|
|
|
_view_distance_voxels = p_distance_in_voxels;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::start_updater() {
|
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 VoxelLodTerrain::stop_updater() {
|
|
|
|
struct ResetMeshStateAction {
|
2021-04-03 20:39:37 +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-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
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
_reception_buffers.mesh_output.clear();
|
2019-08-24 01:44:27 +01:00
|
|
|
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int i = 0; i < _lods.size(); ++i) {
|
2019-08-24 01:44:27 +01:00
|
|
|
Lod &lod = _lods[i];
|
|
|
|
lod.blocks_pending_update.clear();
|
|
|
|
|
2020-11-21 18:28:06 +00:00
|
|
|
ResetMeshStateAction a;
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.mesh_map.for_all_blocks(a);
|
2019-08-24 01:44:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::start_streamer() {
|
2020-08-31 21:51:30 +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
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::stop_streamer() {
|
2020-08-31 21:51:30 +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-24 01:44:27 +01:00
|
|
|
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int i = 0; i < _lods.size(); ++i) {
|
2019-08-24 01:44:27 +01:00
|
|
|
Lod &lod = _lods[i];
|
2020-08-30 21:38:38 +01:00
|
|
|
lod.loading_blocks.clear();
|
2019-08-24 01:44:27 +01:00
|
|
|
lod.blocks_to_load.clear();
|
|
|
|
}
|
2020-08-31 21:51:30 +01:00
|
|
|
|
|
|
|
_reception_buffers.data_output.clear();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
void VoxelLodTerrain::set_lod_distance(float p_lod_distance) {
|
|
|
|
if (p_lod_distance == _lod_distance) {
|
2019-08-29 22:55:02 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
_lod_distance = clamp(p_lod_distance, VoxelConstants::MINIMUM_LOD_DISTANCE, VoxelConstants::MAXIMUM_LOD_DISTANCE);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
|
|
|
OctreeItem &item = E->value();
|
2021-03-27 16:38:20 +00:00
|
|
|
item.octree.set_lod_distance(_lod_distance);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
// Because `set_lod_distance` may clamp it...
|
|
|
|
_lod_distance = item.octree.get_lod_distance();
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
2020-10-20 23:58:55 +01:00
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
VoxelServer::get_singleton()->set_volume_octree_lod_distance(_volume_id, get_lod_distance());
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
float VoxelLodTerrain::get_lod_distance() const {
|
|
|
|
return _lod_distance;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::set_lod_count(int p_lod_count) {
|
2020-02-13 21:49:11 +08:00
|
|
|
ERR_FAIL_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD);
|
2019-08-29 22:55:02 +01:00
|
|
|
ERR_FAIL_COND(p_lod_count < 1);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
|
|
|
if (get_lod_count() != p_lod_count) {
|
2019-08-24 01:44:27 +01:00
|
|
|
_set_lod_count(p_lod_count);
|
|
|
|
}
|
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::_set_lod_count(int p_lod_count) {
|
2020-02-13 21:49:11 +08:00
|
|
|
CRASH_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD);
|
2019-08-29 22:55:02 +01:00
|
|
|
CRASH_COND(p_lod_count < 1);
|
|
|
|
|
|
|
|
_lod_count = p_lod_count;
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
LodOctree::NoDestroyAction nda;
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
|
|
|
OctreeItem &item = E->value();
|
2021-04-03 20:39:37 +01:00
|
|
|
item.octree.create_from_lod_count(get_mesh_block_size(), p_lod_count, nda);
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-10-19 23:22:23 +01:00
|
|
|
// Not entirely required, but changing LOD count at runtime is rarely needed
|
2019-08-24 01:44:27 +01:00
|
|
|
reset_maps();
|
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-24 01:44:27 +01:00
|
|
|
void VoxelLodTerrain::reset_maps() {
|
2019-08-29 22:55:02 +01:00
|
|
|
// Clears all blocks and reconfigures maps to account for new LOD count and block sizes
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
// Don't reset while streaming, the result can be dirty?
|
|
|
|
//CRASH_COND(_stream_thread != nullptr);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
for (int lod_index = 0; lod_index < (int)_lods.size(); ++lod_index) {
|
2019-08-24 01:44:27 +01:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
// Instance new maps if we have more lods, or clear them otherwise
|
|
|
|
if (lod_index < get_lod_count()) {
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.data_map.create(lod.data_map.get_block_size_pow2(), lod_index);
|
|
|
|
lod.mesh_map.create(lod.mesh_map.get_block_size_pow2(), lod_index);
|
2020-10-19 23:22:23 +01:00
|
|
|
lod.blocks_to_load.clear();
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.last_view_distance_data_blocks = 0;
|
|
|
|
lod.last_view_distance_mesh_blocks = 0;
|
2019-08-24 01:44:27 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
} else {
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.data_map.clear();
|
|
|
|
lod.mesh_map.clear();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
2021-02-18 19:50:47 +00:00
|
|
|
|
|
|
|
lod.deferred_collision_updates.clear();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
2020-08-14 20:33:09 +01:00
|
|
|
|
|
|
|
// Reset previous state caches to force rebuilding the view area
|
|
|
|
_last_octree_region_box = Rect3i();
|
|
|
|
_lod_octrees.clear();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
unsigned int VoxelLodTerrain::get_lod_count() const {
|
2019-08-29 22:55:02 +01:00
|
|
|
return _lod_count;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2019-08-25 13:04:49 +01:00
|
|
|
void VoxelLodTerrain::set_generate_collisions(bool enabled) {
|
|
|
|
_generate_collisions = enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::set_collision_lod_count(int lod_count) {
|
2021-04-18 01:29:26 +01:00
|
|
|
ERR_FAIL_COND(lod_count < 0);
|
|
|
|
_collision_lod_count = min(static_cast<unsigned int>(lod_count), get_lod_count());
|
2019-08-25 13:04:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
unsigned int VoxelLodTerrain::get_collision_lod_count() const {
|
2019-08-25 13:04:49 +01:00
|
|
|
return _collision_lod_count;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
int VoxelLodTerrain::get_data_block_region_extent() const {
|
|
|
|
return VoxelServer::get_octree_lod_block_region_extent(_lod_distance, get_data_block_size());
|
|
|
|
}
|
|
|
|
|
|
|
|
int VoxelLodTerrain::get_mesh_block_region_extent() const {
|
|
|
|
return VoxelServer::get_octree_lod_block_region_extent(_lod_distance, get_mesh_block_size());
|
2019-05-08 00:57:34 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
Vector3 VoxelLodTerrain::voxel_to_data_block_position(Vector3 vpos, int lod_index) const {
|
2019-09-01 01:47:45 +01:00
|
|
|
ERR_FAIL_COND_V(lod_index < 0, Vector3());
|
2019-05-08 00:57:34 +01:00
|
|
|
ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3());
|
2019-05-04 00:02:10 +01:00
|
|
|
const Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
Vector3i bpos = lod.data_map.voxel_to_block(Vector3i(vpos)) >> lod_index;
|
2019-05-08 00:57:34 +01:00
|
|
|
return bpos.to_vec3();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-04-05 03:44:52 +01:00
|
|
|
Vector3 VoxelLodTerrain::voxel_to_mesh_block_position(Vector3 vpos, int lod_index) const {
|
|
|
|
ERR_FAIL_COND_V(lod_index < 0, Vector3());
|
|
|
|
ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3());
|
|
|
|
const Lod &lod = _lods[lod_index];
|
|
|
|
Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i(vpos)) >> lod_index;
|
|
|
|
return bpos.to_vec3();
|
|
|
|
}
|
|
|
|
|
2020-10-30 19:02:00 +00:00
|
|
|
void VoxelLodTerrain::set_process_mode(ProcessMode mode) {
|
|
|
|
_process_mode = mode;
|
|
|
|
set_process(_process_mode == PROCESS_MODE_IDLE);
|
|
|
|
set_physics_process(_process_mode == PROCESS_MODE_PHYSICS);
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
void VoxelLodTerrain::_notification(int p_what) {
|
|
|
|
switch (p_what) {
|
2020-10-30 19:02:00 +00:00
|
|
|
// TODO Should use NOTIFICATION_INTERNAL_PROCESS instead?
|
2019-05-04 00:02:10 +01:00
|
|
|
case NOTIFICATION_PROCESS:
|
2020-10-30 19:02:00 +00:00
|
|
|
if (_process_mode == PROCESS_MODE_IDLE) {
|
|
|
|
// 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.
|
|
|
|
// This hack is quite miserable.
|
|
|
|
VoxelServerUpdater::ensure_existence(get_tree());
|
2021-03-02 22:49:42 +00:00
|
|
|
_process(get_process_delta_time());
|
2020-10-30 19:02:00 +00:00
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
break;
|
|
|
|
|
2020-10-30 19:02:00 +00:00
|
|
|
// TODO Should use NOTIFICATION_INTERNAL_PHYSICS_PROCESS instead?
|
|
|
|
case NOTIFICATION_PHYSICS_PROCESS:
|
|
|
|
if (_process_mode == PROCESS_MODE_PHYSICS) {
|
|
|
|
// 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.
|
|
|
|
// This hack is quite miserable.
|
|
|
|
VoxelServerUpdater::ensure_existence(get_tree());
|
2021-03-02 22:49:42 +00:00
|
|
|
_process(get_physics_process_delta_time());
|
2020-10-30 19:02:00 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
case NOTIFICATION_EXIT_TREE:
|
|
|
|
break;
|
|
|
|
|
2019-08-15 01:21:45 +01:00
|
|
|
case NOTIFICATION_ENTER_WORLD: {
|
2019-10-03 18:55:13 +01:00
|
|
|
World *world = *get_world();
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[lod_index].mesh_map.for_all_blocks([world](VoxelMeshBlock *block) {
|
2020-11-21 18:28:06 +00:00
|
|
|
block->set_world(world);
|
|
|
|
});
|
2019-10-03 18:55:13 +01:00
|
|
|
}
|
2020-10-24 00:08:14 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
if (is_showing_gizmos()) {
|
|
|
|
_debug_renderer.set_world(is_visible_in_tree() ? world : nullptr);
|
|
|
|
}
|
|
|
|
#endif
|
2021-04-05 03:47:25 +01:00
|
|
|
// DEBUG
|
|
|
|
//set_show_gizmos(true);
|
2019-08-15 01:21:45 +01:00
|
|
|
} break;
|
|
|
|
|
|
|
|
case NOTIFICATION_EXIT_WORLD: {
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[lod_index].mesh_map.for_all_blocks([](VoxelMeshBlock *block) {
|
2020-11-21 18:28:06 +00:00
|
|
|
block->set_world(nullptr);
|
|
|
|
});
|
2019-10-03 18:55:13 +01:00
|
|
|
}
|
2020-10-24 00:08:14 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
_debug_renderer.set_world(nullptr);
|
|
|
|
#endif
|
2019-08-15 01:21:45 +01:00
|
|
|
} break;
|
|
|
|
|
|
|
|
case NOTIFICATION_VISIBILITY_CHANGED: {
|
2020-10-25 00:55:57 +01:00
|
|
|
const bool visible = is_visible();
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[lod_index].mesh_map.for_all_blocks([visible](VoxelMeshBlock *block) {
|
2020-11-21 18:28:06 +00:00
|
|
|
block->set_parent_visible(visible);
|
|
|
|
});
|
2019-10-03 18:55:13 +01:00
|
|
|
}
|
2020-10-24 00:08:14 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
if (is_showing_gizmos()) {
|
|
|
|
_debug_renderer.set_world(is_visible_in_tree() ? *get_world() : nullptr);
|
|
|
|
}
|
|
|
|
#endif
|
2019-08-15 01:21:45 +01:00
|
|
|
} break;
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
case NOTIFICATION_TRANSFORM_CHANGED: {
|
2020-10-29 00:53:04 +00:00
|
|
|
VOXEL_PROFILE_SCOPE_NAMED("VoxelLodTerrain::NOTIFICATION_TRANSFORM_CHANGED");
|
|
|
|
|
2020-10-25 00:55:57 +01:00
|
|
|
const Transform transform = get_global_transform();
|
|
|
|
VoxelServer::get_singleton()->set_volume_transform(_volume_id, transform);
|
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
if (!is_inside_tree()) {
|
|
|
|
// The transform and other properties can be set by the scene loader,
|
|
|
|
// before we enter the tree
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[lod_index].mesh_map.for_all_blocks([&transform](VoxelMeshBlock *block) {
|
2020-11-21 18:28:06 +00:00
|
|
|
block->set_parent_transform(transform);
|
|
|
|
});
|
2020-10-24 03:22:02 +01:00
|
|
|
}
|
|
|
|
} break;
|
2019-05-04 00:02:10 +01:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 21:21:37 +00:00
|
|
|
Vector3 VoxelLodTerrain::get_local_viewer_pos() const {
|
2021-04-03 20:39:37 +01:00
|
|
|
// Pick this by default
|
|
|
|
Vector3 pos = (_lods[0].last_viewer_data_block_pos << _lods[0].data_map.get_block_size_pow2()).to_vec3();
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-12-19 23:16:10 +00:00
|
|
|
// TODO Support for multiple viewers, this is a placeholder implementation
|
|
|
|
VoxelServer::get_singleton()->for_each_viewer([&pos](const VoxelServer::Viewer &viewer, uint32_t viewer_id) {
|
|
|
|
pos = viewer.world_position;
|
|
|
|
});
|
2020-10-25 21:21:37 +00:00
|
|
|
|
2020-12-19 23:16:10 +00:00
|
|
|
const Transform world_to_local = get_global_transform().affine_inverse();
|
|
|
|
pos = world_to_local.xform(pos);
|
|
|
|
return pos;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void VoxelLodTerrain::try_schedule_loading_with_neighbors(const Vector3i &p_data_block_pos, int lod_index) {
|
2019-05-08 20:53:51 +01:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int p = lod.data_map.get_block_size_pow2() + lod_index;
|
2020-10-29 22:58:19 +00:00
|
|
|
|
|
|
|
const int bound_min_x = _bounds_in_voxels.pos.x >> p;
|
|
|
|
const int bound_min_y = _bounds_in_voxels.pos.y >> p;
|
|
|
|
const int bound_min_z = _bounds_in_voxels.pos.z >> p;
|
|
|
|
const int bound_max_x = (_bounds_in_voxels.pos.x + _bounds_in_voxels.size.x) >> p;
|
2021-03-12 23:36:11 +00:00
|
|
|
const int bound_max_y = (_bounds_in_voxels.pos.y + _bounds_in_voxels.size.y) >> p;
|
|
|
|
const int bound_max_z = (_bounds_in_voxels.pos.z + _bounds_in_voxels.size.z) >> p;
|
2020-10-29 22:58:19 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int min_x = max(p_data_block_pos.x - 1, bound_min_x);
|
|
|
|
const int min_y = max(p_data_block_pos.y - 1, bound_min_y);
|
|
|
|
const int min_z = max(p_data_block_pos.z - 1, bound_min_z);
|
|
|
|
const int max_x = min(p_data_block_pos.x + 2, bound_max_x);
|
|
|
|
const int max_y = min(p_data_block_pos.y + 2, bound_max_y);
|
|
|
|
const int max_z = min(p_data_block_pos.z + 2, bound_max_z);
|
2019-05-08 20:53:51 +01:00
|
|
|
|
2020-10-29 22:58:19 +00:00
|
|
|
Vector3i bpos;
|
|
|
|
for (bpos.y = min_y; bpos.y < max_y; ++bpos.y) {
|
|
|
|
for (bpos.z = min_z; bpos.z < max_z; ++bpos.z) {
|
|
|
|
for (bpos.x = min_x; bpos.x < max_x; ++bpos.x) {
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelDataBlock *block = lod.data_map.get_block(bpos);
|
2019-05-08 20:53:51 +01:00
|
|
|
|
|
|
|
if (block == nullptr) {
|
2019-05-09 19:11:17 +01:00
|
|
|
if (!lod.loading_blocks.has(bpos)) {
|
2019-05-08 20:53:51 +01:00
|
|
|
lod.blocks_to_load.push_back(bpos);
|
2019-05-09 19:11:17 +01:00
|
|
|
lod.loading_blocks.insert(bpos);
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
bool VoxelLodTerrain::is_block_surrounded(const Vector3i &p_bpos, int lod_index, const VoxelDataMap &map) const {
|
2020-10-29 22:58:19 +00:00
|
|
|
const int p = map.get_block_size_pow2() + lod_index;
|
|
|
|
|
|
|
|
const int bound_min_x = _bounds_in_voxels.pos.x >> p;
|
|
|
|
const int bound_min_y = _bounds_in_voxels.pos.y >> p;
|
|
|
|
const int bound_min_z = _bounds_in_voxels.pos.z >> p;
|
|
|
|
const int bound_max_x = (_bounds_in_voxels.pos.x + _bounds_in_voxels.size.x) >> p;
|
2021-03-12 23:36:11 +00:00
|
|
|
const int bound_max_y = (_bounds_in_voxels.pos.y + _bounds_in_voxels.size.y) >> p;
|
|
|
|
const int bound_max_z = (_bounds_in_voxels.pos.z + _bounds_in_voxels.size.z) >> p;
|
2020-10-29 22:58:19 +00:00
|
|
|
|
|
|
|
const int min_x = max(p_bpos.x - 1, bound_min_x);
|
|
|
|
const int min_y = max(p_bpos.y - 1, bound_min_y);
|
|
|
|
const int min_z = max(p_bpos.z - 1, bound_min_z);
|
|
|
|
const int max_x = min(p_bpos.x + 2, bound_max_x);
|
|
|
|
const int max_y = min(p_bpos.y + 2, bound_max_y);
|
|
|
|
const int max_z = min(p_bpos.z + 2, bound_max_z);
|
|
|
|
|
|
|
|
Vector3i bpos;
|
|
|
|
for (bpos.y = min_y; bpos.y < max_y; ++bpos.y) {
|
|
|
|
for (bpos.z = min_z; bpos.z < max_z; ++bpos.z) {
|
|
|
|
for (bpos.x = min_x; bpos.x < max_x; ++bpos.x) {
|
|
|
|
if (bpos != p_bpos && !map.has_block(bpos)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
bool VoxelLodTerrain::check_block_loaded_and_meshed(const Vector3i &p_mesh_block_pos, int lod_index) {
|
2019-09-06 23:24:56 +01:00
|
|
|
Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
|
|
|
|
const int mesh_block_size = get_mesh_block_size();
|
|
|
|
const int data_block_size = get_data_block_size();
|
|
|
|
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
ERR_FAIL_COND_V(!check_block_sizes(data_block_size, mesh_block_size), false);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (mesh_block_size > data_block_size) {
|
|
|
|
const int factor = mesh_block_size / data_block_size;
|
|
|
|
const Vector3i data_block_pos0 = p_mesh_block_pos * factor;
|
|
|
|
|
|
|
|
bool loaded = true;
|
|
|
|
|
|
|
|
for (int z = 0; z < factor; ++z) {
|
|
|
|
for (int x = 0; x < factor; ++x) {
|
|
|
|
for (int y = 0; y < factor; ++y) {
|
|
|
|
const Vector3i data_block_pos(data_block_pos0 + Vector3i(x, y, z));
|
|
|
|
VoxelDataBlock *data_block = lod.data_map.get_block(data_block_pos);
|
|
|
|
|
|
|
|
if (data_block == nullptr) {
|
|
|
|
loaded = false;
|
|
|
|
// TODO This is quite lossy in this case, if we ask for 8 blocks in an octant
|
|
|
|
try_schedule_loading_with_neighbors(data_block_pos, lod_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!loaded) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (mesh_block_size == data_block_size) {
|
|
|
|
const Vector3i data_block_pos = p_mesh_block_pos;
|
|
|
|
VoxelDataBlock *block = lod.data_map.get_block(data_block_pos);
|
|
|
|
if (block == nullptr) {
|
|
|
|
try_schedule_loading_with_neighbors(data_block_pos, lod_index);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VoxelMeshBlock *mesh_block = lod.mesh_map.get_block(p_mesh_block_pos);
|
|
|
|
if (mesh_block == nullptr) {
|
|
|
|
mesh_block = VoxelMeshBlock::create(p_mesh_block_pos, mesh_block_size, lod_index);
|
|
|
|
lod.mesh_map.set_block(p_mesh_block_pos, mesh_block);
|
2019-05-10 18:56:48 +01:00
|
|
|
}
|
2021-04-03 20:39:37 +01:00
|
|
|
|
|
|
|
return check_block_mesh_updated(mesh_block);
|
2019-09-06 23:24:56 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
bool VoxelLodTerrain::check_block_mesh_updated(VoxelMeshBlock *block) {
|
2020-10-29 22:58:19 +00:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-04-03 20:39:37 +01:00
|
|
|
|
2019-09-06 23:24:56 +01:00
|
|
|
CRASH_COND(block == nullptr);
|
|
|
|
Lod &lod = _lods[block->lod_index];
|
|
|
|
|
|
|
|
switch (block->get_mesh_state()) {
|
2021-04-03 20:39:37 +01:00
|
|
|
case VoxelMeshBlock::MESH_NEVER_UPDATED:
|
|
|
|
case VoxelMeshBlock::MESH_NEED_UPDATE: {
|
|
|
|
const int mesh_block_size = get_mesh_block_size();
|
|
|
|
const int data_block_size = get_data_block_size();
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
ERR_FAIL_COND_V(!check_block_sizes(data_block_size, mesh_block_size), false);
|
|
|
|
#endif
|
|
|
|
// Find data block neighbors positions
|
|
|
|
const int factor = mesh_block_size / data_block_size;
|
|
|
|
Vector3i data_block_pos0 = factor * block->position;
|
|
|
|
Rect3i data_box(data_block_pos0 - Vector3i(1), Vector3i(factor) + Vector3i(2));
|
|
|
|
Rect3i bounds = _bounds_in_voxels.downscaled(data_block_size);
|
|
|
|
FixedArray<Vector3i, 56> neighbor_positions;
|
|
|
|
int neighbor_positions_count = 0;
|
|
|
|
data_box.for_inner_outline([bounds, &neighbor_positions, &neighbor_positions_count](Vector3i pos) {
|
|
|
|
if (bounds.contains(pos)) {
|
|
|
|
neighbor_positions[neighbor_positions_count] = pos;
|
|
|
|
++neighbor_positions_count;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check if neighbors are loaded
|
|
|
|
bool surrounded = true;
|
|
|
|
for (unsigned int i = 0; i < neighbor_positions_count; ++i) {
|
|
|
|
const Vector3i npos = neighbor_positions[i];
|
|
|
|
if (!lod.data_map.has_block(npos)) {
|
|
|
|
// That neighbor is not loaded
|
|
|
|
surrounded = false;
|
|
|
|
if (!lod.loading_blocks.has(npos)) {
|
|
|
|
// Schedule loading for that neighbor
|
|
|
|
lod.blocks_to_load.push_back(npos);
|
|
|
|
lod.loading_blocks.insert(npos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (surrounded) {
|
2019-05-25 15:50:01 +01:00
|
|
|
lod.blocks_pending_update.push_back(block->position);
|
2021-04-03 20:39:37 +01:00
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
2019-05-10 18:56:48 +01:00
|
|
|
}
|
2021-04-03 20:39:37 +01:00
|
|
|
|
2019-09-06 23:24:56 +01:00
|
|
|
return false;
|
2021-04-03 20:39:37 +01:00
|
|
|
}
|
2019-09-06 23:24:56 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
case VoxelMeshBlock::MESH_UPDATE_NOT_SENT:
|
|
|
|
case VoxelMeshBlock::MESH_UPDATE_SENT:
|
2019-09-06 23:24:56 +01:00
|
|
|
return false;
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
case VoxelMeshBlock::MESH_UP_TO_DATE:
|
2020-01-23 23:20:23 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
CRASH_NOW();
|
2019-09-06 23:24:56 +01:00
|
|
|
break;
|
2019-05-10 18:56:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-07 21:19:12 +01:00
|
|
|
void VoxelLodTerrain::send_block_data_requests() {
|
2020-08-31 21:51:30 +01:00
|
|
|
// Blocks to load
|
2021-02-07 17:22:50 +00:00
|
|
|
const bool request_instances = _instancer != nullptr;
|
2019-09-07 21:19:12 +01:00
|
|
|
for (int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < lod.blocks_to_load.size(); ++i) {
|
2020-08-31 21:51:30 +01:00
|
|
|
const Vector3i block_pos = lod.blocks_to_load[i];
|
2021-02-07 17:22:50 +00:00
|
|
|
VoxelServer::get_singleton()->request_block_load(_volume_id, block_pos, lod_index, request_instances);
|
2019-09-07 21:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
lod.blocks_to_load.clear();
|
|
|
|
}
|
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
// Blocks to save
|
2019-09-07 00:44:19 +09:00
|
|
|
for (unsigned int i = 0; i < _blocks_to_save.size(); ++i) {
|
2020-07-08 20:48:52 +01:00
|
|
|
PRINT_VERBOSE(String("Requesting save of block {0} lod {1}")
|
|
|
|
.format(varray(_blocks_to_save[i].position.to_vec3(), _blocks_to_save[i].lod)));
|
2021-02-07 17:22:50 +00:00
|
|
|
BlockToSave &b = _blocks_to_save[i];
|
|
|
|
VoxelServer::get_singleton()->request_voxel_block_save(_volume_id, b.voxels, b.position, b.lod);
|
2019-09-07 21:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_blocks_to_save.clear();
|
|
|
|
}
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
void VoxelLodTerrain::_process(float delta) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
if (get_lod_count() == 0) {
|
|
|
|
// If there isn't a LOD 0, there is nothing to load
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-25 00:55:57 +01:00
|
|
|
// Get viewer location in voxel space
|
2020-10-25 21:21:37 +00:00
|
|
|
const Vector3 viewer_pos = get_local_viewer_pos();
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-05-12 16:30:45 +01:00
|
|
|
_stats.dropped_block_loads = 0;
|
|
|
|
_stats.dropped_block_meshs = 0;
|
2019-08-29 22:55:02 +01:00
|
|
|
_stats.blocked_lods = 0;
|
2019-05-12 16:30:45 +01:00
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
// Here we go...
|
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
// Update pending LOD data modifications due to edits.
|
|
|
|
// These are deferred from edits so we can batch them.
|
|
|
|
// It has to happen first because blocks can be unloaded afterwards.
|
|
|
|
flush_pending_lod_edits();
|
|
|
|
|
|
|
|
ProfilingClock profiling_clock;
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
// Unload data blocks falling out of block region extent
|
2019-05-04 00:02:10 +01:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-08-29 22:55:02 +01:00
|
|
|
// TODO Could it actually be enough to have a rolling update on all blocks?
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
// This should be the same distance relatively to each LOD
|
2021-04-03 20:39:37 +01:00
|
|
|
const int data_block_region_extent = get_data_block_region_extent();
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-10-29 00:53:04 +00:00
|
|
|
// Ignore largest lod because it can extend a little beyond due to the view distance setting.
|
2020-01-23 00:37:13 +00:00
|
|
|
// Instead, those blocks are unloaded by the octree forest management.
|
2020-10-29 00:53:04 +00:00
|
|
|
// Iterating from big to small LOD so we can exit earlier if bounds don't intersect.
|
|
|
|
for (int lod_index = get_lod_count() - 2; lod_index >= 0; --lod_index) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-05-04 00:02:10 +01:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
// Each LOD keeps a box of loaded blocks, and only some of the blocks will get polygonized.
|
|
|
|
// The player can edit them so changes can be propagated to lower lods.
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
unsigned int block_size_po2 = _lods[0].data_map.get_block_size_pow2() + lod_index;
|
|
|
|
Vector3i viewer_block_pos_within_lod = VoxelDataMap::voxel_to_block_b(viewer_pos, block_size_po2);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
const Rect3i bounds_in_blocks = Rect3i(
|
|
|
|
_bounds_in_voxels.pos >> block_size_po2,
|
|
|
|
_bounds_in_voxels.size >> block_size_po2);
|
|
|
|
|
|
|
|
const Rect3i new_box =
|
2021-04-03 20:39:37 +01:00
|
|
|
Rect3i::from_center_extents(viewer_block_pos_within_lod, Vector3i(data_block_region_extent));
|
2020-10-22 22:43:31 +01:00
|
|
|
const Rect3i prev_box =
|
2021-04-03 20:39:37 +01:00
|
|
|
Rect3i::from_center_extents(
|
|
|
|
lod.last_viewer_data_block_pos, Vector3i(lod.last_view_distance_data_blocks));
|
2020-10-22 22:43:31 +01:00
|
|
|
|
|
|
|
if (!new_box.intersects(bounds_in_blocks) && !prev_box.intersects(bounds_in_blocks)) {
|
|
|
|
// If this box doesn't intersect either now or before, there is no chance a smaller one will
|
|
|
|
break;
|
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
|
|
|
// Eliminate pending blocks that aren't needed
|
2019-05-20 20:34:41 +01:00
|
|
|
|
2019-09-06 23:24:56 +01:00
|
|
|
// This vector must be empty at this point.
|
2019-05-20 20:34:41 +01:00
|
|
|
// Let's assert so it will pop on your face the day that assumption changes
|
|
|
|
CRASH_COND(!lod.blocks_to_load.empty());
|
2019-05-04 00:02:10 +01:00
|
|
|
|
|
|
|
if (prev_box != new_box) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-09-02 20:08:33 +01:00
|
|
|
prev_box.difference(new_box, [this, lod_index](Rect3i out_of_range_box) {
|
2019-09-02 00:45:20 +01:00
|
|
|
out_of_range_box.for_each_cell([=](Vector3i pos) {
|
2020-01-23 00:37:13 +00:00
|
|
|
//print_line(String("Immerge {0}").format(varray(pos.to_vec3())));
|
2021-04-03 20:39:37 +01:00
|
|
|
unload_data_block(pos, lod_index);
|
2019-09-02 00:45:20 +01:00
|
|
|
});
|
|
|
|
});
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2020-01-02 20:29:02 +00:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-04-03 20:39:37 +01:00
|
|
|
// Cancel block updates that are not within the padded region
|
|
|
|
// (since neighbors are always required to remesh)
|
|
|
|
|
|
|
|
const Rect3i padded_new_box = new_box.padded(-1);
|
|
|
|
Rect3i mesh_box;
|
|
|
|
if (get_mesh_block_size() > get_data_block_size()) {
|
|
|
|
const int factor = get_mesh_block_size() / get_data_block_size();
|
|
|
|
mesh_box = padded_new_box.downscaled_inner(factor);
|
|
|
|
} else {
|
|
|
|
mesh_box = padded_new_box;
|
|
|
|
}
|
|
|
|
|
|
|
|
unordered_remove_if(lod.blocks_pending_update, [&lod, mesh_box](Vector3i bpos) {
|
|
|
|
if (mesh_box.contains(bpos)) {
|
2020-01-02 20:29:02 +00:00
|
|
|
return false;
|
|
|
|
} else {
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-02 20:29:02 +00:00
|
|
|
if (block != nullptr) {
|
2021-04-03 20:39:37 +01:00
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_NEED_UPDATE);
|
2020-01-02 20:29:02 +00:00
|
|
|
}
|
|
|
|
return true;
|
2019-09-06 23:24:56 +01:00
|
|
|
}
|
2020-01-02 20:29:02 +00:00
|
|
|
});
|
|
|
|
}
|
2019-09-06 23:24:56 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.last_viewer_data_block_pos = viewer_block_pos_within_lod;
|
|
|
|
lod.last_view_distance_data_blocks = data_block_region_extent;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
// Unload mesh blocks falling out of block region extent
|
|
|
|
{
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
// TODO Could it actually be enough to have a rolling update on all blocks?
|
|
|
|
|
|
|
|
// This should be the same distance relatively to each LOD
|
|
|
|
const int mesh_block_region_extent = get_mesh_block_region_extent();
|
|
|
|
|
|
|
|
// Ignore largest lod because it can extend a little beyond due to the view distance setting.
|
|
|
|
// Instead, those blocks are unloaded by the octree forest management.
|
|
|
|
// Iterating from big to small LOD so we can exit earlier if bounds don't intersect.
|
|
|
|
for (int lod_index = get_lod_count() - 2; lod_index >= 0; --lod_index) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
unsigned int block_size_po2 = _lods[0].mesh_map.get_block_size_pow2() + lod_index;
|
|
|
|
Vector3i viewer_block_pos_within_lod = VoxelMeshMap::voxel_to_block_b(viewer_pos, block_size_po2);
|
|
|
|
|
|
|
|
const Rect3i bounds_in_blocks = Rect3i(
|
|
|
|
_bounds_in_voxels.pos >> block_size_po2,
|
|
|
|
_bounds_in_voxels.size >> block_size_po2);
|
|
|
|
|
|
|
|
const Rect3i new_box =
|
|
|
|
Rect3i::from_center_extents(viewer_block_pos_within_lod, Vector3i(mesh_block_region_extent));
|
|
|
|
const Rect3i prev_box =
|
|
|
|
Rect3i::from_center_extents(lod.last_viewer_mesh_block_pos, Vector3i(lod.last_view_distance_mesh_blocks));
|
|
|
|
|
|
|
|
if (!new_box.intersects(bounds_in_blocks) && !prev_box.intersects(bounds_in_blocks)) {
|
|
|
|
// If this box doesn't intersect either now or before, there is no chance a smaller one will
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Eliminate pending blocks that aren't needed
|
|
|
|
|
|
|
|
if (prev_box != new_box) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
prev_box.difference(new_box, [this, lod_index](Rect3i out_of_range_box) {
|
|
|
|
out_of_range_box.for_each_cell([=](Vector3i pos) {
|
|
|
|
//print_line(String("Immerge {0}").format(varray(pos.to_vec3())));
|
|
|
|
unload_mesh_block(pos, lod_index);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
// Cancel block updates that are not within the new region
|
|
|
|
unordered_remove_if(lod.blocks_pending_update, [&lod, new_box](Vector3i bpos) {
|
|
|
|
return !new_box.contains(bpos);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
lod.last_viewer_mesh_block_pos = viewer_block_pos_within_lod;
|
|
|
|
lod.last_view_distance_mesh_blocks = mesh_block_region_extent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create and remove octrees in a grid around the viewer.
|
|
|
|
// Mesh blocks drive the loading of voxel data and visuals.
|
2019-05-08 20:53:51 +01:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-09-01 21:49:58 +01:00
|
|
|
// TODO Investigate if multi-octree can produce cracks in the terrain (so far I haven't noticed)
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const unsigned int octree_size_po2 = get_mesh_block_size_pow2() + get_lod_count() - 1;
|
2019-09-04 20:59:08 +01:00
|
|
|
const unsigned int octree_size = 1 << octree_size_po2;
|
|
|
|
const unsigned int octree_region_extent = 1 + _view_distance_voxels / (1 << octree_size_po2);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
const Vector3i viewer_octree_pos = (Vector3i(viewer_pos) + Vector3i(octree_size / 2)) >> octree_size_po2;
|
|
|
|
|
|
|
|
const Rect3i bounds_in_octrees = _bounds_in_voxels.downscaled(octree_size);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
const Rect3i new_box = Rect3i::from_center_extents(viewer_octree_pos, Vector3i(octree_region_extent))
|
|
|
|
.clipped(bounds_in_octrees);
|
|
|
|
const Rect3i prev_box = _last_octree_region_box;
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
if (new_box != prev_box) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
struct CleanOctreeAction {
|
|
|
|
VoxelLodTerrain *self;
|
|
|
|
Vector3i block_offset_lod0;
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
void operator()(Vector3i node_pos, unsigned int lod_index) {
|
2019-08-29 22:55:02 +01:00
|
|
|
Lod &lod = self->_lods[lod_index];
|
|
|
|
|
2019-09-01 21:07:38 +01:00
|
|
|
Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2021-03-02 22:49:42 +00:00
|
|
|
if (block != nullptr) {
|
2021-04-03 20:39:37 +01:00
|
|
|
self->set_mesh_block_active(*block, false);
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
2019-08-29 22:55:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ExitAction {
|
|
|
|
VoxelLodTerrain *self;
|
|
|
|
void operator()(const Vector3i &pos) {
|
|
|
|
|
|
|
|
Map<Vector3i, OctreeItem>::Element *E = self->_lod_octrees.find(pos);
|
|
|
|
if (E == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
OctreeItem &item = E->value();
|
2021-04-03 20:39:37 +01:00
|
|
|
const Vector3i block_pos_maxlod = E->key();
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int last_lod_index = self->get_lod_count() - 1;
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
// We just drop the octree and hide blocks it was considering as visible.
|
|
|
|
// Normally such octrees shouldn't bee too deep as they will likely be at the edge
|
|
|
|
// of the loaded area, unless the player teleported far away.
|
|
|
|
CleanOctreeAction a;
|
|
|
|
a.self = self;
|
|
|
|
a.block_offset_lod0 = block_pos_maxlod << last_lod_index;
|
|
|
|
item.octree.clear(a);
|
|
|
|
|
|
|
|
self->_lod_octrees.erase(E);
|
2020-01-23 00:37:13 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
// Unload last lod from here, as it may extend a bit further than the others.
|
|
|
|
// Other LODs are unloaded earlier using a sliding region.
|
|
|
|
self->unload_mesh_block(pos, last_lod_index);
|
|
|
|
// TODO Unload data block too?
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
2019-08-29 22:55:02 +01:00
|
|
|
};
|
2019-05-08 20:53:51 +01:00
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
struct EnterAction {
|
|
|
|
VoxelLodTerrain *self;
|
|
|
|
int block_size;
|
|
|
|
void operator()(const Vector3i &pos) {
|
2019-09-01 16:10:01 +01:00
|
|
|
// That's a new cell we are entering, shouldn't be anything there
|
2019-08-29 22:55:02 +01:00
|
|
|
CRASH_COND(self->_lod_octrees.has(pos));
|
|
|
|
|
2019-08-31 21:47:00 +01:00
|
|
|
// Create new octree
|
2019-09-01 21:49:58 +01:00
|
|
|
// TODO Use ObjectPool to store them, deletion won't be cheap
|
2019-08-29 22:55:02 +01:00
|
|
|
Map<Vector3i, OctreeItem>::Element *E = self->_lod_octrees.insert(pos, OctreeItem());
|
|
|
|
CRASH_COND(E == nullptr);
|
|
|
|
OctreeItem &item = E->value();
|
2020-01-23 00:37:13 +00:00
|
|
|
LodOctree::NoDestroyAction nda;
|
2019-08-29 22:55:02 +01:00
|
|
|
item.octree.create_from_lod_count(block_size, self->get_lod_count(), nda);
|
2021-03-27 16:38:20 +00:00
|
|
|
item.octree.set_lod_distance(self->get_lod_distance());
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
2019-08-29 22:55:02 +01:00
|
|
|
};
|
2019-05-08 20:53:51 +01:00
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
ExitAction exit_action;
|
|
|
|
exit_action.self = this;
|
|
|
|
|
|
|
|
EnterAction enter_action;
|
|
|
|
enter_action.self = this;
|
2021-04-03 20:39:37 +01:00
|
|
|
enter_action.block_size = get_mesh_block_size();
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2019-09-02 20:08:33 +01:00
|
|
|
prev_box.difference(new_box, [exit_action](Rect3i out_of_range_box) {
|
|
|
|
out_of_range_box.for_each_cell(exit_action);
|
|
|
|
});
|
|
|
|
|
|
|
|
new_box.difference(prev_box, [enter_action](Rect3i box_to_load) {
|
|
|
|
box_to_load.for_each_cell(enter_action);
|
|
|
|
});
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
|
|
|
|
2019-09-02 00:44:28 +01:00
|
|
|
_last_octree_region_box = new_box;
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
CRASH_COND(_blocks_pending_transition_update.size() != 0);
|
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
// Find which blocks we need to load and see, within each octree
|
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
// TODO Maintain a vector to make iteration faster?
|
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
OctreeItem &item = E->value();
|
|
|
|
Vector3i block_pos_maxlod = E->key();
|
|
|
|
Vector3i block_offset_lod0 = block_pos_maxlod << (get_lod_count() - 1);
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
struct OctreeActions {
|
|
|
|
VoxelLodTerrain *self = nullptr;
|
2019-08-29 22:55:02 +01:00
|
|
|
Vector3i block_offset_lod0;
|
|
|
|
unsigned int blocked_count = 0;
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
void create_child(Vector3i node_pos, int lod_index) {
|
|
|
|
Lod &lod = self->_lods[lod_index];
|
|
|
|
Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index);
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-23 00:37:13 +00:00
|
|
|
|
|
|
|
// Never show a child that hasn't been meshed
|
|
|
|
CRASH_COND(block == nullptr);
|
2021-04-03 20:39:37 +01:00
|
|
|
CRASH_COND(block->get_mesh_state() != VoxelMeshBlock::MESH_UP_TO_DATE);
|
2020-01-23 00:37:13 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
self->set_mesh_block_active(*block, true);
|
2020-01-23 00:37:13 +00:00
|
|
|
self->add_transition_update(block);
|
|
|
|
self->add_transition_updates_around(bpos, lod_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
void destroy_child(Vector3i node_pos, int lod_index) {
|
|
|
|
Lod &lod = self->_lods[lod_index];
|
|
|
|
Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index);
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-23 00:37:13 +00:00
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
if (block != nullptr) {
|
2021-04-03 20:39:37 +01:00
|
|
|
self->set_mesh_block_active(*block, false);
|
2020-01-23 00:37:13 +00:00
|
|
|
self->add_transition_updates_around(bpos, lod_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_parent(Vector3i node_pos, int lod_index) {
|
|
|
|
Lod &lod = self->_lods[lod_index];
|
|
|
|
Vector3i bpos = node_pos + (block_offset_lod0 >> lod_index);
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-23 00:37:13 +00:00
|
|
|
|
|
|
|
// If we teleport far away, the area we were in is going to merge,
|
|
|
|
// and blocks may have been unloaded completely.
|
|
|
|
// So in that case it's normal to not find any block.
|
|
|
|
// Otherwise, there must always be a visible parent in the end, unless the octree vanished.
|
2021-04-03 20:39:37 +01:00
|
|
|
if (block != nullptr && block->get_mesh_state() == VoxelMeshBlock::MESH_UP_TO_DATE) {
|
|
|
|
self->set_mesh_block_active(*block, true);
|
2020-01-23 00:37:13 +00:00
|
|
|
self->add_transition_update(block);
|
|
|
|
self->add_transition_updates_around(bpos, lod_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void hide_parent(Vector3i node_pos, int lod_index) {
|
|
|
|
destroy_child(node_pos, lod_index); // Same
|
|
|
|
}
|
|
|
|
|
|
|
|
bool can_create_root(int lod_index) {
|
2019-08-29 22:55:02 +01:00
|
|
|
Vector3i offset = block_offset_lod0 >> lod_index;
|
2021-04-03 20:39:37 +01:00
|
|
|
return self->check_block_loaded_and_meshed(offset, lod_index);
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
bool can_split(Vector3i node_pos, int child_lod_index) {
|
2019-08-29 22:55:02 +01:00
|
|
|
Vector3i offset = block_offset_lod0 >> child_lod_index;
|
|
|
|
bool can = true;
|
|
|
|
|
|
|
|
// Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
// Get block pos local-to-region
|
2020-01-23 00:37:13 +00:00
|
|
|
Vector3i child_pos = LodOctree::get_child_position(node_pos, i);
|
2019-08-29 22:55:02 +01:00
|
|
|
// Convert to local-to-terrain
|
|
|
|
child_pos += offset;
|
|
|
|
// We have to ping ALL children, because the reason we are here is we want them loaded
|
2021-04-03 20:39:37 +01:00
|
|
|
can &= self->check_block_loaded_and_meshed(child_pos, child_lod_index);
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 23:35:51 +00:00
|
|
|
// Can only subdivide if blocks of a higher LOD index are present around,
|
|
|
|
// otherwise it will cause cracks.
|
|
|
|
// Need to check meshes, not voxels?
|
|
|
|
// const int lod_index = child_lod_index + 1;
|
|
|
|
// if (lod_index < self->get_lod_count()) {
|
|
|
|
// const Vector3i parent_offset = block_offset_lod0 >> lod_index;
|
|
|
|
// const Lod &lod = self->_lods[lod_index];
|
|
|
|
// can &= self->is_block_surrounded(node_pos + parent_offset, lod_index, lod.map);
|
|
|
|
// }
|
|
|
|
|
2019-08-29 22:55:02 +01:00
|
|
|
if (!can) {
|
|
|
|
++blocked_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return can;
|
|
|
|
}
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
bool can_join(Vector3i node_pos, int parent_lod_index) {
|
2020-01-23 23:20:23 +00:00
|
|
|
// Can only unsubdivide if the parent mesh is ready
|
|
|
|
Lod &lod = self->_lods[parent_lod_index];
|
|
|
|
|
|
|
|
Vector3i bpos = node_pos + (block_offset_lod0 >> parent_lod_index);
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-23 23:20:23 +00:00
|
|
|
|
|
|
|
if (block == nullptr) {
|
|
|
|
// The block got unloaded. Exceptionally, we can join.
|
|
|
|
// There will always be a grand-parent because we never destroy them when they split,
|
|
|
|
// and we never create a child without creating a parent first.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
// The block is loaded (?) but the mesh isn't up to date, we need to ping and wait.
|
2020-01-23 23:20:23 +00:00
|
|
|
bool can = self->check_block_mesh_updated(block);
|
|
|
|
|
|
|
|
if (!can) {
|
|
|
|
++blocked_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return can;
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-01-23 00:37:13 +00:00
|
|
|
OctreeActions octree_actions;
|
|
|
|
octree_actions.self = this;
|
|
|
|
octree_actions.block_offset_lod0 = block_offset_lod0;
|
2019-08-29 22:55:02 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
Vector3 relative_viewer_pos = viewer_pos - get_mesh_block_size() * block_offset_lod0.to_vec3();
|
2020-01-23 00:37:13 +00:00
|
|
|
item.octree.update(relative_viewer_pos, octree_actions);
|
2019-08-29 22:55:02 +01:00
|
|
|
|
|
|
|
// Ideally, this stat should stabilize to zero.
|
2020-01-23 00:37:13 +00:00
|
|
|
// If not, something in block management prevents LODs from properly show up and should be fixed.
|
|
|
|
_stats.blocked_lods += octree_actions.blocked_count;
|
2019-08-29 22:55:02 +01:00
|
|
|
}
|
2019-12-31 16:48:46 +00:00
|
|
|
|
2020-01-02 20:29:02 +00:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
process_transition_updates();
|
|
|
|
}
|
2019-05-08 20:53:51 +01:00
|
|
|
}
|
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
CRASH_COND(_blocks_pending_transition_update.size() != 0);
|
|
|
|
|
2019-08-25 18:47:43 +01:00
|
|
|
_stats.time_detect_required_blocks = profiling_clock.restart();
|
|
|
|
|
2021-01-17 17:18:05 +00:00
|
|
|
const bool stream_enabled = (_stream.is_valid() || _generator.is_valid()) &&
|
|
|
|
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor);
|
|
|
|
|
2020-08-30 21:36:43 +01:00
|
|
|
// It's possible the user didn't set a stream yet, or it is turned off
|
2021-01-17 17:18:05 +00:00
|
|
|
if (stream_enabled) {
|
2020-08-30 21:36:43 +01:00
|
|
|
send_block_data_requests();
|
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-05-05 01:09:12 +01:00
|
|
|
_stats.time_request_blocks_to_load = profiling_clock.restart();
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
// Get block loading responses
|
|
|
|
// Note: if block loading is too fast, this can cause stutters.
|
|
|
|
// It should only happen on first load, though.
|
2020-08-31 21:51:30 +01:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2020-10-31 01:15:50 +00:00
|
|
|
for (size_t reception_index = 0; reception_index < _reception_buffers.data_output.size(); ++reception_index) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-10-31 01:15:50 +00:00
|
|
|
const VoxelServer::BlockDataOutput &ob = _reception_buffers.data_output[reception_index];
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
if (ob.type == VoxelServer::BlockDataOutput::TYPE_SAVE) {
|
2020-02-04 15:37:52 +01:00
|
|
|
// That's a save confirmation event.
|
|
|
|
// Note: in the future, if blocks don't get copied before being sent for saving,
|
|
|
|
// we will need to use block versionning to know when we can reset the `modified` flag properly
|
2020-08-31 21:51:30 +01:00
|
|
|
|
|
|
|
// TODO Now that's the case. Use version? Or just keep copying?
|
2020-02-04 15:37:52 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
if (ob.lod >= get_lod_count()) {
|
2019-05-04 00:02:10 +01:00
|
|
|
// That block was requested at a time where LOD was higher... drop it
|
2019-05-12 16:30:45 +01:00
|
|
|
++_stats.dropped_block_loads;
|
2019-05-04 00:02:10 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
Lod &lod = _lods[ob.lod];
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-25 15:14:10 +01:00
|
|
|
{
|
|
|
|
Set<Vector3i>::Element *E = lod.loading_blocks.find(ob.position);
|
|
|
|
if (E == nullptr) {
|
|
|
|
// That block was not requested, or is no longer needed. drop it...
|
|
|
|
++_stats.dropped_block_loads;
|
|
|
|
continue;
|
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-25 15:14:10 +01:00
|
|
|
lod.loading_blocks.erase(E);
|
|
|
|
}
|
2019-05-09 22:00:54 +01:00
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
if (ob.dropped) {
|
2019-05-20 20:48:58 +01:00
|
|
|
// That block was dropped by the data loader thread, but we were still expecting it...
|
2020-01-15 21:04:23 +00:00
|
|
|
// This is most likely caused by the loader not keeping up with the speed at which the player is moving.
|
|
|
|
// We should recover with the removal from `loading_blocks` so it will be re-queried again later...
|
|
|
|
|
|
|
|
// print_line(String("Received a block loading drop while we were still expecting it: lod{0} ({1}, {2}, {3})")
|
|
|
|
// .format(varray(ob.lod, ob.position.x, ob.position.y, ob.position.z)));
|
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
++_stats.dropped_block_loads;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
if (ob.voxels->get_size() != Vector3i(lod.data_map.get_block_size())) {
|
2019-05-04 00:02:10 +01:00
|
|
|
// Voxel block size is incorrect, drop it
|
2019-05-28 01:10:50 +01:00
|
|
|
ERR_PRINT("Block size obtained from stream is different from expected size");
|
2019-05-12 16:30:45 +01:00
|
|
|
++_stats.dropped_block_loads;
|
2019-05-04 00:02:10 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store buffer
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelDataBlock *block = lod.data_map.set_block_buffer(ob.position, ob.voxels);
|
2021-03-02 22:49:42 +00:00
|
|
|
CRASH_COND(block == nullptr);
|
2021-02-07 17:22:50 +00:00
|
|
|
|
|
|
|
if (_instancer != nullptr && ob.instances != nullptr) {
|
|
|
|
VoxelServer::BlockDataOutput &wob = _reception_buffers.data_output[reception_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
_instancer->on_data_block_loaded(wob.position, wob.lod, std::move(wob.instances));
|
2021-02-07 17:22:50 +00:00
|
|
|
}
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
2021-02-08 01:22:09 +00:00
|
|
|
|
|
|
|
_reception_buffers.data_output.clear();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
process_fading_blocks(delta);
|
|
|
|
|
2019-05-05 01:09:12 +01:00
|
|
|
_stats.time_process_load_responses = profiling_clock.restart();
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
// Send mesh update requests
|
2019-05-04 00:02:10 +01:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-01-02 20:29:02 +00:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int render_to_data_factor = get_mesh_block_size() / get_data_block_size();
|
|
|
|
|
2019-09-01 01:47:45 +01:00
|
|
|
for (int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-05-04 00:02:10 +01:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
2020-09-09 18:58:58 +01:00
|
|
|
for (unsigned int bi = 0; bi < lod.blocks_pending_update.size(); ++bi) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-04-03 20:39:37 +01:00
|
|
|
const Vector3i mesh_block_pos = lod.blocks_pending_update[bi];
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(mesh_block_pos);
|
2019-05-09 19:11:17 +01:00
|
|
|
CRASH_COND(block == nullptr);
|
2019-09-06 23:24:56 +01:00
|
|
|
// All blocks we get here must be in the scheduled state
|
2021-04-03 20:39:37 +01:00
|
|
|
CRASH_COND(block->get_mesh_state() != VoxelMeshBlock::MESH_UPDATE_NOT_SENT);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
// Get block and its neighbors
|
2020-09-09 18:58:58 +01:00
|
|
|
VoxelServer::BlockMeshInput mesh_request;
|
2021-04-13 23:48:35 +01:00
|
|
|
mesh_request.render_block_position = mesh_block_pos;
|
2020-09-09 18:58:58 +01:00
|
|
|
mesh_request.lod = lod_index;
|
2021-04-03 20:39:37 +01:00
|
|
|
|
|
|
|
const Rect3i data_box =
|
|
|
|
Rect3i(render_to_data_factor * mesh_block_pos, Vector3i(render_to_data_factor)).padded(1);
|
|
|
|
|
|
|
|
// Iteration order matters for thread access.
|
|
|
|
// The array also implicitely encodes block position due to the convention being used,
|
|
|
|
// so there is no need to also include positions in the request
|
|
|
|
data_box.for_each_cell_zxy([&mesh_request, &lod](Vector3i data_block_pos) {
|
|
|
|
VoxelDataBlock *nblock = lod.data_map.get_block(data_block_pos);
|
2020-08-31 21:51:30 +01:00
|
|
|
// The block can actually be null on some occasions. Not sure yet if it's that bad
|
|
|
|
//CRASH_COND(nblock == nullptr);
|
2021-04-03 20:39:37 +01:00
|
|
|
if (nblock != nullptr) {
|
2021-04-13 23:48:35 +01:00
|
|
|
mesh_request.data_blocks[mesh_request.data_blocks_count] = nblock->voxels;
|
2020-08-31 21:51:30 +01:00
|
|
|
}
|
2021-04-13 23:48:35 +01:00
|
|
|
++mesh_request.data_blocks_count;
|
2021-04-03 20:39:37 +01:00
|
|
|
});
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2020-09-09 18:58:58 +01:00
|
|
|
VoxelServer::get_singleton()->request_block_mesh(_volume_id, mesh_request);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_SENT);
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
lod.blocks_pending_update.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-05 01:09:12 +01:00
|
|
|
_stats.time_request_blocks_to_update = profiling_clock.restart();
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
const uint32_t main_thread_task_timeout = get_ticks_msec() + VoxelConstants::MAIN_THREAD_MESHING_BUDGET_MS;
|
|
|
|
|
|
|
|
// Receive mesh updates:
|
|
|
|
// This contains work that should normally be threaded, but isn't because of Godot limitations.
|
|
|
|
// So after a timeout, it stops processing and will resume next frame.
|
2019-05-04 00:02:10 +01:00
|
|
|
{
|
2020-08-31 21:51:30 +01:00
|
|
|
VOXEL_PROFILE_SCOPE_NAMED("Receive mesh updates");
|
2019-08-25 18:47:43 +01:00
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
const Transform global_transform = get_global_transform();
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
// The following is done on the main thread because Godot doesn't really support multithreaded Mesh allocation.
|
|
|
|
// This also proved to be very slow compared to the meshing process itself...
|
|
|
|
// hopefully Vulkan will allow us to upload graphical resources without stalling rendering as they upload?
|
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
size_t queue_index = 0;
|
2021-02-18 19:50:47 +00:00
|
|
|
for (; queue_index < _reception_buffers.mesh_output.size() && get_ticks_msec() < main_thread_task_timeout;
|
|
|
|
++queue_index) {
|
|
|
|
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-08-31 21:51:30 +01:00
|
|
|
const VoxelServer::BlockMeshOutput &ob = _reception_buffers.mesh_output[queue_index];
|
2019-05-04 00:02:10 +01:00
|
|
|
|
|
|
|
if (ob.lod >= get_lod_count()) {
|
|
|
|
// Sorry, LOD configuration changed, drop that mesh
|
2019-05-12 16:30:45 +01:00
|
|
|
++_stats.dropped_block_meshs;
|
2019-05-04 00:02:10 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Lod &lod = _lods[ob.lod];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(ob.position);
|
2020-07-25 23:19:08 +01:00
|
|
|
if (block == nullptr) {
|
2019-05-04 00:02:10 +01:00
|
|
|
// That block is no longer loaded, drop the result
|
2019-05-12 16:30:45 +01:00
|
|
|
++_stats.dropped_block_meshs;
|
2019-05-04 00:02:10 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-31 21:51:30 +01:00
|
|
|
if (ob.type == VoxelServer::BlockMeshOutput::TYPE_DROPPED) {
|
2019-05-20 20:48:58 +01:00
|
|
|
// 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
|
2020-07-08 20:48:52 +01:00
|
|
|
PRINT_VERBOSE("Received a block mesh drop while we were still expecting it");
|
2019-05-20 20:48:58 +01:00
|
|
|
++_stats.dropped_block_meshs;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
if (block->get_mesh_state() == VoxelMeshBlock::MESH_UPDATE_SENT) {
|
|
|
|
block->set_mesh_state(VoxelMeshBlock::MESH_UP_TO_DATE);
|
2019-05-09 19:11:17 +01:00
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
const VoxelMesher::Output mesh_data = ob.surfaces;
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-12-31 16:48:46 +00:00
|
|
|
Ref<ArrayMesh> mesh = build_mesh(
|
|
|
|
mesh_data.surfaces,
|
|
|
|
mesh_data.primitive_type,
|
|
|
|
mesh_data.compression_flags,
|
2020-05-07 13:55:16 -07:00
|
|
|
_material);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-08-25 13:04:49 +01:00
|
|
|
bool has_collision = _generate_collisions;
|
2021-04-18 01:29:26 +01:00
|
|
|
if (has_collision && _collision_lod_count != 0) {
|
2019-08-25 13:04:49 +01:00
|
|
|
has_collision = ob.lod < _collision_lod_count;
|
|
|
|
}
|
|
|
|
|
2020-12-29 22:25:22 +00:00
|
|
|
if (block->got_first_mesh_update == false) {
|
|
|
|
block->got_first_mesh_update = true;
|
|
|
|
|
|
|
|
// TODO Need a more generic API for this kind of stuff
|
|
|
|
if (_instancer != nullptr && ob.surfaces.surfaces.size() > 0) {
|
|
|
|
// TODO The mesh could come from an edited region!
|
|
|
|
// We would have to know if specific voxels got edited, or different from the generator
|
2021-04-03 20:39:37 +01:00
|
|
|
_instancer->on_mesh_block_enter(ob.position, ob.lod, ob.surfaces.surfaces[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lazy initialization
|
|
|
|
|
|
|
|
//print_line(String("Adding block {0} at lod {1}").format(varray(eo.block_position.to_vec3(), eo.lod)));
|
|
|
|
//set_mesh_block_active(*block, false);
|
|
|
|
block->set_parent_visible(is_visible());
|
|
|
|
block->set_world(get_world());
|
|
|
|
|
|
|
|
Ref<ShaderMaterial> shader_material = _material;
|
|
|
|
if (shader_material.is_valid() && block->get_shader_material().is_null()) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
|
|
|
// Pooling shader materials is necessary for now, to avoid stuttering in the editor.
|
|
|
|
// Due to a signal used to keep the inspector up to date, even though these
|
|
|
|
// material copies will never be seen in the inspector
|
|
|
|
// See https://github.com/godotengine/godot/issues/34741
|
|
|
|
Ref<ShaderMaterial> sm;
|
|
|
|
if (_shader_material_pool.size() > 0) {
|
|
|
|
sm = _shader_material_pool.back();
|
|
|
|
// The joys of pooling materials
|
|
|
|
sm->set_shader_param(VoxelStringNames::get_singleton()->u_transition_mask, 0);
|
|
|
|
_shader_material_pool.pop_back();
|
|
|
|
} else {
|
|
|
|
sm = shader_material->duplicate(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set individual shader material, because each block can have dynamic parameters,
|
|
|
|
// used to smooth seams without re-uploading meshes and allow to implement LOD fading
|
|
|
|
block->set_shader_material(sm);
|
2020-12-29 22:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-06 19:01:12 +01:00
|
|
|
block->set_mesh(mesh);
|
2020-01-02 20:29:02 +00:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int dir = 0; dir < mesh_data.transition_surfaces.size(); ++dir) {
|
2020-01-02 20:29:02 +00:00
|
|
|
Ref<ArrayMesh> transition_mesh = build_mesh(
|
|
|
|
mesh_data.transition_surfaces[dir],
|
|
|
|
mesh_data.primitive_type,
|
|
|
|
mesh_data.compression_flags,
|
2020-05-07 13:55:16 -07:00
|
|
|
_material);
|
2019-12-31 16:48:46 +00:00
|
|
|
|
2020-01-07 02:04:24 +00:00
|
|
|
block->set_transition_mesh(transition_mesh, dir);
|
2020-01-02 20:29:02 +00:00
|
|
|
}
|
2019-12-31 16:48:46 +00:00
|
|
|
}
|
2020-10-24 03:22:02 +01:00
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
const uint32_t now = get_ticks_msec();
|
|
|
|
if (has_collision) {
|
2021-02-19 01:51:06 +00:00
|
|
|
if (_collision_update_delay == 0 ||
|
|
|
|
static_cast<int>(now - block->last_collider_update_time) > _collision_update_delay) {
|
2021-02-18 19:50:47 +00:00
|
|
|
block->set_collision_mesh(mesh_data.surfaces, get_tree()->is_debugging_collisions_hint(), this);
|
|
|
|
block->last_collider_update_time = now;
|
|
|
|
block->has_deferred_collider_update = false;
|
|
|
|
block->deferred_collider_data.clear();
|
|
|
|
} else {
|
|
|
|
if (!block->has_deferred_collider_update) {
|
|
|
|
lod.deferred_collision_updates.push_back(ob.position);
|
|
|
|
block->has_deferred_collider_update = true;
|
|
|
|
}
|
|
|
|
block->deferred_collider_data = mesh_data.surfaces;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
block->set_parent_transform(global_transform);
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2020-01-02 20:29:02 +00:00
|
|
|
{
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2020-08-31 21:51:30 +01:00
|
|
|
shift_up(_reception_buffers.mesh_output, queue_index);
|
2020-01-02 20:29:02 +00:00
|
|
|
}
|
2020-12-25 17:08:40 +00:00
|
|
|
|
2021-01-16 13:05:50 +00:00
|
|
|
_stats.remaining_main_thread_blocks = (int)_reception_buffers.mesh_output.size();
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2019-05-05 01:09:12 +01:00
|
|
|
_stats.time_process_update_responses = profiling_clock.restart();
|
2020-10-24 00:08:14 +01:00
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
process_deferred_collision_updates(main_thread_task_timeout);
|
|
|
|
|
2020-10-24 00:08:14 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
2020-12-27 15:51:03 +00:00
|
|
|
if (is_showing_gizmos() && is_visible_in_tree()) {
|
2020-10-24 00:08:14 +01:00
|
|
|
update_gizmos();
|
|
|
|
}
|
|
|
|
#endif
|
2019-05-05 01:09:12 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2021-02-18 19:50:47 +00:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < lod.deferred_collision_updates.size(); ++i) {
|
|
|
|
const Vector3i block_pos = lod.deferred_collision_updates[i];
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = lod.mesh_map.get_block(block_pos);
|
2021-02-18 19:50:47 +00:00
|
|
|
|
|
|
|
if (block == nullptr || block->has_deferred_collider_update == false) {
|
|
|
|
// Block was unloaded or no longer needs a collision update
|
|
|
|
unordered_remove(lod.deferred_collision_updates, i);
|
|
|
|
--i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t now = get_ticks_msec();
|
|
|
|
|
2021-02-19 01:51:06 +00:00
|
|
|
if (static_cast<int>(now - block->last_collider_update_time) > _collision_update_delay) {
|
2021-02-18 19:50:47 +00:00
|
|
|
block->set_collision_mesh(
|
|
|
|
block->deferred_collider_data, get_tree()->is_debugging_collisions_hint(), this);
|
|
|
|
block->last_collider_update_time = now;
|
|
|
|
block->has_deferred_collider_update = false;
|
|
|
|
block->deferred_collider_data.clear();
|
|
|
|
|
|
|
|
unordered_remove(lod.deferred_collision_updates, i);
|
|
|
|
--i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We always process at least one, then we to check the timeout
|
|
|
|
if (get_ticks_msec() >= timeout_msec) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
void VoxelLodTerrain::process_fading_blocks(float delta) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
|
|
|
const float speed = _lod_fade_duration < 0.001f ? 99999.f : delta / _lod_fade_duration;
|
|
|
|
|
2021-03-27 22:27:55 +00:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
|
2021-03-02 22:49:42 +00:00
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
Map<Vector3i, VoxelMeshBlock *>::Element *e = lod.fading_blocks.front();
|
2021-03-02 22:49:42 +00:00
|
|
|
|
|
|
|
while (e != nullptr) {
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = e->value();
|
2021-03-12 23:35:51 +00:00
|
|
|
// The collection of fading blocks must only contain fading blocks
|
2021-04-03 20:39:37 +01:00
|
|
|
ERR_FAIL_COND(block->fading_state == VoxelMeshBlock::FADING_NONE);
|
2021-03-12 23:35:51 +00:00
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
const bool finished = block->update_fading(speed);
|
|
|
|
|
|
|
|
if (finished) {
|
2021-04-03 20:39:37 +01:00
|
|
|
Map<Vector3i, VoxelMeshBlock *>::Element *next = e->next();
|
2021-03-02 22:49:42 +00:00
|
|
|
lod.fading_blocks.erase(e);
|
|
|
|
e = next;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
e = e->next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
void VoxelLodTerrain::flush_pending_lod_edits() {
|
2019-09-07 21:19:12 +01:00
|
|
|
// Propagates edits performed so far to other LODs.
|
|
|
|
// These LODs must be currently in memory, otherwise terrain data will miss it.
|
2019-09-07 22:59:09 +01:00
|
|
|
// This is currently ensured by the fact we load blocks in a "pyramidal" way,
|
|
|
|
// i.e there is no way for a block to be loaded if its parent LOD isn't loaded already.
|
|
|
|
// In the future we may implement storing of edits to be applied later if blocks can't be found.
|
2019-09-07 21:19:12 +01:00
|
|
|
|
|
|
|
//ProfilingClock profiling_clock;
|
2019-09-03 22:54:40 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int data_to_mesh_factor = get_mesh_block_size() / get_data_block_size();
|
|
|
|
|
2019-09-07 22:59:09 +01:00
|
|
|
// Make sure LOD0 gets updates even if _lod_count is 1
|
|
|
|
Lod &lod0 = _lods[0];
|
2019-09-07 00:44:19 +09:00
|
|
|
for (unsigned int i = 0; i < lod0.blocks_pending_lodding.size(); ++i) {
|
2021-04-03 20:39:37 +01:00
|
|
|
const Vector3i data_block_pos = lod0.blocks_pending_lodding[i];
|
|
|
|
VoxelDataBlock *data_block = lod0.data_map.get_block(data_block_pos);
|
|
|
|
ERR_CONTINUE(data_block == nullptr);
|
|
|
|
data_block->set_needs_lodding(false);
|
|
|
|
|
|
|
|
const Vector3i mesh_block_pos = data_block_pos.floordiv(data_to_mesh_factor);
|
|
|
|
VoxelMeshBlock *mesh_block = lod0.mesh_map.get_block(mesh_block_pos);
|
|
|
|
ERR_CONTINUE(mesh_block == nullptr);
|
|
|
|
schedule_mesh_update(mesh_block, lod0.blocks_pending_update);
|
2019-09-07 22:59:09 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int half_bs = get_data_block_size() >> 1;
|
2019-09-07 22:59:09 +01:00
|
|
|
|
|
|
|
// Process downscales upwards in pairs of consecutive LODs.
|
|
|
|
// This ensures we don't process multiple times the same blocks.
|
|
|
|
// Only LOD0 is editable at the moment, so we'll downscale from there
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int dst_lod_index = 1; dst_lod_index < _lod_count; ++dst_lod_index) {
|
2019-09-07 22:59:09 +01:00
|
|
|
Lod &src_lod = _lods[dst_lod_index - 1];
|
|
|
|
Lod &dst_lod = _lods[dst_lod_index];
|
|
|
|
|
2019-09-07 00:44:19 +09:00
|
|
|
for (unsigned int i = 0; i < src_lod.blocks_pending_lodding.size(); ++i) {
|
2020-02-05 21:08:23 +01:00
|
|
|
const Vector3i src_bpos = src_lod.blocks_pending_lodding[i];
|
|
|
|
const Vector3i dst_bpos = src_bpos >> 1;
|
2019-09-03 22:54:40 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelDataBlock *src_block = src_lod.data_map.get_block(src_bpos);
|
|
|
|
VoxelDataBlock *dst_block = dst_lod.data_map.get_block(dst_bpos);
|
2019-09-03 22:54:40 +01:00
|
|
|
|
2020-10-29 00:53:04 +00:00
|
|
|
src_block->set_needs_lodding(false);
|
|
|
|
|
|
|
|
if (dst_block == nullptr) {
|
|
|
|
ERR_PRINT(String("Destination block {0} not found when cascading edits on LOD {1}")
|
|
|
|
.format(varray(dst_bpos.to_vec3(), dst_lod_index)));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-09-07 22:59:09 +01:00
|
|
|
// The block and its lower LODs are expected to be available.
|
|
|
|
// Otherwise it means the function was called too late
|
|
|
|
CRASH_COND(src_block == nullptr);
|
2020-10-29 00:53:04 +00:00
|
|
|
//CRASH_COND(dst_block == nullptr);
|
2019-09-07 22:59:09 +01:00
|
|
|
CRASH_COND(src_block->voxels.is_null());
|
|
|
|
CRASH_COND(dst_block->voxels.is_null());
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
{
|
|
|
|
const Vector3i mesh_block_pos = dst_bpos.floordiv(data_to_mesh_factor);
|
|
|
|
VoxelMeshBlock *mesh_block = dst_lod.mesh_map.get_block(mesh_block_pos);
|
|
|
|
if (mesh_block != nullptr) {
|
|
|
|
schedule_mesh_update(mesh_block, lod0.blocks_pending_update);
|
|
|
|
} else {
|
|
|
|
ERR_PRINT("Could not find mesh block while lodding modified data block");
|
|
|
|
}
|
|
|
|
}
|
2019-09-07 22:59:09 +01:00
|
|
|
|
|
|
|
dst_block->set_modified(true);
|
|
|
|
|
|
|
|
if (dst_lod_index != _lod_count - 1 && !dst_block->get_needs_lodding()) {
|
|
|
|
dst_block->set_needs_lodding(true);
|
|
|
|
dst_lod.blocks_pending_lodding.push_back(dst_bpos);
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
|
|
|
|
2020-02-05 21:08:23 +01:00
|
|
|
const Vector3i rel = src_bpos - (dst_bpos << 1);
|
2019-09-06 23:24:56 +01:00
|
|
|
|
2019-09-07 22:59:09 +01:00
|
|
|
// Update lower LOD
|
|
|
|
// This must always be done after an edit before it gets saved, otherwise LODs won't match and it will look ugly.
|
|
|
|
// TODO Try to narrow to edited region instead of taking whole block
|
2020-08-29 22:09:54 +01:00
|
|
|
{
|
|
|
|
RWLockWrite lock(src_block->voxels->get_lock());
|
|
|
|
src_block->voxels->downscale_to(
|
|
|
|
**dst_block->voxels, Vector3i(), src_block->voxels->get_size(), rel * half_bs);
|
|
|
|
}
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
|
|
|
|
2019-09-07 22:59:09 +01:00
|
|
|
src_lod.blocks_pending_lodding.clear();
|
|
|
|
}
|
2019-09-07 21:19:12 +01:00
|
|
|
|
|
|
|
// uint64_t time_spent = profiling_clock.restart();
|
|
|
|
// if (time_spent > 10) {
|
|
|
|
// print_line(String("Took {0} us to update lods").format(varray(time_spent)));
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2020-12-29 22:33:30 +00:00
|
|
|
void VoxelLodTerrain::set_instancer(VoxelInstancer *instancer) {
|
|
|
|
if (_instancer != nullptr && instancer != nullptr) {
|
|
|
|
ERR_FAIL_COND_MSG(_instancer != nullptr, "No more than one VoxelInstancer per terrain");
|
|
|
|
}
|
|
|
|
_instancer = instancer;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void VoxelLodTerrain::unload_data_block(Vector3i block_pos, int lod_index) {
|
2020-08-29 23:20:51 +01:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2019-09-07 21:19:12 +01:00
|
|
|
ERR_FAIL_COND(lod_index >= get_lod_count());
|
|
|
|
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.data_map.remove_block(block_pos, BeforeUnloadDataAction{ _blocks_to_save, _stream.is_valid() });
|
2019-09-07 21:19:12 +01:00
|
|
|
|
|
|
|
lod.loading_blocks.erase(block_pos);
|
2021-04-03 20:39:37 +01:00
|
|
|
|
|
|
|
// if (_instancer != nullptr) {
|
|
|
|
// _instancer->on_block_exit(block_pos, lod_index);
|
|
|
|
// }
|
|
|
|
|
|
|
|
// No need to remove things from blocks_pending_load,
|
|
|
|
// This vector is filled and cleared immediately in the main process.
|
|
|
|
// It is a member only to re-use its capacity memory over frames.
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::unload_mesh_block(Vector3i block_pos, int lod_index) {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
ERR_FAIL_COND(lod_index >= get_lod_count());
|
|
|
|
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
lod.mesh_map.remove_block(block_pos, BeforeUnloadMeshAction{ _shader_material_pool });
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
lod.fading_blocks.erase(block_pos);
|
2019-09-07 21:19:12 +01:00
|
|
|
|
2020-12-29 22:25:22 +00:00
|
|
|
if (_instancer != nullptr) {
|
2021-04-03 20:39:37 +01:00
|
|
|
_instancer->on_mesh_block_exit(block_pos, lod_index);
|
2020-12-29 22:25:22 +00:00
|
|
|
}
|
|
|
|
|
2019-09-07 21:19:12 +01: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-02-14 21:55:31 +00:00
|
|
|
// This function is primarily intented for editor use cases at the moment.
|
|
|
|
// It will be slower than using the instancing generation events,
|
|
|
|
// because it has to query VisualServer, which then allocates and decodes vertex buffers (assuming they are cached).
|
2021-04-03 20:39:37 +01:00
|
|
|
Array VoxelLodTerrain::get_mesh_block_surface(Vector3i block_pos, int lod_index) const {
|
2021-02-14 21:55:31 +00:00
|
|
|
VOXEL_PROFILE_SCOPE();
|
2021-04-18 01:29:26 +01:00
|
|
|
ERR_FAIL_COND_V(lod_index >= static_cast<int>(_lod_count), Array());
|
2021-02-15 21:45:12 +00:00
|
|
|
const Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *block = lod.mesh_map.get_block(block_pos);
|
2021-02-14 21:55:31 +00:00
|
|
|
if (block != nullptr) {
|
|
|
|
Ref<Mesh> mesh = block->get_mesh();
|
|
|
|
if (mesh.is_valid()) {
|
2021-02-15 18:25:48 +00:00
|
|
|
return mesh->surface_get_arrays(0);
|
2021-02-14 21:55:31 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-15 18:25:48 +00:00
|
|
|
return Array();
|
2021-02-14 21:55:31 +00:00
|
|
|
}
|
|
|
|
|
2021-02-15 21:45:12 +00:00
|
|
|
Vector<Vector3i> VoxelLodTerrain::get_meshed_block_positions_at_lod(int lod_index) const {
|
|
|
|
Vector<Vector3i> positions;
|
2021-04-18 01:29:26 +01:00
|
|
|
ERR_FAIL_COND_V(lod_index >= static_cast<int>(_lod_count), positions);
|
2021-02-15 21:45:12 +00:00
|
|
|
const Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.mesh_map.for_all_blocks([&positions](const VoxelMeshBlock *block) {
|
2021-02-15 21:45:12 +00:00
|
|
|
if (block->has_mesh()) {
|
|
|
|
positions.push_back(block->position);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return positions;
|
|
|
|
}
|
|
|
|
|
2019-09-07 21:19:12 +01:00
|
|
|
void VoxelLodTerrain::save_all_modified_blocks(bool with_copy) {
|
|
|
|
flush_pending_lod_edits();
|
|
|
|
|
2021-01-23 23:01:02 +00:00
|
|
|
if (_stream.is_valid()) {
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int i = 0; i < _lod_count; ++i) {
|
2021-01-23 23:01:02 +00:00
|
|
|
// That may cause a stutter, so should be used when the player won't notice
|
2021-04-03 20:39:37 +01:00
|
|
|
_lods[i].data_map.for_all_blocks(ScheduleSaveAction{ _blocks_to_save });
|
2021-01-23 23:01:02 +00:00
|
|
|
}
|
2021-02-07 17:22:50 +00:00
|
|
|
|
|
|
|
if (_instancer != nullptr && _stream->supports_instance_blocks()) {
|
|
|
|
_instancer->save_all_modified_blocks();
|
|
|
|
}
|
2019-09-07 21:19:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// And flush immediately
|
|
|
|
send_block_data_requests();
|
2019-09-03 22:54:40 +01:00
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
void VoxelLodTerrain::add_transition_update(VoxelMeshBlock *block) {
|
2019-12-31 16:48:46 +00:00
|
|
|
if (!block->pending_transition_update) {
|
|
|
|
_blocks_pending_transition_update.push_back(block);
|
|
|
|
block->pending_transition_update = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::add_transition_updates_around(Vector3i block_pos, int lod_index) {
|
|
|
|
Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
|
|
|
Vector3i npos = block_pos + Cube::g_side_normals[dir];
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *nblock = lod.mesh_map.get_block(npos);
|
2019-12-31 16:48:46 +00:00
|
|
|
|
2020-01-05 04:57:40 +00:00
|
|
|
if (nblock != nullptr) {
|
2019-12-31 16:48:46 +00:00
|
|
|
add_transition_update(nblock);
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 00:22:58 +00:00
|
|
|
// TODO If a block appears at lod, neighbor blocks at lod-1 need to be updated.
|
|
|
|
// or maybe get_transition_mask needs a different approach that also looks at higher lods?
|
2019-12-31 16:48:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::process_transition_updates() {
|
|
|
|
for (unsigned int i = 0; i < _blocks_pending_transition_update.size(); ++i) {
|
2021-04-03 20:39:37 +01:00
|
|
|
VoxelMeshBlock *block = _blocks_pending_transition_update[i];
|
2019-12-31 16:48:46 +00:00
|
|
|
CRASH_COND(block == nullptr);
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
if (block->active) {
|
2019-12-31 16:48:46 +00:00
|
|
|
block->set_transition_mask(get_transition_mask(block->position, block->lod_index));
|
|
|
|
}
|
|
|
|
|
|
|
|
block->pending_transition_update = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_blocks_pending_transition_update.clear();
|
|
|
|
}
|
|
|
|
|
2020-01-07 00:22:58 +00:00
|
|
|
uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) const {
|
2019-12-31 16:48:46 +00:00
|
|
|
uint8_t transition_mask = 0;
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
if (lod_index + 1 >= static_cast<int>(_lod_count)) {
|
2019-12-31 16:48:46 +00:00
|
|
|
return transition_mask;
|
|
|
|
}
|
|
|
|
|
2020-01-07 00:22:58 +00:00
|
|
|
const Lod &lower_lod = _lods[lod_index + 1];
|
|
|
|
const Lod &lod = _lods[lod_index];
|
2020-11-21 18:28:06 +00:00
|
|
|
|
|
|
|
const Vector3i lower_pos = block_pos >> 1;
|
|
|
|
const Vector3i upper_pos = block_pos << 1;
|
2020-01-07 00:22:58 +00:00
|
|
|
|
|
|
|
// Based on octree rules, and the fact it must have run before, check neighbor blocks of same LOD:
|
|
|
|
// If one is missing or not visible, it means either of the following:
|
|
|
|
// - The neighbor at lod+1 is visible or not loaded (there must be a transition)
|
|
|
|
// - The neighbor at lod-1 is visible (no transition)
|
|
|
|
|
|
|
|
uint8_t visible_neighbors_of_same_lod = 0;
|
2019-12-31 16:48:46 +00:00
|
|
|
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
2020-01-07 00:22:58 +00:00
|
|
|
Vector3i npos = block_pos + Cube::g_side_normals[dir];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *nblock = lod.mesh_map.get_block(npos);
|
2020-01-07 00:22:58 +00:00
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
if (nblock != nullptr && nblock->active) {
|
2020-01-07 00:22:58 +00:00
|
|
|
visible_neighbors_of_same_lod |= (1 << dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (visible_neighbors_of_same_lod != 0b111111) {
|
|
|
|
// At least one neighbor isn't visible.
|
|
|
|
// Check for neighbors at different LOD (there can be only one kind on a given side)
|
|
|
|
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
|
|
|
int dir_mask = (1 << dir);
|
|
|
|
|
|
|
|
if (visible_neighbors_of_same_lod & dir_mask) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-12-31 16:48:46 +00:00
|
|
|
|
2020-01-07 00:22:58 +00:00
|
|
|
const Vector3i side_normal = Cube::g_side_normals[dir];
|
|
|
|
const Vector3i lower_neighbor_pos = (block_pos + side_normal) >> 1;
|
2019-12-31 16:48:46 +00:00
|
|
|
|
2020-01-07 00:22:58 +00:00
|
|
|
if (lower_neighbor_pos != lower_pos) {
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *lower_neighbor_block = lower_lod.mesh_map.get_block(lower_neighbor_pos);
|
2020-01-07 00:22:58 +00:00
|
|
|
|
2021-03-12 23:35:51 +00:00
|
|
|
if (lower_neighbor_block != nullptr && lower_neighbor_block->active) {
|
2020-01-07 00:22:58 +00:00
|
|
|
// The block has a visible neighbor of lower LOD
|
|
|
|
transition_mask |= dir_mask;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lod_index > 0) {
|
|
|
|
// Check upper LOD neighbors.
|
|
|
|
// There are always 4 on each side, checking any is enough
|
|
|
|
|
|
|
|
Vector3i upper_neighbor_pos = upper_pos;
|
2020-02-13 21:49:11 +08:00
|
|
|
for (unsigned int i = 0; i < Vector3i::AXIS_COUNT; ++i) {
|
2020-01-07 00:22:58 +00:00
|
|
|
if (side_normal[i] == -1) {
|
|
|
|
--upper_neighbor_pos[i];
|
|
|
|
} else if (side_normal[i] == 1) {
|
|
|
|
upper_neighbor_pos[i] += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const Lod &upper_lod = _lods[lod_index - 1];
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *upper_neighbor_block = upper_lod.mesh_map.get_block(upper_neighbor_pos);
|
2020-01-07 00:22:58 +00:00
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
if (upper_neighbor_block == nullptr || upper_neighbor_block->active == false) {
|
2020-01-07 00:22:58 +00:00
|
|
|
// The block has no visible neighbor yet. World border? Assume lower LOD.
|
|
|
|
transition_mask |= dir_mask;
|
|
|
|
}
|
2019-12-31 16:48:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return transition_mask;
|
|
|
|
}
|
|
|
|
|
2020-12-25 17:08:40 +00:00
|
|
|
const VoxelLodTerrain::Stats &VoxelLodTerrain::get_stats() const {
|
|
|
|
return _stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dictionary VoxelLodTerrain::_b_get_statistics() const {
|
2019-05-05 01:09:12 +01:00
|
|
|
Dictionary d;
|
2019-08-25 18:47:43 +01:00
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
int deferred_collision_updates = 0;
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2021-02-18 19:50:47 +00:00
|
|
|
const Lod &lod = _lods[lod_index];
|
|
|
|
deferred_collision_updates += lod.deferred_collision_updates.size();
|
|
|
|
}
|
|
|
|
|
2019-08-25 18:47:43 +01:00
|
|
|
// Breakdown of time spent in _process
|
|
|
|
d["time_detect_required_blocks"] = _stats.time_detect_required_blocks;
|
|
|
|
d["time_request_blocks_to_load"] = _stats.time_request_blocks_to_load;
|
|
|
|
d["time_process_load_responses"] = _stats.time_process_load_responses;
|
|
|
|
d["time_request_blocks_to_update"] = _stats.time_request_blocks_to_update;
|
|
|
|
d["time_process_update_responses"] = _stats.time_process_update_responses;
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
d["remaining_main_thread_blocks"] = _stats.remaining_main_thread_blocks + deferred_collision_updates;
|
2019-05-12 16:30:45 +01:00
|
|
|
d["dropped_block_loads"] = _stats.dropped_block_loads;
|
|
|
|
d["dropped_block_meshs"] = _stats.dropped_block_meshs;
|
2019-08-25 18:47:43 +01:00
|
|
|
d["updated_blocks"] = _stats.updated_blocks;
|
|
|
|
d["blocked_lods"] = _stats.blocked_lods;
|
2019-05-05 01:09:12 +01:00
|
|
|
|
|
|
|
return d;
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
void VoxelLodTerrain::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 VoxelLodTerrain::is_stream_running_in_editor() const {
|
|
|
|
return _run_stream_in_editor;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::restart_stream() {
|
|
|
|
_on_stream_params_changed();
|
|
|
|
}
|
|
|
|
|
2020-12-18 21:01:50 +00:00
|
|
|
void VoxelLodTerrain::remesh_all_blocks() {
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2020-12-18 21:01:50 +00:00
|
|
|
Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.mesh_map.for_all_blocks([&lod](VoxelMeshBlock *block) {
|
2020-12-18 21:01:50 +00:00
|
|
|
schedule_mesh_update(block, lod.blocks_pending_update);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
void VoxelLodTerrain::set_voxel_bounds(Rect3i p_box) {
|
|
|
|
_bounds_in_voxels =
|
|
|
|
p_box.clipped(Rect3i::from_center_extents(Vector3i(), Vector3i(VoxelConstants::MAX_VOLUME_EXTENT)));
|
|
|
|
// Round to octree size
|
2021-04-03 20:39:37 +01:00
|
|
|
const int octree_size = get_mesh_block_size() << (get_lod_count() - 1);
|
2020-10-22 22:43:31 +01:00
|
|
|
_bounds_in_voxels = _bounds_in_voxels.snapped(octree_size);
|
|
|
|
// Can't have a smaller region than one octree
|
|
|
|
for (unsigned i = 0; i < Vector3i::AXIS_COUNT; ++i) {
|
|
|
|
if (_bounds_in_voxels.size[i] < octree_size) {
|
|
|
|
_bounds_in_voxels.size[i] = octree_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
void VoxelLodTerrain::set_collision_update_delay(int delay_msec) {
|
|
|
|
_collision_update_delay = clamp(delay_msec, 0, 4000);
|
|
|
|
}
|
|
|
|
|
|
|
|
int VoxelLodTerrain::get_collision_update_delay() const {
|
|
|
|
return _collision_update_delay;
|
|
|
|
}
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
void VoxelLodTerrain::set_lod_fade_duration(float seconds) {
|
|
|
|
_lod_fade_duration = clamp(seconds, 0.f, 1.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
float VoxelLodTerrain::get_lod_fade_duration() const {
|
|
|
|
return _lod_fade_duration;
|
|
|
|
}
|
|
|
|
|
2020-07-25 16:29:16 +01:00
|
|
|
void VoxelLodTerrain::_b_save_modified_blocks() {
|
|
|
|
save_all_modified_blocks(true);
|
|
|
|
}
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
void VoxelLodTerrain::_b_set_voxel_bounds(AABB aabb) {
|
|
|
|
// TODO Please Godot, have an integer AABB!
|
|
|
|
set_voxel_bounds(Rect3i(aabb.position.round(), aabb.size.round()));
|
|
|
|
}
|
|
|
|
|
|
|
|
AABB VoxelLodTerrain::_b_get_voxel_bounds() const {
|
|
|
|
const Rect3i b = get_voxel_bounds();
|
|
|
|
return AABB(b.pos.to_vec3(), b.size.to_vec3());
|
|
|
|
}
|
|
|
|
|
2020-01-15 21:04:23 +00:00
|
|
|
// DEBUG LAND
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
Array VoxelLodTerrain::debug_raycast_mesh_block(Vector3 world_origin, Vector3 world_direction) const {
|
2020-10-25 00:55:57 +01:00
|
|
|
const Transform world_to_local = get_global_transform().affine_inverse();
|
|
|
|
Vector3 pos = world_to_local.xform(world_origin);
|
|
|
|
const Vector3 dir = world_to_local.basis.xform(world_direction);
|
|
|
|
const float max_distance = 256;
|
|
|
|
const float step = 2.f;
|
2020-01-05 23:27:43 +00:00
|
|
|
float distance = 0.f;
|
|
|
|
|
|
|
|
Array hits;
|
|
|
|
while (distance < max_distance && hits.size() == 0) {
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2020-01-05 23:27:43 +00:00
|
|
|
const Lod &lod = _lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i(pos)) >> lod_index;
|
|
|
|
const VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-05 23:27:43 +00:00
|
|
|
if (block != nullptr && block->is_visible() && block->has_mesh()) {
|
|
|
|
Dictionary d;
|
|
|
|
d["position"] = block->position.to_vec3();
|
|
|
|
d["lod"] = block->lod_index;
|
|
|
|
hits.append(d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
distance += step;
|
|
|
|
pos += dir * step;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hits;
|
|
|
|
}
|
|
|
|
|
2021-04-05 03:46:15 +01:00
|
|
|
Dictionary VoxelLodTerrain::debug_get_data_block_info(Vector3 fbpos, int lod_index) const {
|
|
|
|
Dictionary d;
|
|
|
|
ERR_FAIL_COND_V(lod_index < 0, d);
|
|
|
|
ERR_FAIL_COND_V(lod_index >= get_lod_count(), d);
|
|
|
|
|
|
|
|
const Lod &lod = _lods[lod_index];
|
|
|
|
Vector3i bpos = Vector3i::from_floored(fbpos);
|
|
|
|
|
|
|
|
int loading_state = 0;
|
|
|
|
const VoxelDataBlock *block = lod.data_map.get_block(bpos);
|
|
|
|
|
|
|
|
if (block != nullptr) {
|
|
|
|
loading_state = 2;
|
|
|
|
|
|
|
|
} else if (lod.loading_blocks.has(bpos)) {
|
|
|
|
loading_state = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
d["loading_state"] = loading_state;
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
Dictionary VoxelLodTerrain::debug_get_mesh_block_info(Vector3 fbpos, int lod_index) const {
|
2020-01-15 21:04:23 +00:00
|
|
|
Dictionary d;
|
|
|
|
ERR_FAIL_COND_V(lod_index < 0, d);
|
|
|
|
ERR_FAIL_COND_V(lod_index >= get_lod_count(), d);
|
|
|
|
|
|
|
|
const Lod &lod = _lods[lod_index];
|
2021-04-05 03:46:15 +01:00
|
|
|
Vector3i bpos = Vector3i::from_floored(fbpos);
|
2020-01-15 21:04:23 +00:00
|
|
|
|
2021-04-05 03:46:15 +01:00
|
|
|
bool loaded = false;
|
2020-01-15 21:04:23 +00:00
|
|
|
bool meshed = false;
|
|
|
|
bool visible = false;
|
2021-04-05 03:46:15 +01:00
|
|
|
bool active = false;
|
|
|
|
bool loading = false;
|
|
|
|
int mesh_state = VoxelMeshBlock::MESH_NEVER_UPDATED;
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
|
2020-01-15 21:04:23 +00:00
|
|
|
|
2021-04-05 03:46:15 +01:00
|
|
|
if (block != nullptr) {
|
|
|
|
loaded = true;
|
|
|
|
meshed = block->has_mesh();
|
|
|
|
mesh_state = block->get_mesh_state();
|
2020-01-15 21:04:23 +00:00
|
|
|
visible = block->is_visible();
|
2021-04-05 03:46:15 +01:00
|
|
|
active = block->active;
|
2020-01-15 21:04:23 +00:00
|
|
|
d["transition_mask"] = block->get_transition_mask();
|
2021-04-05 03:46:15 +01:00
|
|
|
// This can highlight possible bugs between the current state and what it should be
|
2020-01-15 21:04:23 +00:00
|
|
|
d["recomputed_transition_mask"] = get_transition_mask(block->position, block->lod_index);
|
|
|
|
}
|
|
|
|
|
2021-04-05 03:46:15 +01:00
|
|
|
d["loaded"] = loaded;
|
2020-01-15 21:04:23 +00:00
|
|
|
d["meshed"] = meshed;
|
2021-04-05 03:46:15 +01:00
|
|
|
d["mesh_state"] = mesh_state;
|
2020-01-15 21:04:23 +00:00
|
|
|
d["visible"] = visible;
|
2021-04-05 03:46:15 +01:00
|
|
|
d["active"] = active;
|
2020-01-15 21:04:23 +00:00
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2021-03-11 22:55:52 +00:00
|
|
|
Array VoxelLodTerrain::debug_get_octree_positions() const {
|
2020-01-23 00:37:13 +00:00
|
|
|
Array positions;
|
|
|
|
positions.resize(_lod_octrees.size());
|
|
|
|
int i = 0;
|
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
|
|
|
positions[i++] = E->key().to_vec3();
|
|
|
|
}
|
|
|
|
return positions;
|
|
|
|
}
|
|
|
|
|
2021-03-11 22:55:52 +00:00
|
|
|
Array VoxelLodTerrain::debug_get_octrees_detailed() const {
|
|
|
|
// [
|
|
|
|
// Vector3,
|
|
|
|
// Octree,
|
|
|
|
// ...
|
|
|
|
// ]
|
|
|
|
// Octree [
|
|
|
|
// state: State,
|
|
|
|
// Octree[8] or null
|
|
|
|
// ]
|
|
|
|
// State {
|
|
|
|
// 0: no block
|
|
|
|
// 1: no mesh
|
|
|
|
// 2: mesh
|
|
|
|
// }
|
|
|
|
|
|
|
|
struct L {
|
|
|
|
static void read_node(const LodOctree &octree, const LodOctree::Node *node, Vector3i position, int lod_index,
|
|
|
|
const VoxelLodTerrain *self, Array &out_data) {
|
|
|
|
|
|
|
|
ERR_FAIL_COND(lod_index < 0);
|
|
|
|
Variant state;
|
|
|
|
|
|
|
|
const Lod &lod = self->_lods[lod_index];
|
2021-04-03 20:39:37 +01:00
|
|
|
const VoxelMeshBlock *block = lod.mesh_map.get_block(position);
|
2021-03-11 22:55:52 +00:00
|
|
|
if (block == nullptr) {
|
|
|
|
state = 0;
|
|
|
|
} else {
|
2021-04-03 20:39:37 +01:00
|
|
|
if (block->get_mesh_state() == VoxelMeshBlock::MESH_UP_TO_DATE) {
|
2021-03-11 22:55:52 +00:00
|
|
|
state = 2;
|
|
|
|
} else {
|
|
|
|
state = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out_data.append(state);
|
|
|
|
|
|
|
|
if (node->has_children()) {
|
|
|
|
Array children_data;
|
|
|
|
for (unsigned int i = 0; i < 8; ++i) {
|
|
|
|
Array child_data;
|
|
|
|
const LodOctree::Node *child = octree.get_child(node, i);
|
|
|
|
const Vector3i child_pos = LodOctree::get_child_position(position, i);
|
|
|
|
read_node(octree, child, child_pos, lod_index - 1, self, child_data);
|
|
|
|
children_data.append(child_data);
|
|
|
|
}
|
|
|
|
out_data.append(children_data);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
out_data.append(Variant());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Array forest_data;
|
|
|
|
|
|
|
|
for (const Map<Vector3i, OctreeItem>::Element *e = _lod_octrees.front(); e; e = e->next()) {
|
|
|
|
const LodOctree &octree = e->value().octree;
|
|
|
|
const LodOctree::Node *root = octree.get_root();
|
|
|
|
Array root_data;
|
|
|
|
const Vector3i octree_pos = e->key();
|
|
|
|
L::read_node(octree, root, octree_pos, get_lod_count() - 1, this, root_data);
|
|
|
|
forest_data.append(octree_pos.to_vec3());
|
|
|
|
forest_data.append(root_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return forest_data;
|
|
|
|
}
|
|
|
|
|
2020-10-24 00:08:14 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
|
|
|
|
void VoxelLodTerrain::update_gizmos() {
|
|
|
|
VOXEL_PROFILE_SCOPE();
|
|
|
|
|
|
|
|
VoxelDebug::DebugRenderer &dr = _debug_renderer;
|
|
|
|
dr.begin();
|
|
|
|
|
2020-10-24 03:22:02 +01:00
|
|
|
const Transform parent_transform = get_global_transform();
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
const int octree_size = get_mesh_block_size() << (get_lod_count() - 1);
|
2020-10-24 03:22:02 +01:00
|
|
|
const Basis local_octree_basis = Basis().scaled(Vector3(octree_size, octree_size, octree_size));
|
2020-10-24 00:08:14 +01:00
|
|
|
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
|
2020-10-24 03:22:02 +01:00
|
|
|
const Transform local_transform(local_octree_basis, (E->key() * octree_size).to_vec3());
|
|
|
|
dr.draw_box(parent_transform * local_transform, VoxelDebug::ID_OCTREE_BOUNDS);
|
2020-10-24 00:08:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const float bounds_in_voxels_len = _bounds_in_voxels.size.length();
|
|
|
|
if (bounds_in_voxels_len < 10000) {
|
2020-10-24 03:22:02 +01:00
|
|
|
const Vector3 margin = Vector3(1, 1, 1) * bounds_in_voxels_len * 0.0025f;
|
|
|
|
const Vector3 size = _bounds_in_voxels.size.to_vec3();
|
|
|
|
const Transform local_transform(Basis().scaled(size + margin * 2.f), _bounds_in_voxels.pos.to_vec3() - margin);
|
|
|
|
dr.draw_box(parent_transform * local_transform, VoxelDebug::ID_VOXEL_BOUNDS);
|
2020-10-24 00:08:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dr.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelLodTerrain::set_show_gizmos(bool enable) {
|
|
|
|
_show_gizmos_enabled = enable;
|
|
|
|
if (_show_gizmos_enabled) {
|
|
|
|
_debug_renderer.set_world(is_visible_in_tree() ? *get_world() : nullptr);
|
|
|
|
} else {
|
|
|
|
_debug_renderer.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2020-02-05 21:09:14 +01:00
|
|
|
Array VoxelLodTerrain::_b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const {
|
|
|
|
Array image_array;
|
|
|
|
image_array.resize(get_lod_count());
|
|
|
|
|
2020-02-15 16:37:23 +00:00
|
|
|
for (int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
|
2020-02-05 21:09:14 +01:00
|
|
|
const Rect3i world_box = Rect3i::from_center_extents(Vector3i(center) >> lod_index, Vector3i(extents) >> lod_index);
|
|
|
|
|
|
|
|
if (world_box.size.volume() == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ref<VoxelBuffer> buffer_ref;
|
|
|
|
buffer_ref.instance();
|
|
|
|
VoxelBuffer &buffer = **buffer_ref;
|
|
|
|
buffer.create(world_box.size);
|
|
|
|
|
|
|
|
const Lod &lod = _lods[lod_index];
|
|
|
|
|
|
|
|
world_box.for_each_cell([&](const Vector3i &world_pos) {
|
2021-04-03 20:39:37 +01:00
|
|
|
const float v = lod.data_map.get_voxel_f(world_pos, VoxelBuffer::CHANNEL_SDF);
|
2020-02-05 21:09:14 +01:00
|
|
|
const Vector3i rpos = world_pos - world_box.pos;
|
|
|
|
buffer.set_voxel_f(v, rpos.x, rpos.y, rpos.z, VoxelBuffer::CHANNEL_SDF);
|
|
|
|
});
|
|
|
|
|
|
|
|
Ref<Image> image = buffer.debug_print_sdf_to_image_top_down();
|
|
|
|
image_array[lod_index] = image;
|
|
|
|
}
|
|
|
|
|
|
|
|
return image_array;
|
|
|
|
}
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
int VoxelLodTerrain::_b_debug_get_mesh_block_count() const {
|
|
|
|
int sum = 0;
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
sum += _lods[lod_index].mesh_map.get_block_count();
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
|
|
|
int VoxelLodTerrain::_b_debug_get_data_block_count() const {
|
2020-10-29 00:49:12 +00:00
|
|
|
int sum = 0;
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2021-04-03 20:39:37 +01:00
|
|
|
sum += _lods[lod_index].data_map.get_block_count();
|
2020-10-29 00:49:12 +00:00
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
2020-11-21 18:31:28 +00:00
|
|
|
Error VoxelLodTerrain::_b_debug_dump_as_scene(String fpath) const {
|
|
|
|
Spatial *root = memnew(Spatial);
|
|
|
|
root->set_name(get_name());
|
|
|
|
|
2021-04-18 01:29:26 +01:00
|
|
|
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
|
2020-11-21 18:31:28 +00:00
|
|
|
const Lod &lod = _lods[lod_index];
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
lod.mesh_map.for_all_blocks([root](const VoxelMeshBlock *block) {
|
2020-11-21 18:31:28 +00:00
|
|
|
block->for_each_mesh_instance_with_transform([root, block](const DirectMeshInstance &dmi, Transform t) {
|
|
|
|
Ref<Mesh> mesh = dmi.get_mesh();
|
|
|
|
|
|
|
|
if (mesh.is_valid()) {
|
|
|
|
MeshInstance *mi = memnew(MeshInstance);
|
|
|
|
mi->set_mesh(mesh);
|
|
|
|
mi->set_transform(t);
|
|
|
|
// TODO Transition mesh visibility?
|
|
|
|
mi->set_visible(block->is_visible());
|
|
|
|
root->add_child(mi);
|
|
|
|
// The owner must be set after adding to parent
|
|
|
|
mi->set_owner(root);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Ref<PackedScene> scene = memnew(PackedScene);
|
|
|
|
Error err = scene->pack(root);
|
|
|
|
if (err != OK) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
err = ResourceSaver::save(fpath, scene, ResourceSaver::FLAG_BUNDLE_RESOURCES);
|
|
|
|
|
|
|
|
memdelete(root);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
void VoxelLodTerrain::_bind_methods() {
|
2019-05-05 19:54:47 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_material", "material"), &VoxelLodTerrain::set_material);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_material"), &VoxelLodTerrain::get_material);
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_view_distance", "distance_in_voxels"), &VoxelLodTerrain::set_view_distance);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_view_distance"), &VoxelLodTerrain::get_view_distance);
|
|
|
|
|
2019-08-25 13:04:49 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelLodTerrain::get_generate_collisions);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelLodTerrain::set_generate_collisions);
|
|
|
|
|
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_lod_count"), &VoxelLodTerrain::get_collision_lod_count);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_lod_count", "count"), &VoxelLodTerrain::set_collision_lod_count);
|
|
|
|
|
2021-02-18 19:50:47 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("get_collision_update_delay"), &VoxelLodTerrain::get_collision_update_delay);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_collision_update_delay", "delay_msec"),
|
|
|
|
&VoxelLodTerrain::set_collision_update_delay);
|
|
|
|
|
2021-03-02 22:49:42 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("get_lod_fade_duration"), &VoxelLodTerrain::get_lod_fade_duration);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_lod_fade_duration", "seconds"), &VoxelLodTerrain::set_lod_fade_duration);
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_lod_count", "lod_count"), &VoxelLodTerrain::set_lod_count);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_lod_count"), &VoxelLodTerrain::get_lod_count);
|
|
|
|
|
2021-03-27 16:38:20 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("set_lod_distance", "lod_distance"), &VoxelLodTerrain::set_lod_distance);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_lod_distance"), &VoxelLodTerrain::get_lod_distance);
|
2019-05-05 19:54:47 +01:00
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_mesh_block_size"), &VoxelLodTerrain::get_mesh_block_size);
|
2021-04-03 23:51:11 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_mesh_block_size"), &VoxelLodTerrain::set_mesh_block_size);
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_data_block_size"), &VoxelLodTerrain::get_data_block_size);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_data_block_region_extent"), &VoxelLodTerrain::get_data_block_region_extent);
|
2021-04-03 23:51:11 +01:00
|
|
|
|
2020-12-25 17:08:40 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelLodTerrain::_b_get_statistics);
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("voxel_to_data_block_position", "lod_index"),
|
|
|
|
&VoxelLodTerrain::voxel_to_data_block_position);
|
2021-04-05 03:44:52 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("voxel_to_mesh_block_position", "lod_index"),
|
|
|
|
&VoxelLodTerrain::voxel_to_mesh_block_position);
|
2019-05-04 00:02:10 +01:00
|
|
|
|
2019-09-03 22:54:40 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_voxel_tool"), &VoxelLodTerrain::get_voxel_tool);
|
2020-07-25 16:29:16 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelLodTerrain::_b_save_modified_blocks);
|
2019-09-03 22:54:40 +01:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_run_stream_in_editor"), &VoxelLodTerrain::set_run_stream_in_editor);
|
|
|
|
ClassDB::bind_method(D_METHOD("is_stream_running_in_editor"), &VoxelLodTerrain::is_stream_running_in_editor);
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_voxel_bounds"), &VoxelLodTerrain::_b_set_voxel_bounds);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_voxel_bounds"), &VoxelLodTerrain::_b_get_voxel_bounds);
|
|
|
|
|
2020-10-30 19:02:00 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &VoxelLodTerrain::set_process_mode);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_process_mode"), &VoxelLodTerrain::get_process_mode);
|
|
|
|
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_raycast_mesh_block", "origin", "dir"),
|
|
|
|
&VoxelLodTerrain::debug_raycast_mesh_block);
|
2021-04-05 03:46:15 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_get_data_block_info", "block_pos", "lod"),
|
|
|
|
&VoxelLodTerrain::debug_get_data_block_info);
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_get_mesh_block_info", "block_pos", "lod"),
|
|
|
|
&VoxelLodTerrain::debug_get_mesh_block_info);
|
2021-03-11 22:55:52 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_get_octrees_detailed"), &VoxelLodTerrain::debug_get_octrees_detailed);
|
2020-10-22 22:43:31 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_print_sdf_top_down", "center", "extents"),
|
|
|
|
&VoxelLodTerrain::_b_debug_print_sdf_top_down);
|
2021-04-03 20:39:37 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_get_mesh_block_count"), &VoxelLodTerrain::_b_debug_get_mesh_block_count);
|
|
|
|
ClassDB::bind_method(D_METHOD("debug_get_data_block_count"), &VoxelLodTerrain::_b_debug_get_data_block_count);
|
2020-11-21 18:31:28 +00:00
|
|
|
ClassDB::bind_method(D_METHOD("debug_dump_as_scene", "path"), &VoxelLodTerrain::_b_debug_dump_as_scene);
|
2020-01-05 23:27:43 +00:00
|
|
|
|
2020-08-14 20:33:09 +01:00
|
|
|
//ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelLodTerrain::_on_stream_params_changed);
|
2019-08-24 01:44:27 +01:00
|
|
|
|
2020-10-30 19:02:00 +00:00
|
|
|
BIND_ENUM_CONSTANT(PROCESS_MODE_IDLE);
|
|
|
|
BIND_ENUM_CONSTANT(PROCESS_MODE_PHYSICS);
|
|
|
|
BIND_ENUM_CONSTANT(PROCESS_MODE_DISABLED);
|
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Bounds", "");
|
|
|
|
|
2019-05-04 00:02:10 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance");
|
2021-03-02 22:49:42 +00:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::AABB, "voxel_bounds"), "set_voxel_bounds", "get_voxel_bounds");
|
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Level of detail", "");
|
|
|
|
|
2019-05-05 19:54:47 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_count"), "set_lod_count", "get_lod_count");
|
2021-03-27 16:38:20 +00:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "lod_distance"), "set_lod_distance", "get_lod_distance");
|
2021-03-02 22:49:42 +00:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_fade_duration"), "set_lod_fade_duration", "get_lod_fade_duration");
|
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Material", "");
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Material"),
|
|
|
|
"set_material", "get_material");
|
2021-03-02 22:49:42 +00:00
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Collisions", "");
|
|
|
|
|
2020-10-22 22:43:31 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"),
|
|
|
|
"set_generate_collisions", "get_generate_collisions");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_lod_count"),
|
|
|
|
"set_collision_lod_count", "get_collision_lod_count");
|
2021-03-27 16:38:52 +00:00
|
|
|
// TODO Collision mask and layer
|
2021-02-18 19:50:47 +00:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_update_delay"),
|
|
|
|
"set_collision_update_delay", "get_collision_update_delay");
|
2021-03-02 22:49:42 +00:00
|
|
|
|
2021-03-27 16:38:52 +00:00
|
|
|
ADD_GROUP("Advanced", "");
|
|
|
|
|
|
|
|
// TODO Probably should be in parent class?
|
2020-08-14 20:33:09 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "run_stream_in_editor"),
|
|
|
|
"set_run_stream_in_editor", "is_stream_running_in_editor");
|
2021-04-03 23:51:11 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_block_size"), "set_mesh_block_size", "get_mesh_block_size");
|
2019-05-04 00:02:10 +01:00
|
|
|
}
|