pandemonium_engine/scene/3d/merge_group.cpp
lawnjelly c7888ff2da Mesh merging - refactor to be backward compatible for CPU / GPU storage
Allows the old `merge_meshes()` function to work from the editor.
2024-07-14 08:16:10 +02:00

1196 lines
35 KiB
C++

/*************************************************************************/
/* merge_group.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 "merge_group.h"
#include "core/config/engine.h"
#include "core/containers/bitfield_dynamic.h"
#include "core/os/os.h"
#include "scene/3d/mesh_instance.h"
#include "scene/3d/physics_body.h"
#include "scene/resources/mesh/merging_tool.h"
#include "scene/resources/world_3d.h"
#include "modules/modules_enabled.gen.h" // For csg.
#ifdef MODULE_CSG_ENABLED
#include "modules/csg/csg_shape.h"
#endif
#ifdef MODULE_GRIDMAP_ENABLED
#include "modules/gridmap/grid_map.h"
#endif
int MergeGroup::MeshAABB::_sort_axis = 0;
MergeGroup::BakeStepFunc MergeGroup::bake_step_function = nullptr;
MergeGroup::BakeStepFunc MergeGroup::bake_substep_function = nullptr;
MergeGroup::BakeEndFunc MergeGroup::bake_end_function = nullptr;
void MergeGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("merge_meshes"), &MergeGroup::merge_meshes);
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &MergeGroup::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &MergeGroup::get_param);
ClassDB::bind_method(D_METHOD("set_param_enabled", "param", "value"), &MergeGroup::set_param_enabled);
ClassDB::bind_method(D_METHOD("get_param_enabled", "param"), &MergeGroup::get_param_enabled);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "auto_merge"), "set_param_enabled", "get_param_enabled", PARAM_ENABLED_AUTO_MERGE);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "shadow_proxy"), "set_param_enabled", "get_param_enabled", PARAM_ENABLED_SHADOW_PROXY);
BIND_ENUM_CONSTANT(PARAM_ENABLED_AUTO_MERGE);
BIND_ENUM_CONSTANT(PARAM_ENABLED_SHADOW_PROXY);
BIND_ENUM_CONSTANT(PARAM_ENABLED_CONVERT_CSGS);
BIND_ENUM_CONSTANT(PARAM_ENABLED_CONVERT_GRIDMAPS);
BIND_ENUM_CONSTANT(PARAM_ENABLED_COMBINE_SURFACES);
BIND_ENUM_CONSTANT(PARAM_ENABLED_CLEAN_MESHES);
BIND_ENUM_CONSTANT(PARAM_GROUP_SIZE);
BIND_ENUM_CONSTANT(PARAM_SPLITS_HORIZONTAL);
BIND_ENUM_CONSTANT(PARAM_SPLITS_VERTICAL);
BIND_ENUM_CONSTANT(PARAM_MIN_SPLIT_POLY_COUNT);
}
void MergeGroup::merge_meshes() {
if (!Engine::get_singleton()->is_editor_hint()) {
_merge_meshes();
}
}
bool MergeGroup::merge_meshes_in_editor() {
return _merge_meshes();
}
void MergeGroup::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
ERR_FAIL_COND(get_world_3d().is_null());
if (data.params_enabled[PARAM_ENABLED_AUTO_MERGE] && !Engine::get_singleton()->is_editor_hint()) {
_merge_meshes();
}
} break;
}
}
// If this CSGShape is successfully split, all children are added to the first sibling (as the transform
// relationship should be preserved) and the source CSGShape is queue deleted.
// The children of a CSGCombiner are also treated specially, as they may not need to be preserved
// after the baking operation.
void MergeGroup::_split_csg_by_surface(CSGShape *p_shape) {
#ifdef MODULE_CSG_ENABLED
ERR_FAIL_NULL(p_shape);
// Probably a child of a CSG combiner.
if (p_shape->is_queued_for_deletion()) {
return;
}
// Shapes will not be up to date on the first frame due to a quirk
// of CSG - it defers updates to the next frame. So we need to explicitly
// force an update to make sure the CSG is correct on level load.
p_shape->force_update_shape();
Array arr = p_shape->get_meshes();
if (!arr.size()) {
return;
}
Ref<ArrayMesh> arr_mesh = arr[1];
if (!arr_mesh.is_valid()) {
return;
}
int num_surfaces = arr_mesh->get_surface_count();
if (num_surfaces == 0) {
return;
}
// First create siblings.
Node *parent = p_shape->get_parent();
ERR_FAIL_NULL(parent);
// The first new sibling will be the first new child.
int first_sibling_id = parent->get_child_count();
Vector<Variant> siblings;
for (int n = 0; n < num_surfaces; n++) {
MeshInstance *sib = memnew(MeshInstance);
MergingTool::_reparent(sib, parent, data.scene_root);
String new_name = String(p_shape->get_name()) + "_surf_" + itos(n);
sib->set_name(new_name);
siblings.push_back(sib);
}
_node_changed(parent);
if (!p_shape->split_by_surface(siblings)) {
return;
}
// Failed to split.
if (parent->get_child_count() <= first_sibling_id) {
return;
}
Node *first_sibling = parent->get_child(first_sibling_id);
ERR_FAIL_NULL(first_sibling);
// Special case, do not move CSG children of a CSG combiner.
if (Object::cast_to<CSGCombiner>(p_shape)) {
for (int n = 0; n < p_shape->get_child_count(); n++) {
CSGShape *child = Object::cast_to<CSGShape>(p_shape->get_child(n));
if (child) {
if (!child->get_child_count()) {
child->queue_delete();
#ifdef TOOLS_ENABLED
_log("CSGShape \"" + child->get_name() + "\" child of CSGCombiner detected, deleting.");
#endif
} else {
// Panic stations .. convert to spatial to preserve the children.
_convert_source_to_spatial(child);
#ifdef TOOLS_ENABLED
_log("CSGShape \"" + child->get_name() + "\" child of CSGCombiner detected, converting to Spatial.");
#endif
}
}
}
}
_move_children(p_shape, first_sibling);
// Remove source.
_delete_node(p_shape);
#endif
}
void MergeGroup::_logt(int p_tabs, String p_string) {
#ifdef TOOLS_ENABLED
if (p_tabs) {
String str;
for (int n = 0; n < p_tabs; n++) {
str += "\t";
}
str += p_string;
_log(str);
} else {
_log(p_string);
}
#endif
}
void MergeGroup::_log(String p_string) {
#ifdef TOOLS_ENABLED
#ifdef DEV_ENABLED
print_line(p_string);
#else
print_verbose(p_string);
#endif
#endif
}
bool MergeGroup::_split_by_locality() {
LocalVector<MeshInstance *> mis;
_find_mesh_instances_recursive(0, this, mis, false);
if (!mis.size()) {
return true;
}
// Find the overall AABB.
AABB aabb = mis[0]->get_transformed_aabb();
for (unsigned int n = 1; n < mis.size(); n++) {
aabb.merge_with(mis[n]->get_transformed_aabb());
}
for (unsigned int n = 0; n < mis.size(); n++) {
MeshInstance *mi = mis[n];
Node *parent = mi->get_parent();
if (!parent) {
continue;
}
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
if (bake_substep_function((float)n / mis.size(), mi->get_name(), nullptr, false)) {
return false;
}
}
#endif
// The first new sibling will be the first new child.
int first_sibling_id = parent->get_child_count();
MergingTool::split_mesh_instance_by_locality(*mi, aabb, data.params[PARAM_SPLITS_HORIZONTAL], data.params[PARAM_SPLITS_VERTICAL], data.params[PARAM_MIN_SPLIT_POLY_COUNT]);
// Failed to split.
if (parent->get_child_count() <= first_sibling_id) {
continue;
}
Node *first_sibling = parent->get_child(first_sibling_id);
// This really should not happen.
ERR_FAIL_NULL_V(first_sibling, true);
_move_children(mi, first_sibling);
// Delete source.
_delete_node(mi);
}
return true;
}
// If this MeshInstance is successfully split, all children are added to the first sibling (as the transform
// relationship should be preserved) and the source MeshInstance is queue deleted.
void MergeGroup::_split_mesh_by_surface(MeshInstance *p_mi, int p_num_surfaces) {
ERR_FAIL_COND(p_num_surfaces <= 1);
// First create siblings.
Node *parent = p_mi->get_parent();
ERR_FAIL_NULL(parent);
Vector<Variant> siblings;
// The first new sibling will be the first new child.
int first_sibling_id = parent->get_child_count();
for (int n = 0; n < p_num_surfaces; n++) {
MeshInstance *sib = memnew(MeshInstance);
MergingTool::_reparent(sib, parent, data.scene_root);
String new_name = String(p_mi->get_name()) + "_surf_" + itos(n);
sib->set_name(new_name);
#ifdef TOOLS_ENABLED
_log("split by surface to " + new_name + ".");
#endif
siblings.push_back(sib);
}
_node_changed(parent);
MergingTool::wrapped_split_by_surface(*p_mi, siblings, Mesh::STORAGE_MODE_CPU);
// Failed to split.
if (parent->get_child_count() <= first_sibling_id) {
return;
}
Node *first_sibling = parent->get_child(first_sibling_id);
ERR_FAIL_NULL(first_sibling);
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
if (bake_substep_function(1.0, p_mi->get_name(), nullptr, false)) {
return;
}
}
#endif
_move_children(p_mi, first_sibling);
// Remove source.
_delete_node(p_mi);
}
void MergeGroup::_node_changed_internal(Node *p_node) {
// Wipe filenames.
if (p_node->get_filename().size()) {
// Don't wipe the filename of the tree root (well actually the child, as the root is a Viewport).
if (p_node->get_parent() != (Node *)p_node->get_tree()->get_root()) {
#ifdef TOOLS_ENABLED
#if 0
_log("\tchanging filename of \"" + p_node->get_name() + "\" from " + p_node->get_filename() + " to NULL");
#endif
#endif
p_node->set_filename("");
// Invalidate any child / grandchild nodes that were owned by this scene,
// make them owned by the scene root.
MergingTool::_invalidate_owner_recursive(p_node, p_node, data.scene_root);
}
}
// Terminate if we reach the owning MergeGroup, we don't want to "corrupt" scene trees
// when baking in the editor.
// Note, this may cause problems if people attempt to merge at runtime then save at runtime
// above the MergeGroup, because subscenes won't have been invalidated.
if (p_node == this) {
return;
}
// Set owner to the merge group to clear any subscenes from this point upward.
MergingTool::_set_owner_logged(p_node, data.scene_root);
Node *parent = p_node->get_parent();
if (parent) {
_node_changed_internal(parent);
}
}
void MergeGroup::_node_changed(Node *p_node) {
//MergingTool::_invalidate_owner_recursive(p_node, p_node, data._scene_root);
_node_changed_internal(p_node);
}
void MergeGroup::_move_children(Node *p_from, Node *p_to, bool p_recalculate_transforms) {
ERR_FAIL_NULL(p_from);
ERR_FAIL_NULL(p_to);
// Invalidate any child nodes owned by this.
MergingTool::_invalidate_owner_recursive(p_from, p_from, data.scene_root);
int num_children = p_from->get_child_count();
// Note these will be readded in reverse order.
// This is more efficient but users should not rely on this order.
for (int n = num_children - 1; n >= 0; n--) {
Node *child = p_from->get_child(n);
if (p_recalculate_transforms) {
Spatial *child_spatial = Object::cast_to<Spatial>(child);
Transform old_global_xform = child_spatial->get_global_transform();
MergingTool::_reparent(child, p_to, data.scene_root);
// only set the new transform if it is out, to prevent float error when not needed
if (!child_spatial->get_global_transform().is_equal_approx(old_global_xform)) {
child_spatial->set_global_transform(old_global_xform);
}
} else {
MergingTool::_reparent(child, p_to, data.scene_root);
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
MergingTool::append_editor_description(child, "moved from parent", p_from);
}
#endif
}
// Subscenes are also invalidated.
_node_changed(p_to);
}
bool MergeGroup::_merge_meshes() {
#ifdef TOOLS_ENABLED
uint32_t before = OS::get_singleton()->get_ticks_msec();
#endif
data.iteration = 0;
data.scene_root = get_owner();
ERR_FAIL_NULL_V(data.scene_root, false);
if (data.params_enabled[PARAM_ENABLED_CONVERT_CSGS]) {
if (bake_step_function) {
if (bake_step_function(0.0, "Converting CSGs", nullptr, true)) {
return false;
}
}
// First find csgs and convert to meshes.
LocalVector<CSGShape *> csgs;
_find_csg_recursive(0, this, csgs);
if (csgs.size()) {
_log("converting " + itos(csgs.size()) + " CSGShapes to MeshInstances.");
}
for (unsigned int n = 0; n < csgs.size(); n++) {
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
if (bake_substep_function((float)n / csgs.size(), itos(n), nullptr, false)) {
return false;
}
}
#endif
CSGShape *csg = csgs[n];
_split_csg_by_surface(csg);
}
}
if (data.params_enabled[PARAM_ENABLED_CONVERT_GRIDMAPS]) {
if (bake_step_function) {
if (bake_step_function(1.0 / 8, "Converting GridMaps", nullptr, true)) {
return false;
}
}
// First find gridmaps and convert to meshes.
LocalVector<GridMap *> gridmaps;
_find_gridmap_recursive(0, this, gridmaps);
if (gridmaps.size()) {
_log("converting " + itos(gridmaps.size()) + " GridMaps to MeshInstances.");
}
for (unsigned int n = 0; n < gridmaps.size(); n++) {
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
if (bake_substep_function((float)n / gridmaps.size(), "Gridmap " + itos(n), nullptr, false)) {
return false;
}
}
#endif
GridMap *gridmap = gridmaps[n];
_bake_gridmap(gridmap);
}
}
// Split by surface?
if (data.split_by_surface) {
if (bake_step_function) {
if (bake_step_function(2.0 / 8, "Split by Surface", nullptr, true)) {
return false;
}
}
_log("split by surface");
LocalVector<MeshInstance *> split_instances;
_find_mesh_instances_recursive(0, this, split_instances, false);
for (unsigned int n = 0; n < split_instances.size(); n++) {
MeshInstance *mi = split_instances[n];
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
if (bake_substep_function((float)n / split_instances.size(), mi->get_name(), nullptr, false)) {
return false;
}
}
#endif
// Should be checked in the find recursive routine.
DEV_ASSERT(mi->get_mesh().is_valid());
int num_surfs = mi->get_mesh()->get_surface_count();
if (num_surfs > 1) {
_split_mesh_by_surface(mi, num_surfs);
}
} // for n
}
// First create a list of mesh instances.
uint32_t try_to_merge_count = 0;
{
_log("merging meshes");
#ifdef TOOLS_ENABLED
if (bake_step_function) {
if (bake_step_function(3.0 / 8, "Merging Meshes", nullptr, true)) {
return false;
}
}
#endif
LocalVector<MeshInstance *> mesh_instances;
_find_mesh_instances_recursive(0, this, mesh_instances, false, true);
try_to_merge_count = mesh_instances.size();
while (_merge_similar(mesh_instances, false)) {
if (bake_substep_function && mesh_instances.size()) {
if (bake_substep_function((float)(try_to_merge_count - mesh_instances.size()) / try_to_merge_count, mesh_instances[0]->get_name(), nullptr, false)) {
return false;
}
}
data.iteration++;
}
}
if (data.params_enabled[PARAM_ENABLED_CLEAN_MESHES]) {
#ifdef TOOLS_ENABLED
if (bake_step_function) {
if (bake_step_function(4.0 / 8, "Cleaning Meshes", nullptr, true)) {
return false;
}
}
#endif
_log("cleaning meshes");
LocalVector<MeshInstance *> mesh_instances_clean;
_find_mesh_instances_recursive(0, this, mesh_instances_clean, false);
for (uint32_t n = 0; n < mesh_instances_clean.size(); n++) {
if (bake_substep_function) {
if (bake_substep_function((float)n / mesh_instances_clean.size(), mesh_instances_clean[n]->get_name(), nullptr, false)) {
return false;
}
}
if (MergingTool::clean_mesh_instance(*mesh_instances_clean[n])) {
_node_changed(mesh_instances_clean[n]);
}
}
}
if (data.params_enabled[PARAM_ENABLED_SHADOW_PROXY]) {
#ifdef TOOLS_ENABLED
if (bake_step_function) {
if (bake_step_function(5.0 / 8, "Creating Shadow Proxy", nullptr, true)) {
return false;
}
}
#endif
_log("creating shadow proxy");
LocalVector<MeshInstance *> mesh_instances_shadow;
_find_mesh_instances_recursive(0, this, mesh_instances_shadow, true);
unsigned int orig_num_found = mesh_instances_shadow.size();
while (_merge_similar(mesh_instances_shadow, true)) {
if (bake_substep_function && mesh_instances_shadow.size()) {
if (bake_substep_function((float)(orig_num_found - mesh_instances_shadow.size()) / orig_num_found, mesh_instances_shadow[0]->get_name(), nullptr, false)) {
return false;
}
}
}
}
bool split_by_locality = (data.params[PARAM_SPLITS_HORIZONTAL] > 1) || (data.params[PARAM_SPLITS_VERTICAL] > 1);
if (split_by_locality) {
_log("split by locality");
if (bake_step_function) {
if (bake_step_function(6.0 / 8, "Split by Locality", nullptr, true)) {
return false;
}
}
if (!_split_by_locality()) {
return false;
}
}
if (data.params_enabled[PARAM_ENABLED_COMBINE_SURFACES]) {
if ((data.params[PARAM_GROUP_SIZE] > 1) || (split_by_locality)) {
WARN_PRINT("Mesh Joining is disabled for MergeGroups with split by locality or max merges.");
} else {
#ifdef GODOT_MERGING_VERBOSE
MergingTool::debug_branch(this, "BEFORE JOINING");
#endif
#ifdef TOOLS_ENABLED
if (bake_step_function) {
if (bake_step_function(7.0 / 8, "Joining Meshes", nullptr, true)) {
return false;
}
}
#endif
_log("join meshes");
LocalVector<MeshInstance *> mesh_instances_join;
_find_mesh_instances_recursive(0, this, mesh_instances_join, false);
unsigned int orig_num_found = mesh_instances_join.size();
while (_join_similar(mesh_instances_join)) {
if (bake_substep_function && mesh_instances_join.size()) {
if (bake_substep_function((float)(orig_num_found - mesh_instances_join.size()) / orig_num_found, mesh_instances_join[0]->get_name(), nullptr, false)) {
return false;
}
}
}
#ifdef GODOT_MERGING_VERBOSE
MergingTool::debug_branch(this, "AFTER JOINING");
#endif
}
}
#ifdef TOOLS_ENABLED
uint32_t after = OS::get_singleton()->get_ticks_msec();
_log("Merging for \"" + get_name() + "\" took " + itos(after - before) + " ms. Attempted to merge " + itos(try_to_merge_count) + " meshes.");
#endif
return true;
}
bool MergeGroup::_join_similar(LocalVector<MeshInstance *> &r_mis) {
if (!r_mis.size()) {
return false;
}
LocalVector<MeshInstance *> list;
MeshInstance *first = nullptr;
for (int n = 0; n < (int)r_mis.size(); n++) {
MeshInstance *mi = r_mis[n];
// Is this mesh suitable?
Ref<Mesh> rmesh = mi->get_mesh();
if (!rmesh.is_valid()) {
r_mis.remove_unordered(n);
n--;
continue;
}
// Either the first member of the list, or mergeable with the existing list.
if (!first || MergingTool::is_mergeable_with(*first, *mi, false)) {
first = mi;
list.push_back(mi);
r_mis.remove_unordered(n);
n--;
}
}
// No joins possible for this mi.
if (list.size() <= 1) {
return true;
}
MeshInstance *joined = memnew(MeshInstance);
MergingTool::_reparent(joined, this, data.scene_root);
_node_changed(this);
// Rename.
joined->set_name("Joined [" + first->get_name() + "]");
// Either all of them join, or none.
if (!MergingTool::join_meshes(*joined, list)) {
// Failed to join.
_delete_node(joined);
return false;
}
_cleanup_source_meshes(list);
// MergingTool::debug_mesh_instance(*joined);
return true;
}
bool MergeGroup::_merge_similar(LocalVector<MeshInstance *> &r_mis, bool p_shadows) {
if (!r_mis.size()) {
return false;
}
MeshInstance *first = r_mis[0];
LocalVector<MeshInstance *> list;
list.push_back(first);
r_mis.remove_unordered(0);
for (int n = 0; n < (int)r_mis.size(); n++) {
MeshInstance *mi = r_mis[n];
if (first->is_mergeable_with(mi, p_shadows)) {
list.push_back(mi);
r_mis.remove_unordered(n);
n--;
}
}
// Don't whittle for shadows for now.
if (p_shadows) {
_merge_list(list, p_shadows);
return true;
}
// If set to zero, we merge everything we can..
// If set to 1, we merge nothing.
if (data.params[PARAM_GROUP_SIZE] <= 1) {
if (!data.params[PARAM_GROUP_SIZE] && (list.size() > 1)) {
_merge_list(list, p_shadows);
}
return true;
}
int whittle_group = 0;
LocalVector<MeshAABB> mesh_aabbs;
mesh_aabbs.resize(list.size());
for (uint32_t n = 0; n < list.size(); n++) {
mesh_aabbs[n].mi = list[n];
mesh_aabbs[n].aabb = list[n]->get_transformed_aabb();
}
_recursive_tree_merge(whittle_group, mesh_aabbs);
return true;
}
void MergeGroup::_recursive_tree_merge(int &r_whittle_group, LocalVector<MeshAABB> p_list) {
DEV_ASSERT(data.params[PARAM_GROUP_SIZE] > 1);
// If less than the leaf size, merge
if (p_list.size() <= data.params[PARAM_GROUP_SIZE]) {
if (p_list.size() > 1) {
_merge_list_ex(p_list, false, r_whittle_group++);
}
return;
}
// Attempt to split.
// Calculate AABB.
AABB aabb = p_list[0].aabb;
for (uint32_t n = 1; n < p_list.size(); n++) {
aabb.merge_with(p_list[n].aabb);
}
int order[3];
order[0] = aabb.get_longest_axis_index();
order[2] = aabb.get_shortest_axis_index();
order[1] = 3 - (order[0] + order[2]);
bool sort_ok = false;
// Try sorting on each axis in order of longest first.
for (int n = 0; n < 3; n++) {
int axis = order[n];
MeshAABB::_sort_axis = axis;
p_list.sort();
// Is this sorting ok?
// some epsilon? NYI
if (p_list[0].aabb.position.coord[axis] != p_list[p_list.size() - 1].aabb.position.coord[axis]) {
sort_ok = true;
break;
}
}
// If the sort failed, they are all in kind of the same place, we will just merge them all
// and abandon the whittling...
if (!sort_ok) {
_merge_list_ex(p_list, false, r_whittle_group++);
return;
}
// Sort was ok, lets split into 2 lists and call recursive.
LocalVector<MeshAABB> list_b;
int last = p_list.size() / 2;
for (int n = (int)p_list.size() - 1; n >= last; n--) {
list_b.push_back(p_list[n]);
p_list.remove_unordered(n);
}
_recursive_tree_merge(r_whittle_group, p_list);
_recursive_tree_merge(r_whittle_group, list_b);
}
void MergeGroup::_merge_list_ex(const LocalVector<MeshAABB> &p_mesh_aabbs, bool p_shadows, int p_whittle_group) {
LocalVector<MeshInstance *> mis;
mis.resize(p_mesh_aabbs.size());
for (uint32_t n = 0; n < mis.size(); n++) {
mis[n] = p_mesh_aabbs[n].mi;
}
_merge_list(mis, p_shadows, p_whittle_group);
}
void MergeGroup::_merge_list(const LocalVector<MeshInstance *> &p_mis, bool p_shadows, int p_whittle_group) {
MeshInstance *merged = memnew(MeshInstance);
MergingTool::_reparent(merged, this, data.scene_root);
_node_changed(this);
String new_name;
if (p_shadows) {
new_name = "Shadow";
} else {
new_name = "Merged";
}
new_name += " [" + get_name() + "]";
if (!p_shadows) {
new_name += " " + itos(data.iteration);
}
if (p_whittle_group != -1) {
new_name += " [wg " + itos(p_whittle_group) + "]";
}
merged->set_name(new_name);
Vector<Variant> varlist;
for (unsigned int n = 0; n < p_mis.size(); n++) {
varlist.push_back(Variant(p_mis[n]));
}
if (!MergingTool::wrapped_merge_meshes(*merged, varlist, false, false, p_shadows, Mesh::STORAGE_MODE_CPU)) {
_log("MERGE_MESHES failed.");
_delete_node(merged);
return;
}
if (!p_shadows) {
// For deleting the old MeshInstances, we should not delete
// nodes that have children (e.g. Static physics). However MeshInstances
// that are deleted, can then free up parent MeshInstances for deletion,
// so we should call this in a recursive fashion.
LocalVector<MeshInstance *> del_list = p_mis;
if (data.delete_sources) {
_cleanup_source_meshes(del_list);
}
if (data.convert_sources) {
// Any that have not been deleted can now be converted to spatials.
for (unsigned int n = 0; n < del_list.size(); n++) {
_convert_source_to_spatial(del_list[n]);
}
} else {
// or have their mesh set to NULL.
for (unsigned int n = 0; n < del_list.size(); n++) {
_reset_mesh_instance(del_list[n]);
}
}
} else {
// Shadows .. turn shadow casting off for all these meshes.
for (unsigned int n = 0; n < p_mis.size(); n++) {
p_mis[n]->set_cast_shadows_setting(GeometryInstance::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF);
_node_changed(p_mis[n]);
}
}
}
void MergeGroup::_cleanup_source_meshes(LocalVector<MeshInstance *> &r_cleanup_list) {
for (unsigned int n = 0; n < r_cleanup_list.size(); n++) {
MeshInstance *mi = r_cleanup_list[n];
Node *parent = mi->get_parent();
_move_children(mi, parent, true);
_delete_node(mi);
_delete_dangling_spatials(parent);
}
// All are deleted.
r_cleanup_list.clear();
}
void MergeGroup::_delete_node(Node *p_node) {
p_node->queue_delete();
// This is only a problem in the editor, Godot saving code cannot currently deal with
// nodes queued for deletion, so they must be detached.
// This makes the whole process much slower after detaching, because of the logarithmic
// calling of notifications (Godot doesn't deal well with large numbers of nodes).
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && p_node->get_parent()) {
p_node->get_parent()->remove_child(p_node);
}
#endif
}
bool MergeGroup::_node_ok_to_delete(Node *p_node) {
return (!MergingTool::_node_has_valid_children(p_node)) && (!p_node->get_script_instance());
}
void MergeGroup::_delete_dangling_spatials(Node *p_node) {
while (p_node) {
if (_node_ok_to_delete(p_node) && (get_class() == "Spatial")) {
Node *parent = p_node->get_parent();
_delete_node(p_node);
p_node = parent;
} else {
return;
}
}
}
void MergeGroup::_find_gridmap_recursive(int p_depth, Node *p_node, LocalVector<GridMap *> &r_gridmaps) {
#ifdef MODULE_GRIDMAP_ENABLED
if (_terminate_search(p_node)) {
return;
}
GridMap *gridmap = Object::cast_to<GridMap>(p_node);
if (gridmap && !gridmap->is_queued_for_deletion() && !gridmap->get_script_instance()) {
_logt(p_depth, "found GridMap : \"" + gridmap->get_name() + "\"");
r_gridmaps.push_back(gridmap);
}
for (int c = p_node->get_child_count() - 1; c >= 0; c--) {
_find_gridmap_recursive(p_depth + 1, p_node->get_child(c), r_gridmaps);
}
#endif
}
void MergeGroup::_bake_gridmap(GridMap *p_gridmap) {
#ifdef MODULE_GRIDMAP_ENABLED
Node *parent = p_gridmap->get_parent();
ERR_FAIL_NULL(parent);
Array meshes = p_gridmap->get_meshes();
Transform gridmap_xform = p_gridmap->get_transform();
for (int n = 0; n < meshes.size(); n++) {
Transform tr = meshes[n];
n++;
Ref<Mesh> rmesh = meshes[n];
MeshInstance *mi = memnew(MeshInstance);
String new_name = String(p_gridmap->get_name()) + " [cell " + itos(n / 2) + "]";
mi->set_name(new_name);
MergingTool::_reparent(mi, parent, data.scene_root);
mi->set_mesh(rmesh);
mi->set_transform(gridmap_xform * tr);
#ifdef TOOLS_ENABLED
if (bake_substep_function) {
_log("baking gridmap, creating mesh instance \"" + new_name + "\"");
if (bake_substep_function((float)n / meshes.size(), new_name, nullptr, false)) {
break;
}
}
#endif
}
_node_changed(p_gridmap);
// ALTERNATIVE IMPLEMENTATION - may be better if we decide to bake physics reps
// Array cells = p_gridmap->get_used_cells();
// Ref<MeshLibrary> rmeshlib = p_gridmap->get_mesh_library();
// Transform gridmap_xform = p_gridmap->get_transform();
// real_t cell_scale = p_gridmap->get_cell_scale();
// for (int32_t k = 0; k < cells.size(); k++) {
// Vector3 cell_location = cells[k];
// int x = Math::round(cell_location.x);
// int y = Math::round(cell_location.y);
// int z = Math::round(cell_location.z);
// int32_t cell_item = p_gridmap->get_cell_item(x, y, z);
// if (cell_item == GridMap::INVALID_CELL_ITEM) {
// continue;
// }
// Transform cell_xform;
// int orientation = p_gridmap->get_cell_item_orientation(x, y, z);
// DEV_ASSERT(orientation != -1);
// cell_xform.basis.set_orthogonal_index(orientation);
// cell_xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale));
// cell_xform.set_origin(p_gridmap->map_to_world(x, y, z));
// // may not be required, fire didn't have this
// //const Transform &item_xform = rmeshlib->get_item_mesh_transform(cell_item);
// //cell_xform *= item_xform;
// MeshInstance *mi = memnew(MeshInstance);
// parent->add_child(mi);
// _set_reasonable_owner(mi);
// String new_name = "Gridmap [" + p_gridmap->get_name() + "] " + itos(x) + "," + itos(y) + "," + itos(z);
// _log("creating " + new_name);
// mi->set_name(new_name);
// mi->set_mesh(rmeshlib->get_item_mesh(cell_item));
// mi->set_transform(gridmap_xform * cell_xform);
// }
// Move children of source gridmap, so hiding doesn't affect them.
_move_children(p_gridmap, p_gridmap->get_parent(), true);
// Set to invisible rather than delete to allow physics to work.
p_gridmap->set_visible(false);
#endif
}
// Certain node types will terminate finding mesh instances etc, for convenience,
// as they will always be part of a different moving "block" and not suitable
// for static merging.
bool MergeGroup::_terminate_search(Node *p_node) {
if (Object::cast_to<RigidBody>(p_node)) {
return true;
}
if (Object::cast_to<KinematicBody>(p_node)) {
return true;
}
if (Object::cast_to<MergeGroup>(p_node) && (p_node != this)) {
return true;
}
return false;
}
void MergeGroup::_find_csg_recursive(int p_depth, Node *p_node, LocalVector<CSGShape *> &r_csgs) {
#ifdef MODULE_CSG_ENABLED
if (_terminate_search(p_node)) {
return;
}
CSGShape *shape = Object::cast_to<CSGShape>(p_node);
if (shape && shape->is_merging_allowed() && !shape->is_queued_for_deletion() && !shape->get_script_instance()) {
// Is this the child of a CSG combiner?
CSGCombiner *parent = Object::cast_to<CSGCombiner>(shape->get_parent());
if (parent && parent->is_merging_allowed()) {
// Do not add children of combiners, as the combiner will use the children to generate
// the mesh.
// Possible problem:
// CSGShape children of CSGCombiners that themselves have children (e.g. static bodies?)
// What should we do with these?
_logt(p_depth, "found CSGShape with CSGCombiner parent : \"" + shape->get_name() + "\"");
} else {
r_csgs.push_back(shape);
_logt(p_depth, "found CSGShape : \"" + shape->get_name() + "\"");
}
}
for (int c = p_node->get_child_count() - 1; c >= 0; c--) {
_find_csg_recursive(p_depth + 1, p_node->get_child(c), r_csgs);
}
#endif
}
void MergeGroup::_find_mesh_instances_recursive(int p_depth, Node *p_node, LocalVector<MeshInstance *> &r_mis, bool p_shadows, bool p_flag_invalid_meshes) {
if (_terminate_search(p_node)) {
return;
}
MeshInstance *mi = Object::cast_to<MeshInstance>(p_node);
if (mi && mi->is_merging_allowed() && !mi->is_queued_for_deletion() && !mi->get_script_instance()) {
Ref<Mesh> rmesh = mi->get_mesh();
if (rmesh.is_valid()) {
if (rmesh->get_surface_count()) {
if (!p_shadows || mi->is_mergeable_with(mi, true)) {
r_mis.push_back(mi);
// _logt(p_depth, "found MeshInstance : \"" + mi->get_name() + "\"");
}
} // Contains surfaces.
else if (p_flag_invalid_meshes) {
#ifdef TOOLS_ENABLED
WARN_PRINT("MeshInstance \"" + mi->get_name() + "\" contains no surfaces.");
#endif
}
} // Mesh valid.
}
// Important:
// Ensure meshes are added in reverse order.
// This is important for performance because
// it turns out queue_delete is very inefficient
// deleteing multiple child nodes from the front of the list
// due to ordered_remove() etc.
for (int c = p_node->get_child_count() - 1; c >= 0; c--) {
_find_mesh_instances_recursive(p_depth + 1, p_node->get_child(c), r_mis, p_shadows, p_flag_invalid_meshes);
}
}
void MergeGroup::_reset_mesh_instance(MeshInstance *p_mi) {
p_mi->set_mesh(Ref<Mesh>());
_node_changed(p_mi);
}
// Convert any dangling MeshInstances to Spatials so they will be cheaper
// in the VIsualServer. They are only required now for relative positioning
// of children.
// Note that this will go horribly wrong if the user code keeps a
// reference / pointer to the source node before this stage,
// hence why this step is optional.
void MergeGroup::_convert_source_to_spatial(Spatial *p_node) {
ERR_FAIL_NULL(p_node);
if (p_node->get_script_instance()) {
return;
}
#ifdef TOOLS_ENABLED
_log("converting source to Spatial \"" + p_node->get_name() + "\"");
#endif
Node *parent = p_node->get_parent();
// This should not happen, as sources should always be under a merge group.
ERR_FAIL_NULL(parent);
// Change the name of the node to be deleted.
String string_full_name = p_node->get_name();
p_node->set_name("_merge_source_ " + string_full_name);
// Create the new class T object.
Spatial *pNew = memnew(Spatial);
pNew->set_name(string_full_name);
// Add the child at the same position as the old node
// (this is more convenient for users)
//parent->add_child_below_node(p_node, pNew);
MergingTool::_reparent(pNew, parent, data.scene_root);
// New node should have same transform.
pNew->set_transform(p_node->get_transform());
// Move each child.
_move_children(p_node, pNew);
// Delete old node.
_delete_node(p_node);
}
void MergeGroup::set_param_enabled(ParamEnabled p_param, bool p_enabled) {
data.params_enabled[p_param] = p_enabled;
}
bool MergeGroup::get_param_enabled(ParamEnabled p_param) {
return data.params_enabled[p_param];
}
void MergeGroup::set_param(Param p_param, int p_value) {
// Check specific param ranges.
switch (p_param) {
case PARAM_SPLITS_HORIZONTAL:
case PARAM_SPLITS_VERTICAL: {
p_value = CLAMP(p_value, 1, 16);
} break;
case PARAM_GROUP_SIZE: {
p_value = CLAMP(p_value, 0, 128);
} break;
default:
break;
}
data.params[p_param] = (uint32_t)CLAMP(p_value, 0, INT32_MAX);
}
int MergeGroup::get_param(Param p_param) {
return data.params[p_param];
}
MergeGroup::Data::Data() {
for (int n = 0; n < PARAM_ENABLED_MAX; n++) {
params_enabled[n] = false;
}
for (int n = 0; n < PARAM_MAX; n++) {
params[n] = 0;
}
params_enabled[PARAM_ENABLED_AUTO_MERGE] = true;
params_enabled[PARAM_ENABLED_SHADOW_PROXY] = true;
params_enabled[PARAM_ENABLED_CONVERT_CSGS] = true;
params_enabled[PARAM_ENABLED_CONVERT_GRIDMAPS] = false;
params_enabled[PARAM_ENABLED_COMBINE_SURFACES] = true;
params_enabled[PARAM_ENABLED_CLEAN_MESHES] = false;
params[PARAM_GROUP_SIZE] = 0;
params[PARAM_SPLITS_HORIZONTAL] = 1;
params[PARAM_SPLITS_VERTICAL] = 1;
params[PARAM_MIN_SPLIT_POLY_COUNT] = 1024;
delete_sources = true;
convert_sources = true;
split_by_surface = true;
iteration = 0;
}