#include "voxel_lod_terrain.h" #include "../../constants/voxel_string_names.h" #include "../../edition/voxel_tool_lod_terrain.h" #include "../../meshers/blocky/voxel_mesher_blocky.h" #include "../../meshers/transvoxel/voxel_mesher_transvoxel.h" #include "../../server/load_all_blocks_data_task.h" #include "../../server/voxel_server_gd.h" #include "../../server/voxel_server_updater.h" #include "../../storage/voxel_buffer_gd.h" #include "../../util/container_funcs.h" #include "../../util/godot/funcs.h" #include "../../util/log.h" #include "../../util/math/conv.h" #include "../../util/profiling.h" #include "../../util/profiling_clock.h" #include "../../util/string_funcs.h" #include "../../util/tasks/async_dependency_tracker.h" #include "../../util/thread/mutex.h" #include "../../util/thread/rw_lock.h" #include "../instancing/voxel_instancer.h" #include "voxel_lod_terrain_update_task.h" #include #include #include #include namespace zylann::voxel { namespace { Ref build_mesh(Span surfaces, Mesh::PrimitiveType primitive, int flags, Ref material) { ZN_PROFILE_SCOPE(); Ref mesh; unsigned int surface_index = 0; for (unsigned int i = 0; i < surfaces.size(); ++i) { const VoxelMesher::Output::Surface &surface = surfaces[i]; Array arrays = surface.arrays; if (arrays.is_empty()) { continue; } CRASH_COND(arrays.size() != Mesh::ARRAY_MAX); if (!is_surface_triangulated(arrays)) { continue; } if (mesh.is_null()) { mesh.instantiate(); } // 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, arrays, 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 (mesh.is_valid() && is_mesh_empty(**mesh)) { mesh = Ref(); } return mesh; } struct BeforeUnloadMeshAction { std::vector> &shader_material_pool; void operator()(VoxelMeshBlockVLT &block) { ZN_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()))); VoxelLodTerrainUpdateData::BlockToSave b; b.voxels = make_shared_instance(); { RWLockRead lock(block.get_voxels().get_lock()); block.get_voxels_const().duplicate_to(*b.voxels, true); } b.position = block.get_position(); b.lod = block.get_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. ZN_PRINT_VERBOSE("Construct VoxelLodTerrain"); _data = make_shared_instance(); _update_data = make_shared_instance(); _update_data->task_is_complete = true; _streaming_dependency = make_shared_instance(); _meshing_dependency = make_shared_instance(); set_notify_transform(true); // Doing this to setup the defaults set_process_callback(_process_callback); // Infinite by default _update_data->settings.bounds_in_voxels = Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)); struct ApplyMeshUpdateTask : public ITimeSpreadTask { void run(TimeSpreadTaskContext &ctx) override { if (!VoxelServer::get_singleton().is_volume_valid(volume_id)) { // The node can have been destroyed while this task was still pending ZN_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_main_thread_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::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() { ZN_PRINT_VERBOSE("Destroy VoxelLodTerrain"); abort_async_edits(); _streaming_dependency->valid = false; _meshing_dependency->valid = false; 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) { // TODO Update existing block surfaces _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 _update_data->settings.mesh_block_size_po2; } unsigned int VoxelLodTerrain::get_mesh_block_size() const { return 1 << _update_data->settings.mesh_block_size_po2; } void VoxelLodTerrain::set_stream(Ref p_stream) { if (p_stream == _stream) { return; } _stream = p_stream; StreamingDependency::reset(_streaming_dependency, _stream, _generator); #ifdef TOOLS_ENABLED if (p_stream.is_valid()) { if (Engine::get_singleton()->is_editor_hint()) { Ref