diff --git a/project/demo/src/DemoScene.gd b/project/demo/src/DemoScene.gd index d7ac5f5c..7934aa0d 100644 --- a/project/demo/src/DemoScene.gd +++ b/project/demo/src/DemoScene.gd @@ -1,6 +1,10 @@ @tool extends Node +@export var dump_tree: bool = false : + set(value): + print_tree() + func _ready(): if not Engine.is_editor_hint() and has_node("UI"): diff --git a/src/constants.h b/src/constants.h index f99125b0..ee2019c1 100644 --- a/src/constants.h +++ b/src/constants.h @@ -87,4 +87,23 @@ using namespace godot; return ret; \ } +// Global Types + +struct Vector2iHash { + std::size_t operator()(const Vector2i &v) const { + std::size_t h1 = std::hash()(v.x); + std::size_t h2 = std::hash()(v.y); + return h1 ^ (h2 << 1); + } +}; + +struct Vector3Hash { + std::size_t operator()(const Vector3 &v) const { + std::size_t h1 = std::hash()(v.x); + std::size_t h2 = std::hash()(v.y); + std::size_t h3 = std::hash()(v.z); + return h1 ^ (h2 << 1) ^ (h3 << 2); + } +}; + #endif // CONSTANTS_CLASS_H \ No newline at end of file diff --git a/src/geoclipmap.cpp b/src/geoclipmap.cpp index 7bf91961..af4f0cff 100644 --- a/src/geoclipmap.cpp +++ b/src/geoclipmap.cpp @@ -11,17 +11,6 @@ // Half each triangle, have to check for longest side. void GeoClipMap::_subdivide_half(PackedVector3Array &vertices, PackedInt32Array &indices) { - - // Hash for map for quick vertex search, for loop was very very slow! - struct Vector3Hash { - std::size_t operator()(const Vector3 &v) const { - std::size_t h1 = std::hash()(v.x); - std::size_t h2 = std::hash()(v.y); - std::size_t h3 = std::hash()(v.z); - return h1 ^ (h2 << 1) ^ (h3 << 2); - } - }; - PackedVector3Array new_vertices; PackedInt32Array new_indices; diff --git a/src/register_types.cpp b/src/register_types.cpp index 0c135b37..a3abd6cf 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -21,12 +21,12 @@ void initialize_terrain_3d(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); // Deprecated 0.9.3 - Remove 0.9.4+ + ClassDB::register_class(); // Deprecated 0.9.3 - Remove 1.0 ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); // Deprecated 0.9.2 - Remove 0.9.3+ - ClassDB::register_class(); // Deprecated 0.9.2 - Remove 0.9.3+ + ClassDB::register_class(); // Deprecated 0.9.2 - Remove 1.0 + ClassDB::register_class(); // Deprecated 0.9.2 - Remove 1.0 } void uninitialize_terrain_3d(ModuleInitializationLevel p_level) { diff --git a/src/shaders/debug_views.glsl b/src/shaders/debug_views.glsl index a71568fb..3c949a0b 100644 --- a/src/shaders/debug_views.glsl +++ b/src/shaders/debug_views.glsl @@ -133,8 +133,8 @@ R"( //INSERT: DEBUG_REGION_GRID // Show region grid vec3 __pixel_pos1 = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz; - float __region_line = 0.5; // Region line thickness - __region_line = .1*sqrt(length(v_camera_pos - __pixel_pos1)); + float __region_line = 1.0; // Region line thickness + __region_line *= .1*sqrt(length(v_camera_pos - __pixel_pos1)); if (mod(__pixel_pos1.x * _vertex_density + __region_line*.5, _region_size) <= __region_line || mod(__pixel_pos1.z * _vertex_density + __region_line*.5, _region_size) <= __region_line ) { ALBEDO = vec3(1.); @@ -162,4 +162,15 @@ R"( } ALBEDO = fma(ALBEDO, 1.-__vertex_mul, __vertex_add); +//INSERT: DEBUG_INSTANCER_GRID + // Show region grid + vec3 __pixel_pos3 = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz; + float __cell_line = 0.5; // Cell line thickness + __cell_line *= .1*sqrt(length(v_camera_pos - __pixel_pos3)); + #define CELL_SIZE 32 + if (mod(__pixel_pos3.x * _vertex_density + __cell_line*.5, CELL_SIZE) <= __cell_line || + mod(__pixel_pos3.z * _vertex_density + __cell_line*.5, CELL_SIZE) <= __cell_line ) { + ALBEDO = vec3(.033); + } + )" \ No newline at end of file diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 8314d1e9..7749bdb6 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -147,21 +147,21 @@ void Terrain3D::_grab_camera() { } void Terrain3D::_build_containers() { - _label_nodes = memnew(Node); - _label_nodes->set_name("Labels"); - add_child(_label_nodes, true); - _mmi_nodes = memnew(Node); - _mmi_nodes->set_name("MMIs"); - add_child(_mmi_nodes, true); + _label_parent = memnew(Node); + _label_parent->set_name("Labels"); + add_child(_label_parent, true); + _mmi_parent = memnew(Node); + _mmi_parent->set_name("MMI"); + add_child(_mmi_parent, true); } void Terrain3D::_destroy_containers() { - memdelete_safely(_label_nodes); - memdelete_safely(_mmi_nodes); + memdelete_safely(_label_parent); + memdelete_safely(_mmi_parent); } void Terrain3D::_destroy_labels() { - Array labels = _label_nodes->get_children(); + Array labels = _label_parent->get_children(); LOG(DEBUG, "Destroying ", labels.size(), " region labels"); for (int i = 0; i < labels.size(); i++) { Node *label = cast_to(labels[i]); @@ -223,7 +223,7 @@ void Terrain3D::_update_collision() { int time = Time::get_singleton()->get_ticks_msec(); int shape_size = _region_size + 1; float hole_const = NAN; - // DEPRECATED - Jolt v0.12 supports NAN. Remove check when it's old. + // DEPRECATED - Jolt v0.12 supports NAN. Remove 1.0. Jolt 0.13 supports 4.3. if (ProjectSettings::get_singleton()->get_setting("physics/3d/physics_engine") == "JoltPhysics3D") { hole_const = FLT_MAX; } @@ -879,7 +879,7 @@ void Terrain3D::update_region_labels() { label->set_visibility_range_end(_label_distance); label->set_visibility_range_end_margin(_label_distance / 10.f); label->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_SELF); - _label_nodes->add_child(label, true); + _label_parent->add_child(label, true); Vector2i loc = region_locations[i]; Vector3 pos = Vector3(real_t(loc.x) + .5f, 0.f, real_t(loc.y) + .5f) * _region_size * _vertex_spacing; real_t height = _data->get_height(pos); @@ -984,7 +984,6 @@ void Terrain3D::set_mesh_size(const int p_size) { void Terrain3D::set_vertex_spacing(const real_t p_spacing) { real_t spacing = CLAMP(p_spacing, 0.25f, 100.0f); if (_vertex_spacing != spacing) { - real_t scale = spacing / _vertex_spacing; _vertex_spacing = spacing; LOG(INFO, "Setting vertex spacing: ", _vertex_spacing); _clear_meshes(); @@ -992,15 +991,8 @@ void Terrain3D::set_vertex_spacing(const real_t p_spacing) { _destroy_instancer(); _initialize(); _data->_vertex_spacing = _vertex_spacing; - Dictionary mmis = _instancer->get_mmis(); - Array keys = mmis.keys(); - for (int i = 0; i < keys.size(); i++) { - MultiMeshInstance3D *mmi = cast_to(mmis[keys[i]]); - if (mmi != nullptr) { - mmi->set_scale(Vector3(_vertex_spacing, 1.f, _vertex_spacing)); - } - } update_region_labels(); + _instancer->_update_vertex_spacing(_vertex_spacing); } if (IS_EDITOR && _plugin != nullptr) { _plugin->call("update_region_grid"); @@ -1573,12 +1565,12 @@ void Terrain3D::_bind_methods() { ADD_SIGNAL(MethodInfo("material_changed")); ADD_SIGNAL(MethodInfo("assets_changed")); - // DEPRECATED 0.9.2 - Remove 0.9.3+ + // DEPRECATED 0.9.2 - Remove 1.0 ClassDB::bind_method(D_METHOD("set_texture_list", "texture_list"), &Terrain3D::set_texture_list); ClassDB::bind_method(D_METHOD("get_texture_list"), &Terrain3D::get_texture_list); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_list", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DTextureList", PROPERTY_USAGE_NONE), "set_texture_list", "get_texture_list"); - // DEPRECATED 0.9.3 - Remove 0.9.4+ + // DEPRECATED 0.9.3 - Remove 1.0 ClassDB::bind_method(D_METHOD("set_storage", "storage"), &Terrain3D::set_storage); ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); ClassDB::bind_method(D_METHOD("split_storage"), &Terrain3D::split_storage); @@ -1589,7 +1581,7 @@ void Terrain3D::_bind_methods() { // DEPRECATED Functions /////////////////////////// -// DEPRECATED 0.9.2 - Remove 0.9.3+ +// DEPRECATED 0.9.2 - Remove 1.0 void Terrain3D::set_texture_list(const Ref &p_texture_list) { if (p_texture_list.is_null()) { LOG(ERROR, "Attempted to upgrade Terrain3DTextureList, but received null (perhaps already a Terrain3DAssets). Reconnect manually and save."); @@ -1603,7 +1595,7 @@ void Terrain3D::set_texture_list(const Ref &p_texture_list set_assets(assets); } -// DEPRECATED 0.9.3 - Remove 0.9.4+ +// DEPRECATED 0.9.3 - Remove 1.0 void Terrain3D::set_storage(const Ref &p_storage) { _storage = p_storage; if (p_storage.is_valid()) { @@ -1640,7 +1632,7 @@ void Terrain3D::split_storage() { region->set_height_map(hmaps[i]); region->set_control_map(ctlmaps[i]); region->set_color_map(clrmaps[i]); - region->set_multimeshes(mms[locations[i]]); + region->set_multimeshes(mms[locations[i]]); // TODO BROKEN _data->add_region(region, false); LOG(INFO, "Splicing region ", locations[i]); } diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 5afdbed7..9413c49b 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -105,8 +105,8 @@ class Terrain3D : public Node3D { uint32_t _mouse_layer = 32; // Parent containers for child nodes - Node *_label_nodes; - Node *_mmi_nodes; + Node *_label_parent; + Node *_mmi_parent; void _initialize(); void __process(const double p_delta); @@ -155,7 +155,7 @@ class Terrain3D : public Node3D { void set_assets(const Ref &p_assets); Ref get_assets() const { return _assets; } Terrain3DInstancer *get_instancer() const { return _instancer; } - Node *get_mmi_parent() const { return _mmi_nodes; } + Node *get_mmi_parent() const { return _mmi_parent; } void set_editor(Terrain3DEditor *p_editor); Terrain3DEditor *get_editor() const { return _editor; } void set_plugin(EditorPlugin *p_plugin); @@ -228,11 +228,11 @@ class Terrain3D : public Node3D { static void _bind_methods(); public: - // DEPRECATED 0.9.2 - Remove 0.9.3+ + // DEPRECATED 0.9.2 - Remove 1.0 void set_texture_list(const Ref &p_texture_list); Ref get_texture_list() const { return Ref(); } - // DEPRECATED 0.9.3 - Remove 0.9.4+ + // DEPRECATED 0.9.3 - Remove 1.0 Ref _storage; void set_storage(const Ref &p_storage); Ref get_storage() const { return _storage; } diff --git a/src/terrain_3d_assets.cpp b/src/terrain_3d_assets.cpp index f5e9eacf..e545138e 100644 --- a/src/terrain_3d_assets.cpp +++ b/src/terrain_3d_assets.cpp @@ -596,7 +596,7 @@ void Terrain3DAssets::_bind_methods() { ADD_SIGNAL(MethodInfo("textures_changed")); } -// Deprecated 0.9.2 - Remove 0.9.3+ +// Deprecated 0.9.2 - Remove 1.0 void Terrain3DTextureList::set_textures(const TypedArray &p_textures) { LOG(WARN, "Terrain3DTextureList: Converting Terrain3DTextures to Terrain3DTextureAssets. Save to complete."); for (int i = 0; i < p_textures.size(); i++) { diff --git a/src/terrain_3d_assets.h b/src/terrain_3d_assets.h index a647fde1..a511cb08 100644 --- a/src/terrain_3d_assets.h +++ b/src/terrain_3d_assets.h @@ -89,7 +89,7 @@ class Terrain3DAssets : public Resource { VARIANT_ENUM_CAST(Terrain3DAssets::AssetType); -// Deprecated 0.9.2 - Remove 0.9.3+ +// Deprecated 0.9.2 - Remove 1.0 class Terrain3DTexture : public Terrain3DTextureAsset { GDCLASS(Terrain3DTexture, Terrain3DTextureAsset); diff --git a/src/terrain_3d_data.cpp b/src/terrain_3d_data.cpp index dcc2e148..cad9cde8 100644 --- a/src/terrain_3d_data.cpp +++ b/src/terrain_3d_data.cpp @@ -144,6 +144,7 @@ void Terrain3DData::change_region_size(int p_new_size) { new_region.instantiate(); new_region->set_location(loc); new_region->set_region_size(p_new_size); + new_region->set_vertex_spacing(_vertex_spacing); new_region->set_modified(true); new_region->sanitize_maps(); @@ -220,6 +221,7 @@ Ref Terrain3DData::add_region_blank(const Vector2i &p_region_lo region.instantiate(); region->set_location(p_region_loc); region->set_region_size(_region_size); + region->set_vertex_spacing(_vertex_spacing); if (add_region(region, p_update) == OK) { region->set_modified(true); return region; @@ -257,6 +259,7 @@ Error Terrain3DData::add_region(const Ref &p_region, const bool LOG(DEBUG, "Storing region ", region_loc, " version ", vformat("%.3f", p_region->get_version()), " id: ", _region_locations.size()); if (p_update) { force_update_maps(); + _terrain->get_instancer()->force_update_mmis(); } return OK; } @@ -293,6 +296,7 @@ void Terrain3DData::remove_region(const Ref &p_region, const bo if (p_update) { LOG(DEBUG, "Updating generated maps"); force_update_maps(); + _terrain->get_instancer()->force_update_mmis(); } } @@ -587,7 +591,11 @@ void Terrain3DData::set_pixel(const MapType p_map_type, const Vector3 &p_global_ Vector2i region_loc = get_region_location(p_global_position); Ref region = get_region(region_loc); if (region.is_null()) { - LOG(ERROR, "No region found at: ", p_global_position); + LOG(ERROR, "No active region found at: ", p_global_position); + return; + } + if (region->is_deleted()) { + LOG(ERROR, "No active region found at: ", p_global_position); return; } Vector2i global_offset = region_loc * _region_size; @@ -609,6 +617,9 @@ Color Terrain3DData::get_pixel(const MapType p_map_type, const Vector3 &p_global if (region.is_null()) { return COLOR_NAN; } + if (region->is_deleted()) { + return COLOR_NAN; + } Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); @@ -625,7 +636,7 @@ real_t Terrain3DData::get_height(const Vector3 &p_global_position) const { const real_t &step = _vertex_spacing; pos.y = 0.f; // Round to nearest vertex - Vector3 pos_round = Vector3(round_multiple(pos.x, step), 0.f, round_multiple(pos.z, step)); + Vector3 pos_round = pos.snapped(Vector3(step, 0.f, step)); // If requested position is close to a vertex, return its height if ((pos - pos_round).length() < 0.01f) { return get_pixel(TYPE_HEIGHT, pos).r; @@ -672,7 +683,7 @@ bool Terrain3DData::is_in_slope(const Vector3 &p_global_position, const Vector2 auto get_height = [&](Vector3 pos) -> real_t { real_t step = _terrain->get_vertex_spacing(); // Round to nearest vertex - Vector3 pos_round = Vector3(round_multiple(pos.x, step), 0.f, round_multiple(pos.z, step)); + Vector3 pos_round = pos.snapped(Vector3(step, 0.f, step)); real_t height = get_pixel(TYPE_HEIGHT, pos_round).r; return std::isnan(height) ? 0.f : height; }; @@ -791,7 +802,7 @@ void Terrain3DData::add_edited_area(const AABB &p_area) { } else { _edited_area = p_area; } - emit_signal("maps_edited", _edited_area); + emit_signal("maps_edited", p_area); } // Recalculates master height range from all active regions current height ranges diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 9b7da9bb..4bec7cf1 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -50,9 +50,9 @@ Ref Terrain3DEditor::_operate_region(const Vector2i &p_region_l if (can_print) { LOG(DEBUG, "Tool: ", _tool, " Op: ", _operation, " processing region ", p_region_loc, ": ", region.is_valid() ? String::num_uint64(region->get_instance_id()) : "Null"); - if (region.is_valid()) { - LOG(DEBUG, region->get_data()); - } + /*if (region.is_valid()) { + LOG(EXTREME, region->get_data()); + }*/ } // Create new region if location is null or deleted @@ -74,7 +74,6 @@ Ref Terrain3DEditor::_operate_region(const Vector2i &p_region_l _original_regions.push_back(region); height_range = region->get_height_range(); _terrain->get_data()->remove_region(region); - _terrain->get_instancer()->force_update_mmis(); changed = true; } @@ -543,14 +542,14 @@ void Terrain3DEditor::_store_undo() { undo_redo->add_undo_method(this, "apply_undo", _undo_data); for (int i = 0; i < _original_regions.size(); i++) { Ref region = _original_regions[i]; - LOG(DEBUG, "Original Region: ", region->get_data()); + //LOG(EXTREME, "Original Region: ", region->get_data()); } LOG(DEBUG, "Storing redo snapshot: ", redo_data); undo_redo->add_do_method(this, "apply_undo", redo_data); for (int i = 0; i < _edited_regions.size(); i++) { Ref region = _edited_regions[i]; - LOG(DEBUG, "Edited Region: ", region->get_data()); + //LOG(EXTREME, "Edited Region: ", region->get_data()); } LOG(DEBUG, "Committing undo action"); @@ -579,7 +578,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_data) { region->set_modified(true); // Tell update_maps() this region has layers that can be individually updated region->set_edited(true); - LOG(DEBUG, "Edited: ", region->get_data()); + //LOG(EXTREME, "Edited: ", region->get_data()); } } @@ -716,7 +715,7 @@ void Terrain3DEditor::start_operation(const Vector3 &p_global_position) { _edited_regions = TypedArray(); _added_removed_locations = TypedArray(); // Reset counter at start to ensure first click places an instance - _terrain->get_instancer()->reset_instance_counter(); + _terrain->get_instancer()->reset_density_counter(); _terrain->get_data()->clear_edited_area(); _operation_position = p_global_position; _operation_movement = Vector3(); @@ -771,7 +770,7 @@ void Terrain3DEditor::stop_operation() { for (int i = 0; i < _edited_regions.size(); i++) { Ref region = _edited_regions[i]; region->set_edited(false); - LOG(DEBUG, "Edited region: ", region->get_data()); + //LOG(EXTREME, "Edited region: ", region->get_data()); // Make duplicate for redo backup _edited_regions[i] = region->duplicate(true); } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 8f96b74a..96a75271 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -11,15 +11,70 @@ // Private Functions /////////////////////////// +void Terrain3DInstancer::_update_vertex_spacing(const real_t p_vertex_spacing) { + IS_DATA_INIT(VOID); + Array region_locations = _terrain->get_data()->get_region_locations(); + for (int r = 0; r < region_locations.size(); r++) { + Vector2i region_loc = region_locations[r]; + Ref region = _terrain->get_data()->get_region(region_loc); + if (region.is_null()) { + LOG(WARN, "Errant null region found at: ", region_loc); + continue; + } + real_t old_spacing = region->get_vertex_spacing(); + if (old_spacing == p_vertex_spacing) { + LOG(DEBUG, "region vertex spacing == vertex spacing, skipping update transform spacing for region at: ", region_loc); + continue; + } + + Dictionary mesh_inst_dict = region->get_instances(); + LOG(DEBUG, "Updating MMIs from: ", region_loc); + + // For all mesh id in region + Array mesh_types = mesh_inst_dict.keys(); + for (int m = 0; m < mesh_types.size(); m++) { + int mesh_id = mesh_types[m]; + Dictionary cell_inst_dict = mesh_inst_dict[mesh_id]; + Array cell_locations = cell_inst_dict.keys(); + for (int c = 0; c < cell_locations.size(); c++) { + // Get instances + Vector2i cell = cell_locations[c]; + Array triple = cell_inst_dict[cell]; + TypedArray xforms = triple[0]; + if (xforms.size() > 0) { + //LOG(MESG, "Found data, Xforms size: ", xforms.size(), ", colors size: ", colors.size()); + } else { + LOG(WARN, "Empty cell in region ", region_loc, " cell ", cell); + // remove this or we have a problem elsewhere + continue; + } + // Descale, then Scale to the new value + for (int i = 0; i < xforms.size(); i++) { + Transform3D t = xforms[i]; + t.origin.x /= old_spacing; + t.origin.x *= p_vertex_spacing; + t.origin.z /= old_spacing; + t.origin.z *= p_vertex_spacing; + xforms[i] = t; + } + triple[0] = xforms; + triple[2] = true; + cell_inst_dict[cell] = triple; + } + } + // after all transforms are updated, set the new region vertex spacing value + region->set_vertex_spacing(p_vertex_spacing); + region->set_modified(true); + } + destroy(); + _update_mmis(); +} + // Creates MMIs based on stored Multimesh data void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_mesh_id) { IS_DATA_INIT(VOID); LOG(INFO, "Updating MMIs for ", (p_region_loc.x == INT32_MAX) ? "all regions" : "region " + String(p_region_loc), (p_mesh_id == -1) ? ", all meshes" : ", mesh " + String::num_int64(p_mesh_id)); - if (_mmis.has(Variant())) { - _mmis.erase(Variant()); - LOG(WARN, "Removed errant null in MMI dictionary"); - } // For specified region_location, or max for all Array region_locations; @@ -35,28 +90,18 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ LOG(WARN, "Errant null region found at: ", region_loc); continue; } - Dictionary mesh_dict = region->get_multimeshes(); - LOG(DEBUG, "Updating MMIs from: ", region_loc); + Dictionary mesh_inst_dict = region->get_instances(); // For specified mesh id in that region, or -1 for all Array mesh_types; if (p_mesh_id < 0) { - mesh_types = mesh_dict.keys(); + mesh_types = mesh_inst_dict.keys(); } else { mesh_types.push_back(p_mesh_id); } for (int m = 0; m < mesh_types.size(); m++) { int mesh_id = mesh_types[m]; - bool fail = false; - - /// Verify the Multimesh data - // Verify Multimesh exists. It should since its keyed - Ref mm = mesh_dict.get(mesh_id, Ref()); - if (mm.is_null()) { - LOG(DEBUG, "Dictionary for mesh id ", mesh_id, " is null, skipping"); - fail = true; - } // Verify mesh id is valid and has a mesh Ref ma = _terrain->get_assets()->get_mesh_asset(mesh_id); Ref mesh; @@ -64,57 +109,168 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ mesh = ma->get_mesh(); if (mesh.is_null()) { LOG(WARN, "MeshAsset ", mesh_id, " valid but mesh is null, skipping"); - fail = true; + continue; } } else { LOG(WARN, "MeshAsset ", mesh_id, " is null, skipping"); - fail = true; - } - // Clear this mesh id for this region and skip if fails the above checks - if (fail) { - clear_by_location(region_loc, mesh_id); continue; } - /// Data seems good, apply it - - // Update mesh in the Multimesh in case IDs or meshes changed. - mm->set_mesh(mesh); - - // Assign MMs to MMIs, creating any missing MMIs - Vector3i mmi_key = Vector3i(region_loc.x, region_loc.y, mesh_id); - MultiMeshInstance3D *mmi; - if (!_mmis.has(mmi_key)) { - LOG(DEBUG, "No MMI found, creating new MultiMeshInstance3D, attaching to tree"); - mmi = memnew(MultiMeshInstance3D); - mmi->set_as_top_level(true); - _terrain->get_mmi_parent()->add_child(mmi, true); - _mmis[mmi_key] = mmi; - LOG(DEBUG, _mmis); + Dictionary cell_inst_dict = mesh_inst_dict[mesh_id]; + Array cell_locations = cell_inst_dict.keys(); + for (int c = 0; c < cell_locations.size(); c++) { + // Get instances + Vector2i cell = cell_locations[c]; + Array triple = cell_inst_dict[cell]; + if (triple.size() < 3) { + LOG(WARN, "Triple is empty"); + continue; + } + TypedArray xforms = triple[0]; + PackedColorArray colors = triple[1]; + bool modified = triple[2]; + if (xforms.size() == 0) { + LOG(WARN, "Empty cell in region ", region_loc, " cell ", cell); + // remove it or problem elsewhere? + continue; + } + + // There are instances in the current region, mesh_type, cell_location + // Need to: + // If needed, create a region container Node, attach to tree, store in _mmi_containers + // If needed, create an MMI, attach to tree, and insert in _mmi_nodes + // Always, create an MM w/ mesh asset, attach to MMI + + // Create MMI container if needed + String rname("Region" + Util::location_to_string(region_loc)); + if (!_mmi_containers.has(region_loc)) { + LOG(DEBUG, "Creating new region MMI container Terrain3D/MMI/", rname); + Node *node = memnew(Node); + node->set_name(rname); + _mmi_containers[region_loc] = node; + _terrain->get_mmi_parent()->add_child(node, true); + } + + // Retrieve MMI or create one + MeshMMIDict &mesh_mmi_dict = _mmi_nodes[region_loc]; + + int lod = 0; // TODO Hard coded LOD0 for now + Vector2i mesh_key(mesh_id, lod); + CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; + + MultiMeshInstance3D *mmi; + if (cell_mmi_dict.count(cell) == 0) { + mmi = memnew(MultiMeshInstance3D); + LOG(DEBUG, "No MMI found, Created new MultiMeshInstance3D: ", uint64_t(mmi)); + // Node name is MMI3D_Cell##_##_Mesh# + String cstring = "_C" + Util::location_to_string(cell).trim_prefix("_"); + mmi->set_name("MMI3D" + cstring + "_M" + String::num_int64(mesh_id)); + mmi->set_as_top_level(true); + cell_mmi_dict[cell] = mmi; + //Attach to tree + Node *node_container = _terrain->get_mmi_parent()->get_node_internal(rname); + if (node_container == nullptr) { + LOG(ERROR, rname, " isn't attached to the tree."); + continue; + } + node_container->add_child(mmi, true); + // new MMI, cannot skip. + modified = true; + } + // If data hasn't changed since last _update_mmis, skip + if (modified == false) { + continue; + } + + mmi = cell_mmi_dict[cell]; + //dump_mmis(); + + // Create MM and assign to MMI + Ref mm = _create_multimesh(mesh_id, xforms, colors); + mmi->set_multimesh(mm); + mmi->set_cast_shadows_setting(ma->get_cast_shadows()); + + // Reposition the MMIs to their region location + Transform3D t = Transform3D(); + int region_size = region->get_region_size(); + real_t vertex_spacing = _terrain->get_vertex_spacing(); + t.origin.x += region_loc.x * region_size * vertex_spacing; + t.origin.z += region_loc.y * region_size * vertex_spacing; + mmi->set_global_transform(t); + + // Set the cell modified state to false + triple[2] = false; } - mmi = cast_to(_mmis[mmi_key]); - mmi->set_multimesh(mm); - mmi->set_cast_shadows_setting(ma->get_cast_shadows()); - if (mmi->is_inside_tree() && mmi->get_global_transform() != Transform3D()) { - LOG(WARN, "Terrain3D parent nodes have non-zero transform. Resetting instancer global_transform"); - mmi->set_global_transform(Transform3D()); + } + } +} +void Terrain3DInstancer::_destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell) { + //LOG(MESG, "Pre process"); + //dump_mmis(); + + if (_mmi_nodes.count(p_region_loc) == 0) { + return; + } + MeshMMIDict &mesh_mmi_dict = _mmi_nodes[p_region_loc]; + + // TODO Hardcoded LOD0, loop through lods + Vector2i mesh_key(p_mesh_id, 0); + if (mesh_mmi_dict.count(mesh_key) == 0) { + return; + } + CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; + + if (cell_mmi_dict.count(p_cell) == 0) { + return; + } + MultiMeshInstance3D *mmi = cell_mmi_dict[p_cell]; + cell_mmi_dict.erase(p_cell); + remove_from_tree(mmi); + memdelete_safely(mmi); + + if (cell_mmi_dict.empty()) { + LOG(DEBUG, "Removing mesh ", mesh_key, " from cell MMI dictionary"); + mesh_mmi_dict.erase(mesh_key); + } + + if (mesh_mmi_dict.empty()) { + LOG(DEBUG, "Removing region ", p_region_loc, " from mesh MMI dictionary"); + _mmi_nodes.erase(p_region_loc); + + Node *node = cast_to(_mmi_containers[p_region_loc]); + if (node && node->get_child_count() == 0) { + LOG(DEBUG, "Removing ", node->get_name()); + _mmi_containers.erase(p_region_loc); + remove_from_tree(node); + memdelete_safely(node); + } else { + // TODO remove + if (node) { + LOG(ERROR, "Removed ", p_region_loc, " from _mmi_nodes, but container still has children."); + } else { + LOG(ERROR, "Removed ", p_region_loc, " from _mmi_nodes, but container is missing."); } } - LOG(DEBUG, "mm: ", mesh_dict); + _terrain->get_mmi_parent()->print_tree(); } - LOG(DEBUG, "_mmis: ", _mmis); + + //LOG(MESG, "Post process"); + //dump_mmis(); } void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id) { - Vector3i mmi_key = Vector3i(p_region_loc.x, p_region_loc.y, p_mesh_id); LOG(DEBUG, "Deleting MMI at: ", p_region_loc, " mesh_id: ", p_mesh_id); - MultiMeshInstance3D *mmi = cast_to(_mmis[mmi_key]); - bool result = _mmis.erase(mmi_key); - LOG(DEBUG, "Removing mmi from dictionary: ", mmi, ", success: ", result); - result = remove_from_tree(mmi); - LOG(DEBUG, "Removing from tree, success: ", result); - result = memdelete_safely(mmi); - LOG(DEBUG, "Deleting MMI, success: ", result); + if (_mmi_nodes.count(p_region_loc) == 0) { + return; + } + MeshMMIDict &mesh_mmi_dict = _mmi_nodes[p_region_loc]; + + // TODO Hard coded LOD0 - loop through + Vector2i mesh_key(p_mesh_id, 0); + CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; + for (auto &cell_pair : cell_mmi_dict) { + _destroy_mmi_by_cell(p_region_loc, p_mesh_id, cell_pair.first); + } } void Terrain3DInstancer::_backup_regionl(const Vector2i &p_region_loc) { @@ -132,7 +288,7 @@ void Terrain3DInstancer::_backup_region(const Ref &p_region) { } } -Ref Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const TypedArray &p_xforms, const TypedArray &p_colors) const { +Ref Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors) const { Ref mm; IS_INIT(mm); Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id); @@ -146,6 +302,7 @@ Ref Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const mm->set_use_colors(true); mm->set_mesh(mesh); + // TODO Transfer data buffer instead of this. See Terrain3DInstancer::print_multimesh_buffer() if (p_xforms.size() > 0) { mm->set_instance_count(p_xforms.size()); for (int i = 0; i < p_xforms.size(); i++) { @@ -158,6 +315,14 @@ Ref Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const return mm; } +Vector2i Terrain3DInstancer::_get_cell(const Vector3 &p_global_position, const int p_region_size) { + real_t vertex_spacing = _terrain->get_vertex_spacing(); + Vector2i cell; + cell.x = (UtilityFunctions::floori(p_global_position.x / vertex_spacing) % p_region_size) / CELL_SIZE; + cell.y = (UtilityFunctions::floori(p_global_position.z / vertex_spacing) % p_region_size) / CELL_SIZE; + return cell; +} + /////////////////////////// // Public Functions /////////////////////////// @@ -174,11 +339,18 @@ void Terrain3DInstancer::initialize(Terrain3D *p_terrain) { void Terrain3DInstancer::destroy() { IS_DATA_INIT(VOID); LOG(INFO, "Destroying all MMIs"); - while (_mmis.size() > 0) { - Vector3i key = _mmis.keys()[0]; - _destroy_mmi_by_location(Vector2i(key.x, key.y), key.z); + int mesh_count = _terrain->get_assets()->get_mesh_count(); + for (auto ®ion_pairs : _mmi_nodes) { + for (int m = 0; m < mesh_count; m++) { + _destroy_mmi_by_location(region_pairs.first, m); + } } - _mmis.clear(); + LOG(WARN, "Verify cleanup:"); + dump_mmis(); + LOG(MESG, "_mmi_nodes size: ", _mmi_nodes.size()); + LOG(MESG, "_mmi_containers size: ", _mmi_containers.size()); + LOG(MESG, "_mmi tree: "); + _terrain->get_mmi_parent()->print_tree(); } void Terrain3DInstancer::clear_by_mesh(const int p_mesh_id) { @@ -202,10 +374,10 @@ void Terrain3DInstancer::clear_by_region(const Ref &p_region, c } Vector2i region_loc = p_region->get_location(); LOG(INFO, "Deleting Multimeshes w/ mesh_id: ", p_mesh_id, " in region: ", region_loc); - Dictionary mesh_dict = p_region->get_multimeshes(); - if (mesh_dict.has(p_mesh_id)) { + Dictionary mesh_inst_dict = p_region->get_instances(); + if (mesh_inst_dict.has(p_mesh_id)) { _backup_region(p_region); - mesh_dict.erase(p_mesh_id); + mesh_inst_dict.erase(p_mesh_id); } _destroy_mmi_by_location(region_loc, p_mesh_id); } @@ -230,7 +402,7 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D .001f, 1000.f); // Density based on strength, mesh AABB and input scale determines how many to place, even fractional - uint32_t count = _get_instace_count(density); + uint32_t count = _get_density_count(density); if (count <= 0) { return; } @@ -254,7 +426,7 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D Terrain3DData *data = _terrain->get_data(); TypedArray xforms; - TypedArray colors; + PackedColorArray colors; for (int i = 0; i < count; i++) { Transform3D t; @@ -316,6 +488,7 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D // Append multimesh if (xforms.size() > 0) { + //LOG(MESG, "Sending ", xforms.size(), " xforms, ", colors.size(), " colors to add_transforms"); add_transforms(mesh_id, xforms, colors); } } @@ -332,6 +505,7 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons bool modifier_shift = p_params.get("modifier_shift", false); real_t brush_size = CLAMP(real_t(p_params.get("size", 10.f)), .5f, 4096.f); // Meters + real_t half_brush_size = brush_size * 0.5 + 1.f; // 1m margin real_t radius = brush_size * .4f; // Ring1's inner radius real_t strength = CLAMP(real_t(p_params.get("strength", .1f)), .01f, 100.f); // (premul) 1-10k% real_t fixed_scale = CLAMP(real_t(p_params.get("fixed_scale", 100.f)) * .01f, .01f, 100.f); // 1-10k% @@ -340,59 +514,125 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons Vector2 slope_range = p_params["slope"]; // 0-90 degrees already clamped in Editor bool invert = p_params["modifier_alt"]; Terrain3DData *data = _terrain->get_data(); + int region_size = _terrain->get_region_size(); + real_t vertex_spacing = _terrain->get_vertex_spacing(); + + // Build list of potential regions to search, rather than searching the entire terrain, calculate possible regions covered + // and check if they are valid; if so add that location to the dictionary keys. + Dictionary r_locs; + // Calculate step distance to ensure every region is checked inside the bounds of brush size. + real_t step = brush_size / ceil(brush_size / real_t(region_size) / vertex_spacing); + for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += step) { + for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += step) { + Vector2i region_loc = data->get_region_location(Vector3(x, 0.f, z)); + if (data->has_region(region_loc)) { + r_locs[region_loc] = 1; + } + } + } - Vector2i region_loc = data->get_region_location(p_global_position); + Array region_queue = r_locs.keys(); + if (region_queue.size() == 0) { + return; + } - // If CTRL+SHIFT pressed, repeat for every mesh, otherwise only do mesh_id - for (int m = (modifier_shift ? 0 : mesh_id); m <= (modifier_shift ? mesh_count - 1 : mesh_id); m++) { - Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); - real_t density = CLAMP(.1f * brush_size * strength * mesh_asset->get_density() / - MAX(0.01f, fixed_scale + .5f * random_scale), - .001f, 1000.f); + for (int r = 0; r < region_queue.size(); r++) { + Vector2i region_loc = region_queue[r]; + Ref region = data->get_region(region_loc); - // Density based on strength, mesh AABB and input scale determines how many to place, even fractional - uint32_t count = _get_instace_count(density); - if (count == 0) { - continue; - } + _backup_region(region); // TODO region hasn't changed yet and might not. Move lower - Ref multimesh = get_multimesh(region_loc, m); - if (multimesh.is_null()) { - LOG(EXTREME, "Multimesh is already null. doing nothing"); + Dictionary mesh_inst_dict = region->get_instances(); + Array mesh_types = mesh_inst_dict.keys(); + if (mesh_types.size() == 0) { continue; } - - LOG(EXTREME, "Removing ", count, " instances from ", p_global_position); - TypedArray xforms; - TypedArray colors; - for (int i = 0; i < multimesh->get_instance_count(); i++) { - Transform3D t = multimesh->get_instance_transform(i); - // If quota not yet met, instance is within a cylinder radius, and can work on slope, remove it - Vector2 origin2d = Vector2(t.origin.x, t.origin.z); - Vector2 mouse2d = Vector2(p_global_position.x, p_global_position.z); - if (count > 0 && (origin2d - mouse2d).length() < radius && - data->is_in_slope(t.origin, slope_range, invert)) { - count--; + Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); + Vector2 localised_ring_center = Vector2(p_global_position.x - global_local_offset.x, p_global_position.z - global_local_offset.z); + // For this mesh id, or all mesh ids + for (int m = (modifier_shift ? 0 : mesh_id); m <= (modifier_shift ? mesh_count - 1 : mesh_id); m++) { + // Check potential cells rather than searching the entire region, whilst marginally + // slower if there are very few cells for the given mesh present it is significantly + // faster when a very large number of cells are present. + int mesh_id = mesh_types[m]; + Dictionary cell_inst_dict = mesh_inst_dict[mesh_id]; + Array cell_locations = cell_inst_dict.keys(); + if (cell_locations.size() == 0) { continue; - } else { - xforms.push_back(t); - colors.push_back(multimesh->get_instance_color(i)); + } + Dictionary c_locs; + // Calculate step distance to ensure every cell is checked inside the bounds of brush size. + real_t cell_step = brush_size / ceil(brush_size / real_t(CELL_SIZE) / vertex_spacing); + for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += cell_step) { + for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += cell_step) { + Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; + // Manually calculate cell pos without modulus, locations not in the current region will not be found. + Vector2i cell_loc; + cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; + cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; + if (cell_locations.has(cell_loc)) { + c_locs[cell_loc] = 1; + } + } + } + Array cell_queue = c_locs.keys(); + if (cell_queue.size() == 0) { + continue; + } + Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); + real_t density = CLAMP(.1f * brush_size * strength * mesh_asset->get_density() / + MAX(0.01f, fixed_scale + .5f * random_scale), + .001f, 1000.f); + // Density based on strength, mesh AABB and input scale determines how many to remove, even fractional + uint32_t quota = _get_density_count(density); + if (quota == 0) { + continue; + } + for (int c = 0; c < cell_queue.size(); c++) { + Vector2i cell = cell_queue[c]; + Array triple = cell_inst_dict[cell]; + TypedArray xforms = triple[0]; + PackedColorArray colors = triple[1]; + TypedArray updated_xforms; + PackedColorArray updated_colors; + uint32_t cell_quota = quota; + // Remove transforms if inside ring radius + for (int i = 0; i < xforms.size(); i++) { + Transform3D t = xforms[i]; + // Use localised ring center + real_t radial_distance = localised_ring_center.distance_to(Vector2(t.origin.x, t.origin.z)); + bool rng = radial_distance < UtilityFunctions::randf() * radius; + if (cell_quota > 0 && rng && data->is_in_slope(t.origin + global_local_offset, slope_range, invert)) { + --cell_quota; + continue; + } else { + updated_xforms.push_back(t); + updated_colors.push_back(colors[i]); + } + } + if (updated_xforms.size() > 0) { + triple[0] = updated_xforms; + triple[1] = updated_colors; + triple[2] = true; + cell_inst_dict[cell] = triple; + // TODO must we also reassign cell_inst_dict to mesh_inst_dict and get_instances? + } else { + cell_inst_dict.erase(cell); + _destroy_mmi_by_cell(region_loc, m, cell); + } + } + if (cell_inst_dict.is_empty()) { + mesh_inst_dict.erase(mesh_id); } } - - if (xforms.size() == 0) { - LOG(DEBUG, "Removed all instances, erasing multimesh in region"); - clear_by_location(region_loc, m); - } else { - append_location(region_loc, m, xforms, colors, true); - } + _update_mmis(region_loc); } } void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform) { LOG(INFO, "Extracting ", p_multimesh->get_instance_count(), " transforms from multimesh"); TypedArray xforms; - TypedArray colors; + PackedColorArray colors; for (int i = 0; i < p_multimesh->get_instance_count(); i++) { xforms.push_back(p_xform * p_multimesh->get_instance_transform(i)); Color c = COLOR_WHITE; @@ -404,7 +644,8 @@ void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref add_transforms(p_mesh_id, xforms, colors); } -void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const TypedArray &p_colors) { +// Expects transforms in global space +void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); if (p_xforms.size() == 0) { return; @@ -418,7 +659,7 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id); - // Separate incoming transforms/colors into Dictionary { region_loc => Array[Transform3D] } + // Separate incoming transforms/colors by region Dict{ region_loc => Array() } LOG(INFO, "Separating ", p_xforms.size(), " transforms and ", p_colors.size(), " colors into regions"); for (int i = 0; i < p_xforms.size(); i++) { // Get adjusted xform/color @@ -433,12 +674,13 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArrayget_data()->get_region_location(trns.origin); if (!xforms_dict.has(region_loc)) { xforms_dict[region_loc] = TypedArray(); - colors_dict[region_loc] = TypedArray(); + colors_dict[region_loc] = PackedColorArray(); } TypedArray xforms = xforms_dict[region_loc]; - TypedArray colors = colors_dict[region_loc]; + PackedColorArray colors = colors_dict[region_loc]; xforms.push_back(trns); colors.push_back(col); + colors_dict[region_loc] = colors; // Note similar bug as godot-cpp#1149 needs this for PCA } // Merge incoming transforms with existing transforms @@ -446,65 +688,82 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray xforms = xforms_dict[region_loc]; - TypedArray colors = colors_dict[region_loc]; - LOG(DEBUG, "Adding ", xforms.size(), " transforms to region location: ", region_loc); + PackedColorArray colors = colors_dict[region_loc]; + //LOG(MESG, "Appending ", xforms.size(), " xforms, ", colors, " colors to region location: ", region_loc); append_location(region_loc, p_mesh_id, xforms, colors); } } -// Appends new transforms to existing multimeshes +// Appends new global transforms to existing cells, offsetting transforms to region space, scaled by vertex spacing void Terrain3DInstancer::append_location(const Vector2i &p_region_loc, const int p_mesh_id, - const TypedArray &p_xforms, const TypedArray &p_colors, const bool p_clear, const bool p_update) { + const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_clear, const bool p_update) { IS_DATA_INIT(VOID); Ref region = _terrain->get_data()->get_region(p_region_loc); if (region.is_null()) { LOG(WARN, "Null region found at: ", p_region_loc); return; } - append_region(region, p_mesh_id, p_xforms, p_colors, p_clear, p_update); + int region_size = region->get_region_size(); + real_t vertex_spacing = _terrain->get_vertex_spacing(); + Vector2 global_local_offset = Vector2(p_region_loc.x * region_size * vertex_spacing, p_region_loc.y * region_size * vertex_spacing); + TypedArray localised_xforms; + for (int i = 0; i < p_xforms.size(); i++) { + Transform3D t = p_xforms[i]; + // Localise the transform to "region space" + t.origin.x -= global_local_offset.x; + t.origin.z -= global_local_offset.y; + localised_xforms.push_back(t); + } + append_region(region, p_mesh_id, localised_xforms, p_colors, p_clear, p_update); } +// append_region requires all transforms are in region space, 0 - region_size * vertex_spacing void Terrain3DInstancer::append_region(const Ref &p_region, const int p_mesh_id, - const TypedArray &p_xforms, const TypedArray &p_colors, const bool p_clear, const bool p_update) { - Dictionary mesh_dict = p_region->get_multimeshes(); - - // Collect old data - TypedArray old_xforms; - TypedArray old_colors; - if (!p_clear) { - Ref multimesh = mesh_dict.get(p_mesh_id, Ref()); - if (multimesh.is_valid()) { - uint32_t old_count = multimesh->get_instance_count(); - LOG(EXTREME, "Merging w/ old instances: ", old_count, ": ", multimesh); - for (int i = 0; i < old_count; i++) { - old_xforms.push_back(multimesh->get_instance_transform(i)); - old_colors.push_back(multimesh->get_instance_color(i)); - } - } + const TypedArray &p_xforms, const PackedColorArray &p_colors, const bool p_clear, const bool p_update) { + if (p_region.is_null()) { + LOG(ERROR, "Null region provided. Doing nothing."); + return; } - - // Erase empties if no transforms in both the old and new data - if (old_xforms.size() == 0 && p_xforms.size() == 0) { - clear_by_region(p_region, p_mesh_id); + if (p_xforms.size() == 0) { + LOG(ERROR, "No transforms to add. Doing nothing."); return; } - // Create a new Multimesh - Ref mm = _create_multimesh(p_mesh_id); - int old_count = old_xforms.size(); - mm->set_instance_count(old_count + p_xforms.size()); - for (int i = 0; i < old_count; i++) { - mm->set_instance_transform(i, old_xforms[i]); - mm->set_instance_color(i, old_colors[i]); - } + _backup_region(p_region); + + Dictionary cell_locations = p_region->get_instances()[p_mesh_id]; + int region_size = p_region->get_region_size(); + for (int i = 0; i < p_xforms.size(); i++) { - mm->set_instance_transform(i + old_count, p_xforms[i]); - mm->set_instance_color(i + old_count, p_colors[i]); + Transform3D xform = p_xforms[i]; + Color col = p_colors[i]; + Vector2i cell = _get_cell(xform.origin, region_size); + + // Get current instance arrays or create if none + Array triple = cell_locations[cell]; + bool modified = true; + if (p_clear || triple.size() != 3) { + LOG(DEBUG, "No data at ", p_region->get_location(), ":", cell, ". Creating triple"); + triple.resize(3); + triple[0] = TypedArray(); + triple[1] = PackedColorArray(); + triple[2] = modified; + } + TypedArray xforms = triple[0]; + PackedColorArray colors = triple[1]; + xforms.push_back(xform); + colors.push_back(col); + + // Shouldn't have to write this back, but seems we do; there are copy constructors somewhere + // see godot-cpp#1149 + triple[0] = xforms; // not needed + triple[1] = colors; // needed for colors + triple[2] = modified; + cell_locations[cell] = triple; // needed for both } - LOG(EXTREME, "Setting multimesh in region: ", p_region->get_location(), ", mesh_id: ", p_mesh_id, " instance count: ", mm->get_instance_count(), " mm: ", mm); - _backup_region(p_region); - mesh_dict[p_mesh_id] = mm; + // Write back dictionary. See above comments + p_region->get_instances()[p_mesh_id] = cell_locations; if (p_update) { _update_mmis(p_region->get_location(), p_mesh_id); } @@ -513,94 +772,166 @@ void Terrain3DInstancer::append_region(const Ref &p_region, con // Review all transforms in one area and adjust their transforms w/ the current height void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); - LOG(EXTREME, "Updating transforms for all meshes within ", p_aabb); - - Array region_locations = _terrain->get_data()->get_region_locations(); Rect2 brush_rect = aabb2rect(p_aabb); - for (int r = 0; r < region_locations.size(); r++) { - Vector2i region_loc = region_locations[r]; + Vector2 global_position = brush_rect.get_center(); + Vector2 size = brush_rect.get_size(); + Vector2 half_size = size * 0.5 + Vector2(1.f, 1.f); // 1m margin + + Terrain3DData *data = _terrain->get_data(); + int region_size = _terrain->get_region_size(); + real_t vertex_spacing = _terrain->get_vertex_spacing(); + + // Build list of potential regions to search, rather than searching the entire terrain, calculate possible regions covered + // and check if they are valid; if so add that location to the dictionary keys. + Dictionary r_locs; + // Calculate step distance to ensure every region is checked inside the bounds of brush size. + Vector2 step = Vector2(size.x / ceil(size.x / real_t(region_size) / vertex_spacing), size.y / ceil(size.y / real_t(region_size) / vertex_spacing)); + for (real_t x = global_position.x - half_size.x; x <= global_position.x + half_size.x; x += step.x) { + for (real_t z = global_position.y - half_size.y; z <= global_position.y + half_size.y; z += step.y) { + Vector2i region_loc = data->get_region_location(Vector3(x, 0.f, z)); + if (data->has_region(region_loc)) { + r_locs[region_loc] = 0; + } + } + } + + Array region_queue = r_locs.keys(); + if (region_queue.size() == 0) { + return; + } + + for (int r = 0; r < region_queue.size(); r++) { + Vector2i region_loc = region_queue[r]; Ref region = _terrain->get_data()->get_region(region_loc); - if (region.is_null()) { - LOG(WARN, "No region found at: ", region_loc); + _backup_region(region); + + Dictionary mesh_inst_dict = region->get_instances(); + Array mesh_types = mesh_inst_dict.keys(); + if (mesh_types.size() == 0) { continue; } - int region_size = _terrain->get_region_size(); - Rect2 region_rect; - region_rect.set_position(region_loc * region_size); - region_rect.set_size(Vector2(region_size, region_size)); - LOG(EXTREME, "RO: ", region_loc, " RAABB: ", region_rect, " intersects: ", brush_rect.intersects(region_rect)); - - // If specified area includes this region, update all MMs within - if (brush_rect.intersects(region_rect)) { - Dictionary mesh_dict = region->get_multimeshes(); - LOG(EXTREME, "Region ", region_loc, " intersect AABB and contains ", mesh_dict.size(), " mesh types"); - // For all mesh ids - for (int m = 0; m < mesh_dict.keys().size(); m++) { - int mesh_id = mesh_dict.keys()[m]; - Ref mm = mesh_dict.get(mesh_id, Ref()); - if (mm.is_null()) { - continue; + Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); + + // For this mesh id, or all mesh ids + for (int m = 0; m < mesh_types.size(); m++) { + // Check potential cells rather than searching the entire region, whilst marginally + // slower if there are very few cells for the given mesh present it is significantly + // faster when a very large number of cells are present. + int region_mesh_id = mesh_types[m]; + Dictionary cell_inst_dict = mesh_inst_dict[region_mesh_id]; + Array cell_locations = cell_inst_dict.keys(); + if (cell_locations.size() == 0) { + continue; + } + Dictionary c_locs; + // Calculate step distance to ensure every cell is checked inside the bounds of brush size. + Vector2 cell_step = Vector2(size.x / ceil(size.x / real_t(CELL_SIZE) / vertex_spacing), size.y / ceil(size.y / real_t(CELL_SIZE) / vertex_spacing)); + for (real_t x = global_position.x - half_size.x; x <= global_position.x + half_size.x; x += cell_step.x) { + for (real_t z = global_position.y - half_size.y; z <= global_position.y + half_size.y; z += cell_step.y) { + Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; + // Manually calculate cell pos without modulus, locations not in the current region will not be found. + Vector2i cell_loc; + cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; + cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; + if (cell_locations.has(cell_loc)) { + c_locs[cell_loc] = 0; + } } - Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(mesh_id); - TypedArray xforms; - TypedArray colors; - LOG(EXTREME, "Multimesh ", mesh_id, " has ", mm->get_instance_count(), " to review"); - for (int i = 0; i < mm->get_instance_count(); i++) { - Transform3D t = mm->get_instance_transform(i); - if (brush_rect.has_point(Vector2(t.origin.x, t.origin.z))) { - // Reset height to terrain height + mesh height offset along UP axis - real_t height = _terrain->get_data()->get_height(t.origin); - // If the new height is a nan due to creating a hole, remove the instance - if (std::isnan(height)) { - continue; - } - t.origin.y = height + mesh_asset->get_height_offset(); + } + Array cell_queue = c_locs.keys(); + if (cell_queue.size() == 0) { + continue; + } + Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); + for (int c = 0; c < cell_queue.size(); c++) { + Vector2i cell = cell_queue[c]; + Array triple = cell_inst_dict[cell]; + TypedArray xforms = triple[0]; + PackedColorArray colors = triple[1]; + TypedArray updated_xforms; + PackedColorArray updated_colors; + for (int i = 0; i < xforms.size(); i++) { + Transform3D t = xforms[i]; + real_t height = _terrain->get_data()->get_height(t.origin + global_local_offset); + // If the new height is a nan due to creating a hole, remove the instance + if (std::isnan(height)) { + continue; } - xforms.push_back(t); - colors.push_back(mm->get_instance_color(i)); + t.origin.y = height + mesh_asset->get_height_offset(); + updated_xforms.push_back(t); + updated_colors.push_back(colors[i]); + } + if (updated_xforms.size() > 0) { + triple[0] = updated_xforms; + triple[1] = updated_colors; + triple[2] = true; + cell_inst_dict[cell] = triple; + } else { + // Removed if a hole erased everything + cell_inst_dict.erase(cell); + _destroy_mmi_by_cell(region_loc, m, cell); } - // Replace multimesh - append_location(region_loc, mesh_id, xforms, colors, true); } } + _update_mmis(region_loc); } } // Transfer foliage data from one region to another // p_src_rect is the vertex/pixel offset into the region data, NOT a global position // Need to force_update_mmis() after -void Terrain3DInstancer::copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2 &p_src_rect, const Terrain3DRegion *p_dst_region) { +void Terrain3DInstancer::copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Terrain3DRegion *p_dst_region) { if (p_src_region == nullptr || p_dst_region == nullptr) { LOG(ERROR, "Source (", p_src_region, ") or destination (", p_dst_region, ") regions are null"); return; } LOG(INFO, "Copying foliage data from src ", p_src_region->get_location(), " to dest ", p_dst_region->get_location()); - // Get absolute global area, including vertex spacing - Vector2 global_pos = p_src_rect.position + p_src_region->get_location() * p_src_region->get_region_size(); - Rect2 abs_area(global_pos * _terrain->get_vertex_spacing(), - p_src_rect.size * _terrain->get_vertex_spacing()); - Dictionary src_mms = p_src_region->get_multimeshes(); - Array keys = src_mms.keys(); - for (int i = 0; i < keys.size(); i++) { - int mesh_id = keys[i]; - TypedArray xforms; - TypedArray colors; - Ref src_mm = src_mms[mesh_id]; - if (src_mm.is_null()) { - LOG(ERROR, "Region has null multimesh for mesh_id ", mesh_id); - continue; + + real_t vertex_spacing = _terrain->get_vertex_spacing(); + // Offset to dst from src + Vector2i src_region_loc = p_src_region->get_location(); + int src_region_size = p_src_region->get_region_size(); + Vector2i src_offset = Vector2i(src_region_loc.x * src_region_size, src_region_loc.y * src_region_size); + Vector2i dst_region_loc = p_dst_region->get_location(); + int dst_region_size = p_dst_region->get_region_size(); + Vector2i dst_offset = src_offset - Vector2i(dst_region_loc.x * dst_region_size, dst_region_loc.y * dst_region_size); + Vector3 dst_translate = Vector3(dst_offset.x, 0.f, dst_offset.y) * vertex_spacing; + + // Get all Cell locations in rect, which is already in region space. + Vector2i cell_start = p_src_rect.get_position() / CELL_SIZE; + Vector2i steps = p_src_rect.get_size() / CELL_SIZE; + Dictionary cells_to_copy; + for (int x = cell_start.x; x < cell_start.x + steps.x; x++) { + for (int y = cell_start.y; y < cell_start.y + steps.y; y++) { + cells_to_copy[Vector2i(x, y)] = 0; } - // Get all transforms within src_area - for (int j = 0; j < src_mm->get_instance_count(); j++) { - Transform3D xf = src_mm->get_instance_transform(j); - if (abs_area.has_point(Point2(xf.origin.x, xf.origin.z))) { - xforms.push_back(xf); - if (src_mm->is_using_colors()) { - colors.push_back(src_mm->get_instance_color(j)); + } + + // For each mesh, for each cell, if in rect, convert xforms to target region space, append to target region. + Dictionary mesh_inst_dict = p_src_region->get_instances(); + Array mesh_types = mesh_inst_dict.keys(); + for (int m = 0; m < mesh_types.size(); m++) { + TypedArray xforms; + PackedColorArray colors; + Dictionary cell_inst_dict = p_src_region->get_instances()[m]; + Array cell_locs = cell_inst_dict.keys(); + for (int c = 0; c < cell_locs.size(); c++) { + if (cells_to_copy.has(cell_locs[c])) { + Array triple = cell_inst_dict[cell_locs[c]]; + TypedArray cell_xforms = triple[0]; + PackedColorArray cell_colors = triple[1]; + for (int i = 0; i < cell_xforms.size(); i++) { + Transform3D t = cell_xforms[i]; + t.origin += dst_translate; + xforms.push_back(t); + colors.push_back(cell_colors[i]); } } } - append_region(Ref(p_dst_region), mesh_id, xforms, colors, false, false); + if (xforms.size() == 0) { + continue; + } + append_region(Ref(p_dst_region), m, xforms, colors, false, false); } } @@ -619,9 +950,9 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { Ref region = _terrain->get_data()->get_region(region_loc); if (region.is_null()) { LOG(WARN, "No region found at: ", region_loc); - return; + continue; } - Dictionary mesh_dict = region->get_multimeshes(); + Dictionary mesh_dict = region->get_multimeshes(); // TODO BROKEN // mesh_dict could have src, src&dst, dst or nothing. All 4 must be considered // Pop out any existing MMs Ref mm_src; @@ -649,28 +980,29 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { LOG(DEBUG, "Swapped multimesh ids at: ", region_loc); } - // Change key in _mmi dictionary - Array mmi_keys = _mmis.keys(); - Dictionary to_src_mmis; - Dictionary to_dst_mmis; - - for (int i = 0; i < mmi_keys.size(); i++) { - Vector3i key = mmi_keys[i]; - if (key.z == p_src_id) { - Vector3i dst_key = key; // setup destination key - dst_key.z = p_dst_id; - to_dst_mmis[dst_key] = _mmis[key]; // store MMI under dest key - _mmis.erase(key); - } else if (key.z == p_dst_id) { - Vector3i src_key = key; // setup source key - src_key.z = p_src_id; - to_src_mmis[src_key] = _mmis[key]; // store MMI under src key - _mmis.erase(key); - } - } - _mmis.merge(to_src_mmis); - _mmis.merge(to_dst_mmis); - LOG(DEBUG, "Swapped multimesh instance ids"); + // TODO Rewrite all of this + // Change key in _mmi_nodes dictionary + //Array mmi_keys = _mmi_nodes.keys(); + //Dictionary to_src_mmis; + //Dictionary to_dst_mmis; + + //for (int i = 0; i < mmi_keys.size(); i++) { + // Vector3i key = mmi_keys[i]; + // if (key.z == p_src_id) { + // Vector3i dst_key = key; // setup destination key + // dst_key.z = p_dst_id; + // to_dst_mmis[dst_key] = _mmi_nodes[key]; // store MMI under dest key + // _mmi_nodes.erase(key); + // } else if (key.z == p_dst_id) { + // Vector3i src_key = key; // setup source key + // src_key.z = p_src_id; + // to_src_mmis[src_key] = _mmi_nodes[key]; // store MMI under src key + // _mmi_nodes.erase(key); + // } + //} + //_mmi_nodes.merge(to_src_mmis); + //_mmi_nodes.merge(to_dst_mmis); + //LOG(DEBUG, "Swapped multimesh instance ids"); } } @@ -686,7 +1018,7 @@ Ref Terrain3DInstancer::get_multimesh(const Vector2i &p_region_loc, c LOG(WARN, "Null region found at: ", p_region_loc); return Ref(); } - Dictionary mesh_dict = region->get_multimeshes(); + Dictionary mesh_dict = region->get_multimeshes(); // TODO BROKEN Ref mm = mesh_dict.get(p_mesh_id, Ref()); LOG(EXTREME, "Retrieving MultiMesh at region: ", p_region_loc, " mesh_id: ", p_mesh_id, " : ", mm); return mm; @@ -698,19 +1030,26 @@ MultiMeshInstance3D *Terrain3DInstancer::get_multimesh_instancep(const Vector3 & } MultiMeshInstance3D *Terrain3DInstancer::get_multimesh_instance(const Vector2i &p_region_loc, const int p_mesh_id) const { - Vector3i key = Vector3i(p_region_loc.x, p_region_loc.y, p_mesh_id); - MultiMeshInstance3D *mmi = cast_to(_mmis[key]); - LOG(EXTREME, "Retrieving MultiMeshInstance3D at region: ", p_region_loc, " mesh_id: ", p_mesh_id, " : ", mmi); - return mmi; + return nullptr; + // TODO rewrite + //Vector3i key = Vector3i(p_region_loc.x, p_region_loc.y, p_mesh_id); + ////MultiMeshInstance3D *mmi = cast_to(_mmi_nodes[key]); + //MultiMeshInstance3D *mmi = _mmi_nodes[key]; + //LOG(EXTREME, "Retrieving MultiMeshInstance3D at region: ", p_region_loc, " mesh_id: ", p_mesh_id, " : ", mmi); + //return mmi; } void Terrain3DInstancer::set_cast_shadows(const int p_mesh_id, const GeometryInstance3D::ShadowCastingSetting p_cast_shadows) { LOG(INFO, "Setting shadow casting on MMIS with mesh: ", p_mesh_id, " to mode: ", p_cast_shadows); - Array keys = _mmis.keys(); - for (int i = 0; i < keys.size(); i++) { - Vector3i key = keys[i]; - if (key.z == p_mesh_id) { - MultiMeshInstance3D *mmi = cast_to(_mmis[key]); + for (auto ®ion_pair : _mmi_nodes) { + MeshMMIDict &mesh_mmi_dict = region_pair.second; + Vector2i mesh_key(p_mesh_id, 0); + if (mesh_mmi_dict.count(mesh_key) == 0) { + continue; + } + CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key]; + for (auto &cell_pair : cell_mmi_dict) { + MultiMeshInstance3D *mmi = cell_pair.second; if (mmi) { mmi->set_cast_shadows_setting(p_cast_shadows); } @@ -745,6 +1084,18 @@ void Terrain3DInstancer::print_multimesh_buffer(MultiMeshInstance3D *p_mmi) cons } } +void Terrain3DInstancer::dump_mmis() { + for (auto &i : _mmi_nodes) { + LOG(MESG, "_mmi_nodes region: ", i.first, ", dict ptr: ", uint64_t(&i.second)); + for (auto &j : i.second) { + LOG(MESG, "mesh_mmi_dict mesh: ", j.first, ", dict ptr: ", uint64_t(&j.second)); + for (auto &k : j.second) { + LOG(MESG, "cell_mmi_dict cell: ", k.first, ", mmi ptr: ", uint64_t(k.second)); + } + } + } +} + /////////////////////////// // Protected Functions /////////////////////////// @@ -755,13 +1106,13 @@ void Terrain3DInstancer::_bind_methods() { ClassDB::bind_method(D_METHOD("add_instances", "global_position", "params"), &Terrain3DInstancer::add_instances); ClassDB::bind_method(D_METHOD("remove_instances", "global_position", "params"), &Terrain3DInstancer::remove_instances); ClassDB::bind_method(D_METHOD("add_multimesh", "mesh_id", "multimesh", "transform"), &Terrain3DInstancer::add_multimesh, DEFVAL(Transform3D())); - ClassDB::bind_method(D_METHOD("add_transforms", "mesh_id", "transforms", "colors"), &Terrain3DInstancer::add_transforms, DEFVAL(TypedArray())); + ClassDB::bind_method(D_METHOD("add_transforms", "mesh_id", "transforms", "colors"), &Terrain3DInstancer::add_transforms, DEFVAL(PackedColorArray())); ClassDB::bind_method(D_METHOD("append_location", "region_location", "mesh_id", "transforms", "colors", "clear", "update"), &Terrain3DInstancer::append_location, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("append_region", "region", "mesh_id", "transforms", "colors", "clear", "update"), &Terrain3DInstancer::append_region, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("update_transforms", "aabb"), &Terrain3DInstancer::update_transforms); ClassDB::bind_method(D_METHOD("swap_ids", "src_id", "dest_id"), &Terrain3DInstancer::swap_ids); - ClassDB::bind_method(D_METHOD("get_mmis"), &Terrain3DInstancer::get_mmis); + //ClassDB::bind_method(D_METHOD("get_mmis"), &Terrain3DInstancer::get_mmis); ClassDB::bind_method(D_METHOD("set_cast_shadows", "mesh_id", "mode"), &Terrain3DInstancer::set_cast_shadows); ClassDB::bind_method(D_METHOD("force_update_mmis"), &Terrain3DInstancer::force_update_mmis); } diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index 5453ce21..dd801b43 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -5,6 +5,7 @@ #include #include +#include #include "constants.h" @@ -18,22 +19,36 @@ class Terrain3DInstancer : public Object { CLASS_NAME(); friend Terrain3D; +public: // Constants + static inline const int CELL_SIZE = 32; + +private: Terrain3D *_terrain = nullptr; - // MM Resources stored in Terrain3DRegion::_multimeshes as - // Dictionary[mesh_id:int] -> MultiMesh + // MM Resources stored in Terrain3DRegion::_instances as + // _instances{mesh_id:int} -> cell{v2i} -> [ TypedArray, PackedColorArray, modified:bool ] + // MMI Objects attached to tree, freed in destructor, stored as - // Dictionary[Vector3i(region_location.x, region_location.y, mesh_id)] -> MultiMeshInstance3D - Dictionary _mmis; + // _mmi_nodes{region_loc} -> mesh{v2i(mesh_id,lod)} -> cell{v2i} -> MultiMeshInstance3D + typedef std::unordered_map CellMMIDict; + typedef std::unordered_map MeshMMIDict; + std::unordered_map _mmi_nodes; + + // Region MMI containers named Terrain3D/MMI/Region* are stored here as + // _mmi_containers{region_loc} -> Node + Dictionary _mmi_containers; - uint32_t _instance_counter = 0; - uint32_t _get_instace_count(const real_t p_density); + uint32_t _density_counter = 0; + uint32_t _get_density_count(const real_t p_density); void _update_mmis(const Vector2i &p_region_loc = V2I_MAX, const int p_mesh_id = -1); + void _update_vertex_spacing(const real_t p_vertex_spacing); + void _destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell); void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id); void _backup_regionl(const Vector2i &p_region_loc); void _backup_region(const Ref &p_region); - Ref _create_multimesh(const int p_mesh_id, const TypedArray &p_xforms = TypedArray(), const TypedArray &p_colors = TypedArray()) const; + Ref _create_multimesh(const int p_mesh_id, const TypedArray &p_xforms = TypedArray(), const PackedColorArray &p_colors = PackedColorArray()) const; + Vector2i _get_cell(const Vector3 &p_global_position, const int p_region_size); public: Terrain3DInstancer() {} @@ -49,35 +64,36 @@ class Terrain3DInstancer : public Object { void add_instances(const Vector3 &p_global_position, const Dictionary &p_params); void remove_instances(const Vector3 &p_global_position, const Dictionary &p_params); void add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform = Transform3D()); - void add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const TypedArray &p_colors = TypedArray()); + void add_transforms(const int p_mesh_id, const TypedArray &p_xforms, const PackedColorArray &p_colors = PackedColorArray()); void append_location(const Vector2i &p_region_loc, const int p_mesh_id, const TypedArray &p_xforms, - const TypedArray &p_colors, const bool p_clear = false, const bool p_update = true); + const PackedColorArray &p_colors, const bool p_clear = false, const bool p_update = true); void append_region(const Ref &p_region, const int p_mesh_id, const TypedArray &p_xforms, - const TypedArray &p_colors, const bool p_clear = false, const bool p_update = true); + const PackedColorArray &p_colors, const bool p_clear = false, const bool p_update = true); void update_transforms(const AABB &p_aabb); - void copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2 &p_src_rect, const Terrain3DRegion *p_dst_region); + void copy_paste_dfr(const Terrain3DRegion *p_src_region, const Rect2i &p_src_rect, const Terrain3DRegion *p_dst_region); void swap_ids(const int p_src_id, const int p_dst_id); Ref get_multimeshp(const Vector3 &p_global_position, const int p_mesh_id) const; Ref get_multimesh(const Vector2i &p_region_loc, const int p_mesh_id) const; MultiMeshInstance3D *get_multimesh_instancep(const Vector3 &p_global_position, const int p_mesh_id) const; MultiMeshInstance3D *get_multimesh_instance(const Vector2i &p_region_loc, const int p_mesh_id) const; - Dictionary get_mmis() const { return _mmis; } + //std:: get_mmis() const { return _mmi_nodes; } void set_cast_shadows(const int p_mesh_id, const GeometryInstance3D::ShadowCastingSetting p_cast_shadows); void force_update_mmis(); - void reset_instance_counter() { _instance_counter = 0; } + void reset_density_counter() { _density_counter = 0; } void print_multimesh_buffer(MultiMeshInstance3D *p_mmi) const; + void dump_mmis(); protected: static void _bind_methods(); }; // Allows us to instance every X function calls for sparse placement -// Modifies _instance_counter, not const! -inline uint32_t Terrain3DInstancer::_get_instace_count(const real_t p_density) { +// Modifies _density_counter, not const! +inline uint32_t Terrain3DInstancer::_get_density_count(const real_t p_density) { uint32_t count = 0; - if (p_density < 1.f && _instance_counter++ % uint32_t(1.f / p_density) == 0) { + if (p_density < 1.f && _density_counter++ % uint32_t(1.f / p_density) == 0) { count = 1; } else if (p_density >= 1.f) { count = uint32_t(p_density); diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index 966def5d..c5404e2f 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -244,6 +244,9 @@ String Terrain3DMaterial::_inject_editor_code(const String &p_shader) const { if (_debug_view_vertex_grid) { insert_names.push_back("DEBUG_VERTEX_GRID"); } + if (_debug_view_instancer_grid) { + insert_names.push_back("DEBUG_INSTANCER_GRID"); + } if (_show_navigation || (IS_EDITOR && _terrain && _terrain->get_editor() && _terrain->get_editor()->get_tool() == Terrain3DEditor::NAVIGATION)) { insert_names.push_back("EDITOR_NAVIGATION"); } @@ -643,6 +646,12 @@ void Terrain3DMaterial::set_show_vertex_grid(const bool p_enabled) { _update_shader(); } +void Terrain3DMaterial::set_show_instancer_grid(const bool p_enabled) { + LOG(INFO, "Enable show_vertex_grid: ", p_enabled); + _debug_view_instancer_grid = p_enabled; + _update_shader(); +} + void Terrain3DMaterial::save() { LOG(DEBUG, "Generating parameter list from shaders"); // Get shader parameters from default shader (eg world_noise) @@ -858,6 +867,8 @@ void Terrain3DMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_show_region_grid"), &Terrain3DMaterial::get_show_region_grid); ClassDB::bind_method(D_METHOD("set_show_vertex_grid", "enabled"), &Terrain3DMaterial::set_show_vertex_grid); ClassDB::bind_method(D_METHOD("get_show_vertex_grid"), &Terrain3DMaterial::get_show_vertex_grid); + ClassDB::bind_method(D_METHOD("set_show_instancer_grid", "enabled"), &Terrain3DMaterial::set_show_instancer_grid); + ClassDB::bind_method(D_METHOD("get_show_instancer_grid"), &Terrain3DMaterial::get_show_instancer_grid); ClassDB::bind_method(D_METHOD("save"), &Terrain3DMaterial::save); @@ -886,4 +897,5 @@ void Terrain3DMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_rough", PROPERTY_HINT_NONE), "set_show_texture_rough", "get_show_texture_rough"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_region_grid", PROPERTY_HINT_NONE), "set_show_region_grid", "get_show_region_grid"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_vertex_grid", PROPERTY_HINT_NONE), "set_show_vertex_grid", "get_show_vertex_grid"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_instancer_grid", PROPERTY_HINT_NONE), "set_show_instancer_grid", "get_show_instancer_grid"); } diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h index 94d6f0c1..10153220 100644 --- a/src/terrain_3d_material.h +++ b/src/terrain_3d_material.h @@ -66,6 +66,7 @@ class Terrain3DMaterial : public Resource { bool _debug_view_tex_rough = false; bool _debug_view_region_grid = false; bool _debug_view_vertex_grid = false; + bool _debug_view_instancer_grid = false; // Functions void _preload_shaders(); @@ -139,6 +140,8 @@ class Terrain3DMaterial : public Resource { bool get_show_region_grid() const { return _debug_view_region_grid; } void set_show_vertex_grid(const bool p_enabled); bool get_show_vertex_grid() const { return _debug_view_vertex_grid; } + void set_show_instancer_grid(const bool p_enabled); + bool get_show_instancer_grid() const { return _debug_view_instancer_grid; } void save(); diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index d9e869e0..b4c57def 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -257,11 +257,12 @@ void Terrain3DRegion::set_data(const Dictionary &p_data) { SET_IF_HAS(_modified, "modified"); SET_IF_HAS(_version, "version"); SET_IF_HAS(_region_size, "region_size"); + SET_IF_HAS(_vertex_spacing, "vertex_spacing"); SET_IF_HAS(_height_range, "height_range"); SET_IF_HAS(_height_map, "height_map"); SET_IF_HAS(_control_map, "control_map"); SET_IF_HAS(_color_map, "color_map"); - SET_IF_HAS(_multimeshes, "multimeshes"); + SET_IF_HAS(_instances, "instances"); } Dictionary Terrain3DRegion::get_data() const { @@ -273,11 +274,12 @@ Dictionary Terrain3DRegion::get_data() const { dict["instance_id"] = String::num_uint64(get_instance_id()); // don't commit dict["version"] = _version; dict["region_size"] = _region_size; + dict["vertex_spacing"] = _vertex_spacing; dict["height_range"] = _height_range; dict["height_map"] = _height_map; dict["control_map"] = _control_map; dict["color_map"] = _color_map; - dict["multimeshes"] = _multimeshes; + dict["instances"] = _instances; return dict; } @@ -291,6 +293,7 @@ Ref Terrain3DRegion::duplicate(const bool p_deep) { // Native type copies dict["version"] = _version; dict["region_size"] = _region_size; + dict["vertex_spacing"] = _vertex_spacing; dict["height_range"] = _height_range; dict["modified"] = _modified; dict["deleted"] = _deleted; @@ -299,20 +302,22 @@ Ref Terrain3DRegion::duplicate(const bool p_deep) { dict["height_map"] = _height_map->duplicate(); dict["control_map"] = _control_map->duplicate(); dict["color_map"] = _color_map->duplicate(); - Dictionary mms; - Array keys = _multimeshes.keys(); - for (int i = 0; i < keys.size(); i++) { - int mesh_id = keys[i]; - Ref mm = _multimeshes[mesh_id]; - mm->duplicate(); - mms[mesh_id] = mm; - } - dict["multimeshes"] = mms; + dict["instances"] = _instances.duplicate(true); // TODO test this region->set_data(dict); } return region; } +// DEPRECATED 0.9.3-dev - Remove 1.0 +void Terrain3DRegion::set_multimeshes(const Dictionary &p_multimeshes) { + // TODO + // This function will upgrade 0.9.3-dev users to _instances + // Drop _multimeshes + // Translate p_multimeshes into _instances + // Consider changing data format to 0.931 + _multimeshes = p_multimeshes; +} + ///////////////////// // Protected Functions ///////////////////// @@ -327,6 +332,8 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); ClassDB::bind_method(D_METHOD("set_region_size", "region_size"), &Terrain3DRegion::set_region_size); ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DRegion::get_region_size); + ClassDB::bind_method(D_METHOD("set_vertex_spacing", "vertex_spacing"), &Terrain3DRegion::set_vertex_spacing); + ClassDB::bind_method(D_METHOD("get_vertex_spacing"), &Terrain3DRegion::get_vertex_spacing); ClassDB::bind_method(D_METHOD("set_map", "map_type", "map"), &Terrain3DRegion::set_map); ClassDB::bind_method(D_METHOD("get_map", "map_type"), &Terrain3DRegion::get_map); @@ -348,8 +355,8 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("update_heights", "low_high"), &Terrain3DRegion::update_heights); ClassDB::bind_method(D_METHOD("calc_height_range"), &Terrain3DRegion::calc_height_range); - ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DRegion::set_multimeshes); - ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DRegion::get_multimeshes); + ClassDB::bind_method(D_METHOD("set_instances", "instances"), &Terrain3DRegion::set_instances); + ClassDB::bind_method(D_METHOD("get_instances"), &Terrain3DRegion::get_instances); ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); @@ -369,15 +376,21 @@ void Terrain3DRegion::_bind_methods() { int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_NONE, "", ro_flags), "set_region_size", "get_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertex_spacing", PROPERTY_HINT_NONE, "", ro_flags), "set_vertex_spacing", "get_vertex_spacing"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "height_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "control_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "instances", PROPERTY_HINT_NONE, "", ro_flags), "set_instances", "get_instances"); // Double-clicking a region .res file shows what's on disk, the defaults, not in memory. So these are hidden ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edited", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_edited", "is_edited"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deleted", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_deleted", "is_deleted"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_modified", "is_modified"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_location", "get_location"); + + // DEPRECATED 0.9.3-dev - Remove 1.0 + ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DRegion::set_multimeshes); + ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DRegion::get_multimeshes); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); } diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index b08ac859..fdcdc254 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -51,7 +51,8 @@ class Terrain3DRegion : public Resource { Ref _control_map; Ref _color_map; // Instancer - Dictionary _multimeshes; // Dictionary[mesh_id:int] -> MultiMesh + Dictionary _instances; // Meshes{int} -> Cells{v2i} -> [ Transform3D, Color, Modified ] + real_t _vertex_spacing = 1.f; // Vertex Spacing value that transforms are currently scaled. // Working data not saved to disk bool _deleted = false; // Marked for deletion on save @@ -59,6 +60,9 @@ class Terrain3DRegion : public Resource { bool _modified = false; // Marked for saving Vector2i _location = V2I_MAX; + // DEPRECATED 0.9.3-dev - Remove 1.0 + Dictionary _multimeshes; // Dictionary[mesh_id:int] -> MultiMesh + public: Terrain3DRegion() {} ~Terrain3DRegion() {} @@ -90,8 +94,10 @@ class Terrain3DRegion : public Resource { void calc_height_range(); // Instancer - void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } - Dictionary get_multimeshes() const { return _multimeshes; } + void set_instances(const Dictionary &p_instances) { _instances = p_instances; } + Dictionary get_instances() const { return _instances; } + void set_vertex_spacing(const real_t p_vertex_spacing) { _vertex_spacing = CLAMP(p_vertex_spacing, 0.25f, 10.f); } + real_t get_vertex_spacing() const { return _vertex_spacing; } // File I/O Error save(const String &p_path = "", const bool p_16_bit = false); @@ -111,6 +117,11 @@ class Terrain3DRegion : public Resource { Dictionary get_data() const; Ref duplicate(const bool p_deep = false); + // DEPRECATED 0.9.3-dev - Remove 1.0 + void set_multimeshes(const Dictionary &p_multimeshes); + // TODO Drop this + Dictionary get_multimeshes() const { return _multimeshes; } + protected: static void _bind_methods(); }; diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index 8c38cc06..7f2dbc8d 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -77,24 +77,34 @@ void Terrain3DUtil::dump_maps(const TypedArray &p_maps, const String &p_n // Expects a filename in a String like: "terrain3d-01_02.res" which returns (-1, 2) Vector2i Terrain3DUtil::filename_to_location(const String &p_filename) { - String working_string = p_filename.trim_suffix(".res"); - String y_str = working_string.right(3).replace("_", ""); - working_string = working_string.erase(working_string.length() - 3, 3); - String x_str = working_string.right(3).replace("_", ""); + String location_string = p_filename.trim_prefix("terrain3d").trim_suffix(".res"); + return string_to_location(location_string); +} + +// Expects a string formatted as: "±##±##" which returns (##,##) +Vector2i Terrain3DUtil::string_to_location(const String &p_string) { + String x_str = p_string.left(3).replace("_", ""); + String y_str = p_string.right(3).replace("_", ""); if (!x_str.is_valid_int() || !y_str.is_valid_int()) { - LOG(ERROR, "Malformed filename at ", p_filename, ": got x ", x_str, " y ", y_str); + LOG(ERROR, "Malformed string '", p_string, "'. Result: ", x_str, ", ", y_str); return V2I_MAX; } return Vector2i(x_str.to_int(), y_str.to_int()); } +// Expects a v2i(-1,2) and returns terrain3d-01_02.res String Terrain3DUtil::location_to_filename(const Vector2i &p_region_loc) { + return "terrain3d" + location_to_string(p_region_loc) + ".res"; +} + +// Expects a v2i(-1,2) and returns -01_02 +String Terrain3DUtil::location_to_string(const Vector2i &p_region_loc) { const String POS_REGION_FORMAT = "_%02d"; const String NEG_REGION_FORMAT = "%03d"; String x_str, y_str; x_str = vformat((p_region_loc.x >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.x); y_str = vformat((p_region_loc.y >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.y); - return "terrain3d" + x_str + y_str + ".res"; + return x_str + y_str; } Ref Terrain3DUtil::black_to_alpha(const Ref &p_image) { diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index 8fa5ac2f..9f5231ea 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -29,7 +29,9 @@ class Terrain3DUtil : public Object { // String functions static Vector2i filename_to_location(const String &p_filename); + static Vector2i string_to_location(const String &p_string); static String location_to_filename(const Vector2i &p_region_loc); + static String location_to_string(const Vector2i &p_region_loc); // Image operations static Ref black_to_alpha(const Ref &p_image); @@ -60,15 +62,6 @@ typedef Terrain3DUtil Util; // Math /////////////////////////// -// Rounds a decimal to the nearest multiple eg round_multiple(2.7, 4) -> 4 -template -T round_multiple(const T p_value, const T p_multiple) { - if (p_multiple == 0) { - return p_value; - } - return static_cast(std::round(static_cast(p_value) / static_cast(p_multiple)) * static_cast(p_multiple)); -} - inline bool is_power_of_2(const int p_n) { return p_n && !(p_n & (p_n - 1)); }