/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software  Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2013 by the Blender Foundation.
 * All rights reserved.
 *
 * Contributor(s): Alexander Pinzon Fernandez
 *
 * ***** END GPL LICENSE BLOCK *****
 *
 */

/** \file blender/modifiers/intern/MOD_laplaciandeform.c
 *  \ingroup modifiers
 */

#include "BLI_utildefines.h"
#include "BLI_stackdefines.h"
#include "BLI_math.h"
#include "BLI_string.h"

#include "MEM_guardedalloc.h"

#include "BKE_mesh_mapping.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_particle.h"
#include "BKE_deform.h"

#include "MOD_util.h"

#include "eigen_capi.h"

enum {
	LAPDEFORM_SYSTEM_NOT_CHANGE = 0,
	LAPDEFORM_SYSTEM_IS_DIFFERENT,
	LAPDEFORM_SYSTEM_ONLY_CHANGE_ANCHORS,
	LAPDEFORM_SYSTEM_ONLY_CHANGE_GROUP,
	LAPDEFORM_SYSTEM_ONLY_CHANGE_MESH,
	LAPDEFORM_SYSTEM_CHANGE_VERTEXES,
	LAPDEFORM_SYSTEM_CHANGE_EDGES,
	LAPDEFORM_SYSTEM_CHANGE_NOT_VALID_GROUP,
};

typedef struct LaplacianSystem {
	bool is_matrix_computed;
	bool has_solution;
	int total_verts;
	int total_edges;
	int total_tris;
	int total_anchors;
	int repeat;
	char anchor_grp_name[64];	/* Vertex Group name */
	float (*co)[3];				/* Original vertex coordinates */
	float (*no)[3];				/* Original vertex normal */
	float (*delta)[3];			/* Differential Coordinates */
	unsigned int (*tris)[3];	/* Copy of MLoopTri (tesselation triangle) v1-v3 */
	int *index_anchors;			/* Static vertex index list */
	int *unit_verts;			/* Unit vectors of projected edges onto the plane orthogonal to n */
	int *ringf_indices;			/* Indices of faces per vertex */
	int *ringv_indices;			/* Indices of neighbors(vertex) per vertex */
	LinearSolver *context;			/* System for solve general implicit rotations */
	MeshElemMap *ringf_map;		/* Map of faces per vertex */
	MeshElemMap *ringv_map;		/* Map of vertex per vertex */
} LaplacianSystem;

static LaplacianSystem *newLaplacianSystem(void)
{
	LaplacianSystem *sys;
	sys = MEM_callocN(sizeof(LaplacianSystem), "DeformCache");

	sys->is_matrix_computed = false;
	sys->has_solution = false;
	sys->total_verts = 0;
	sys->total_edges = 0;
	sys->total_anchors = 0;
	sys->total_tris = 0;
	sys->repeat = 1;
	sys->anchor_grp_name[0] = '\0';

	return sys;
}

static LaplacianSystem *initLaplacianSystem(
        int totalVerts, int totalEdges, int totalTris, int totalAnchors,
        const char defgrpName[64], int iterations)
{
	LaplacianSystem *sys = newLaplacianSystem();

	sys->is_matrix_computed = false;
	sys->has_solution = false;
	sys->total_verts = totalVerts;
	sys->total_edges = totalEdges;
	sys->total_tris = totalTris;
	sys->total_anchors = totalAnchors;
	sys->repeat = iterations;
	BLI_strncpy(sys->anchor_grp_name, defgrpName, sizeof(sys->anchor_grp_name));
	sys->co = MEM_malloc_arrayN(totalVerts, sizeof(float[3]), "DeformCoordinates");
	sys->no = MEM_calloc_arrayN(totalVerts, sizeof(float[3]), "DeformNormals");
	sys->delta = MEM_calloc_arrayN(totalVerts, sizeof(float[3]), "DeformDeltas");
	sys->tris = MEM_malloc_arrayN(totalTris, sizeof(int[3]), "DeformFaces");
	sys->index_anchors = MEM_malloc_arrayN((totalAnchors), sizeof(int), "DeformAnchors");
	sys->unit_verts = MEM_calloc_arrayN(totalVerts, sizeof(int), "DeformUnitVerts");
	return sys;
}

