/*
* $Id$
*
* ***** 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) 2005 by the Blender Foundation.
* All rights reserved.
*
* Contributor(s): Daniel Dunbar
*                 Ton Roosendaal,
*                 Ben Batt,
*                 Brecht Van Lommel,
*                 Campbell Barton
*
* ***** END GPL LICENSE BLOCK *****
*
*/

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


#include "DNA_meshdata_types.h"

#include "BLI_math.h"
#include "BLI_edgehash.h"
#include "BLI_utildefines.h"

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


#include "MOD_modifiertypes.h"
#include "MOD_util.h"

#include "MEM_guardedalloc.h"

typedef struct EdgeFaceRef {
	int f1; /* init as -1 */
	int f2;
} EdgeFaceRef;

static void dm_calc_normal(DerivedMesh *dm, float (*temp_nors)[3])
{
	int i, numVerts, numEdges, numFaces;
	MFace *mface, *mf;
	MVert *mvert, *mv;

	float (*face_nors)[3];
	float *f_no;
	int calc_face_nors= 0;

	numVerts = dm->getNumVerts(dm);
	numEdges = dm->getNumEdges(dm);
	numFaces = dm->getNumFaces(dm);
	mface = dm->getFaceArray(dm);
	mvert = dm->getVertArray(dm);

	/* we don't want to overwrite any referenced layers */

	/*
	Dosnt work here!
	mv = CustomData_duplicate_referenced_layer(&dm->vertData, CD_MVERT);
	cddm->mvert = mv;
	*/

	face_nors = CustomData_get_layer(&dm->faceData, CD_NORMAL);
	if(!face_nors) {
		calc_face_nors = 1;
		face_nors = CustomData_add_layer(&dm->faceData, CD_NORMAL, CD_CALLOC, NULL, numFaces);
	}

	mv = mvert;
	mf = mface;

	{
		EdgeHash *edge_hash = BLI_edgehash_new();
		EdgeHashIterator *edge_iter;
		int edge_ref_count = 0;
		int ed_v1, ed_v2; /* use when getting the key */
		EdgeFaceRef *edge_ref_array = MEM_callocN(numEdges * sizeof(EdgeFaceRef), "Edge Connectivity");
		EdgeFaceRef *edge_ref;
		float edge_normal[3];

		/* This function adds an edge hash if its not there, and adds the face index */
#define NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(EDV1, EDV2); \
				edge_ref = (EdgeFaceRef *)BLI_edgehash_lookup(edge_hash, EDV1, EDV2); \
				if (!edge_ref) { \
					edge_ref = &edge_ref_array[edge_ref_count]; edge_ref_count++; \
					edge_ref->f1=i; \
					edge_ref->f2=-1; \
					BLI_edgehash_insert(edge_hash, EDV1, EDV2, edge_ref); \
				} else { \
					edge_ref->f2=i; \
				}

		for(i = 0; i < numFaces; i++, mf++) {
			f_no = face_nors[i];

			if(mf->v4) {
				if(calc_face_nors)
					normal_quad_v3(f_no, mv[mf->v1].co, mv[mf->v2].co, mv[mf->v3].co, mv[mf->v4].co);

				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v1, mf->v2);
				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v2, mf->v3);
				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v3, mf->v4);
				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v4, mf->v1);
			} else {
				if(calc_face_nors)
					normal_tri_v3(f_no, mv[mf->v1].co, mv[mf->v2].co, mv[mf->v3].co);

				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v1, mf->v2);
				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v2, mf->v3);
				NOCALC_EDGEWEIGHT_ADD_EDGEREF_FACE(mf->v3, mf->v1);
			}
		}

		for(edge_iter = BLI_edgehashIterator_new(edge_hash); !BLI_edgehashIterator_isDone(edge_iter); BLI_edgehashIterator_step(edge_iter)) {
			/* Get the edge vert indices, and edge value (the face indices that use it)*/
			BLI_edgehashIterator_getKey(edge_iter, (int*)&ed_v1, (int*)&ed_v2);
			edge_ref = BLI_edgehashIterator_getValue(edge_iter);

			if (edge_ref->f2 != -1) {
				/* We have 2 faces using this edge, calculate the edges normal
				 * using the angle between the 2 faces as a weighting */
				add_v3_v3v3(edge_normal, face_nors[edge_ref->f1], face_nors[edge_ref->f2]);
				normalize_v3(edge_normal);
				mul_v3_fl(edge_normal, angle_normalized_v3v3(face_nors[edge_ref->f1], face_nors[edge_ref->f2]));
			} else {
				/* only one face attached to that edge */
				/* an edge without another attached- the weight on this is
				 * undefined, M_PI/2 is 90d in radians and that seems good enough */
				mul_v3_v3fl(edge_normal, face_nors[edge_ref->f1], M_PI/2);
			}
			add_v3_v3(temp_nors[ed_v1], edge_normal);
			add_v3_v3(temp_nors[ed_v2], edge_normal);
		}
		BLI_edgehashIterator_free(edge_iter);
		BLI_edgehash_free(edge_hash, NULL);
		MEM_freeN(edge_ref_array);
	}

	/* normalize vertex normals and assign */
	for(i = 0; i < numVerts; i++, mv++) {
		if(normalize_v3(temp_nors[i]) == 0.0f) {
			normal_short_to_float_v3(temp_nors[i], mv->no);
		}
	}
}
 
