#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(0), Vector3i(VoxelConstants::MAX_VOLUME_EXTENT)); struct ApplyMeshUpdateTask : public IVoxelTimeSpreadTask { 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 _reception_buffers.callback_data = this; _reception_buffers.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); }; _volume_id = VoxelServer::get_singleton()->add_volume(&_reception_buffers, VoxelServer::VOLUME_SPARSE_GRID); // For ease of use in editor Ref default_mesher; default_mesher.instance(); _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) { p_list->push_back(PropertyInfo( Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); } } 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