Portals - Improve mesh merging

Some improvements to robustness to account for more properties.
Addition of an "allow merging" flag in the cull instance.
This commit is contained in:
lawnjelly 2022-01-05 10:24:27 +00:00
parent 7ca4eb478e
commit 10eb9564ca
6 changed files with 140 additions and 30 deletions

View file

@ -15,6 +15,10 @@
<methods>
</methods>
<members>
<member name="allow_merging" type="bool" setter="set_allow_merging" getter="get_allow_merging" default="true">
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.
</member>
<member name="autoplace_priority" type="int" setter="set_portal_autoplace_priority" getter="get_portal_autoplace_priority" default="0">
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.

View file

@ -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;
}

View file

@ -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

View file

@ -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<MeshInstance>(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<Mesh> rmesh_a = get_mesh();
Ref<Mesh> 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<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &r_inds) {
void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, const Transform &p_dest_tr_inv, int p_surface_id, PoolVector<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &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<Vector2> uv2s = arrays[VS::ARRAY_TEX_UV2];
PoolVector<int> 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<int> &r_indices, const PoolVector<Vector3> &p_verts) {
bool MeshInstance::_ensure_indices_valid(PoolVector<int> &r_indices, const PoolVector<Vector3> &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<int> &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<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *r_inds) {
bool MeshInstance::_check_for_valid_indices(const PoolVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *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<int> &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<MeshInstance *> 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<MeshInstance *> 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<MeshInstance *> 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<bool> 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<ArrayMesh> 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<Vector3> verts;
PoolVector<Vector3> normals;
@ -1119,7 +1208,12 @@ bool MeshInstance::create_by_merging(Vector<MeshInstance *> p_list) {
PoolVector<int> 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<MeshInstance *> 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);
}

View file

@ -96,11 +96,12 @@ protected:
private:
// merging
void _merge_into_mesh_data(const MeshInstance &p_mi, int p_surface_id, PoolVector<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &r_inds);
bool _ensure_indices_valid(PoolVector<int> &r_indices, const PoolVector<Vector3> &p_verts);
bool _check_for_valid_indices(const PoolVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *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<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &r_inds);
bool _ensure_indices_valid(PoolVector<int> &r_indices, const PoolVector<Vector3> &p_verts) const;
bool _check_for_valid_indices(const PoolVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *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<MeshInstance *> p_list);
bool is_mergeable_with(Node *p_other) const;
bool merge_meshes(Vector<MeshInstance *> p_list, bool p_use_global_space, bool p_check_compatibility);
virtual AABB get_aabb() const;
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;

View file

@ -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);