/*************************************************************************/ /* merging_tool.cpp */ /*************************************************************************/ /* This file is part of: */ /* PANDEMONIUM ENGINE */ /* https://github.com/Relintai/pandemonium_engine */ /*************************************************************************/ /* Copyright (c) 2022-present Péter Magyar. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "merging_tool.h" #include "core/config/engine.h" #include "core/os/os.h" #include "scene/3d/mesh_instance.h" #include "scene/resources/material/spatial_material.h" #include "scene/resources/mesh/surface_tool.h" #include "modules/modules_enabled.gen.h" // For csg. #ifdef MODULE_CSG_ENABLED #include "modules/csg/csg_shape.h" #endif bool MergingTool::wrapped_split_by_surface(const MeshInstance &p_source_mi, Vector p_destination_mesh_instances, Mesh::StorageMode p_storage_mode) { ERR_FAIL_COND_V_MSG(!p_source_mi.is_inside_tree(), false, "Source MeshInstance must be inside the SceneTree."); ERR_FAIL_COND_V_MSG(!p_source_mi.get_mesh().is_valid(), false, "Source MeshInstance must have a valid mesh to split."); // For simplicity we are requiring that the destination MeshInstances have the same parent // as the source. This means we can use identical transforms. Node *parent = p_source_mi.get_parent(); ERR_FAIL_NULL_V_MSG(parent, false, "Source MeshInstance must have a parent node."); // Bound function only support variants, so we need to convert to a list of MeshInstances. Vector mis; for (int n = 0; n < p_destination_mesh_instances.size(); n++) { MeshInstance *mi = Object::cast_to(p_destination_mesh_instances[n]); ERR_FAIL_NULL_V_MSG(mi, false, "Can only be split to MeshInstances."); ERR_FAIL_COND_V_MSG(mi == &p_source_mi, false, "Source MeshInstance cannot be a destination."); ERR_FAIL_COND_V_MSG(mi->get_parent() != parent, false, "Destination MeshInstances must be siblings of the source MeshInstance."); mis.push_back(mi); } ERR_FAIL_COND_V_MSG(mis.size() != p_source_mi.get_mesh()->get_surface_count(), false, "Number of source surfaces and number of destination MeshInstances must match."); // Go through each surface, and fill the relevant mesh instance. const Mesh *source_mesh = p_source_mi.get_mesh().ptr(); DEV_ASSERT(source_mesh); ERR_FAIL_COND_V_MSG(source_mesh->get_surface_count() <= 1, false, "Source MeshInstance must contain multiple surfaces."); for (int s = 0; s < source_mesh->get_surface_count(); s++) { MeshInstance &dest_mi = *mis[s]; if (split_surface_to_mesh_instance(p_source_mi, s, dest_mi)) { // Change storage mode if required. #ifdef TOOLS_ENABLED Ref rmesh = dest_mi.get_mesh(); if (rmesh.is_valid()) { _mesh_set_storage_mode(rmesh.ptr(), p_storage_mode); } #endif } } return true; } bool MergingTool::wrapped_merge_meshes(MeshInstance &r_dest_mi, Vector p_list, bool p_use_global_space, bool p_check_compatibility, bool p_shadows_only, Mesh::StorageMode p_storage_mode) { // Bound function only support variants, so we need to convert to a list of MeshInstances. Vector mis; for (int n = 0; n < p_list.size(); n++) { MeshInstance *mi = Object::cast_to(p_list[n]); if (mi) { ERR_FAIL_COND_V_MSG(mi == &r_dest_mi, false, "Destination MeshInstance cannot be a source."); mis.push_back(mi); } else { ERR_PRINT("Only MeshInstances can be merged."); } } ERR_FAIL_COND_V_MSG(!mis.size(), false, "Array contains no MeshInstances"); bool result; if (p_shadows_only) { result = merge_shadow_meshes(r_dest_mi, mis, p_use_global_space, p_check_compatibility); } else { result = merge_meshes(r_dest_mi, mis, p_use_global_space, p_check_compatibility); } // Change storage mode if required. if (result) { #ifdef TOOLS_ENABLED Ref rmesh = r_dest_mi.get_mesh(); if (rmesh.is_valid()) { _mesh_set_storage_mode(rmesh.ptr(), p_storage_mode); } #endif } return result; } bool MergingTool::_is_material_opaque(const Ref &p_mat) { if (p_mat.is_null()) { return true; } Ref material = p_mat; if (material.is_null()) { // Shaders not yet supported. return false; } if (material->get_feature(SpatialMaterial::FEATURE_TRANSPARENT)) { return false; } // Not sure if this can only occur with FEATURE_TRANSPARENT? if (material->get_flag(SpatialMaterial::FLAG_USE_ALPHA_SCISSOR)) { return false; } // Only supporting default cull mode for now. if (material->get_cull_mode() != SpatialMaterial::CULL_BACK) { return false; } return true; } bool MergingTool::_is_shadow_mergeable(const MeshInstance &p_mi) { if (p_mi.get_cast_shadows_setting() == GeometryInstance::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF) { return false; } if (!_is_material_opaque(p_mi.get_material_overlay())) { return false; } if (!_is_material_opaque(p_mi.get_material_override())) { return false; } int num_surfaces = p_mi.get_mesh()->get_surface_count(); for (int n = 0; n < num_surfaces; n++) { if (!_is_material_opaque(p_mi.get_active_material(n))) { return false; } } return true; } bool MergingTool::is_shadow_mergeable_with(const MeshInstance &p_mi, const MeshInstance &p_other) { // Various settings that must match. if (!_is_mergeable_with_common(p_mi, p_other)) { return false; } if (!_is_shadow_mergeable(p_mi) || !_is_shadow_mergeable(p_other)) { return false; } return true; } bool MergingTool::_is_mergeable_with_common(const MeshInstance &p_mi, const MeshInstance &p_other) { if (!p_mi.get_mesh().is_valid() || !p_other.get_mesh().is_valid()) { return false; } if (!p_mi.is_merging_allowed() || !p_other.is_merging_allowed()) { return false; } if (p_mi.get_cast_shadows_setting() != p_other.get_cast_shadows_setting()) { return false; } if (p_mi.is_visible() != p_other.is_visible()) { return false; } if (p_mi.is_visible_in_tree() != p_other.is_visible_in_tree()) { return false; } if (p_mi.get_layer_mask() != p_other.get_layer_mask()) { return false; } if (p_mi.get_portal_mode() != p_other.get_portal_mode()) { return false; } if (p_mi.get_include_in_bound() != p_other.get_include_in_bound()) { return false; } if (p_mi.get_portal_autoplace_priority() != p_other.get_portal_autoplace_priority()) { return false; } if (p_mi.get_extra_cull_margin() != p_other.get_extra_cull_margin()) { return false; } return true; } bool MergingTool::is_mergeable_with(const MeshInstance &p_mi, const MeshInstance &p_other, bool p_check_surface_material_match) { if (!_is_mergeable_with_common(p_mi, p_other)) { return false; } // Various settings that must match. if (p_mi.is_visible() != p_other.is_visible()) { return false; } if (p_mi.get_material_overlay() != p_other.get_material_overlay()) { return false; } if (p_mi.get_material_override() != p_other.get_material_override()) { return false; } if (p_mi.get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) != p_other.get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT)) { return false; } if (p_mi.get_generate_lightmap() != p_other.get_generate_lightmap()) { return false; } if (p_mi.get_lightmap_scale() != p_other.get_lightmap_scale()) { return false; } if (p_check_surface_material_match) { Ref rmesh_a = p_mi.get_mesh(); Ref rmesh_b = p_other.get_mesh(); int num_surfaces = rmesh_a->get_surface_count(); if (num_surfaces != rmesh_b->get_surface_count()) { return false; } for (int n = 0; n < num_surfaces; n++) { // Materials must match. if (p_mi.get_active_material(n) != p_other.get_active_material(n)) { return false; } // Formats must match. uint32_t format_a = rmesh_a->surface_get_format(n); uint32_t format_b = rmesh_b->surface_get_format(n); if (format_a != format_b) { return false; } } } // NOTE : These three commented out sections below are more conservative // checks for whether to allow mesh merging. I am not absolutely sure a priori // how conservative we need to be, so we can further enable this if testing // shows they are required. // if (get_surface_material_count() != p_other.get_surface_material_count()) { // return false; // } // for (int n = 0; n < get_surface_material_count(); n++) { // if (get_surface_material(n) != p_other.get_surface_material(n)) { // return false; // } // } // test only allow identical meshes // if (get_mesh() != p_other.get_mesh()) { // return false; // } return true; } void MergingTool::split_mesh_instance_by_locality(MeshInstance &r_mi, const AABB &p_bound, uint32_t p_splits_horz, uint32_t p_splits_vert, uint32_t p_min_split_poly_count) { Ref rmesh = r_mi.get_mesh(); if (!rmesh.is_valid()) { return; } // Need a parent to attach results to. if (!r_mi.get_parent()) { return; } Vector3 cell_size = p_bound.size; cell_size.x /= p_splits_horz; cell_size.y /= p_splits_vert; cell_size.z /= p_splits_horz; DEV_ASSERT(p_splits_horz); DEV_ASSERT(p_splits_vert); int splits_horz_minus_one = p_splits_horz - 1; int splits_vert_minus_one = p_splits_vert - 1; // This is to prevent a warning as error in release builds, as this is only used // for DEV_ASSERT #ifdef DEV_ENABLED uint32_t total_zones = p_splits_horz * p_splits_horz * p_splits_vert; #endif AABB aabb; aabb.size = cell_size; ERR_FAIL_COND(!r_mi.is_inside_tree()); Transform xform = r_mi.get_global_transform(); SurfaceTool st_main; for (int s = 0; s < rmesh->get_surface_count(); s++) { st_main.create_from(rmesh, s); uint32_t tri_count = st_main.get_num_draw_vertices() / 3; // Bug .. we want to keep this surface in this case! and not delete the whole mesh instance? // at the moment this ASSUMES there is only one surface. if (tri_count < p_min_split_poly_count) { continue; } // Input for bounds routine should be deindexed. st_main.deindex(); // Assign each triangle to a split zone. uint32_t num_tris = st_main.vertex_array.size() / 3; Vector3 v[3]; const SurfaceTool::Vertex *input = st_main.vertex_array.ptr(); LocalVector tri_ids; tri_ids.resize(num_tris); for (uint32_t t = 0; t < num_tris; t++) { // Split in world space. Vector3 center; for (int c = 0; c < 3; c++) { v[c] = input->vertex; input++; v[c] = xform.xform(v[c]); center += v[c]; } center /= 3; // Get relative to bound. center -= p_bound.position; // Find the x y z . center /= cell_size; int x = center.x; int y = center.y; int z = center.z; x = CLAMP(x, 0, splits_horz_minus_one); y = CLAMP(y, 0, splits_vert_minus_one); z = CLAMP(z, 0, splits_horz_minus_one); uint32_t id = (x + (z * p_splits_horz) + (y * p_splits_horz * p_splits_vert)); tri_ids[t] = id; DEV_ASSERT(id < total_zones); } for (uint32_t x = 0; x < p_splits_horz; x++) { for (uint32_t y = 0; y < p_splits_vert; y++) { for (uint32_t z = 0; z < p_splits_horz; z++) { uint32_t id = (x + (z * p_splits_horz) + (y * p_splits_horz * p_splits_vert)); _split_mesh_instance_by_locality(st_main, r_mi, tri_ids, id, s, x, y, z); } } } } // for s } void MergingTool::_split_mesh_instance_by_locality(const SurfaceTool &p_st_main, const MeshInstance &p_source_mi, const LocalVector &p_tri_ids, uint32_t p_local_id, uint32_t p_surface_id, uint32_t p_x, uint32_t p_y, uint32_t p_z) { SurfaceTool st; int num_inds = st.create_from_subset(p_st_main, p_tri_ids, p_local_id); // This could be quite common, bounds with no triangles within. if (!num_inds) { return; } Node *parent = p_source_mi.get_parent(); DEV_ASSERT(parent); // Create a mesh instance to hold this "zone". MeshInstance *sib = memnew(MeshInstance); parent->add_child(sib); sib->set_owner(p_source_mi.get_owner()); String new_name = String(p_source_mi.get_name()); if (p_surface_id) { new_name += " _surf_" + itos(p_surface_id); } new_name += " split (" + itos(p_x) + "," + itos(p_y) + "," + itos(p_z) + ")"; sib->set_name(new_name); #ifdef TOOLS_ENABLED #if 0 _merge_log("_split_mesh_instance_by_locality " + itos(num_inds) + " inds : " + new_name); #endif #endif Ref am; am.instance(); Array arr = st.commit_to_arrays(); am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); // Set all the surfaces on the mesh. sib->set_mesh(am); _copy_mesh_instance_settings(p_source_mi, *sib, true, true); } bool MergingTool::clean_mesh_instance(MeshInstance &p_mi) { Ref rmesh = p_mi.get_mesh(); ERR_FAIL_COND_V(!rmesh.is_valid(), false); ERR_FAIL_COND_V(!p_mi.is_inside_tree(), false); Transform tr = p_mi.get_global_transform(); String name = p_mi.get_name(); bool data_changed = false; Ref am; am.instance(); int inds_removed = 0; for (int s = 0; s < rmesh->get_surface_count(); s++) { inds_removed += _clean_mesh_surface(name, tr, rmesh, s, am); } if (inds_removed) { _merge_log("cleaning MeshInstance \"" + p_mi.get_name() + "\" removed " + itos(inds_removed) + " indices.", 2); p_mi.set_mesh(am); } return data_changed; } int MergingTool::_clean_mesh_surface(const String &p_source_name, const Transform &p_xform, Ref &p_rmesh, int p_surface_id, Ref r_dest_mesh) { Array arrays = p_rmesh->surface_get_arrays(p_surface_id); LocalVector verts = PoolVector(arrays[RS::ARRAY_VERTEX]); if (!verts.size()) { // Early out if there are no vertices, no point in doing anything else. return 0; } LocalVector indices = PoolVector(arrays[RS::ARRAY_INDEX]); // Transform verts to world space. for (uint32_t n = 0; n < verts.size(); n++) { verts[n] = p_xform.xform(verts[n]); } // Special case, if no indices, create some. unsigned int num_indices_before = indices.size(); if (!_ensure_indices_valid(indices, verts)) { #ifdef TOOLS_ENABLED _merge_log("\tignoring INVALID TRIANGLES (duplicate indices or zero area triangle) detected in " + p_source_name + ", num inds before / after " + itos(num_indices_before) + " / " + itos(indices.size())); #endif // Save the modified index array. arrays[RS::ARRAY_INDEX] = PoolVector(indices); // Note we aren't removing the unused verts here, to save hassle, but hopefully there won't be too many. r_dest_mesh->add_surface_from_arrays(p_rmesh->surface_get_primitive_type(p_surface_id), arrays); r_dest_mesh->surface_set_material(p_surface_id, p_rmesh->surface_get_material(p_surface_id)); // Returns true if data changed. if (indices.size() >= num_indices_before) { ERR_PRINT_ONCE("Indices after cleaning is higher than before."); return 1; } return num_indices_before - indices.size(); } // Still add the surface, as a later one may be modified. r_dest_mesh->add_surface_from_arrays(p_rmesh->surface_get_primitive_type(p_surface_id), arrays); r_dest_mesh->surface_set_material(p_surface_id, p_rmesh->surface_get_material(p_surface_id)); return 0; } bool MergingTool::_ensure_indices_valid(LocalVector &r_indices, const PoolVector &p_verts) { // No indices? create some. if (!r_indices.size()) { #ifdef TOOLS_ENABLED _merge_log("\t\t\t\tindices are blank, creating..."); #endif // Indices are blank!! Let's create some, assuming the mesh is using triangles. r_indices.resize(p_verts.size()); // This is assuming each triangle vertex is unique. for (unsigned int n = 0; n < r_indices.size(); n++) { r_indices[n] = n; } } if (!_check_for_valid_indices(r_indices, p_verts, nullptr)) { LocalVector new_inds; _check_for_valid_indices(r_indices, p_verts, &new_inds); // Copy the new indices. r_indices = new_inds; return false; } return true; } // Check for invalid tris, or make a list of the valid triangles, depending on whether r_inds is set. bool MergingTool::_check_for_valid_indices(const LocalVector &p_inds, const PoolVector &p_verts, LocalVector *r_inds) { int nTris = p_inds.size(); nTris /= 3; int indCount = 0; for (int t = 0; t < nTris; t++) { int i0 = p_inds[indCount++]; int i1 = p_inds[indCount++]; int i2 = p_inds[indCount++]; bool ok = true; // If the indices are the same, the triangle is invalid. if (i0 == i1) { ok = false; } if (i1 == i2) { ok = false; } if (i0 == i2) { ok = false; } // Check positions. if (ok) { // Vertex positions. const Vector3 &p0 = p_verts[i0]; const Vector3 &p1 = p_verts[i1]; const Vector3 &p2 = p_verts[i2]; // If the area is zero, the triangle is invalid (and will crash xatlas if we use it). if (_triangle_is_degenerate(p0, p1, p2, 0.00001)) { #ifdef TOOLS_ENABLED _merge_log("\t\tdetected zero area triangle, ignoring"); #endif ok = false; } } if (ok) { // If the triangle is ok, we will output it if we are outputting. if (r_inds) { r_inds->push_back(i0); r_inds->push_back(i1); r_inds->push_back(i2); } } else { // If triangle not ok, return failed check if we are not outputting. if (!r_inds) { return false; } } } return true; } bool MergingTool::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) { // Not interested in the actual area, but numerical stability. Vector3 edge1 = p_b - p_a; Vector3 edge2 = p_c - p_a; // For numerical stability keep these values reasonably high. edge1 *= 1024.0; edge2 *= 1024.0; Vector3 vec = edge1.cross(edge2); real_t sl = vec.length_squared(); if (sl <= p_epsilon) { return true; } return false; } // If p_check_compatibility is set to false you MUST have performed a prior check using // is_shadow_mergeable_with, otherwise you could get mismatching surface formats leading to graphical errors etc. bool MergingTool::merge_shadow_meshes(MeshInstance &r_dest_mi, Vector p_list, bool p_use_global_space, bool p_check_compatibility) { ERR_FAIL_COND_V(p_list.size() < 1, false); // Use the first mesh instance to get common data like number of surfaces. const MeshInstance *first = p_list[0]; // Mesh compatibility checking. This is relatively expensive, so if done already (e.g. in Room system) // this step can be avoided. LocalVector compat_list; if (p_check_compatibility) { compat_list.resize(p_list.size()); for (int n = 0; n < p_list.size(); n++) { compat_list[n] = false; } compat_list[0] = true; for (uint32_t n = 1; n < compat_list.size(); n++) { compat_list[n] = is_shadow_mergeable_with(*first, *p_list[n]); if (compat_list[n] == false) { WARN_PRINT("MeshInstance " + p_list[n]->get_name() + " is incompatible for shadow merging with " + first->get_name() + ", ignoring."); } } } Ref am; am.instance(); // If we want a local space result, we need the world space transform of this MeshInstance // available to back transform verts from world space. Transform dest_tr_inv; if (!p_use_global_space) { if (r_dest_mi.is_inside_tree()) { dest_tr_inv = r_dest_mi.get_global_transform(); dest_tr_inv.affine_invert(); } else { WARN_PRINT("MeshInstance must be inside tree to merge using local space, falling back to global space."); } } SurfaceTool surface_tool; for (int n = 0; n < p_list.size(); n++) { // Ignore if the mesh is incompatible. if (p_check_compatibility && (!compat_list[n])) { continue; } MeshInstance *source_mi = p_list[n]; Ref rmesh = source_mi->get_mesh(); Transform adjustment_xform = dest_tr_inv * source_mi->get_global_transform(); for (int s = 0; s < rmesh->get_surface_count(); s++) { surface_tool.append_from(rmesh, s, adjustment_xform); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { _merge_log("merging from \"" + source_mi->get_name() + "\" surf " + itos(s) + " to \"" + r_dest_mi.get_name() + "\""); MergingTool::append_editor_description(&r_dest_mi, "merging from", source_mi); } #endif } } // for n through source meshes // We are only interested in position data for shadow proxy meshes, and indices if present. surface_tool._mask_format_flags(Mesh::ARRAY_FORMAT_VERTEX | Mesh::ARRAY_FORMAT_INDEX); Array arr = surface_tool.commit_to_arrays(); am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); // Set all the surfaces on the mesh. r_dest_mi.set_mesh(am); _copy_geometry_instance_settings(*first, r_dest_mi, false); r_dest_mi.set_cast_shadows_setting(GeometryInstance::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY); // Don't want these set, they get set by the _copy_geometry_instance_settings call. r_dest_mi.set_material_overlay(Ref()); r_dest_mi.set_material_override(Ref()); return true; } void MergingTool::_mesh_set_storage_mode(Mesh *p_mesh, Mesh::StorageMode p_mode) { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { ERR_FAIL_NULL(p_mesh); p_mesh->set_storage_mode(p_mode); } #endif } bool MergingTool::split_surface_to_mesh_instance(const MeshInstance &p_source_mi, int p_surface_id, MeshInstance &r_mi) { SurfaceTool surface_tool; Ref rmesh = p_source_mi.get_mesh(); if (!rmesh.is_valid()) { return false; } // Hard coded to local space for now. surface_tool.append_from(rmesh, p_surface_id, Transform()); Ref am; am.instance(); _mesh_set_storage_mode(am.ptr(), Mesh::STORAGE_MODE_CPU); Array arr = surface_tool.commit_to_arrays(); am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); r_mi.set_mesh(am); // Set the material on the new mesh instance. _set_rmesh_material(r_mi, r_mi.get_mesh(), 0, p_source_mi.get_active_material(p_surface_id)); // Set some properties to match the source mesh. // As they are guaranteed siblings, the transform can be identical. _copy_geometry_instance_settings(p_source_mi, r_mi, true); return true; } #ifdef TOOLS_ENABLED void MergingTool::append_editor_description(Node *p_node, String p_string, Node *p_node_named) { ERR_FAIL_NULL(p_node); String existing = p_node->get_editor_description(); if (existing.size() > 512) { // limit the max length of the description to prevent things getting ridiculous return; } String add; if (existing.size()) { add += "\n"; } add += p_string; if (p_node_named) { add += " \"" + p_node_named->get_name() + "\""; } p_node->set_editor_description(existing + add); } #endif #ifdef DEV_ENABLED void MergingTool::debug_branch(Node *p_node, const char *p_title, int p_depth) { if (OS::get_singleton()->is_stdout_verbose()) { if (p_title) { _merge_log(p_title); } if (p_node->is_queued_for_deletion()) { return; } String s; for (int n = 0; n < p_depth; n++) { s += "\t"; } s += "\"" + p_node->get_name() + "\"\t"; String filename = p_node->get_filename(); if (filename.size()) { s += "[filename " + p_node->get_filename() + "] "; } s += "owner ("; if (p_node->get_owner()) { s += p_node->get_owner()->get_name(); } else { s += "NULL"; } s += ")"; _merge_log(s); for (int n = 0; n < p_node->get_child_count(); n++) { debug_branch(p_node->get_child(n), nullptr, p_depth + 1); } } // if verbose output } #endif void MergingTool::debug_mesh_instance(const MeshInstance &p_mi) { #ifdef DEV_ENABLED _merge_log("debug " + p_mi.get_name()); Ref rmesh = p_mi.get_mesh(); if (!rmesh.is_valid()) { _merge_log("\tinvalid mesh"); return; } for (int s = 0; s < rmesh->get_surface_count(); s++) { _merge_log("\tsurf " + itos(s) + " inds " + itos(rmesh->surface_get_array_index_len(s)) + " verts " + itos(rmesh->surface_get_array_len(s))); } #endif } bool MergingTool::join_mesh_surface(const MeshInstance &p_source_mi, uint32_t p_source_surface_id, MeshInstance &r_dest_mi) { Ref r_sourcemesh = p_source_mi.get_mesh(); ERR_FAIL_COND_V(!r_sourcemesh.is_valid(), false); ERR_FAIL_COND_V((int)p_source_surface_id >= r_sourcemesh->get_surface_count(), false); // Note this can be NULL if the destination mesh instance contains no meshes yet. // We should deal with this case. Ref ra_destmesh = r_dest_mi.get_mesh(); if (!ra_destmesh.is_valid()) { ra_destmesh.instance(); _mesh_set_storage_mode(ra_destmesh.ptr(), Mesh::STORAGE_MODE_CPU); } // Relative xform .. Transform relative_xform = r_dest_mi.get_global_transform().inverse() * p_source_mi.get_global_transform(); SurfaceTool surface_tool; surface_tool.append_from(r_sourcemesh, p_source_surface_id, relative_xform); int new_surface_id = 0; if (ra_destmesh.is_valid()) { new_surface_id = ra_destmesh->get_surface_count(); r_dest_mi.set_mesh(surface_tool.commit(ra_destmesh)); } else { r_dest_mi.set_mesh(surface_tool.commit()); } Ref new_rmesh = r_dest_mi.get_mesh(); // If no surface has been added. ERR_FAIL_COND_V(new_rmesh->get_surface_count() <= new_surface_id, false); // Deal with materials. _set_rmesh_material(r_dest_mi, new_rmesh, new_surface_id, p_source_mi.get_active_material(p_source_surface_id)); return true; } // No compat checking, no renaming. bool MergingTool::join_meshes(MeshInstance &r_dest_mi, Vector p_list) { if (p_list.size() < 1) { // Should not happen but just in case... return false; } // For future use of compatibility check. LocalVector list = p_list; MeshInstance *first = list[0]; // First copy the properties of the first meshinstance. _copy_mesh_instance_settings(*first, r_dest_mi, false, false); for (unsigned int n = 0; n < list.size(); n++) { MeshInstance *mi = list[n]; Ref rmesh = mi->get_mesh(); for (int s = 0; s < rmesh->get_surface_count(); s++) { if (MergingTool::join_mesh_surface(*mi, s, r_dest_mi)) { #ifdef DEV_ENABLED _merge_log("joining \"" + mi->get_name() + "\" to \"" + r_dest_mi.get_name() + "\""); #endif #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { MergingTool::append_editor_description(&r_dest_mi, "joined to", mi); } #endif } else { #ifdef DEV_ENABLED _merge_log("failed to join \"" + mi->get_name() + "\" to \"" + r_dest_mi.get_name() + "\""); #endif } } } return true; } // If p_check_compatibility is set to false you MUST have performed a prior check using // is_mergeable_with, otherwise you could get mismatching surface formats leading to graphical errors etc. bool MergingTool::merge_meshes(MeshInstance &r_dest_mi, Vector p_list, bool p_use_global_space, bool p_check_compatibility) { ERR_FAIL_COND_V(p_list.size() < 1, false); // Use the first mesh instance to get common data like number of surfaces. const MeshInstance *first = p_list[0]; Ref rmesh_first = first->get_mesh(); if (!rmesh_first.is_valid()) { return false; } int surface_count = rmesh_first->get_surface_count(); if (surface_count <= 0) { #ifdef TOOLS_ENABLED _merge_log("merge_meshes : " + first->get_name() + " contains no surfaces, ignoring."); #endif return false; } // Mesh compatibility checking. This is relatively expensive, so if done already (e.g. in Room system) // this step can be avoided. LocalVector compat_list; if (p_check_compatibility) { compat_list.resize(p_list.size()); for (int n = 0; n < p_list.size(); n++) { compat_list[n] = false; } compat_list[0] = true; for (uint32_t n = 1; n < compat_list.size(); n++) { compat_list[n] = is_mergeable_with(*first, *p_list[n], true); if (compat_list[n] == false) { WARN_PRINT("MeshInstance " + p_list[n]->get_name() + " is incompatible for merging with " + first->get_name() + ", ignoring."); } } } Ref am; am.instance(); _mesh_set_storage_mode(am.ptr(), Mesh::STORAGE_MODE_CPU); // If we want a local space result, we need the world space transform of this MeshInstance // available to back transform verts from world space. Transform dest_tr_inv; if (!p_use_global_space) { if (r_dest_mi.is_inside_tree()) { dest_tr_inv = r_dest_mi.get_global_transform(); dest_tr_inv.affine_invert(); } else { WARN_PRINT("MeshInstance must be inside tree to merge using local space, falling back to global space."); } } for (int s = 0; s < surface_count; s++) { SurfaceTool surface_tool; for (int n = 0; n < p_list.size(); n++) { // Ignore if the mesh is incompatible. if (p_check_compatibility && (!compat_list[n])) { continue; } Ref rmesh = p_list[n]->get_mesh(); Transform adjustment_xform = dest_tr_inv * p_list[n]->get_global_transform(); surface_tool.append_from(rmesh, s, adjustment_xform); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { _merge_log("merging from \"" + p_list[n]->get_name() + "\" surf " + itos(s) + " to \"" + r_dest_mi.get_name() + "\""); MergingTool::append_editor_description(&r_dest_mi, "merging from", p_list[n]); } #endif } Array arr = surface_tool.commit_to_arrays(); am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); } // for s // Set all the surfaces on the mesh. r_dest_mi.set_mesh(am); // Set some properties to match the merged meshes. _copy_mesh_instance_settings(*first, r_dest_mi, false, true); return true; } bool MergingTool::split_csg_surface_to_mesh_instance(const CSGShape &p_shape, MeshInstance &r_mi, const Ref &p_array_mesh, CSGBrush *p_brush, int p_surface) { #ifdef MODULE_CSG_ENABLED SurfaceTool surface_tool; Ref am; am.instance(); Ref rmesh = p_array_mesh; // We are matching the local transforms of the source and destination, as they are always // siblings for now. surface_tool.append_from(rmesh, p_surface, Transform()); Array arr = surface_tool.commit_to_arrays(); am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); r_mi.set_mesh(am); // Set the material on the new mesh instance. if (p_surface < p_brush->materials.size()) { _set_rmesh_material(r_mi, r_mi.get_mesh(), 0, p_brush->materials[p_surface]); } // As they are guaranteed siblings, the transform can be identical. _copy_geometry_instance_settings(p_shape, r_mi, true); return true; #else return false; #endif } void MergingTool::_copy_mesh_instance_settings(const MeshInstance &p_source, MeshInstance &r_dest, bool p_copy_transform, bool p_copy_materials) { _copy_geometry_instance_settings(p_source, r_dest, p_copy_transform); if (p_copy_materials) { // Set merged materials. Ref rmesh = p_source.get_mesh(); if (rmesh.is_valid()) { for (int n = 0; n < rmesh->get_surface_count(); n++) { _set_rmesh_material(r_dest, r_dest.get_mesh(), n, p_source.get_active_material(n)); } } } } void MergingTool::_set_rmesh_material(MeshInstance &r_mi, Ref r_rmesh, int p_surface_id, Ref p_material) { // Here we can either set the material on the rmesh, or on the mesh instance. // Setting it directly in the rmesh seems more desired by users, but perhaps this could be // switchable? r_rmesh->surface_set_material(p_surface_id, p_material); // r_mi.set_surface_material(p_surface_id, p_material); } void MergingTool::_copy_geometry_instance_settings(const GeometryInstance &p_source, MeshInstance &r_dest, bool p_copy_transform) { // Set some properties to match the source mesh. r_dest.set_material_overlay(p_source.get_material_overlay()); r_dest.set_material_override(p_source.get_material_override()); r_dest.set_cast_shadows_setting(p_source.get_cast_shadows_setting()); r_dest.set_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT, p_source.get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT)); r_dest.set_portal_mode(p_source.get_portal_mode()); r_dest.set_include_in_bound(p_source.get_include_in_bound()); r_dest.set_portal_autoplace_priority(p_source.get_portal_autoplace_priority()); r_dest.set_extra_cull_margin(p_source.get_extra_cull_margin()); // As they are guaranteed siblings, the transform can be identical. if (p_copy_transform) { r_dest.set_transform(p_source.get_transform()); } // Preserve visibility. // If they are siblings, they can share the visible flag, if not, we need to take into account visibility in tree. if (p_source.get_parent() == r_dest.get_parent()) { r_dest.set_visible(p_source.is_visible()); } else { r_dest.set_visible(p_source.is_visible_in_tree()); } } void MergingTool::_merge_log(String p_string, int p_priority) { #ifdef TOOLS_ENABLED switch (p_priority) { case 0: { print_verbose(p_string); } break; case 2: { print_line(p_string); } break; default: { #ifdef DEV_ENABLED print_line(p_string); #else print_verbose(p_string); #endif } break; } #endif } void MergingTool::_set_owner_logged(Node *p_node, Node *p_owner) { DEV_ASSERT(p_node != p_owner); #ifdef DEV_ENABLED #if 0 // Check whether the Node::set_owner() routine will allow this .. the owner must be in the tree above // for the call to work. bool valid = false; Node *probe = p_node->get_parent(); while (probe) { if (probe == p_owner) { valid = true; break; } probe = probe->get_parent(); } DEV_ASSERT(valid); #endif #endif if (p_node->get_owner() == p_owner) { return; } #ifdef DEV_ENABLED #if 0 String string = "\tchanging owner of \"" + p_node->get_name() + "\" from "; if (p_node->get_owner()) { string += p_node->get_owner()->get_name(); } else { string += "NULL"; } string += " to "; if (p_owner) { string += p_owner->get_name(); } else { string += "NULL"; } _merge_log(string); #endif #endif p_node->set_owner(p_owner); DEV_ASSERT(p_node->get_owner()); } bool MergingTool::_node_has_valid_children(Node *p_node) { for (int n = 0; n < p_node->get_child_count(); n++) { if (!p_node->get_child(n)->is_queued_for_deletion()) { return true; } } return false; } void MergingTool::_invalidate_owner_recursive(Node *p_node, Node *p_old_owner, Node *p_new_owner) { if (p_node->get_owner() == p_old_owner) { _set_owner_logged(p_node, p_new_owner); } for (int n = 0; n < p_node->get_child_count(); n++) { _invalidate_owner_recursive(p_node->get_child(n), p_old_owner, p_new_owner); } } void MergingTool::_reparent(Node *p_branch, Node *p_new_parent, Node *p_new_owner) { #ifdef PANDEMONIUM_MERGING_VERBOSE if (p_branch->get_parent()) { _merge_log("reparenting child " + p_branch->get_name() + " from parent " + p_branch->get_parent()->get_name() + " to parent " + p_new_parent->get_name()); } else { _merge_log("reparenting child " + p_branch->get_name() + " from parent NULL to parent " + p_new_parent->get_name()); } #endif // noop if (p_branch->get_parent() == p_new_parent) { return; } // Detach (if attached). if (p_branch->get_parent()) { p_branch->get_parent()->remove_child(p_branch); } // Must be added to the scene BEFORE setting the new owner // otherwise the set_owner() calls will fail to find the new owner. p_new_parent->add_child(p_branch); _reparent_subscene_send_new_owner(p_branch, p_new_owner); } void MergingTool::_reparent_subscene_send_new_owner(Node *p_node, Node *p_new_owner) { bool owner_found = false; // Is the current owner in the subscene? If so keep it, else change. Node *current_owner = p_node->get_owner(); if (current_owner) { Node *probe = p_node; while (probe) { if (probe == current_owner) { // Owner already exists in the subscene, no need to change. owner_found = true; break; } probe = probe->get_parent(); } } // if there was a current owner if (!owner_found) { _set_owner_logged(p_node, p_new_owner); } for (int n = 0; n < p_node->get_child_count(); n++) { _reparent_subscene_send_new_owner(p_node->get_child(n), p_new_owner); } }