#include "voxel_terrain.h" #include "../constants/voxel_constants.h" #include "../constants/voxel_string_names.h" #include "../edition/voxel_tool_terrain.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 #include #include VoxelTerrain::VoxelTerrain() { // 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. set_notify_transform(true); // TODO Should it actually be finite for better discovery? // 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; VoxelTerrain *self = nullptr; VoxelServer::BlockMeshOutput data; }; // Mesh updates are spread over frames by scheduling them in a task runner of VoxelServer, // but instead of using a reception buffer we use a callback, // because this kind of task scheduling would otherwise delay the update by 1 frame VoxelServer::VolumeCallbacks callbacks; callbacks.data = this; callbacks.mesh_output_callback = [](void *cb_data, const VoxelServer::BlockMeshOutput &ob) { VoxelTerrain *self = reinterpret_cast(cb_data); ApplyMeshUpdateTask *task = memnew(ApplyMeshUpdateTask); task->volume_id = self->_volume_id; task->self = self; task->data = ob; VoxelServer::get_singleton()->push_time_spread_task(task); }; callbacks.data_output_callback = [](void *cb_data, VoxelServer::BlockDataOutput &ob) { VoxelTerrain *self = reinterpret_cast(cb_data); self->apply_data_block_response(ob); }; _volume_id = VoxelServer::get_singleton()->add_volume(callbacks, VoxelServer::VOLUME_SPARSE_GRID); // TODO Can't setup a default mesher anymore due to a Godot 4 warning... // For ease of use in editor // Ref default_mesher; // default_mesher.instantiate(); // _mesher = default_mesher; } VoxelTerrain::~VoxelTerrain() { PRINT_VERBOSE("Destroying VoxelTerrain"); VoxelServer::get_singleton()->remove_volume(_volume_id); } // TODO See if there is a way to specify materials in voxels directly? bool VoxelTerrain::_set(const StringName &p_name, const Variant &p_value) { if (p_name.operator String().begins_with("material/")) { unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int(); ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false); set_material(idx, p_value); return true; } return false; } bool VoxelTerrain::_get(const StringName &p_name, Variant &r_ret) const { if (p_name.operator String().begins_with("material/")) { unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int(); ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false); r_ret = get_material(idx); return true; } return false; } void VoxelTerrain::_get_property_list(List *p_list) const { // Need to add a group here because otherwise it appears under the last group declared in `_bind_methods` p_list->push_back(PropertyInfo(Variant::NIL, "Materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (unsigned int i = 0; i < VoxelMesherBlocky::MAX_MATERIALS; ++i) { // TODO Can I specify the class names in a type-safe way? p_list->push_back(PropertyInfo( Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,BaseMaterial3D")); } } void VoxelTerrain::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