static void deleteLaplacianSystem(LaplacianSystem *sys)
{
	MEM_SAFE_FREE(sys->co);
	MEM_SAFE_FREE(sys->no);
	MEM_SAFE_FREE(sys->delta);
	MEM_SAFE_FREE(sys->tris);
	MEM_SAFE_FREE(sys->index_anchors);
	MEM_SAFE_FREE(sys->unit_verts);
	MEM_SAFE_FREE(sys->ringf_indices);
	MEM_SAFE_FREE(sys->ringv_indices);
	MEM_SAFE_FREE(sys->ringf_map);
	MEM_SAFE_FREE(sys->ringv_map);

	if (sys->context) {
		EIG_linear_solver_delete(sys->context);
	}
	MEM_SAFE_FREE(sys);
}

static void createFaceRingMap(
        const int mvert_tot, const MLoopTri *mlooptri, const int mtri_tot,
        const MLoop *mloop, MeshElemMap **r_map, int **r_indices)
{
	int i, j, totalr = 0;
	int *indices, *index_iter;
	MeshElemMap *map = MEM_calloc_arrayN(mvert_tot, sizeof(MeshElemMap), "DeformRingMap");
	const MLoopTri *mlt;

	for (i = 0, mlt = mlooptri; i < mtri_tot; i++, mlt++) {

		for (j = 0; j < 3; j++) {
			const unsigned int v_index = mloop[mlt->tri[j]].v;
			map[v_index].count++;
			totalr++;
		}
	}
	indices = MEM_calloc_arrayN(totalr, sizeof(int), "DeformRingIndex");
	index_iter = indices;
	for (i = 0; i < mvert_tot; i++) {
		map[i].indices = index_iter;
		index_iter += map[i].count;
		map[i].count = 0;
	}
	for (i = 0, mlt = mlooptri; i < mtri_tot; i++, mlt++) {
		for (j = 0; j < 3; j++) {
			const unsigned int v_index = mloop[mlt->tri[j]].v;
			map[v_index].indices[map[v_index].count] = i;
			map[v_index].count++;
		}
	}
	*r_map = map;
	*r_indices = indices;
}

static void createVertRingMap(
        const int mvert_tot, const MEdge *medge, const int medge_tot,
        MeshElemMap **r_map, int **r_indices)
{
	MeshElemMap *map = MEM_calloc_arrayN(mvert_tot, sizeof(MeshElemMap), "DeformNeighborsMap");
	int i, vid[2], totalr = 0;
	int *indices, *index_iter;
	const MEdge *me;

	for (i = 0, me = medge; i < medge_tot; i++, me++) {
		vid[0] = me->v1;
		vid[1] = me->v2;
		map[vid[0]].count++;
		map[vid[1]].count++;
		totalr += 2;
	}
	indices = MEM_calloc_arrayN(totalr, sizeof(int), "DeformNeighborsIndex");
	index_iter = indices;
	for (i = 0; i < mvert_tot; i++) {
		map[i].indices = index_iter;
		index_iter += map[i].count;
		map[i].count = 0;
	}
	for (i = 0, me = medge; i < medge_tot; i++, me++) {
		vid[0] = me->v1;
		vid[1] = me->v2;
		map[vid[0]].indices[map[vid[0]].count] = vid[1];
		map[vid[0]].count++;
		map[vid[1]].indices[map[vid[1]].count] = vid[0];
		map[vid[1]].count++;
	}
	*r_map = map;
	*r_indices = indices;
}

