mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-29 07:07:14 +01:00
1119 lines
34 KiB
C++
1119 lines
34 KiB
C++
/*************************************************************************/
|
|
/* 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::_is_material_opaque(const Ref<Material> &p_mat) {
|
|
if (p_mat.is_null()) {
|
|
return true;
|
|
}
|
|
|
|
Ref<SpatialMaterial> 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<Mesh> rmesh_a = p_mi.get_mesh();
|
|
Ref<Mesh> 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<Mesh> 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<uint32_t> 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<uint32_t> &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<ArrayMesh> 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<Mesh> 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<ArrayMesh> 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<Mesh> &p_rmesh, int p_surface_id, Ref<ArrayMesh> r_dest_mesh) {
|
|
Array arrays = p_rmesh->surface_get_arrays(p_surface_id);
|
|
LocalVector<Vector3> verts = PoolVector<Vector3>(arrays[RS::ARRAY_VERTEX]);
|
|
if (!verts.size()) {
|
|
// Early out if there are no vertices, no point in doing anything else.
|
|
return 0;
|
|
}
|
|
LocalVector<int> indices = PoolVector<int>(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<int>(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<int> &r_indices, const PoolVector<Vector3> &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<int> 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<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int> *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<MeshInstance *> 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<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] = 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<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 (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<Mesh> 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<Material>());
|
|
r_dest_mi.set_material_override(Ref<Material>());
|
|
|
|
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<Mesh> 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<ArrayMesh> 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<Mesh> 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<Mesh> 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<ArrayMesh> 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<Mesh> 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<MeshInstance *> p_list) {
|
|
if (p_list.size() < 1) {
|
|
// Should not happen but just in case...
|
|
return false;
|
|
}
|
|
|
|
// For future use of compatibility check.
|
|
LocalVector<MeshInstance *> 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<Mesh> 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<MeshInstance *> 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<Mesh> 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<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] = 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<ArrayMesh> 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<Mesh> 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<ArrayMesh> &p_array_mesh, CSGBrush *p_brush, int p_surface) {
|
|
#ifdef MODULE_CSG_ENABLED
|
|
|
|
SurfaceTool surface_tool;
|
|
Ref<ArrayMesh> am;
|
|
am.instance();
|
|
|
|
Ref<Mesh> 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<Mesh> 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<Mesh> r_rmesh, int p_surface_id, Ref<Material> 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);
|
|
}
|
|
}
|