static void initData(ModifierData *md)
{
	SolidifyModifierData *smd = (SolidifyModifierData*) md;
	smd->offset = 0.01f;
	smd->offset_fac = -1.0f;
	smd->flag = MOD_SOLIDIFY_RIM;
}
 
static void copyData(ModifierData *md, ModifierData *target)
{
	SolidifyModifierData *smd = (SolidifyModifierData*) md;
	SolidifyModifierData *tsmd = (SolidifyModifierData*) target;
	tsmd->offset = smd->offset;
	tsmd->offset_fac = smd->offset_fac;
	tsmd->crease_inner = smd->crease_inner;
	tsmd->crease_outer = smd->crease_outer;
	tsmd->crease_rim = smd->crease_rim;
	tsmd->flag = smd->flag;
	strcpy(tsmd->defgrp_name, smd->defgrp_name);
}

static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md)
{
	SolidifyModifierData *smd = (SolidifyModifierData*) md;
	CustomDataMask dataMask = 0;

	/* ask for vertexgroups if we need them */
	if(smd->defgrp_name[0]) dataMask |= CD_MASK_MDEFORMVERT;

	return dataMask;
}


static DerivedMesh *applyModifier(ModifierData *md, Object *ob, 
						DerivedMesh *dm,
						int UNUSED(useRenderParams),
						int UNUSED(isFinalCalc))
{
	int i;
	DerivedMesh *result;
	const SolidifyModifierData *smd = (SolidifyModifierData*) md;

	MFace *mf, *mface, *orig_mface;
	MEdge *ed, *medge, *orig_medge;
	MVert *mv, *mvert, *orig_mvert;

	const int numVerts = dm->getNumVerts(dm);
	const int numEdges = dm->getNumEdges(dm);
	const int numFaces = dm->getNumFaces(dm);

	/* only use material offsets if we have 2 or more materials  */
	const short mat_nr_max= ob->totcol > 1 ? ob->totcol - 1 : 0;
	const short mat_ofs= mat_nr_max ? smd->mat_ofs : 0;
	const short mat_ofs_rim= mat_nr_max ? smd->mat_ofs_rim : 0;

	/* use for edges */
	int *new_vert_arr= NULL;
	int newFaces = 0;

	int *new_edge_arr= NULL;
	int newEdges = 0;

	int *edge_users= NULL;
	char *edge_order= NULL;

	float (*vert_nors)[3]= NULL;

	const float ofs_orig=				- (((-smd->offset_fac + 1.0f) * 0.5f) * smd->offset);
	const float ofs_new= smd->offset	- (((-smd->offset_fac + 1.0f) * 0.5f) * smd->offset);
	const float offset_fac_vg= smd->offset_fac_vg;
	const float offset_fac_vg_inv= 1.0f - smd->offset_fac_vg;

	/* weights */
	MDeformVert *dvert, *dv= NULL;
	const int defgrp_invert = ((smd->flag & MOD_SOLIDIFY_VGROUP_INV) != 0);
	int defgrp_index;

	modifier_get_vgroup(ob, dm, smd->defgrp_name, &dvert, &defgrp_index);

	orig_mface = dm->getFaceArray(dm);
	orig_medge = dm->getEdgeArray(dm);
	orig_mvert = dm->getVertArray(dm);

	if(smd->flag & MOD_SOLIDIFY_RIM) {
		EdgeHash *edgehash = BLI_edgehash_new();
		EdgeHashIterator *ehi;
		int v1, v2;
		int eidx;

		for(i=0, mv=orig_mvert; i<numVerts; i++, mv++) {
			mv->flag &= ~ME_VERT_TMP_TAG;
		}

		for(i=0, ed=orig_medge; i<numEdges; i++, ed++) {
			BLI_edgehash_insert(edgehash, ed->v1, ed->v2, SET_INT_IN_POINTER(i));
		}

#define INVALID_UNUSED -1
#define INVALID_PAIR -2

#define ADD_EDGE_USER(_v1, _v2, edge_ord) \
		eidx= GET_INT_FROM_POINTER(BLI_edgehash_lookup(edgehash, _v1, _v2)); \
		if(edge_users[eidx] == INVALID_UNUSED) { \
			ed= orig_medge + eidx; \
			edge_users[eidx]= (_v1 < _v2) == (ed->v1 < ed->v2) ? i:(i+numFaces); \
			edge_order[eidx]= edge_ord; \
		} else { \
			edge_users[eidx]= INVALID_PAIR; \
		} \


		edge_users= MEM_mallocN(sizeof(int) * numEdges, "solid_mod edges");
		edge_order= MEM_mallocN(sizeof(char) * numEdges, "solid_mod eorder");
		memset(edge_users, INVALID_UNUSED, sizeof(int) * numEdges);

		for(i=0, mf=orig_mface; i<numFaces; i++, mf++) {
			if(mf->v4) {
				ADD_EDGE_USER(mf->v1, mf->v2, 0);
				ADD_EDGE_USER(mf->v2, mf->v3, 1);
				ADD_EDGE_USER(mf->v3, mf->v4, 2);
				ADD_EDGE_USER(mf->v4, mf->v1, 3);
			}
			else {
				ADD_EDGE_USER(mf->v1, mf->v2, 0);
				ADD_EDGE_USER(mf->v2, mf->v3, 1);
				ADD_EDGE_USER(mf->v3, mf->v1, 2);
			}
		}

#undef ADD_EDGE_USER
#undef INVALID_UNUSED
#undef INVALID_PAIR


		new_edge_arr= MEM_callocN(sizeof(int) * numEdges, "solid_mod arr");

		ehi= BLI_edgehashIterator_new(edgehash);
		for(; !BLI_edgehashIterator_isDone(ehi); BLI_edgehashIterator_step(ehi)) {
			eidx= GET_INT_FROM_POINTER(BLI_edgehashIterator_getValue(ehi));
			if(edge_users[eidx] >= 0) {
				BLI_edgehashIterator_getKey(ehi, &v1, &v2);
				orig_mvert[v1].flag |= ME_VERT_TMP_TAG;
				orig_mvert[v2].flag |= ME_VERT_TMP_TAG;
				new_edge_arr[newFaces]= eidx;
				newFaces++;
			}
		}
		BLI_edgehashIterator_free(ehi);



		new_vert_arr= MEM_callocN(sizeof(int) * numVerts, "solid_mod new_varr");
		for(i=0, mv=orig_mvert; i<numVerts; i++, mv++) {
			if(mv->flag & ME_VERT_TMP_TAG) {
				new_vert_arr[newEdges] = i;
				newEdges++;

				mv->flag &= ~ME_VERT_TMP_TAG;
			}
		}

		BLI_edgehash_free(edgehash, NULL);
	}

	if(smd->flag & MOD_SOLIDIFY_NORMAL_CALC) {
		vert_nors= MEM_callocN(sizeof(float) * numVerts * 3, "mod_solid_vno_hq");
		dm_calc_normal(dm, vert_nors);
	}

	result = CDDM_from_template(dm, numVerts * 2, (numEdges * 2) + newEdges, (numFaces * 2) + newFaces);	

	mface = result->getFaceArray(result);
	medge = result->getEdgeArray(result);
	mvert = result->getVertArray(result);

	DM_copy_face_data(dm, result, 0, 0, numFaces);
	DM_copy_face_data(dm, result, 0, numFaces, numFaces);

	DM_copy_edge_data(dm, result, 0, 0, numEdges);
	DM_copy_edge_data(dm, result, 0, numEdges, numEdges);

	DM_copy_vert_data(dm, result, 0, 0, numVerts);
	DM_copy_vert_data(dm, result, 0, numVerts, numVerts);

	{
		static int corner_indices[4] = {2, 1, 0, 3};
		int is_quad;

		for(i=0, mf=mface+numFaces; i<numFaces; i++, mf++) {
			mf->v1 += numVerts;
			mf->v2 += numVerts;
			mf->v3 += numVerts;
			if(mf->v4)
				mf->v4 += numVerts;

			/* Flip face normal */
			{
				is_quad = mf->v4;
				SWAP(int, mf->v1, mf->v3);
				DM_swap_face_data(result, i+numFaces, corner_indices);
				test_index_face(mf, &result->faceData, numFaces, is_quad ? 4:3);
			}

			if(mat_ofs) {
				mf->mat_nr += mat_ofs;
				CLAMP(mf->mat_nr, 0, mat_nr_max);
			}
		}
	}

	for(i=0, ed=medge+numEdges; i<numEdges; i++, ed++) {
		ed->v1 += numVerts;
		ed->v2 += numVerts;
	}

	/* note, copied vertex layers dont have flipped normals yet. do this after applying offset */
	if((smd->flag & MOD_SOLIDIFY_EVEN) == 0) {
		/* no even thickness, very simple */
		float scalar_short;
		float scalar_short_vgroup;


		if(ofs_new != 0.0f) {
			scalar_short= scalar_short_vgroup= ofs_new / 32767.0f;
			mv= mvert + ((ofs_new >= ofs_orig) ? 0 : numVerts);
			dv= dvert;
			for(i=0; i<numVerts; i++, mv++) {
				if(dv) {
					if(defgrp_invert)	scalar_short_vgroup = 1.0f - defvert_find_weight(dv, defgrp_index);
					else				scalar_short_vgroup = defvert_find_weight(dv, defgrp_index);
					scalar_short_vgroup= (offset_fac_vg + (scalar_short_vgroup * offset_fac_vg_inv)) * scalar_short;
					dv++;
				}
				VECADDFAC(mv->co, mv->co, mv->no, scalar_short_vgroup);
			}
		}

		if(ofs_orig != 0.0f) {
			scalar_short= scalar_short_vgroup= ofs_orig / 32767.0f;
			mv= mvert + ((ofs_new >= ofs_orig) ? numVerts : 0); /* same as above but swapped, intentional use of 'ofs_new' */
			dv= dvert;
			for(i=0; i<numVerts; i++, mv++) {
				if(dv) {
					if(defgrp_invert)	scalar_short_vgroup = 1.0f - defvert_find_weight(dv, defgrp_index);
					else				scalar_short_vgroup = defvert_find_weight(dv, defgrp_index);
					scalar_short_vgroup= (offset_fac_vg + (scalar_short_vgroup * offset_fac_vg_inv)) * scalar_short;
					dv++;
				}
				VECADDFAC(mv->co, mv->co, mv->no, scalar_short_vgroup);
			}
		}

	}
	else {
		/* make a face normal layer if not present */
		float (*face_nors)[3];
		int face_nors_calc= 0;

		/* same as EM_solidify() in editmesh_lib.c */
		float *vert_angles= MEM_callocN(sizeof(float) * numVerts * 2, "mod_solid_pair"); /* 2 in 1 */
		float *vert_accum= vert_angles + numVerts;
		float face_angles[4];
		int j, vidx;

		face_nors = CustomData_get_layer(&dm->faceData, CD_NORMAL);
		if(!face_nors) {
			face_nors = CustomData_add_layer(&dm->faceData, CD_NORMAL, CD_CALLOC, NULL, dm->numFaceData);
			face_nors_calc= 1;
		}

		if(vert_nors==NULL) {
			vert_nors= MEM_mallocN(sizeof(float) * numVerts * 3, "mod_solid_vno");
			for(i=0, mv=mvert; i<numVerts; i++, mv++) {
				normal_short_to_float_v3(vert_nors[i], mv->no);
			}
		}

		for(i=0, mf=mface; i<numFaces; i++, mf++) {

			/* just added, calc the normal */
			if(face_nors_calc) {
				if(mf->v4)
					normal_quad_v3(face_nors[i], mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co, mvert[mf->v4].co);
				else
					normal_tri_v3(face_nors[i] , mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co);
			}

			if(mf->v4) {
				angle_quad_v3(face_angles, mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co, mvert[mf->v4].co);
				j= 3;
			}
			else {
				angle_tri_v3(face_angles, mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co);
				j= 2;
			}

			do {
				vidx = *(&mf->v1 + j);
				vert_accum[vidx] += face_angles[j];
				vert_angles[vidx]+= shell_angle_to_dist(angle_normalized_v3v3(vert_nors[vidx], face_nors[i])) * face_angles[j];
			} while(j--);
		}

		/* vertex group support */
		if(dvert) {
			float scalar;

			dv= dvert;
			if(defgrp_invert) {
				for(i=0; i<numVerts; i++, dv++) {
					scalar= 1.0f - defvert_find_weight(dv, defgrp_index);
					scalar= offset_fac_vg + (scalar * offset_fac_vg_inv);
					vert_angles[i] *= scalar;
				}
			}
			else {
				for(i=0; i<numVerts; i++, dv++) {
					scalar= defvert_find_weight(dv, defgrp_index);
					scalar= offset_fac_vg + (scalar * offset_fac_vg_inv);
					vert_angles[i] *= scalar;
				}
			}
		}

		if(ofs_new) {
			mv= mvert + ((ofs_new >= ofs_orig) ? 0 : numVerts);

			for(i=0; i<numVerts; i++, mv++) {
				if(vert_accum[i]) { /* zero if unselected */
					madd_v3_v3fl(mv->co, vert_nors[i], ofs_new * (vert_angles[i] / vert_accum[i]));
				}
			}
		}

		if(ofs_orig) {
			mv= mvert + ((ofs_new >= ofs_orig) ? numVerts : 0); /* same as above but swapped, intentional use of 'ofs_new' */

			for(i=0; i<numVerts; i++, mv++) {
				if(vert_accum[i]) { /* zero if unselected */
					madd_v3_v3fl(mv->co, vert_nors[i], ofs_orig * (vert_angles[i] / vert_accum[i]));
				}
			}
		}

		MEM_freeN(vert_angles);
	}

	if(vert_nors)
		MEM_freeN(vert_nors);

	/* flip vertex normals for copied verts */
	mv= mvert + numVerts;
	for(i=0; i<numVerts; i++, mv++) {
		mv->no[0]= -mv->no[0];
		mv->no[1]= -mv->no[1];
		mv->no[2]= -mv->no[2];
	}

	if(smd->flag & MOD_SOLIDIFY_RIM) {

		
		/* bugger, need to re-calculate the normals for the new edge faces.
		 * This could be done in many ways, but probably the quickest way is to calculate the average normals for side faces only.
		 * Then blend them with the normals of the edge verts.
		 * 
		 * at the moment its easiest to allocate an entire array for every vertex, even though we only need edge verts - campbell
		 */
		
#define SOLIDIFY_SIDE_NORMALS

#ifdef SOLIDIFY_SIDE_NORMALS
		/* annoying to allocate these since we only need the edge verts, */
		float (*edge_vert_nos)[3]= MEM_callocN(sizeof(float) * numVerts * 3, "solidify_edge_nos");
		float nor[3];
#endif
		const unsigned char crease_rim= smd->crease_rim * 255.0f;
		const unsigned char crease_outer= smd->crease_outer * 255.0f;
		const unsigned char crease_inner= smd->crease_inner * 255.0f;

		const int edge_indices[4][4] = {
				{1, 0, 0, 1},
				{2, 1, 1, 2},
				{3, 2, 2, 3},
				{0, 3, 3, 0}};

		/* add faces & edges */
		ed= medge + (numEdges * 2);
		for(i=0; i<newEdges; i++, ed++) {
			ed->v1= new_vert_arr[i];
			ed->v2= new_vert_arr[i] + numVerts;
			ed->flag |= ME_EDGEDRAW;

			if(crease_rim)
				ed->crease= crease_rim;
		}

		/* faces */
		mf= mface + (numFaces * 2);
		for(i=0; i<newFaces; i++, mf++) {
			int eidx= new_edge_arr[i];
			int fidx= edge_users[eidx];
			int flip;

			if(fidx >= numFaces) {
				fidx -= numFaces;
				flip= 1;
			}
			else {
				flip= 0;
			}

			ed= medge + eidx;

			/* copy most of the face settings */
			DM_copy_face_data(dm, result, fidx, (numFaces * 2) + i, 1);

			if(flip) {
				DM_swap_face_data(result, (numFaces * 2) + i, edge_indices[edge_order[eidx]]);

				mf->v1= ed->v1;
				mf->v2= ed->v2;
				mf->v3= ed->v2 + numVerts;
				mf->v4= ed->v1 + numVerts;
			}
			else {
				DM_swap_face_data(result, (numFaces * 2) + i, edge_indices[edge_order[eidx]]);

				mf->v1= ed->v2;
				mf->v2= ed->v1;
				mf->v3= ed->v1 + numVerts;
				mf->v4= ed->v2 + numVerts;
			}
			
			/* use the next material index if option enabled */
			if(mat_ofs_rim) {
				mf->mat_nr += mat_ofs_rim;
				CLAMP(mf->mat_nr, 0, mat_nr_max);
			}
			if(crease_outer) {
				/* crease += crease_outer; without wrapping */
				unsigned char *cr= (unsigned char *)&(ed->crease);
				int tcr= *cr + crease_outer;
				*cr= tcr > 255 ? 255 : tcr;
			}

			if(crease_inner) {
				/* crease += crease_inner; without wrapping */
				unsigned char *cr= (unsigned char *)&(medge[numEdges + eidx].crease);
				int tcr= *cr + crease_inner;
				*cr= tcr > 255 ? 255 : tcr;
			}
			
#ifdef SOLIDIFY_SIDE_NORMALS
			normal_quad_v3(nor, mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co, mvert[mf->v4].co);

			add_v3_v3(edge_vert_nos[ed->v1], nor);
			add_v3_v3(edge_vert_nos[ed->v2], nor);
#endif
		}
		
#ifdef SOLIDIFY_SIDE_NORMALS
		ed= medge + (numEdges * 2);
		for(i=0; i<newEdges; i++, ed++) {
			float nor_cpy[3];
			short *nor_short;
			int j;
			
			/* note, only the first vertex (lower half of the index) is calculated */
			normalize_v3_v3(nor_cpy, edge_vert_nos[ed->v1]);
			
			for(j=0; j<2; j++) { /* loop over both verts of the edge */
				nor_short= mvert[*(&ed->v1 + j)].no;
				normal_short_to_float_v3(nor, nor_short);
				add_v3_v3(nor, nor_cpy);
				normalize_v3(nor);
				normal_float_to_short_v3(nor_short, nor);
			}
		}

		MEM_freeN(edge_vert_nos);
#endif

		MEM_freeN(new_vert_arr);
		MEM_freeN(new_edge_arr);
		MEM_freeN(edge_users);
		MEM_freeN(edge_order);
	}

	/* must recalculate normals with vgroups since they can displace unevenly [#26888] */
	if(dvert) {
		CDDM_calc_normals(result);
	}

	return result;
}