/**
 * This method computes the Laplacian Matrix and Differential Coordinates for all vertex in the mesh.
 * The Linear system is LV = d
 * Where L is Laplacian Matrix, V as the vertexes in Mesh, d is the differential coordinates
 * The Laplacian Matrix is computes as a
 * Lij = sum(Wij) (if i == j)
 * Lij = Wij (if i != j)
 * Wij is weight between vertex Vi and vertex Vj, we use cotangent weight
 *
 * The Differential Coordinate is computes as a
 * di = Vi * sum(Wij) - sum(Wij * Vj)
 * Where :
 * di is the Differential Coordinate i
 * sum (Wij) is the sum of all weights between vertex Vi and its vertexes neighbors (Vj)
 * sum (Wij * Vj) is the sum of the product between vertex neighbor Vj and weight Wij for all neighborhood.
 *
 * This Laplacian Matrix is described in the paper:
 * Desbrun M. et.al, Implicit fairing of irregular meshes using diffusion and curvature flow, SIGGRAPH '99, pag 317-324,
 * New York, USA
 *
 * The computation of Laplace Beltrami operator on Hybrid Triangle/Quad Meshes is described in the paper:
 * Pinzon A., Romero E., Shape Inflation With an Adapted Laplacian Operator For Hybrid Quad/Triangle Meshes,
 * Conference on Graphics Patterns and Images, SIBGRAPI, 2013
 *
 * The computation of Differential Coordinates is described in the paper:
 * Sorkine, O. Laplacian Surface Editing. Proceedings of the EUROGRAPHICS/ACM SIGGRAPH Symposium on Geometry Processing,
 * 2004. p. 179-188.
 */
static void initLaplacianMatrix(LaplacianSystem *sys)
{
	float no[3];
	float w2, w3;
	int i = 3, j, ti;
	int idv[3];

	for (ti = 0; ti < sys->total_tris; ti++) {
		const unsigned int *vidt = sys->tris[ti];
		const float *co[3];

		co[0] = sys->co[vidt[0]];
		co[1] = sys->co[vidt[1]];
		co[2] = sys->co[vidt[2]];

		normal_tri_v3(no, UNPACK3(co));
		add_v3_v3(sys->no[vidt[0]], no);
		add_v3_v3(sys->no[vidt[1]], no);
		add_v3_v3(sys->no[vidt[2]], no);

		for (j = 0; j < 3; j++) {
			const float *v1, *v2, *v3;

			idv[0] = vidt[j];
			idv[1] = vidt[(j + 1) % i];
			idv[2] = vidt[(j + 2) % i];

			v1 = sys->co[idv[0]];
			v2 = sys->co[idv[1]];
			v3 = sys->co[idv[2]];

			w2 = cotangent_tri_weight_v3(v3, v1, v2);
			w3 = cotangent_tri_weight_v3(v2, v3, v1);

			sys->delta[idv[0]][0] += v1[0] * (w2 + w3);
			sys->delta[idv[0]][1] += v1[1] * (w2 + w3);
			sys->delta[idv[0]][2] += v1[2] * (w2 + w3);

			sys->delta[idv[0]][0] -= v2[0] * w2;
			sys->delta[idv[0]][1] -= v2[1] * w2;
			sys->delta[idv[0]][2] -= v2[2] * w2;

			sys->delta[idv[0]][0] -= v3[0] * w3;
			sys->delta[idv[0]][1] -= v3[1] * w3;
			sys->delta[idv[0]][2] -= v3[2] * w3;

			EIG_linear_solver_matrix_add(sys->context, idv[0], idv[1], -w2);
			EIG_linear_solver_matrix_add(sys->context, idv[0], idv[2], -w3);
			EIG_linear_solver_matrix_add(sys->context, idv[0], idv[0], w2 + w3);
		}
	}
}

static void computeImplictRotations(LaplacianSystem *sys)
{
	int vid, *vidn = NULL;
	float minj, mjt, qj[3], vj[3];
	int i, j, ln;

	for (i = 0; i < sys->total_verts; i++) {
		normalize_v3(sys->no[i]);
		vidn = sys->ringv_map[i].indices;
		ln = sys->ringv_map[i].count;
		minj = 1000000.0f;
		for (j = 0; j < ln; j++) {
			vid = vidn[j];
			copy_v3_v3(qj, sys->co[vid]);
			sub_v3_v3v3(vj, qj, sys->co[i]);
			normalize_v3(vj);
			mjt = fabsf(dot_v3v3(vj, sys->no[i]));
			if (mjt < minj) {
				minj = mjt;
				sys->unit_verts[i] = vidn[j];
			}
		}
	}
}

