/*************************************************************************/
/*  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);

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