#undef SOLIDIFY_SIDE_NORMALS

static DerivedMesh *applyModifierEM(ModifierData *md,
							 Object *ob,
							 struct EditMesh *UNUSED(editData),
							 DerivedMesh *derivedData)
{
	return applyModifier(md, ob, derivedData, 0, 1);
}


ModifierTypeInfo modifierType_Solidify = {
	/* name */              "Solidify",
	/* structName */        "SolidifyModifierData",
	/* structSize */        sizeof(SolidifyModifierData),
	/* type */              eModifierTypeType_Constructive,

	/* flags */             eModifierTypeFlag_AcceptsMesh
							| eModifierTypeFlag_AcceptsCVs
							| eModifierTypeFlag_SupportsMapping
							| eModifierTypeFlag_SupportsEditmode
							| eModifierTypeFlag_EnableInEditmode,

	/* copyData */          copyData,
	/* deformVerts */       NULL,
	/* deformMatrices */    NULL,
	/* deformVertsEM */     NULL,
	/* deformMatricesEM */  NULL,
	/* applyModifier */     applyModifier,
	/* applyModifierEM */   applyModifierEM,
	/* initData */          initData,
	/* requiredDataMask */  requiredDataMask,
	/* freeData */          NULL,
	/* isDisabled */        NULL,
	/* updateDepgraph */    NULL,
	/* dependsOnTime */     NULL,
	/* dependsOnNormals */	NULL,
	/* foreachObjectLink */ NULL,
	/* foreachIDLink */     NULL,
	/* foreachTexLink */    NULL,
};
