|
@@ -0,0 +1,1193 @@
|
|
|
+/**************************************************************************/
|
|
|
+/* merge_group.cpp */
|
|
|
+/**************************************************************************/
|
|
|
+/* This file is part of: */
|
|
|
+/* GODOT ENGINE */
|
|
|
+/* https://godotengine.org */
|
|
|
+/**************************************************************************/
|
|
|
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
|
+/* Copyright (c) 2007-2014 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/bitfield_dynamic.h"
|
|
|
+#include "core/engine.h"
|
|
|
+#include "core/os/os.h"
|
|
|
+#include "scene/3d/mesh_instance.h"
|
|
|
+#include "scene/3d/physics_body.h"
|
|
|
+#include "scene/resources/merging_tool.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().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);
|
|
|
+
|
|
|
+ p_mi->split_by_surface(siblings);
|
|
|
+
|
|
|
+ // 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 (!merged->merge_meshes(varlist, false, false, p_shadows)) {
|
|
|
+ _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;
|
|
|
+}
|