/*************************************************************************/
/*  mesh_utils.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 "mesh_utils.h"
#include "core/containers/local_vector.h"
#include "core/variant/variant.h"
#include "scene/resources/mesh/mesh.h"

#include "servers/rendering_server.h"

#include "xatlas/xatlas.h"

#include "delaunay/delaunay_3d.h"

MeshUtils *MeshUtils::_instance;

MeshUtils *MeshUtils::get_singleton() {
	return _instance;
}

Array MeshUtils::merge_mesh_array(Array arr) const {
	ERR_FAIL_COND_V(arr.size() != RenderingServer::ARRAY_MAX, arr);

	PoolVector3Array verts = arr[RenderingServer::ARRAY_VERTEX];
	PoolVector3Array normals = arr[RenderingServer::ARRAY_NORMAL];
	PoolVector2Array uvs = arr[RenderingServer::ARRAY_TEX_UV];
	PoolColorArray colors = arr[RenderingServer::ARRAY_COLOR];
	PoolIntArray indices = arr[RenderingServer::ARRAY_INDEX];
	PoolIntArray bones = arr[RenderingServer::ARRAY_BONES];
	PoolRealArray weights = arr[RenderingServer::ARRAY_WEIGHTS];

	ERR_FAIL_COND_V(normals.size() != 0 && normals.size() != verts.size(), Array());
	ERR_FAIL_COND_V(uvs.size() != 0 && uvs.size() != verts.size(), Array());
	ERR_FAIL_COND_V(colors.size() != 0 && colors.size() != verts.size(), Array());
	ERR_FAIL_COND_V(bones.size() != 0 && bones.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(weights.size() != 0 && weights.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(bones.size() != weights.size(), Array());

	int i = 0;
	while (i < verts.size()) {
		Vector3 v = verts[i];

		Array equals;
		for (int j = i + 1; j < verts.size(); ++j) {
			Vector3 vc = verts[j];

			if (Math::is_equal_approx(v.x, vc.x) && Math::is_equal_approx(v.y, vc.y) && Math::is_equal_approx(v.z, vc.z))
				equals.push_back(j);
		}

		for (int k = 0; k < equals.size(); ++k) {
			int rem = equals[k];
			int remk = rem - k;

			verts.remove(remk);
			if (normals.size() > 0)
				normals.remove(remk);
			if (uvs.size() > 0)
				uvs.remove(remk);
			if (colors.size() > 0)
				colors.remove(remk);

			if (bones.size() > 0) {
				int bindex = remk * 4;
				for (int l = 0; l < 4; ++l) {
					bones.remove(bindex);
				}
			}

			if (weights.size() > 0) {
				int bindex = remk * 4;
				for (int l = 0; l < 4; ++l) {
					weights.remove(bindex);
				}
			}

			for (int j = 0; j < indices.size(); ++j) {
				int indx = indices[j];

				if (indx == remk) {
					indices.set(j, i);
				} else if (indx > remk) {
					indices.set(j, indx - 1);
				}
			}
		}

		++i;
	}

	arr[RenderingServer::ARRAY_VERTEX] = verts;

	if (normals.size() > 0)
		arr[RenderingServer::ARRAY_NORMAL] = normals;
	if (uvs.size() > 0)
		arr[RenderingServer::ARRAY_TEX_UV] = uvs;
	if (colors.size() > 0)
		arr[RenderingServer::ARRAY_COLOR] = colors;
	if (bones.size() > 0)
		arr[RenderingServer::ARRAY_BONES] = bones;
	if (weights.size() > 0)
		arr[RenderingServer::ARRAY_WEIGHTS] = weights;
	if (indices.size() > 0)
		arr[RenderingServer::ARRAY_INDEX] = indices;

	return arr;
}
Array MeshUtils::bake_mesh_array_uv(Array arr, Ref<Texture> tex, float mul_color) const {
	ERR_FAIL_COND_V(arr.size() != RenderingServer::ARRAY_MAX, arr);
	ERR_FAIL_COND_V(!tex.is_valid(), arr);

	Ref<Image> img = tex->get_data();

	ERR_FAIL_COND_V(!img.is_valid(), arr);

	Vector2 imgsize = img->get_size();

	PoolVector2Array uvs = arr[RenderingServer::ARRAY_TEX_UV];
	PoolColorArray colors = arr[RenderingServer::ARRAY_COLOR];

	img->lock();

	for (int i = 0; i < uvs.size(); ++i) {
		Vector2 uv = uvs[i];
		uv *= imgsize;

		Color c = img->get_pixelv(uv);

		colors.set(i, colors[i] * c * mul_color);
	}

	img->unlock();

	arr[RenderingServer::ARRAY_COLOR] = colors;

	return arr;
}

//If normals are present they need to match too to be removed
Array MeshUtils::remove_doubles(Array arr) const {
	ERR_FAIL_COND_V(arr.size() != RenderingServer::ARRAY_MAX, arr);

	PoolVector3Array verts = arr[RenderingServer::ARRAY_VERTEX];
	PoolVector3Array normals = arr[RenderingServer::ARRAY_NORMAL];
	PoolVector2Array uvs = arr[RenderingServer::ARRAY_TEX_UV];
	PoolColorArray colors = arr[RenderingServer::ARRAY_COLOR];
	PoolIntArray indices = arr[RenderingServer::ARRAY_INDEX];
	PoolIntArray bones = arr[RenderingServer::ARRAY_BONES];
	PoolRealArray weights = arr[RenderingServer::ARRAY_WEIGHTS];

	ERR_FAIL_COND_V(normals.size() != 0 && normals.size() != verts.size(), Array());
	ERR_FAIL_COND_V(uvs.size() != 0 && uvs.size() != verts.size(), Array());
	ERR_FAIL_COND_V(colors.size() != 0 && colors.size() != verts.size(), Array());
	ERR_FAIL_COND_V(bones.size() != 0 && bones.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(weights.size() != 0 && weights.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(bones.size() != weights.size(), Array());

	Vector3 v;
	Vector3 normal;
	Vector2 uv;
	Color color;
	PoolIntArray bone;
	bone.resize(4);
	PoolRealArray weight;
	weight.resize(4);

	int i = 0;
	while (i < verts.size()) {
		v = verts[i];

		if (normals.size() != 0) {
			normal = normals[i];
		}

		if (uvs.size() != 0) {
			uv = uvs[i];
		}

		if (colors.size() != 0) {
			color = colors[i];
		}

		if (bones.size() != 0) {
			int indx = i * 4;

			for (int l = 0; l < 4; ++l) {
				bone.set(l, bones[indx + l]);
				weight.set(l, weights[indx + l]);
			}
		}

		Array equals;
		for (int j = i + 1; j < verts.size(); ++j) {
			Vector3 vc = verts[j];

			if (normals.size() != 0) {
				if (!normals[j].is_equal_approx(normal)) {
					continue;
				}
			}

			if (uvs.size() != 0) {
				if (!uvs[j].is_equal_approx(uv)) {
					continue;
				}
			}

			if (colors.size() != 0) {
				if (!colors[j].is_equal_approx(color)) {
					continue;
				}
			}

			if (bones.size() != 0) {
				bool bequals = true;

				int indx = i * 4;

				for (int l = 0; l < 4; ++l) {
					if (bones[indx + l] != bone[l]) {
						bequals = false;
						break;
					}

					if (!Math::is_equal_approx(weights[indx + l], weight[l])) {
						bequals = false;
						break;
					}
				}

				if (!bequals) {
					continue;
				}
			}

			if (vc.is_equal_approx(v)) {
				equals.push_back(j);
			}
		}

		for (int k = 0; k < equals.size(); ++k) {
			int rem = equals[k];
			int remk = rem - k;

			verts.remove(remk);

			if (normals.size() > 0) {
				normals.remove(remk);
			}

			if (uvs.size() > 0) {
				uvs.remove(remk);
			}

			if (colors.size() > 0) {
				colors.remove(remk);
			}

			if (bones.size() > 0) {
				int bindex = remk * 4;
				for (int l = 0; l < 4; ++l) {
					bones.remove(bindex);
					weights.remove(bindex);
				}
			}

			for (int j = 0; j < indices.size(); ++j) {
				int indx = indices[j];

				if (indx == remk)
					indices.set(j, i);
				else if (indx > remk)
					indices.set(j, indx - 1);
			}
		}

		++i;
	}

	Array retarr;
	retarr.resize(RenderingServer::ARRAY_MAX);

	retarr[RenderingServer::ARRAY_VERTEX] = verts;

	if (normals.size() > 0)
		retarr[RenderingServer::ARRAY_NORMAL] = normals;
	if (uvs.size() > 0)
		retarr[RenderingServer::ARRAY_TEX_UV] = uvs;
	if (colors.size() > 0)
		retarr[RenderingServer::ARRAY_COLOR] = colors;
	if (indices.size() > 0)
		retarr[RenderingServer::ARRAY_INDEX] = indices;
	if (bones.size() > 0)
		retarr[RenderingServer::ARRAY_BONES] = bones;
	if (weights.size() > 0)
		retarr[RenderingServer::ARRAY_WEIGHTS] = weights;

	return retarr;
}

//Normals are always interpolated, merged
Array MeshUtils::remove_doubles_interpolate_normals(Array arr) const {
	ERR_FAIL_COND_V(arr.size() != RenderingServer::ARRAY_MAX, arr);

	PoolVector3Array verts = arr[RenderingServer::ARRAY_VERTEX];
	PoolVector3Array normals = arr[RenderingServer::ARRAY_NORMAL];
	PoolVector2Array uvs = arr[RenderingServer::ARRAY_TEX_UV];
	PoolColorArray colors = arr[RenderingServer::ARRAY_COLOR];
	PoolIntArray indices = arr[RenderingServer::ARRAY_INDEX];
	PoolIntArray bones = arr[RenderingServer::ARRAY_BONES];
	PoolRealArray weights = arr[RenderingServer::ARRAY_WEIGHTS];

	ERR_FAIL_COND_V(normals.size() != 0 && normals.size() != verts.size(), Array());
	ERR_FAIL_COND_V(uvs.size() != 0 && normals.size() != verts.size(), Array());
	ERR_FAIL_COND_V(colors.size() != 0 && normals.size() != verts.size(), Array());
	ERR_FAIL_COND_V(bones.size() != 0 && bones.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(weights.size() != 0 && weights.size() != (verts.size() * 4), Array());
	ERR_FAIL_COND_V(bones.size() != weights.size(), Array());

	Vector3 v;
	Vector2 uv;
	Color color;
	PoolIntArray bone;
	bone.resize(4);
	PoolRealArray weight;
	weight.resize(4);

	int i = 0;
	while (i < verts.size()) {
		v = verts[i];

		if (uvs.size() != 0) {
			uv = uvs[i];
		}

		if (colors.size() != 0) {
			color = colors[i];
		}

		if (bones.size() != 0) {
			int indx = i * 4;

			for (int l = 0; l < 4; ++l) {
				bone.set(l, bones[indx + l]);
				weight.set(l, weights[indx + l]);
			}
		}

		Array equals;
		for (int j = i + 1; j < verts.size(); ++j) {
			Vector3 vc = verts[j];

			if (uvs.size() != 0) {
				if (!uvs[j].is_equal_approx(uv)) {
					continue;
				}
			}

			if (colors.size() != 0) {
				if (!colors[j].is_equal_approx(color)) {
					continue;
				}
			}

			if (bones.size() != 0) {
				bool bequals = true;

				int indx = i * 4;

				for (int l = 0; l < 4; ++l) {
					if (bones[indx + l] != bone[l]) {
						bequals = false;
						break;
					}

					if (!Math::is_equal_approx(weights[indx + l], weight[l])) {
						bequals = false;
						break;
					}
				}

				if (!bequals) {
					continue;
				}
			}

			if (vc.is_equal_approx(v)) {
				equals.push_back(j);
			}
		}

		Vector3 normal;
		for (int k = 0; k < equals.size(); ++k) {
			int rem = equals[k];
			int remk = rem - k;

			verts.remove(remk);

			if (normals.size() > 0) {
				Vector3 n = normals[remk];
				normals.remove(remk);

				if (k == 0) {
					normal = n;
				} else {
					normal = normal.linear_interpolate(n, 0.5);
				}
			}

			if (uvs.size() > 0) {
				uvs.remove(remk);
			}

			if (colors.size() > 0) {
				colors.remove(remk);
			}

			if (bones.size() > 0) {
				int bindex = remk * 4;
				for (int l = 0; l < 4; ++l) {
					bones.remove(bindex);
					weights.remove(bindex);
				}
			}

			for (int j = 0; j < indices.size(); ++j) {
				int indx = indices[j];

				if (indx == remk)
					indices.set(j, i);
				else if (indx > remk)
					indices.set(j, indx - 1);
			}
		}

		++i;
	}

	Array retarr;
	retarr.resize(RenderingServer::ARRAY_MAX);

	retarr[RenderingServer::ARRAY_VERTEX] = verts;

	if (normals.size() > 0)
		retarr[RenderingServer::ARRAY_NORMAL] = normals;
	if (uvs.size() > 0)
		retarr[RenderingServer::ARRAY_TEX_UV] = uvs;
	if (colors.size() > 0)
		retarr[RenderingServer::ARRAY_COLOR] = colors;
	if (indices.size() > 0)
		retarr[RenderingServer::ARRAY_INDEX] = indices;
	if (bones.size() > 0)
		retarr[RenderingServer::ARRAY_BONES] = bones;
	if (weights.size() > 0)
		retarr[RenderingServer::ARRAY_WEIGHTS] = weights;

	return retarr;
}

PoolVector2Array MeshUtils::uv_unwrap(Array arrays, bool p_block_align, float p_texel_size, int p_padding, int p_max_chart_size) const {
	LocalVector<float> vertices;
	LocalVector<float> normals;
	LocalVector<int> indices;

	PoolVector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX];
	int vc = rvertices.size();
	PoolVector<Vector3>::Read r = rvertices.read();

	PoolVector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL];
	PoolVector<Vector3>::Read rn = rnormals.read();

	int vertex_ofs = vertices.size() / 3;

	vertices.resize((vertex_ofs + vc) * 3);
	normals.resize((vertex_ofs + vc) * 3);

	for (int j = 0; j < vc; j++) {
		Vector3 v = r[j];
		Vector3 n = rn[j];

		vertices[(j + vertex_ofs) * 3 + 0] = v.x;
		vertices[(j + vertex_ofs) * 3 + 1] = v.y;
		vertices[(j + vertex_ofs) * 3 + 2] = v.z;
		normals[(j + vertex_ofs) * 3 + 0] = n.x;
		normals[(j + vertex_ofs) * 3 + 1] = n.y;
		normals[(j + vertex_ofs) * 3 + 2] = n.z;
	}

	PoolVector<int> rindices = arrays[Mesh::ARRAY_INDEX];
	int ic = rindices.size();

	if (ic == 0) {
		for (int j = 0; j < vc / 3; j++) {
			indices.push_back(vertex_ofs + j * 3 + 0);
			indices.push_back(vertex_ofs + j * 3 + 1);
			indices.push_back(vertex_ofs + j * 3 + 2);
		}

	} else {
		PoolVector<int>::Read ri = rindices.read();

		for (int j = 0; j < ic / 3; j++) {
			indices.push_back(vertex_ofs + ri[j * 3 + 0]);
			indices.push_back(vertex_ofs + ri[j * 3 + 1]);
			indices.push_back(vertex_ofs + ri[j * 3 + 2]);
		}
	}

	// set up input mesh
	xatlas_mu::MeshDecl input_mesh;
	input_mesh.indexData = indices.ptr();
	input_mesh.indexCount = indices.size();
	input_mesh.indexFormat = xatlas_mu::IndexFormat::UInt32;

	input_mesh.vertexCount = vertices.size() / 3;
	input_mesh.vertexPositionData = vertices.ptr();
	input_mesh.vertexPositionStride = sizeof(float) * 3;
	input_mesh.vertexNormalData = normals.ptr();
	input_mesh.vertexNormalStride = sizeof(uint32_t) * 3;
	input_mesh.vertexUvData = nullptr;
	input_mesh.vertexUvStride = 0;

	xatlas_mu::ChartOptions chart_options;
	//not sure whether this is better off as true or false, since I don't copy back the indices
	//I'm leaving it on off for now
	//TODO if the generated uvs have weird problems try to set this to true
	chart_options.fixWinding = false;

	xatlas_mu::PackOptions pack_options;
	pack_options.padding = p_padding;
	pack_options.maxChartSize = p_max_chart_size; // Lightmap atlassing needs 2 for padding between meshes, so 4096-2
	pack_options.blockAlign = p_block_align;
	pack_options.texelsPerUnit = 1.0 / p_texel_size;

	xatlas_mu::Atlas *atlas = xatlas_mu::Create();

	xatlas_mu::AddMeshError err = xatlas_mu::AddMesh(atlas, input_mesh, 1);
	ERR_FAIL_COND_V_MSG(err != xatlas_mu::AddMeshError::Success, PoolVector2Array(), xatlas_mu::StringForEnum(err));

	xatlas_mu::Generate(atlas, chart_options, pack_options);

	float w = atlas->width;
	float h = atlas->height;

	if (w == 0 || h == 0) {
		xatlas_mu::Destroy(atlas);
		return PoolVector2Array(); //could not bake because there is no area
	}

	const xatlas_mu::Mesh &output = atlas->meshes[0];

	PoolVector2Array retarr;
	retarr.resize(output.vertexCount);

	PoolVector2Array::Write retarrw = retarr.write();

	for (uint32_t i = 0; i < output.vertexCount; i++) {
		int vind = output.vertexArray[i].xref;

		retarrw[vind] = Vector2(output.vertexArray[i].uv[0] / w, output.vertexArray[i].uv[1] / h);
	}

	retarrw.release();

	xatlas_mu::Destroy(atlas);

	return retarr;
}

PoolIntArray MeshUtils::delaunay3d_tetrahedralize(const Vector<Vector3> &p_points) {
	Vector<Delaunay3D::OutputSimplex> data = Delaunay3D::tetrahedralize(p_points);

	PoolIntArray ret;
	ret.resize(data.size() * 4);
	PoolIntArray::Write w = ret.write();

	for (int i = 0; i < data.size(); ++i) {
		int indx = i * 4;

		const Delaunay3D::OutputSimplex &s = data[i];

		w[indx] = s.points[0];
		w[indx + 1] = s.points[1];
		w[indx + 2] = s.points[2];
		w[indx + 3] = s.points[3];
	}

	w.release();

	return ret;
}

MeshUtils::MeshUtils() {
	_instance = this;
}

MeshUtils::~MeshUtils() {
	_instance = NULL;
}

void MeshUtils::_bind_methods() {
	ClassDB::bind_method(D_METHOD("merge_mesh_array", "arr"), &MeshUtils::merge_mesh_array);
	ClassDB::bind_method(D_METHOD("bake_mesh_array_uv", "arr", "tex", "mul_color"), &MeshUtils::bake_mesh_array_uv, DEFVAL(0.7));

	ClassDB::bind_method(D_METHOD("remove_doubles", "arr"), &MeshUtils::remove_doubles);
	ClassDB::bind_method(D_METHOD("remove_doubles_interpolate_normals", "arr"), &MeshUtils::remove_doubles_interpolate_normals);

	ClassDB::bind_method(D_METHOD("uv_unwrap", "arr", "block_align", "texel_size", "padding", "max_chart_size"), &MeshUtils::uv_unwrap, true, 0.05, 1, 4094);

	ClassDB::bind_method(D_METHOD("delaunay3d_tetrahedralize", "points"), &MeshUtils::delaunay3d_tetrahedralize);
}