From 10eb9564ca67908dc0c2740c83475cdce3d12c18 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Wed, 5 Jan 2022 10:24:27 +0000 Subject: [PATCH] Portals - Improve mesh merging Some improvements to robustness to account for more properties. Addition of an "allow merging" flag in the cull instance. --- doc/classes/CullInstance.xml | 4 ++ scene/3d/cull_instance.cpp | 7 +- scene/3d/cull_instance.h | 8 ++- scene/3d/mesh_instance.cpp | 131 ++++++++++++++++++++++++++++++----- scene/3d/mesh_instance.h | 15 ++-- scene/3d/room_manager.cpp | 5 +- 6 files changed, 140 insertions(+), 30 deletions(-) diff --git a/doc/classes/CullInstance.xml b/doc/classes/CullInstance.xml index de6f8b3f43e..f358d018bb7 100644 --- a/doc/classes/CullInstance.xml +++ b/doc/classes/CullInstance.xml @@ -15,6 +15,10 @@ + + This allows fine control over the mesh merging feature in the [RoomManager]. + Setting this option to [code]false[/code] can be used to prevent an instance being merged. + When set to [code]0[/code], [CullInstance]s will be autoplaced in the [Room] with the highest priority. When set to a value other than [code]0[/code], the system will attempt to autoplace in a [Room] with the [code]autoplace_priority[/code], if it is present. diff --git a/scene/3d/cull_instance.cpp b/scene/3d/cull_instance.cpp index 41179f1deca..a8100e35498 100644 --- a/scene/3d/cull_instance.cpp +++ b/scene/3d/cull_instance.cpp @@ -45,9 +45,12 @@ void CullInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("set_portal_mode", "mode"), &CullInstance::set_portal_mode); ClassDB::bind_method(D_METHOD("get_portal_mode"), &CullInstance::get_portal_mode); - ClassDB::bind_method(D_METHOD("set_include_in_bound"), &CullInstance::set_include_in_bound); + ClassDB::bind_method(D_METHOD("set_include_in_bound", "enabled"), &CullInstance::set_include_in_bound); ClassDB::bind_method(D_METHOD("get_include_in_bound"), &CullInstance::get_include_in_bound); + ClassDB::bind_method(D_METHOD("set_allow_merging", "enabled"), &CullInstance::set_allow_merging); + ClassDB::bind_method(D_METHOD("get_allow_merging"), &CullInstance::get_allow_merging); + ClassDB::bind_method(D_METHOD("set_portal_autoplace_priority", "priority"), &CullInstance::set_portal_autoplace_priority); ClassDB::bind_method(D_METHOD("get_portal_autoplace_priority"), &CullInstance::get_portal_autoplace_priority); @@ -61,11 +64,13 @@ void CullInstance::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "portal_mode", PROPERTY_HINT_ENUM, "Static,Dynamic,Roaming,Global,Ignore"), "set_portal_mode", "get_portal_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_in_bound"), "set_include_in_bound", "get_include_in_bound"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_merging"), "set_allow_merging", "get_allow_merging"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autoplace_priority", PROPERTY_HINT_RANGE, "-16,16,1", PROPERTY_USAGE_DEFAULT), "set_portal_autoplace_priority", "get_portal_autoplace_priority"); } CullInstance::CullInstance() { _portal_mode = PORTAL_MODE_STATIC; _include_in_bound = true; + _allow_merging = true; _portal_autoplace_priority = 0; } diff --git a/scene/3d/cull_instance.h b/scene/3d/cull_instance.h index ecbffcdb4da..248f556164b 100644 --- a/scene/3d/cull_instance.h +++ b/scene/3d/cull_instance.h @@ -48,9 +48,12 @@ public: void set_portal_mode(CullInstance::PortalMode p_mode); CullInstance::PortalMode get_portal_mode() const; - void set_include_in_bound(bool p_enable) { _include_in_bound = p_enable; } + void set_include_in_bound(bool p_enabled) { _include_in_bound = p_enabled; } bool get_include_in_bound() const { return _include_in_bound; } + void set_allow_merging(bool p_enabled) { _allow_merging = p_enabled; } + bool get_allow_merging() const { return _allow_merging; } + void set_portal_autoplace_priority(int p_priority) { _portal_autoplace_priority = p_priority; } int get_portal_autoplace_priority() const { return _portal_autoplace_priority; } @@ -63,7 +66,8 @@ protected: private: PortalMode _portal_mode; - bool _include_in_bound; + bool _include_in_bound : 1; + bool _allow_merging : 1; // Allows instances to prefer to be autoplaced // in specific RoomGroups. This allows building exteriors diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index b9b67249ee9..3a7f2844020 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -847,10 +847,40 @@ void MeshInstance::create_debug_tangents() { } } -bool MeshInstance::is_mergeable_with(const MeshInstance &p_other) { +bool MeshInstance::is_mergeable_with(Node *p_other) const { + const MeshInstance *mi = Object::cast_to(p_other); + + if (mi) { + return _is_mergeable_with(*mi); + } + + return false; +} + +bool MeshInstance::_is_mergeable_with(const MeshInstance &p_other) const { if (!get_mesh().is_valid() || !p_other.get_mesh().is_valid()) { return false; } + if (!get_allow_merging() || !p_other.get_allow_merging()) { + return false; + } + + // various settings that must match + if (get_material_overlay() != p_other.get_material_overlay()) { + return false; + } + if (get_material_override() != p_other.get_material_override()) { + return false; + } + if (get_cast_shadows_setting() != p_other.get_cast_shadows_setting()) { + return false; + } + if (get_flag(FLAG_USE_BAKED_LIGHT) != p_other.get_flag(FLAG_USE_BAKED_LIGHT)) { + return false; + } + if (is_visible() != p_other.is_visible()) { + return false; + } Ref rmesh_a = get_mesh(); Ref rmesh_b = p_other.get_mesh(); @@ -860,11 +890,6 @@ bool MeshInstance::is_mergeable_with(const MeshInstance &p_other) { return false; } - // overlay materials must match - if (get_material_overlay() != p_other.get_material_overlay()) { - return false; - } - for (int n = 0; n < num_surfaces; n++) { // materials must match if (get_active_material(n) != p_other.get_active_material(n)) { @@ -903,7 +928,7 @@ bool MeshInstance::is_mergeable_with(const MeshInstance &p_other) { return true; } -void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, int p_surface_id, PoolVector &r_verts, PoolVector &r_norms, PoolVector &r_tangents, PoolVector &r_colors, PoolVector &r_uvs, PoolVector &r_uv2s, PoolVector &r_inds) { +void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, const Transform &p_dest_tr_inv, int p_surface_id, PoolVector &r_verts, PoolVector &r_norms, PoolVector &r_tangents, PoolVector &r_colors, PoolVector &r_uvs, PoolVector &r_uv2s, PoolVector &r_inds) { _merge_log("\t\t\tmesh data from " + p_mi.get_name()); // get the mesh verts in local space @@ -923,7 +948,32 @@ void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, int p_surface PoolVector uv2s = arrays[VS::ARRAY_TEX_UV2]; PoolVector indices = arrays[VS::ARRAY_INDEX]; - // NEW .. the checking for valid triangles should be on WORLD SPACE vertices, + // The attributes present must match the first mesh for the attributes + // to remain in sync. Here we reject meshes with different attributes. + // We could alternatively invent missing attributes. + // This should hopefully be already caught by the mesh_format, but is included just in case here. + + // Don't perform these checks on the first Mesh, the first Mesh is a master + // and determines the attributes we want to be present. + if (r_verts.size() != 0) { + if ((bool)r_norms.size() != (bool)normals.size()) { + ERR_FAIL_MSG("Attribute mismatch with first Mesh (Normals), ignoring surface."); + } + if ((bool)r_tangents.size() != (bool)tangents.size()) { + ERR_FAIL_MSG("Attribute mismatch with first Mesh (Tangents), ignoring surface."); + } + if ((bool)r_colors.size() != (bool)colors.size()) { + ERR_FAIL_MSG("Attribute mismatch with first Mesh (Colors), ignoring surface."); + } + if ((bool)r_uvs.size() != (bool)uvs.size()) { + ERR_FAIL_MSG("Attribute mismatch with first Mesh (UVs), ignoring surface."); + } + if ((bool)r_uv2s.size() != (bool)uv2s.size()) { + ERR_FAIL_MSG("Attribute mismatch with first Mesh (UV2s), ignoring surface."); + } + } + + // The checking for valid triangles should be on WORLD SPACE vertices, // NOT model space // special case, if no indices, create some @@ -938,6 +988,11 @@ void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, int p_surface // transform verts to world space Transform tr = p_mi.get_global_transform(); + // But relative to the destination transform. + // This can either be identity (when the destination is global space), + // or the global transform of the owner MeshInstance (if using local space is selected). + tr = p_dest_tr_inv * tr; + // to transform normals Basis normal_basis = tr.basis.inverse(); normal_basis.transpose(); @@ -985,7 +1040,7 @@ void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, int p_surface } } -bool MeshInstance::_ensure_indices_valid(PoolVector &r_indices, const PoolVector &p_verts) { +bool MeshInstance::_ensure_indices_valid(PoolVector &r_indices, const PoolVector &p_verts) const { // no indices? create some if (!r_indices.size()) { _merge_log("\t\t\t\tindices are blank, creating..."); @@ -1022,7 +1077,7 @@ bool MeshInstance::_ensure_indices_valid(PoolVector &r_indices, const PoolV } // check for invalid tris, or make a list of the valid triangles, depending on whether r_inds is set -bool MeshInstance::_check_for_valid_indices(const PoolVector &p_inds, const PoolVector &p_verts, LocalVector *r_inds) { +bool MeshInstance::_check_for_valid_indices(const PoolVector &p_inds, const PoolVector &p_verts, LocalVector *r_inds) const { int nTris = p_inds.size(); nTris /= 3; int indCount = 0; @@ -1077,7 +1132,7 @@ bool MeshInstance::_check_for_valid_indices(const PoolVector &p_inds, const return true; } -bool MeshInstance::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) { +bool MeshInstance::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) const { // not interested in the actual area, but numerical stability Vector3 edge1 = p_b - p_a; Vector3 edge2 = p_c - p_a; @@ -1096,9 +1151,10 @@ bool MeshInstance::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_ return false; } -bool MeshInstance::create_by_merging(Vector p_list) { - // must be at least 2 meshes to merge - if (p_list.size() < 2) { +// 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 MeshInstance::merge_meshes(Vector p_list, bool p_use_global_space, bool p_check_compatibility) { + if (p_list.size() < 1) { // should not happen but just in case return false; } @@ -1106,9 +1162,42 @@ bool MeshInstance::create_by_merging(Vector p_list) { // 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] = first->_is_mergeable_with(*p_list[n]); + + 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(); + // 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 (is_inside_tree()) { + dest_tr_inv = 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 < first->get_mesh()->get_surface_count(); s++) { PoolVector verts; PoolVector normals; @@ -1119,7 +1208,12 @@ bool MeshInstance::create_by_merging(Vector p_list) { PoolVector inds; for (int n = 0; n < p_list.size(); n++) { - _merge_into_mesh_data(*p_list[n], s, verts, normals, tangents, colors, uvs, uv2s, inds); + // Ignore if the mesh is incompatible + if (p_check_compatibility && (!compat_list[n])) { + continue; + } + + _merge_into_mesh_data(*p_list[n], dest_tr_inv, s, verts, normals, tangents, colors, uvs, uv2s, inds); } // for n through source meshes if (!verts.size()) { @@ -1167,13 +1261,16 @@ bool MeshInstance::create_by_merging(Vector p_list) { set_surface_material(n, first->get_active_material(n)); } - // set overlay material + // set some properties to match the merged meshes set_material_overlay(first->get_material_overlay()); + set_material_override(first->get_material_override()); + set_cast_shadows_setting(first->get_cast_shadows_setting()); + set_flag(FLAG_USE_BAKED_LIGHT, first->get_flag(FLAG_USE_BAKED_LIGHT)); return true; } -void MeshInstance::_merge_log(String p_string) { +void MeshInstance::_merge_log(String p_string) const { print_verbose(p_string); } diff --git a/scene/3d/mesh_instance.h b/scene/3d/mesh_instance.h index 185d2354420..8508b67eb77 100644 --- a/scene/3d/mesh_instance.h +++ b/scene/3d/mesh_instance.h @@ -96,11 +96,12 @@ protected: private: // merging - void _merge_into_mesh_data(const MeshInstance &p_mi, int p_surface_id, PoolVector &r_verts, PoolVector &r_norms, PoolVector &r_tangents, PoolVector &r_colors, PoolVector &r_uvs, PoolVector &r_uv2s, PoolVector &r_inds); - bool _ensure_indices_valid(PoolVector &r_indices, const PoolVector &p_verts); - bool _check_for_valid_indices(const PoolVector &p_inds, const PoolVector &p_verts, LocalVector *r_inds); - bool _triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon); - void _merge_log(String p_string); + bool _is_mergeable_with(const MeshInstance &p_other) const; + void _merge_into_mesh_data(const MeshInstance &p_mi, const Transform &p_dest_tr_inv, int p_surface_id, PoolVector &r_verts, PoolVector &r_norms, PoolVector &r_tangents, PoolVector &r_colors, PoolVector &r_uvs, PoolVector &r_uv2s, PoolVector &r_inds); + bool _ensure_indices_valid(PoolVector &r_indices, const PoolVector &p_verts) const; + bool _check_for_valid_indices(const PoolVector &p_inds, const PoolVector &p_verts, LocalVector *r_inds) const; + bool _triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) const; + void _merge_log(String p_string) const; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -144,8 +145,8 @@ public: void create_debug_tangents(); // merging - bool is_mergeable_with(const MeshInstance &p_other); - bool create_by_merging(Vector p_list); + bool is_mergeable_with(Node *p_other) const; + bool merge_meshes(Vector p_list, bool p_use_global_space, bool p_check_compatibility); virtual AABB get_aabb() const; virtual PoolVector get_faces(uint32_t p_usage_flags) const; diff --git a/scene/3d/room_manager.cpp b/scene/3d/room_manager.cpp index 84acfa3be3d..5bf43ca94a1 100644 --- a/scene/3d/room_manager.cpp +++ b/scene/3d/room_manager.cpp @@ -2139,8 +2139,7 @@ void RoomManager::_merge_meshes_in_room(Room *p_room) { if (!bf.get_bit(c)) { MeshInstance *b = source_meshes[c]; - // if (_are_meshes_mergeable(a, b)) { - if (a->is_mergeable_with(*b)) { + if (a->is_mergeable_with(b)) { merge_list.push_back(b); bf.set_bit(c, true); } @@ -2157,7 +2156,7 @@ void RoomManager::_merge_meshes_in_room(Room *p_room) { _merge_log("\t\t" + merged->get_name()); - if (merged->create_by_merging(merge_list)) { + if (merged->merge_meshes(merge_list, true, false)) { // set all the source meshes to portal mode ignore so not shown for (int i = 0; i < merge_list.size(); i++) { merge_list[i]->set_portal_mode(CullInstance::PORTAL_MODE_IGNORE);