static void rotateDifferentialCoordinates(LaplacianSystem *sys)
{
	float alpha, beta, gamma;
	float pj[3], ni[3], di[3];
	float uij[3], dun[3], e2[3], pi[3], fni[3], vn[3][3];
	int i, j, num_fni, k, fi;
	int *fidn;

	for (i = 0; i < sys->total_verts; i++) {
		copy_v3_v3(pi, sys->co[i]);
		copy_v3_v3(ni, sys->no[i]);
		k = sys->unit_verts[i];
		copy_v3_v3(pj, sys->co[k]);
		sub_v3_v3v3(uij, pj, pi);
		mul_v3_v3fl(dun, ni, dot_v3v3(uij, ni));
		sub_v3_v3(uij, dun);
		normalize_v3(uij);
		cross_v3_v3v3(e2, ni, uij);
		copy_v3_v3(di, sys->delta[i]);
		alpha = dot_v3v3(ni, di);
		beta = dot_v3v3(uij, di);
		gamma = dot_v3v3(e2, di);

		pi[0] = EIG_linear_solver_variable_get(sys->context, 0, i);
		pi[1] = EIG_linear_solver_variable_get(sys->context, 1, i);
		pi[2] = EIG_linear_solver_variable_get(sys->context, 2, i);
		zero_v3(ni);
		num_fni = sys->ringf_map[i].count;
		for (fi = 0; fi < num_fni; fi++) {
			const unsigned int *vin;
			fidn = sys->ringf_map[i].indices;
			vin = sys->tris[fidn[fi]];
			for (j = 0; j < 3; j++) {
				vn[j][0] = EIG_linear_solver_variable_get(sys->context, 0, vin[j]);
				vn[j][1] = EIG_linear_solver_variable_get(sys->context, 1, vin[j]);
				vn[j][2] = EIG_linear_solver_variable_get(sys->context, 2, vin[j]);
				if (vin[j] == sys->unit_verts[i]) {
					copy_v3_v3(pj, vn[j]);
				}
			}

			normal_tri_v3(fni, UNPACK3(vn));
			add_v3_v3(ni, fni);
		}

		normalize_v3(ni);
		sub_v3_v3v3(uij, pj, pi);
		mul_v3_v3fl(dun, ni, dot_v3v3(uij, ni));
		sub_v3_v3(uij, dun);
		normalize_v3(uij);
		cross_v3_v3v3(e2, ni, uij);
		fni[0] = alpha * ni[0] + beta * uij[0] + gamma * e2[0];
		fni[1] = alpha * ni[1] + beta * uij[1] + gamma * e2[1];
		fni[2] = alpha * ni[2] + beta * uij[2] + gamma * e2[2];

		if (len_squared_v3(fni) > FLT_EPSILON) {
			EIG_linear_solver_right_hand_side_add(sys->context, 0, i, fni[0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, i, fni[1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, i, fni[2]);
		}
		else {
			EIG_linear_solver_right_hand_side_add(sys->context, 0, i, sys->delta[i][0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, i, sys->delta[i][1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, i, sys->delta[i][2]);
		}
	}
}

static void laplacianDeformPreview(LaplacianSystem *sys, float (*vertexCos)[3])
{
	int vid, i, j, n, na;
	n = sys->total_verts;
	na = sys->total_anchors;

	if (!sys->is_matrix_computed) {
		sys->context = EIG_linear_least_squares_solver_new(n + na, n, 3);

		for (i = 0; i < n; i++) {
			EIG_linear_solver_variable_set(sys->context, 0, i, sys->co[i][0]);
			EIG_linear_solver_variable_set(sys->context, 1, i, sys->co[i][1]);
			EIG_linear_solver_variable_set(sys->context, 2, i, sys->co[i][2]);
		}
		for (i = 0; i < na; i++) {
			vid = sys->index_anchors[i];
			EIG_linear_solver_variable_set(sys->context, 0, vid, vertexCos[vid][0]);
			EIG_linear_solver_variable_set(sys->context, 1, vid, vertexCos[vid][1]);
			EIG_linear_solver_variable_set(sys->context, 2, vid, vertexCos[vid][2]);
		}

		initLaplacianMatrix(sys);
		computeImplictRotations(sys);

		for (i = 0; i < n; i++) {
			EIG_linear_solver_right_hand_side_add(sys->context, 0, i, sys->delta[i][0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, i, sys->delta[i][1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, i, sys->delta[i][2]);
		}
		for (i = 0; i < na; i++) {
			vid = sys->index_anchors[i];
			EIG_linear_solver_right_hand_side_add(sys->context, 0, n + i, vertexCos[vid][0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, n + i, vertexCos[vid][1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, n + i, vertexCos[vid][2]);
			EIG_linear_solver_matrix_add(sys->context, n + i, vid, 1.0f);
		}
		if (EIG_linear_solver_solve(sys->context)) {
			sys->has_solution = true;

			for (j = 1; j <= sys->repeat; j++) {
				rotateDifferentialCoordinates(sys);

				for (i = 0; i < na; i++) {
					vid = sys->index_anchors[i];
					EIG_linear_solver_right_hand_side_add(sys->context, 0, n + i, vertexCos[vid][0]);
					EIG_linear_solver_right_hand_side_add(sys->context, 1, n + i, vertexCos[vid][1]);
					EIG_linear_solver_right_hand_side_add(sys->context, 2, n + i, vertexCos[vid][2]);
				}

				if (!EIG_linear_solver_solve(sys->context)) {
					sys->has_solution = false;
					break;
				}
			}
			if (sys->has_solution) {
				for (vid = 0; vid < sys->total_verts; vid++) {
					vertexCos[vid][0] = EIG_linear_solver_variable_get(sys->context, 0, vid);
					vertexCos[vid][1] = EIG_linear_solver_variable_get(sys->context, 1, vid);
					vertexCos[vid][2] = EIG_linear_solver_variable_get(sys->context, 2, vid);
				}
			}
			else {
				sys->has_solution = false;
			}

		}
		else {
			sys->has_solution = false;
		}
		sys->is_matrix_computed = true;

	}
	else if (sys->has_solution) {
		for (i = 0; i < n; i++) {
			EIG_linear_solver_right_hand_side_add(sys->context, 0, i, sys->delta[i][0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, i, sys->delta[i][1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, i, sys->delta[i][2]);
		}
		for (i = 0; i < na; i++) {
			vid = sys->index_anchors[i];
			EIG_linear_solver_right_hand_side_add(sys->context, 0, n + i, vertexCos[vid][0]);
			EIG_linear_solver_right_hand_side_add(sys->context, 1, n + i, vertexCos[vid][1]);
			EIG_linear_solver_right_hand_side_add(sys->context, 2, n + i, vertexCos[vid][2]);
			EIG_linear_solver_matrix_add(sys->context, n + i, vid, 1.0f);
		}

		if (EIG_linear_solver_solve(sys->context)) {
			sys->has_solution = true;
			for (j = 1; j <= sys->repeat; j++) {
				rotateDifferentialCoordinates(sys);

				for (i = 0; i < na; i++) {
					vid = sys->index_anchors[i];
					EIG_linear_solver_right_hand_side_add(sys->context, 0, n + i, vertexCos[vid][0]);
					EIG_linear_solver_right_hand_side_add(sys->context, 1, n + i, vertexCos[vid][1]);
					EIG_linear_solver_right_hand_side_add(sys->context, 2, n + i, vertexCos[vid][2]);
				}
				if (!EIG_linear_solver_solve(sys->context)) {
					sys->has_solution = false;
					break;
				}
			}
			if (sys->has_solution) {
				for (vid = 0; vid < sys->total_verts; vid++) {
					vertexCos[vid][0] = EIG_linear_solver_variable_get(sys->context, 0, vid);
					vertexCos[vid][1] = EIG_linear_solver_variable_get(sys->context, 1, vid);
					vertexCos[vid][2] = EIG_linear_solver_variable_get(sys->context, 2, vid);
				}
			}
			else {
				sys->has_solution = false;
			}
		}
		else {
			sys->has_solution = false;
		}
	}
}

static bool isValidVertexGroup(LaplacianDeformModifierData *lmd, Object *ob, DerivedMesh *dm)
{
	int defgrp_index;
	MDeformVert *dvert = NULL;

	modifier_get_vgroup(ob, dm, lmd->anchor_grp_name, &dvert, &defgrp_index);

	return  (dvert != NULL);
}

static void initSystem(LaplacianDeformModifierData *lmd, Object *ob, DerivedMesh *dm,
                       float (*vertexCos)[3], int numVerts)
{
	int i;
	int defgrp_index;
	int total_anchors;
	float wpaint;
	MDeformVert *dvert = NULL;
	MDeformVert *dv = NULL;
	LaplacianSystem *sys;

	if (isValidVertexGroup(lmd, ob, dm)) {
		int *index_anchors = MEM_malloc_arrayN(numVerts, sizeof(int), __func__);  /* over-alloc */
		const MLoopTri *mlooptri;
		const MLoop *mloop;

		STACK_DECLARE(index_anchors);

		STACK_INIT(index_anchors, numVerts);

		modifier_get_vgroup(ob, dm, lmd->anchor_grp_name, &dvert, &defgrp_index);
		BLI_assert(dvert != NULL);
		dv = dvert;
		for (i = 0; i < numVerts; i++) {
			wpaint = defvert_find_weight(dv, defgrp_index);
			dv++;
			if (wpaint > 0.0f) {
				STACK_PUSH(index_anchors, i);
			}
		}

		total_anchors = STACK_SIZE(index_anchors);
		lmd->cache_system = initLaplacianSystem(numVerts, dm->getNumEdges(dm), dm->getNumLoopTri(dm),
		                                       total_anchors, lmd->anchor_grp_name, lmd->repeat);
		sys = (LaplacianSystem *)lmd->cache_system;
		memcpy(sys->index_anchors, index_anchors, sizeof(int) * total_anchors);
		memcpy(sys->co, vertexCos, sizeof(float[3]) * numVerts);
		MEM_freeN(index_anchors);
		lmd->vertexco = MEM_malloc_arrayN(numVerts, sizeof(float[3]), "ModDeformCoordinates");
		memcpy(lmd->vertexco, vertexCos, sizeof(float[3]) * numVerts);
		lmd->total_verts = numVerts;

		createFaceRingMap(
		            dm->getNumVerts(dm), dm->getLoopTriArray(dm), dm->getNumLoopTri(dm),
		            dm->getLoopArray(dm), &sys->ringf_map, &sys->ringf_indices);
		createVertRingMap(
		            dm->getNumVerts(dm), dm->getEdgeArray(dm), dm->getNumEdges(dm),
		            &sys->ringv_map, &sys->ringv_indices);


		mlooptri = dm->getLoopTriArray(dm);
		mloop = dm->getLoopArray(dm);

		for (i = 0; i < sys->total_tris; i++) {
			sys->tris[i][0] = mloop[mlooptri[i].tri[0]].v;
			sys->tris[i][1] = mloop[mlooptri[i].tri[1]].v;
			sys->tris[i][2] = mloop[mlooptri[i].tri[2]].v;
		}
	}
}

static int isSystemDifferent(LaplacianDeformModifierData *lmd, Object *ob, DerivedMesh *dm, int numVerts)
{
	int i;
	int defgrp_index;
	int total_anchors = 0;
	float wpaint;
	MDeformVert *dvert = NULL;
	MDeformVert *dv = NULL;
	LaplacianSystem *sys = (LaplacianSystem *)lmd->cache_system;

	if (sys->total_verts != numVerts) {
		return LAPDEFORM_SYSTEM_CHANGE_VERTEXES;
	}
	if (sys->total_edges != dm->getNumEdges(dm)) {
		return LAPDEFORM_SYSTEM_CHANGE_EDGES;
	}
	if (!STREQ(lmd->anchor_grp_name, sys->anchor_grp_name)) {
		return LAPDEFORM_SYSTEM_ONLY_CHANGE_GROUP;
	}
	modifier_get_vgroup(ob, dm, lmd->anchor_grp_name, &dvert, &defgrp_index);
	if (!dvert) {
		return LAPDEFORM_SYSTEM_CHANGE_NOT_VALID_GROUP;
	}
	dv = dvert;
	for (i = 0; i < numVerts; i++) {
		wpaint = defvert_find_weight(dv, defgrp_index);
		dv++;
		if (wpaint > 0.0f) {
			total_anchors++;
		}
	}
	if (sys->total_anchors != total_anchors) {
		return LAPDEFORM_SYSTEM_ONLY_CHANGE_ANCHORS;
	}

	return LAPDEFORM_SYSTEM_NOT_CHANGE;
}

static void LaplacianDeformModifier_do(
        LaplacianDeformModifierData *lmd, Object *ob, DerivedMesh *dm,
        float (*vertexCos)[3], int numVerts)
{
	float (*filevertexCos)[3];
	int sysdif;
	LaplacianSystem *sys = NULL;
	filevertexCos = NULL;
	if (!(lmd->flag & MOD_LAPLACIANDEFORM_BIND)) {
		if (lmd->cache_system) {
			sys = lmd->cache_system;
			deleteLaplacianSystem(sys);
			lmd->cache_system = NULL;
		}
		lmd->total_verts = 0;
		MEM_SAFE_FREE(lmd->vertexco);
		return;
	}
	if (lmd->cache_system) {
		sysdif = isSystemDifferent(lmd, ob, dm, numVerts);
		sys = lmd->cache_system;
		if (sysdif) {
			if (sysdif == LAPDEFORM_SYSTEM_ONLY_CHANGE_ANCHORS || sysdif == LAPDEFORM_SYSTEM_ONLY_CHANGE_GROUP) {
				filevertexCos = MEM_malloc_arrayN(numVerts, sizeof(float[3]), "TempModDeformCoordinates");
				memcpy(filevertexCos, lmd->vertexco, sizeof(float[3]) * numVerts);
				MEM_SAFE_FREE(lmd->vertexco);
				lmd->total_verts = 0;
				deleteLaplacianSystem(sys);
				lmd->cache_system = NULL;
				initSystem(lmd, ob, dm, filevertexCos, numVerts);
				sys = lmd->cache_system; /* may have been reallocated */
				MEM_SAFE_FREE(filevertexCos);
				if (sys) {
					laplacianDeformPreview(sys, vertexCos);
				}
			}
			else {
				if (sysdif == LAPDEFORM_SYSTEM_CHANGE_VERTEXES) {
					modifier_setError(&lmd->modifier, "Vertices changed from %d to %d", lmd->total_verts, numVerts);
				}
				else if (sysdif == LAPDEFORM_SYSTEM_CHANGE_EDGES) {
					modifier_setError(&lmd->modifier, "Edges changed from %d to %d", sys->total_edges, dm->getNumEdges(dm));
				}
				else if (sysdif == LAPDEFORM_SYSTEM_CHANGE_NOT_VALID_GROUP) {
					modifier_setError(&lmd->modifier, "Vertex group '%s' is not valid", sys->anchor_grp_name);
				}
			}
		}
		else {
			sys->repeat = lmd->repeat;
			laplacianDeformPreview(sys, vertexCos);
		}
	}
	else {
		if (!isValidVertexGroup(lmd, ob, dm)) {
			modifier_setError(&lmd->modifier, "Vertex group '%s' is not valid", lmd->anchor_grp_name);
			lmd->flag &= ~MOD_LAPLACIANDEFORM_BIND;
		}
		else if (lmd->total_verts > 0 && lmd->total_verts == numVerts) {
			filevertexCos = MEM_malloc_arrayN(numVerts, sizeof(float[3]), "TempDeformCoordinates");
			memcpy(filevertexCos, lmd->vertexco, sizeof(float[3]) * numVerts);
			MEM_SAFE_FREE(lmd->vertexco);
			lmd->total_verts = 0;
			initSystem(lmd, ob, dm, filevertexCos, numVerts);
			sys = lmd->cache_system;
			MEM_SAFE_FREE(filevertexCos);
			laplacianDeformPreview(sys, vertexCos);
		}
		else {
			initSystem(lmd, ob, dm, vertexCos, numVerts);
			sys = lmd->cache_system;
			laplacianDeformPreview(sys, vertexCos);
		}
	}
	if (sys && sys->is_matrix_computed && !sys->has_solution) {
		modifier_setError(&lmd->modifier, "The system did not find a solution");
	}
}

static void initData(ModifierData *md)
{
	LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)md;
	lmd->anchor_grp_name[0] = '\0';
	lmd->total_verts = 0;
	lmd->repeat = 1;
	lmd->vertexco = NULL;
	lmd->cache_system = NULL;
	lmd->flag = 0;
}

static void copyData(ModifierData *md, ModifierData *target)
{
	LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)md;
	LaplacianDeformModifierData *tlmd = (LaplacianDeformModifierData *)target;

	modifier_copyData_generic(md, target);

	tlmd->vertexco = MEM_dupallocN(lmd->vertexco);
	tlmd->cache_system = NULL;
}

static bool isDisabled(ModifierData *md, int UNUSED(useRenderParams))
{
	LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)md;
	if (lmd->anchor_grp_name[0]) return 0;
	return 1;
}

static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md)
{
	LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)md;
	CustomDataMask dataMask = 0;
	if (lmd->anchor_grp_name[0]) dataMask |= CD_MASK_MDEFORMVERT;
	return dataMask;
}

static void deformVerts(ModifierData *md, Object *ob, DerivedMesh *derivedData,
                        float (*vertexCos)[3], int numVerts, ModifierApplyFlag UNUSED(flag))
{
	DerivedMesh *dm = get_dm(ob, NULL, derivedData, NULL, false, false);

	LaplacianDeformModifier_do((LaplacianDeformModifierData *)md, ob, dm, vertexCos, numVerts);
	if (dm != derivedData) {
		dm->release(dm);
	}
}

static void deformVertsEM(
        ModifierData *md, Object *ob, struct BMEditMesh *editData,
        DerivedMesh *derivedData, float (*vertexCos)[3], int numVerts)
{
	DerivedMesh *dm = get_dm(ob, editData, derivedData, NULL, false, false);
	LaplacianDeformModifier_do((LaplacianDeformModifierData *)md, ob, dm,
	                           vertexCos, numVerts);
	if (dm != derivedData) {
		dm->release(dm);
	}
}

static void freeData(ModifierData *md)
{
	LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)md;
	LaplacianSystem *sys = (LaplacianSystem *)lmd->cache_system;
	if (sys) {
		deleteLaplacianSystem(sys);
	}
	MEM_SAFE_FREE(lmd->vertexco);
	lmd->total_verts = 0;
}

ModifierTypeInfo modifierType_LaplacianDeform = {
	/* name */              "LaplacianDeform",
	/* structName */        "LaplacianDeformModifierData",
	/* structSize */        sizeof(LaplacianDeformModifierData),
	/* type */              eModifierTypeType_OnlyDeform,
	/* flags */             eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode,
	/* copyData */          copyData,
	/* deformVerts */       deformVerts,
	/* deformMatrices */    NULL,
	/* deformVertsEM */     deformVertsEM,
	/* deformMatricesEM */  NULL,
	/* applyModifier */     NULL,
	/* applyModifierEM */   NULL,
	/* initData */          initData,
	/* requiredDataMask */  requiredDataMask,
	/* freeData */          freeData,
	/* isDisabled */        isDisabled,
	/* updateDepgraph */    NULL,
	/* updateDepsgraph */   NULL,
	/* dependsOnTime */     NULL,
	/* dependsOnNormals */	NULL,
	/* foreachObjectLink */ NULL,
	/* foreachIDLink */     NULL,
	/* foreachTexLink */    NULL,
};
