#include "voxel_lod_terrain.h" #include "../constants/voxel_string_names.h" #include "../edition/voxel_tool_lod_terrain.h" #include "../meshers/transvoxel/voxel_mesher_transvoxel.h" #include "../server/voxel_server.h" #include "../util/funcs.h" #include "../util/godot/funcs.h" #include "../util/macros.h" #include "../util/profiling.h" #include "../util/profiling_clock.h" #include "../util/tasks/async_dependency_tracker.h" #include "instancing/voxel_instancer.h" #include #include #include #include using namespace zylann; using namespace voxel; namespace { Ref build_mesh( const Vector surfaces, Mesh::PrimitiveType primitive, int flags, Ref material) { VOXEL_PROFILE_SCOPE(); Ref mesh; mesh.instantiate(); unsigned int surface_index = 0; for (int i = 0; i < surfaces.size(); ++i) { Array surface = surfaces[i]; if (surface.is_empty()) { continue; } CRASH_COND(surface.size() != Mesh::ARRAY_MAX); if (!is_surface_triangulated(surface)) { continue; } // TODO Use `add_surface`, it's about 20% faster after measuring in Tracy (though we may see if Godot 4 expects // the same) mesh->add_surface_from_arrays(primitive, surface, Array(), Dictionary(), flags); mesh->surface_set_material(surface_index, material); // No multi-material supported yet ++surface_index; } // Debug code to highlight vertex sharing /*if (mesh->get_surface_count() > 0) { Array wireframe_surface = generate_debug_seams_wireframe_surface(mesh, 0); if (wireframe_surface.size() > 0) { const int wireframe_surface_index = mesh->get_surface_count(); mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, wireframe_surface); Ref line_material; line_material.instance(); line_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true); line_material->set_albedo(Color(1.0, 0.0, 1.0)); mesh->surface_set_material(wireframe_surface_index, line_material); } }*/ if (is_mesh_empty(mesh)) { mesh = Ref(); } return mesh; } // To use on loaded blocks static inline void schedule_mesh_update(VoxelMeshBlock *block, std::vector &blocks_pending_update) { if (block->get_mesh_state() != VoxelMeshBlock::MESH_UPDATE_NOT_SENT) { if (block->active) { // Schedule an update block->set_mesh_state(VoxelMeshBlock::MESH_UPDATE_NOT_SENT); blocks_pending_update.push_back(block->position); } else { // Just mark it as needing update, so the visibility system will schedule its update when needed block->set_mesh_state(VoxelMeshBlock::MESH_NEED_UPDATE); } } } struct BeforeUnloadDataAction { std::vector &blocks_to_save; bool save; void operator()(VoxelDataBlock *block) { // Save if modified // TODO Don't ask for save if the stream doesn't support it! if (save && block->is_modified()) { //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->get_voxels_shared(); b.position = block->position; b.lod = block->lod_index; blocks_to_save.push_back(b); } } }; struct BeforeUnloadMeshAction { std::vector> &shader_material_pool; void operator()(VoxelMeshBlock *block) { VOXEL_PROFILE_SCOPE_NAMED("Recycle material"); // Recycle material Ref sm = block->get_shader_material(); if (sm.is_valid()) { shader_material_pool.push_back(sm); block->set_shader_material(Ref()); } } }; struct ScheduleSaveAction { std::vector &blocks_to_save; void operator()(VoxelDataBlock *block) { // Save if modified // TODO Don't ask for save if the stream doesn't support it! if (block->is_modified()) { //print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3()))); VoxelLodTerrain::BlockToSave b; b.voxels = gd_make_shared(); { RWLockRead lock(block->get_voxels().get_lock()); block->get_voxels_const().duplicate_to(*b.voxels, true); } b.position = block->position; b.lod = block->lod_index; blocks_to_save.push_back(b); block->set_modified(false); } } }; static inline uint64_t get_ticks_msec() { return Time::get_singleton()->get_ticks_msec(); } } // namespace VoxelLodTerrain::VoxelLodTerrain() { // 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. PRINT_VERBOSE("Construct VoxelLodTerrain"); _data = gd_make_shared(); set_notify_transform(true); // Doing this to setup the defaults set_process_callback(_process_callback); // Infinite by default _bounds_in_voxels = Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(VoxelConstants::MAX_VOLUME_EXTENT)); struct ApplyMeshUpdateTask : public zylann::ITimeSpreadTask { void run() override { if (!VoxelServer::get_singleton()->is_volume_valid(volume_id)) { // The node can have been destroyed while this task was still pending PRINT_VERBOSE("Cancelling ApplyMeshUpdateTask, volume_id is invalid"); return; } self->apply_mesh_update(data); } uint32_t volume_id = 0; VoxelLodTerrain *self = nullptr; VoxelServer::BlockMeshOutput data; }; // Mesh updates are spread over frames by scheduling them in a task runner of VoxelServer, // but instead of using a reception buffer we use a callback, // because this kind of task scheduling would otherwise delay the update by 1 frame VoxelServer::VolumeCallbacks callbacks; callbacks.data = this; callbacks.mesh_output_callback = [](void *cb_data, const VoxelServer::BlockMeshOutput &ob) { VoxelLodTerrain *self = reinterpret_cast(cb_data); ApplyMeshUpdateTask *task = memnew(ApplyMeshUpdateTask); task->volume_id = self->get_volume_id(); task->self = self; task->data = ob; VoxelServer::get_singleton()->push_time_spread_task(task); }; callbacks.data_output_callback = [](void *cb_data, VoxelServer::BlockDataOutput &ob) { VoxelLodTerrain *self = reinterpret_cast(cb_data); self->apply_data_block_response(ob); }; _volume_id = VoxelServer::get_singleton()->add_volume(callbacks, VoxelServer::VOLUME_SPARSE_OCTREE); VoxelServer::get_singleton()->set_volume_octree_lod_distance(_volume_id, get_lod_distance()); // 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 set_lod_count(4); set_lod_distance(48.f); } VoxelLodTerrain::~VoxelLodTerrain() { PRINT_VERBOSE("Destroy VoxelLodTerrain"); abort_async_edits(); VoxelServer::get_singleton()->remove_volume(_volume_id); // Instancer can take care of itself } Ref VoxelLodTerrain::get_material() const { return _material; } void VoxelLodTerrain::set_material(Ref p_material) { _material = p_material; } unsigned int VoxelLodTerrain::get_data_block_size() const { return _data->lods[0].map.get_block_size(); } unsigned int VoxelLodTerrain::get_data_block_size_pow2() const { return _data->lods[0].map.get_block_size_pow2(); } unsigned int VoxelLodTerrain::get_mesh_block_size_pow2() const { return _lods[0].mesh_map.get_block_size_pow2(); } unsigned int VoxelLodTerrain::get_mesh_block_size() const { return _lods[0].mesh_map.get_block_size(); } void VoxelLodTerrain::set_stream(Ref p_stream) { if (p_stream == _stream) { return; } _stream = p_stream; #ifdef TOOLS_ENABLED if (_stream.is_valid()) { if (Engine::get_singleton()->is_editor_hint()) { Ref