/****************************************************************************************/
/*  GLYPH3D.C                                                                           */
/*                                                                                      */
/*  Author: Jason Wood                                                                  */
/*  Description:                                                                        */
/*                                                                                      */
/*  The contents of this file are subject to the Jet3D Public License                   */
/*  Version 1.02 (the "License"); you may not use this file except in                   */
/*  compliance with the License. You may obtain a copy of the License at                */
/*  http://www.jet3d.com                                                                */
/*                                                                                      */
/*  Software distributed under the License is distributed on an "AS IS"                 */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See                */
/*  the License for the specific language governing rights and limitations              */
/*  under the License.                                                                  */
/*                                                                                      */
/*  The Original Code is Jet3D, released December 12, 1999.                             */
/*  Copyright (C) 1996-1999 Eclipse Entertainment, L.L.C. All Rights Reserved           */
/*                                                                                      */
/****************************************************************************************/

#define	WIN32_LEAN_AND_MEAN
#pragma warning(disable : 4201 4214 4115)
#include <windows.h>
#include <windowsx.h>
#pragma warning(default : 4201 4214 4115)

#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "basetype.h"
#include "vec3d.h"
#include "ram.h"
#include "log.h"

#include "actor.h"
#include "body.h"

#include "vec2.h"
#include "glyph3d.h"

///////////////////////////////////////////////////////////////////////////////////////

#define JE_GLYPH3D_DEFAULT_POINT_SIZE							72
#define JE_GLYPH3D_ONE_OVER_65536									0.0000152587890625f
#define JE_GLYPH3D_MAX_NEDGES_PER_SPAN						32
#define JE_GLYPH3D_MAX_NCONTOURS									10
#define JE_GLYPH3D_MAX_NPOINTS_PER_QSPLINE				10
#define JE_GLYPH3D_MAX_NBEVELS										8
#define JE_GLYPH3D_DEFAULT_EXTRUDE_DEPTH					20.0f
#define JE_GLYPH3D_SCALE_FACTOR										256.0f
#define JE_GLYPH3D_ONE_OVER_SCALE_FACTOR					0.00390625f
#define JE_GLYPH3D_NAME_MAX_NCHARS								256
#define JE_GLYPH3D_VERT_TOL												JE_EPSILON
#define JE_GLYPH3D_MAX_NTESSGROUPS								(JE_GLYPH3D_MAX_NBEVELS + 2)

///////////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	int v0, v1;

}jeGlyph3d_Edge;

typedef struct
{
	jeGlyph3d_Vec2i* aPoints;
	int nPoints;

	jeGlyph3d_Edge* aEdges;
	int nEdges;

}jeGlyph3d_Contour;


typedef struct jeGlyph3d_TriFace
{
	int verts[3];
	jeVec3d vertNormals[3];
	jeVec3d n; // face normal

}jeGlyph3d_TriFace;


typedef struct jeGlyph3d_BevelInfo
{
	float outline; // outline scale, default = 1.0
	float depth; // depth of extrusion

}jeGlyph3d_BevelInfo;

typedef struct jeGlyph3d_Beveler
{
	jeGlyph3d_BevelInfo aBevels[JE_GLYPH3D_MAX_NBEVELS];
	int nBevels;

}jeGlyph3d_Beveler;

typedef struct
{
	jeVec3d* aTessVerts;
	jeGlyph3d_TriFace* aTessFaces;
	int nTessVerts;
	int nTessFaces;

}jeGlyph3d_TessGroup;


typedef struct jeGlyph3d
{
	UINT val; // ascii or unicode value of glyph

	jeGlyph3d_Contour aContours[JE_GLYPH3D_MAX_NCONTOURS]; // outline contours of glyph
		// when first parsed in during the _Create() function
	jeGlyph3d_Contour aBeveledContours[JE_GLYPH3D_MAX_NCONTOURS][2]; // beveled versions
		// of outline contours
	int nContours;

	jeGlyph3d_MetricsInfo metricsInfo;

	jeGlyph3d_Beveler* pBeveler;

	jeGlyph3d_TessGroup aTessGroups[JE_GLYPH3D_MAX_NTESSGROUPS]; // a tess group is
		// a container object for tessellated verts and faces
	int nTessGroups;

	int ymin, ymax; // used during interior geometry scan-conversion process

	float zStart; // start of current bevel (end is start + currentbevel.depth)

	jeBoolean tess;

}jeGlyph3d;




typedef struct
{
	int ymin, ymax, startx;
	float x, oneOverM;

}jeGlyph3d_ETElem;



///////////////////////////////////////////////////////////////////////////////////////
// static fn declarations

static TTPOLYGONHEADER* GetTTPolygonHeader(BYTE** pBuffer);
static void* GetChunk(BYTE** pBuffer, ULONG size);
static void jeGlyph3d_AddPointFIXED(jeGlyph3d* pGlyph, int contourIndex, POINTFX* pPoint);
static void jeGlyph3d_AddPointFloat(jeGlyph3d* pGlyph, int contourIndex, float x, float y);
static void jeGlyph3d_ComputeContourEdgePoints(jeGlyph3d* pGlyph, int contourIndex);
static void jeGlyph3d_ComputeYExtents(jeGlyph3d* pGlyph);
static void jeGlyph3d_Contour_FreeData(jeGlyph3d_Contour* pContour);
static void jeGlyph3d_Contour_Prepare(jeGlyph3d_Contour* pContour);
static void jeGlyph3d_Contour_RemovePoint(jeGlyph3d_Contour* pContour, int pointIndex);
static void jeGlyph3d_Contour_RemoveRedundantPoints(jeGlyph3d_Contour* pContour);
static void jeGlyph3d_BevelGlyphOutline(jeGlyph3d* pGlyph, int bevelIndex);
static void jeGlyph3d_SortEdges(jeGlyph3d* pGlyph, jeBoolean topFace);

///////////////////////////////////////////////////////////////////////////////////////
// misc fns

__inline float FIXEDToFloat(FIXED* a) 
{
	return ((float)a->value + a->fract * JE_GLYPH3D_ONE_OVER_65536);
}

__inline FIXED FloatToFIXED(double a) 
{
	long l;

	l = (long)(a * 65536L);
	return *(FIXED*)&l;
}

void Identity2(MAT2* pMat)
{
	pMat->eM11.value = 1; pMat->eM11.fract = 0;
	pMat->eM12.value = 0; pMat->eM12.fract = 0;
	pMat->eM21.value = 0; pMat->eM21.fract = 0;
	pMat->eM22.value = 1; pMat->eM22.fract = 0;
}

///////////////////////////////////////////////////////////////////////////////////////

static TTPOLYGONHEADER* GetTTPolygonHeader(BYTE** pBuffer)
{
	TTPOLYGONHEADER* pHeader;

	pHeader = (TTPOLYGONHEADER*)jeRam_Allocate(sizeof(TTPOLYGONHEADER));
	assert(pHeader);

	memcpy(pHeader, (*pBuffer), sizeof(TTPOLYGONHEADER));

	(*pBuffer) += sizeof(TTPOLYGONHEADER);

	return pHeader;
}

static void* GetChunk(BYTE** pBuffer, ULONG size)
{
	BYTE* pChunk;

	pChunk = (BYTE*)jeRam_Allocate(size);
	assert(pChunk);

	memcpy(pChunk, (*pBuffer), size);

	(*pBuffer) += size;

	return (void*)pChunk;
}

static void jeGlyph3d_AddPointFIXED(jeGlyph3d* pGlyph, int contourIndex, POINTFX* pPoint)
{
	jeGlyph3d_Vec2i* aPoints;
	int nPoints;

	assert(pGlyph);
	assert(pPoint);

	pGlyph->aContours[contourIndex].aPoints = (jeGlyph3d_Vec2i*)jeRam_Realloc(
		pGlyph->aContours[contourIndex].aPoints, 
		(pGlyph->aContours[contourIndex].nPoints + 1) * sizeof(jeGlyph3d_Vec2i));

	assert(pGlyph->aContours[contourIndex].aPoints);

	aPoints = pGlyph->aContours[contourIndex].aPoints;
	nPoints = pGlyph->aContours[contourIndex].nPoints;

	aPoints[nPoints].X = (int)(FIXEDToFloat(&pPoint->x) * JE_GLYPH3D_SCALE_FACTOR);
	aPoints[nPoints].Y = (int)(FIXEDToFloat(&pPoint->y) * JE_GLYPH3D_SCALE_FACTOR);

	pGlyph->aContours[contourIndex].nPoints ++;
}

static void jeGlyph3d_AddPointFloat(jeGlyph3d* pGlyph, int contourIndex, float x, float y)
{
	jeGlyph3d_Vec2i* aPoints;
	int nPoints;

	assert(pGlyph);

	pGlyph->aContours[contourIndex].aPoints = (jeGlyph3d_Vec2i*)jeRam_Realloc(
		pGlyph->aContours[contourIndex].aPoints, 
		(pGlyph->aContours[contourIndex].nPoints + 1) * sizeof(jeGlyph3d_Vec2i));

	assert(pGlyph->aContours[contourIndex].aPoints);

	aPoints = pGlyph->aContours[contourIndex].aPoints;
	nPoints = pGlyph->aContours[contourIndex].nPoints;

	aPoints[nPoints].X = (int)(x * JE_GLYPH3D_SCALE_FACTOR);
	aPoints[nPoints].Y = (int)(y * JE_GLYPH3D_SCALE_FACTOR);

	pGlyph->aContours[contourIndex].nPoints ++;
}

static void jeGlyph3d_ComputeContourEdgePoints(jeGlyph3d* pGlyph, int contourIndex)
{
	int i;
	jeGlyph3d_Contour* pContour;

	assert(pGlyph);
	assert(pGlyph->aContours[contourIndex].aPoints);

	pContour = &pGlyph->aContours[contourIndex];

	pContour->nEdges = 0;
	pContour->aEdges = NULL;

	for (i = 0; i < pContour->nPoints; i ++)
	{
		int ii = (i + 1) % pContour->nPoints;

		pContour->aEdges = (jeGlyph3d_Edge*)jeRam_Realloc(pContour->aEdges,
			(pContour->nEdges + 1) * sizeof(jeGlyph3d_Edge));

		assert(pContour->aEdges);

		pContour->aEdges[pContour->nEdges].v0 = i;
		pContour->aEdges[pContour->nEdges].v1 = ii;

		pContour->nEdges ++;
	}
}


// compute ymin and ymax prior to scan conversion
static void jeGlyph3d_ComputeYExtents(jeGlyph3d* pGlyph)
{
	int i, j;

	assert(pGlyph);

	if (pGlyph->nContours == 0)
	{
		pGlyph->ymin = pGlyph->ymax = 0;
		return;
	}

	assert(pGlyph->aContours[0].nPoints > 0);
	assert(pGlyph->aContours[0].aPoints != NULL);

	pGlyph->ymin = pGlyph->ymax = pGlyph->aContours[0].aPoints[0].Y;

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		jeGlyph3d_Contour* pContour = &pGlyph->aContours[i];

		for (j = 0; j < pContour->nPoints; j ++)
		{
			pGlyph->ymin = min(pContour->aPoints[j].Y, pGlyph->ymin);
			pGlyph->ymax = max(pContour->aPoints[j].Y, pGlyph->ymax);
		}
	}
}

static void jeGlyph3d_Contour_FreeData(jeGlyph3d_Contour* pContour)
{
	assert(pContour);

	if (pContour->aEdges) jeRam_Free(pContour->aEdges);
	if (pContour->aPoints) jeRam_Free(pContour->aPoints);

	pContour->aEdges = NULL;
	pContour->aPoints = NULL;

	pContour->nEdges = 0;
	pContour->nPoints = 0;
}

static void jeGlyph3d_Contour_Prepare(jeGlyph3d_Contour* pContour)
{
	assert(pContour);

	pContour->aPoints = NULL;
	pContour->nPoints = 0;
	pContour->aEdges = NULL;
	pContour->nEdges = 0;
}

static void jeGlyph3d_Contour_RemovePoint(jeGlyph3d_Contour* pContour, int pointIndex)
{
	int i;

	for (i = pointIndex + 1; i < pContour->nPoints; i ++)
	{
		pContour->aPoints[i - 1] = pContour->aPoints[i];
	}

	pContour->nPoints --;
}

static void jeGlyph3d_Contour_RemoveRedundantPoints(jeGlyph3d_Contour* pContour)
{
	int i, j, k;
	jeGlyph3d_Vec2f diff;
	float len;

	for (i = 0; i < pContour->nPoints; i ++)
	{
		for (j = i + 1; j < pContour->nPoints; j ++)
		{
			diff.X = (float)(pContour->aPoints[j].X - pContour->aPoints[i].X);
			diff.Y = (float)(pContour->aPoints[j].Y - pContour->aPoints[i].Y);

			len = jeGlyph3d_Vec2f_Length(&diff);

			if (len < 0.01f)
			{
				for (k = 0; k < pContour->nEdges; k ++)
				{
					if (pContour->aEdges[k].v0 == j)
						pContour->aEdges[k].v0 = i;
					if (pContour->aEdges[k].v1 == j)
						pContour->aEdges[k].v1 = i;
				}

				jeGlyph3d_Contour_RemovePoint(pContour, j);

				for (k = 0; k < pContour->nEdges; k ++)
				{
					if (pContour->aEdges[k].v0 >= j)
						pContour->aEdges[k].v0 --;

					if (pContour->aEdges[k].v1 >= j)
						pContour->aEdges[k].v1 --;
				}

				i --;
				break;
			}
		}
	}
}

static void jeGlyph3d_Contour_RemoveEdge(jeGlyph3d_Contour* pContour, int edgeIndex)
{
	int i;

	for (i = edgeIndex + 1; i < pContour->nEdges; i ++)
	{
		pContour->aEdges[i - 1].v0 = pContour->aEdges[i].v0;
		pContour->aEdges[i - 1].v1 = pContour->aEdges[i].v1;
	}

	pContour->nEdges --;
}

// sadly, we have to do this
static void jeGlyph3d_Contour_RemoveColinearEdges(jeGlyph3d_Contour* pContour)
{
	jeGlyph3d_Vec2i* aPoints = pContour->aPoints;
	jeGlyph3d_Edge* aEdges = pContour->aEdges;
	jeGlyph3d_Vec2f d0, d1;
	int i, j;
	float dot;

	for (i = 0; i < pContour->nEdges - 1; i ++)
	{
		int in = i + 1;

		assert(aEdges[i].v1 == aEdges[in].v0);

		d0.X = (float)(aPoints[aEdges[i].v1].X - aPoints[aEdges[i].v0].X);
		d0.Y = (float)(aPoints[aEdges[i].v1].Y - aPoints[aEdges[i].v0].Y);

		d1.X = (float)(aPoints[aEdges[in].v1].X - aPoints[aEdges[in].v0].X);
		d1.Y = (float)(aPoints[aEdges[in].v1].Y - aPoints[aEdges[in].v0].Y);

		jeGlyph3d_Vec2f_Normalize(&d0);
		jeGlyph3d_Vec2f_Normalize(&d1);

		dot = jeGlyph3d_Vec2f_DotProduct(&d0, &d1);

		if (dot > 0.9962f) // edges are within ~5 deg of each other
		{
			jeGlyph3d_Contour_RemovePoint(pContour, aEdges[i].v1);
			jeGlyph3d_Contour_RemoveEdge(pContour, in);

			for (j = in; j < pContour->nEdges; j ++)
			{
				aEdges[j].v0 --;

				if (aEdges[j].v1 != 0)
					aEdges[j].v1 --;

				assert(aEdges[j].v0 >= 0);
				assert(aEdges[j].v1 >= 0);
			}

			i --;
		}
	}
}

// expand a contour's outline
static void jeGlyph3d_BevelGlyphOutline(jeGlyph3d* pGlyph, int bevelIndex)
{
	int i, j, jp, jn;
	jeGlyph3d_Contour* pOContour;
	jeGlyph3d_Contour* pBeveledContour0, *pBeveledContour1;
	jeGlyph3d_Vec2i* aPoints;
	jeGlyph3d_Vec2f v, n;
	float bevelAmt0, bevelAmt1;

	assert(pGlyph->pBeveler != NULL);

	if (bevelIndex == 0)
	{
		bevelAmt0 = 0.f;
	}
	else
	{
		bevelAmt0 = pGlyph->pBeveler->aBevels[bevelIndex - 1].outline * 
			JE_GLYPH3D_SCALE_FACTOR;
	}

	bevelAmt1 = pGlyph->pBeveler->aBevels[bevelIndex].outline * JE_GLYPH3D_SCALE_FACTOR;

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		pOContour = &pGlyph->aContours[i];

		jeGlyph3d_Contour_Prepare(&pGlyph->aBeveledContours[i][0]);
		jeGlyph3d_Contour_Prepare(&pGlyph->aBeveledContours[i][1]);

		pBeveledContour0 = &pGlyph->aBeveledContours[i][0];
		pBeveledContour1 = &pGlyph->aBeveledContours[i][1];

		pBeveledContour1->nEdges = pBeveledContour0->nEdges = pOContour->nEdges;
		pBeveledContour1->nPoints = pBeveledContour0->nPoints = pOContour->nPoints;

		pBeveledContour0->aEdges = (jeGlyph3d_Edge*)jeRam_Allocate(
			pBeveledContour0->nEdges * sizeof(jeGlyph3d_Edge));
		pBeveledContour0->aPoints = (jeGlyph3d_Vec2i*)jeRam_Allocate(
			pBeveledContour0->nPoints * sizeof(jeGlyph3d_Vec2i));

		assert(pBeveledContour0->aEdges);
		assert(pBeveledContour0->aPoints);

		pBeveledContour1->aEdges = (jeGlyph3d_Edge*)jeRam_Allocate(
			pBeveledContour1->nEdges * sizeof(jeGlyph3d_Edge));
		pBeveledContour1->aPoints = (jeGlyph3d_Vec2i*)jeRam_Allocate(
			pBeveledContour1->nPoints * sizeof(jeGlyph3d_Vec2i));

		assert(pBeveledContour1->aEdges);
		assert(pBeveledContour1->aPoints);

		aPoints = pOContour->aPoints;

		for (j = 0; j < pOContour->nPoints; j ++)
		{
			float l0, l1;
			jeGlyph3d_Vec2f pj;
			jeGlyph3d_Vec2f d0, d1;
			jeGlyph3d_Vec2f p0, p1;

			jp = ((j - 1) + pOContour->nPoints) % pOContour->nPoints;
			jn = (j + 1) % pOContour->nPoints;

			pj.X = (float)aPoints[j].X;
			pj.Y = (float)aPoints[j].Y;

			d0.X = (float)(aPoints[j].X - aPoints[jp].X);
			d0.Y = (float)(aPoints[j].Y - aPoints[jp].Y);

			d1.X = (float)(aPoints[jn].X - aPoints[j].X);
			d1.Y = (float)(aPoints[jn].Y - aPoints[j].Y);

			l0 = jeGlyph3d_Vec2f_Normalize(&d0);
			l1 = jeGlyph3d_Vec2f_Normalize(&d1);

			assert(l0 > JE_EPSILON);
			assert(l1 > JE_EPSILON);

			p0.X = pj.X - d0.X;
			p0.Y = pj.Y - d0.Y;

			p1.X = pj.X + d1.X;
			p1.Y = pj.Y + d1.Y;

			v.X = 0.5f * (p0.X + p1.X);
			v.Y = 0.5f * (p0.Y + p1.Y);

			jeGlyph3d_Vec2f_Subtract(&v, &pj, &n);

			// see if v lies inside or outside glyph, adjust n accordingly
			{
				jeGlyph3d_Vec2f n0, n1;
				float dot0, dot1;

				n0.X = -d0.Y;
				n0.Y = d0.X;

				n1.X = -d1.Y;
				n1.Y = d1.X;

				dot0 = jeGlyph3d_Vec2f_DotProduct(&n, &n0);
				dot1 = jeGlyph3d_Vec2f_DotProduct(&n, &n1);

				if (dot0 < 0.f && dot1 < 0.f)
				{
					n.X = -n.X;
					n.Y = -n.Y;
				}
				else if (dot0 >= 0.f && dot1 >= 0.f)
				{
					
				}
				else
				{
					assert(0);
				}
			}

			jeGlyph3d_Vec2f_Normalize(&n);

			pBeveledContour0->aPoints[j].X = (int)((float)aPoints[j].X + bevelAmt0 * n.X);
			pBeveledContour0->aPoints[j].Y = (int)((float)aPoints[j].Y + bevelAmt0 * n.Y);

			pBeveledContour1->aPoints[j].X = (int)((float)aPoints[j].X + bevelAmt1 * n.X);
			pBeveledContour1->aPoints[j].Y = (int)((float)aPoints[j].Y + bevelAmt1 * n.Y);
		}

		for (j = 0; j < pOContour->nEdges; j ++)
		{
			memcpy(&pBeveledContour0->aEdges[j], &pOContour->aEdges[j], sizeof(jeGlyph3d_Edge));
			memcpy(&pBeveledContour1->aEdges[j], &pOContour->aEdges[j], sizeof(jeGlyph3d_Edge));
		}
	}
}

static void jeGlyph3d_TessGroup_Prepare(jeGlyph3d_TessGroup* pGroup)
{
	pGroup->aTessFaces = NULL;
	pGroup->aTessVerts = NULL;
	pGroup->nTessFaces = 0;
	pGroup->nTessVerts = 0;
}

static void jeGlyph3d_TessGroup_FreeData(jeGlyph3d_TessGroup* pGroup)
{
	if (pGroup->aTessFaces != NULL)
		jeRam_Free(pGroup->aTessFaces);
	if (pGroup->aTessVerts != NULL)
		jeRam_Free(pGroup->aTessVerts);
}

static jeBoolean alloc2faces(jeGlyph3d* pGlyph, int group)
{
	jeGlyph3d_TessGroup* pTessGroup = &pGlyph->aTessGroups[group];

	pTessGroup->aTessVerts = (jeVec3d*)jeRam_Realloc(pTessGroup->aTessVerts,
		(pTessGroup->nTessVerts + 4) * sizeof(jeVec3d));
	pTessGroup->aTessFaces = (jeGlyph3d_TriFace*)jeRam_Realloc(pTessGroup->aTessFaces,
		(pTessGroup->nTessFaces + 2) * sizeof(jeGlyph3d_TriFace));

	if (pTessGroup->aTessFaces == NULL) return JE_FALSE;
	if (pTessGroup->aTessVerts == NULL) return JE_FALSE;

	return JE_TRUE;
}

#pragma warning(disable:4244)

static void jeGlyph3d_TessellateBeveledOutline(jeGlyph3d* pGlyph, 
	float zStart, float zEnd, int tessGroup)
{
	int i, j;
	jeGlyph3d_Contour* pOContour;
	jeGlyph3d_TessGroup* pTessGroup;
	jeGlyph3d_Contour* pBeveledContour0, *pBeveledContour1;
	jeVec3d* pVert, *aVerts;
	jeGlyph3d_TriFace* pFace;
	jeGlyph3d_Vec2i* aOPoints, *aPoints0, *aPoints1;
	jeGlyph3d_Vec2f diff;
	jeGlyph3d_Edge* pEdge;
	jeVec3d v0v1, v0v2;
	float len;

	///////////////////////////////////////////////////////////////////////////////////////

	assert(pGlyph);

	pTessGroup = &pGlyph->aTessGroups[tessGroup];

	jeGlyph3d_TessGroup_Prepare(pTessGroup);

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		pOContour = &pGlyph->aContours[i];

		pBeveledContour0 = &pGlyph->aBeveledContours[i][0];
		pBeveledContour1 = &pGlyph->aBeveledContours[i][1];

		aOPoints = pOContour->aPoints;
		aPoints0 = pBeveledContour0->aPoints;
		aPoints1 = pBeveledContour1->aPoints;

		for (j = 0; j < pOContour->nEdges; j ++)
		{
			pEdge = &pOContour->aEdges[j];

			diff.X = (float)(aOPoints[pEdge->v1].X - aOPoints[pEdge->v0].X);
			diff.Y = (float)(aOPoints[pEdge->v1].Y - aOPoints[pEdge->v0].Y);
			len = jeGlyph3d_Vec2f_Length(&diff);
			if (len < 0.001f)
				continue;

			alloc2faces(pGlyph, tessGroup);

			aVerts = pTessGroup->aTessVerts;
			pVert = &pTessGroup->aTessVerts[pTessGroup->nTessVerts];
			pFace = &pTessGroup->aTessFaces[pTessGroup->nTessFaces];

			pVert->X = aPoints0[pEdge->v0].X * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Y = aPoints0[pEdge->v0].Y * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Z = zStart;
			pVert ++;

			pVert->X = aPoints0[pEdge->v1].X * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Y = aPoints0[pEdge->v1].Y * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Z = zStart;
			pVert ++;

			pVert->X = aPoints1[pEdge->v1].X * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Y = aPoints1[pEdge->v1].Y * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Z = zEnd;
			pVert ++;

			pVert->X = aPoints1[pEdge->v0].X * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Y = aPoints1[pEdge->v0].Y * JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
			pVert->Z = zEnd;

			//

			pFace->verts[0] = pTessGroup->nTessVerts;
			pFace->verts[1] = pTessGroup->nTessVerts + 1;
			pFace->verts[2] = pTessGroup->nTessVerts + 2;
			// compute face normal
			jeVec3d_Subtract(&aVerts[pFace->verts[1]], &aVerts[pFace->verts[0]], &v0v1);
			jeVec3d_Subtract(&aVerts[pFace->verts[2]], &aVerts[pFace->verts[0]], &v0v2);
			jeVec3d_CrossProduct(&v0v1, &v0v2, &pFace->n);
			jeVec3d_Normalize(&pFace->n);
			pFace ++;

			pFace->verts[0] = pTessGroup->nTessVerts;
			pFace->verts[1] = pTessGroup->nTessVerts + 2;
			pFace->verts[2] = pTessGroup->nTessVerts + 3;
			// compute face normal
			jeVec3d_Subtract(&aVerts[pFace->verts[1]], &aVerts[pFace->verts[0]], &v0v1);
			jeVec3d_Subtract(&aVerts[pFace->verts[2]], &aVerts[pFace->verts[0]], &v0v2);
			jeVec3d_CrossProduct(&v0v1, &v0v2, &pFace->n);
			jeVec3d_Normalize(&pFace->n);
			pFace ++;

			pTessGroup->nTessVerts += 4;
			pTessGroup->nTessFaces += 2;
		}
	}
}

// sort glyph edges by minimum y-coordinate.
// prepares glyph for edge scan used in tessellation
static void jeGlyph3d_SortEdges(jeGlyph3d* pGlyph, jeBoolean topFace)
{
	jeGlyph3d_Vec2i* aPoints;
	jeGlyph3d_Edge* aEdges;
	jeGlyph3d_Edge swap;
	int i, j;
	jeBoolean modified;

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		if (pGlyph->pBeveler == NULL || pGlyph->pBeveler->nBevels < 1)
		{
			aPoints = pGlyph->aContours[i].aPoints;
			aEdges = pGlyph->aContours[i].aEdges;			
		}
		else
		{
			if (topFace)
			{
				aPoints = pGlyph->aContours[i].aPoints;
				aEdges = pGlyph->aContours[i].aEdges;
			}
			else
			{
				aPoints = pGlyph->aBeveledContours[i][1].aPoints;
				aEdges = pGlyph->aBeveledContours[i][1].aEdges;
			}
		}

		for (j = 0; j < pGlyph->aContours[i].nEdges; j ++)
		{
			if (aPoints[aEdges[j].v0].Y > aPoints[aEdges[j].v1].Y)
			{				
				int tmp = aEdges[j].v0;
				aEdges[j].v0 = aEdges[j].v1;
				aEdges[j].v1 = tmp;				
			}
		}
	}

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		if (pGlyph->pBeveler == NULL || pGlyph->pBeveler->nBevels < 1)
		{
			aPoints = pGlyph->aContours[i].aPoints;
			aEdges = pGlyph->aContours[i].aEdges;			
		}
		else
		{
			if (topFace)
			{
				aPoints = pGlyph->aContours[i].aPoints;
				aEdges = pGlyph->aContours[i].aEdges;
			}
			else
			{
				aPoints = pGlyph->aBeveledContours[i][1].aPoints;
				aEdges = pGlyph->aBeveledContours[i][1].aEdges;
			}
		}

		modified = JE_TRUE;

		while (modified)
		{
			modified = JE_FALSE;

			for (j = 0; j < pGlyph->aContours[i].nEdges - 1; j ++)
			{
				if (aPoints[aEdges[j].v0].Y > aPoints[aEdges[j + 1].v0].Y)
				{
					memcpy(&swap, &aEdges[j], sizeof(jeGlyph3d_Edge));
					memcpy(&aEdges[j], &aEdges[j + 1], sizeof(jeGlyph3d_Edge));
					memcpy(&aEdges[j + 1], &swap, sizeof(jeGlyph3d_Edge));

					modified = JE_TRUE;
				}
			}
		}
	}
}

static void jeGlyph3d_MakeInteriorGeometry(jeGlyph3d* pGlyph, 
	jeGlyph3d_ETElem aElems[], int edgeIndex, int y, 
	float z, jeBoolean glyphTop, int tessGroup)
{
	jeVec3d* pVert;
	jeGlyph3d_TessGroup* pTessGroup;
	jeGlyph3d_TriFace* pFace;
	float nz;
	int i;

	assert(pGlyph);

	pTessGroup = &pGlyph->aTessGroups[tessGroup];

	alloc2faces(pGlyph, tessGroup);

	pVert = &pTessGroup->aTessVerts[pTessGroup->nTessVerts];
	pFace = &pTessGroup->aTessFaces[pTessGroup->nTessFaces];

	if (glyphTop)
	{
		pVert->X = aElems[edgeIndex + 1].startx;
		pVert->Y = aElems[edgeIndex + 1].ymin;
		pVert ++;

		pVert->X = aElems[edgeIndex + 1].x;
		pVert->Y = y + 1;
		pVert ++;

		pVert->X = aElems[edgeIndex].x;
		pVert->Y = y + 1;
		pVert ++;

		pVert->X = aElems[edgeIndex].startx;
		pVert->Y = aElems[edgeIndex].ymin;

		nz = 1.0f;
	}
	else
	{
		pVert->X = aElems[edgeIndex].startx;
		pVert->Y = aElems[edgeIndex].ymin;
		pVert ++;

		pVert->X = aElems[edgeIndex].x;
		pVert->Y = y + 1;
		pVert ++;

		pVert->X = aElems[edgeIndex + 1].x;
		pVert->Y = y + 1;
		pVert ++;

		pVert->X = aElems[edgeIndex + 1].startx;
		pVert->Y = aElems[edgeIndex + 1].ymin;

		nz = -1.0f;
	}

	pFace->verts[0] = pTessGroup->nTessVerts;
	pFace->verts[1] = pTessGroup->nTessVerts + 1;
	pFace->verts[2] = pTessGroup->nTessVerts + 2;
	jeVec3d_Set(&pFace->n, 0.f, 0.f, nz);
	pFace ++;

	pFace->verts[0] = pTessGroup->nTessVerts;
	pFace->verts[1] = pTessGroup->nTessVerts + 2;
	pFace->verts[2] = pTessGroup->nTessVerts + 3;
	jeVec3d_Set(&pFace->n, 0.f, 0.f, nz);

	for (i = 0; i < 4; i ++)
	{
		pTessGroup->aTessVerts[pTessGroup->nTessVerts + i].X *= JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
		pTessGroup->aTessVerts[pTessGroup->nTessVerts + i].Y *= JE_GLYPH3D_ONE_OVER_SCALE_FACTOR;
		pTessGroup->aTessVerts[pTessGroup->nTessVerts + i].Z = z;
	}

	pTessGroup->nTessVerts += 4;
	pTessGroup->nTessFaces += 2;
}

#pragma warning(default:4244)

// TODO: would using a linked list for the AET give speed increase?
// TODO: use a more efficient way of generating verts and tris

// scan-convert glyph edges [see Foley and Van Dam, ch.3]
// but modified so that
// when an edge crossing is found, and #of current edges > 0,
// we tessellate pairs of edges into trapezoids and then into tris
static void jeGlyph3d_ScanConvertAndAddInteriorTris(jeGlyph3d* pGlyph, float z, 
	jeBoolean glyphTop, int tessGroup)
{
	int y, i, j, k, dx, dy;
	jeGlyph3d_ETElem aTableElems[JE_GLYPH3D_MAX_NEDGES_PER_SPAN], 
		aOldTableElems[JE_GLYPH3D_MAX_NEDGES_PER_SPAN];
	int nElems, nOldElems;
	jeGlyph3d_Vec2i* aPoints;
	jeGlyph3d_Edge* aEdges;
	jeGlyph3d_TessGroup* pTessGroup;
	jeBoolean found, edgeCrossing;
	jeGlyph3d_Vec2i p0, p1;

	nElems = nOldElems = 0;

	pTessGroup = &pGlyph->aTessGroups[tessGroup];

	jeGlyph3d_TessGroup_Prepare(pTessGroup);

	for (y = pGlyph->ymin; y <= pGlyph->ymax; y ++)
	{
		edgeCrossing = JE_FALSE;

		for (i = 0; i < pGlyph->nContours; i ++)
		{
			if (pGlyph->pBeveler == NULL || pGlyph->pBeveler->nBevels < 1)
			{
				aPoints = pGlyph->aContours[i].aPoints;
				aEdges = pGlyph->aContours[i].aEdges;
			}
			else
			{
				if (glyphTop)
				{
					aPoints = pGlyph->aContours[i].aPoints;
					aEdges = pGlyph->aContours[i].aEdges;
				}
				else
				{
					aPoints = pGlyph->aBeveledContours[i][1].aPoints;
					aEdges = pGlyph->aBeveledContours[i][1].aEdges;
				}
			}

			// add entering edges to AET
			for (j = 0; j < pGlyph->aContours[i].nEdges; j ++)
			{
				p0 = aPoints[aEdges[j].v0];
				p1 = aPoints[aEdges[j].v1];

				if (p0.Y == y)
				{
					dy = p1.Y - p0.Y;					
					if (dy == 0) continue; // horiz edge

					assert(dy > 0);

					aTableElems[nElems].x = (float)p0.X;
					aTableElems[nElems].startx = p0.X;
					aTableElems[nElems].ymin = p0.Y;
					aTableElems[nElems].ymax = p1.Y;

					dx = p1.X - p0.X;
					
					aTableElems[nElems ++].oneOverM = dx / (float)dy;

					assert(nElems < JE_GLYPH3D_MAX_NEDGES_PER_SPAN);

					edgeCrossing = JE_TRUE;
				}
			} // j
		}

		if (nElems == 0) continue;

		// sort edges by increasing values of x
		found = JE_TRUE;
		while (found)
		{
			found = JE_FALSE;
			for (j = 0; j < nElems - 1; j ++)
			{
				if (aTableElems[j].x > aTableElems[j + 1].x)
				{	
					jeGlyph3d_ETElem tmp;

					memcpy(&tmp, &aTableElems[j], sizeof(jeGlyph3d_ETElem));
					memcpy(&aTableElems[j], &aTableElems[j + 1], sizeof(jeGlyph3d_ETElem));
					memcpy(&aTableElems[j + 1], &tmp, sizeof(jeGlyph3d_ETElem));

					found = JE_TRUE;
				}
			}
		} // modified

		// remove edges from AET whose ymax == y
		for (j = 0; j < nElems; j ++)
		{
			if (aTableElems[j].ymax == y)
			{
				edgeCrossing = JE_TRUE;

				for (k = j + 1; k < nElems; k ++)
				{
					memcpy(&aTableElems[k - 1], &aTableElems[k], sizeof(jeGlyph3d_ETElem));
				}

				nElems --;
				j --;
			}
		}

		// tessellate edge pairs
		if (edgeCrossing && nOldElems > 0)
		{
			assert((nOldElems & 1) == 0); // make sure we have pairs

			for (j = 0; j < nOldElems; j += 2)
			{
				jeGlyph3d_MakeInteriorGeometry(pGlyph, aOldTableElems, j, y, 
					z, glyphTop, tessGroup);
			}

			for (j = 0; j < nElems; j ++)
			{
				aTableElems[j].startx = (int)aTableElems[j].x;
				aTableElems[j].ymin = y;
			}
		}

		// update current span info
		for (j = 0; j < nElems; j ++)
		{
			memcpy(&aOldTableElems[j], &aTableElems[j], sizeof(jeGlyph3d_ETElem));
		}
		nOldElems = nElems;

		for (j = 0; j < nElems; j ++)
		{
			aTableElems[j].x += aTableElems[j].oneOverM;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////
// ctor / dtor


///////////////////////////////////////////////////////////////////////////////////////
// jeGlyph3d_Create(char* pFontName, char ascVal, unsigned int nPointsPerCurve)
//-------------------------------------------------------------------------------------
// params								meaning
//-------------------------------------------------------------------------------------
// pFontName						valid windows font name
// ascVal								ascii value of character
// nPointsPerCurve			#of points per spline curve
// pBeveler							ptr to a jeGlyph3d_Beveler object (may be NULL)

JETAPI jeGlyph3d* jeGlyph3d_Create(const char* pFontName, UINT glyphVal, 
	unsigned int nPointsPerCurve, const jeGlyph3d_Beveler* pBeveler)
{
	jeGlyph3d* pGlyph;

	MAT2 mat;
	HDC hdc;
	HFONT font, oldFont;
	DWORD buffSize;
	GLYPHMETRICS gm;
	BYTE* pBuffer, *pBuffPtr, *pDataEndByte;
	TTPOLYGONHEADER* pHeader;
	ULONG offset = 0;
	int i;
	float tstep;

	///////////////////////////////////////////////////////////////////////////////////////

	assert(pFontName);

	pGlyph = (jeGlyph3d*)jeRam_Allocate(sizeof(jeGlyph3d));
	assert(pGlyph);	
	
	pGlyph->val = glyphVal;

	pGlyph->nContours = 0;
	pGlyph->nTessGroups = 0;
	pGlyph->tess = JE_FALSE;

	(const jeGlyph3d_Beveler*)pGlyph->pBeveler = pBeveler;
	

	if (nPointsPerCurve > JE_GLYPH3D_MAX_NPOINTS_PER_QSPLINE)
		nPointsPerCurve = JE_GLYPH3D_MAX_NPOINTS_PER_QSPLINE;

	tstep = 1 / (float)(nPointsPerCurve + 1);

	///////////////////////////////////////////////////////////////////////////////////////

	hdc = GetDC(GetDesktopWindow());
   assert(hdc);

	font = CreateFont(-JE_GLYPH3D_DEFAULT_POINT_SIZE, // height
		0, // width(auto)
		0, // angle
		0, // angle
		FW_REGULAR, // heaviness
		FALSE, // italic
		FALSE, // underline
		FALSE, // strikeout
		0, // charset
		OUT_TT_ONLY_PRECIS, // prec
		0, // prec
		PROOF_QUALITY, // quality
		0, // family
		pFontName); // name

	assert(font);

	oldFont = (HFONT)SelectObject(hdc, font);

	Identity2(&mat);
	// get required buffer size
	buffSize = GetGlyphOutline(hdc,
		glyphVal,
		GGO_NATIVE,
		&gm,
		0,
		0,
		&mat);

	if (buffSize == GDI_ERROR)
	{
		return NULL; // wasn't a valid char for this font (perhaps unhandled)
	}

	// alloc mem for buffer
	pBuffer = (BYTE*)jeRam_Allocate(buffSize);
	assert(pBuffer);

	Identity2(&mat);
	buffSize = GetGlyphOutline(hdc,
		glyphVal,
		GGO_NATIVE,
		&gm,
		buffSize,
		pBuffer,
		&mat);
	assert(buffSize != GDI_ERROR);

	// give object and DC back to Windoze
	SelectObject(hdc, oldFont); 
	ReleaseDC(GetDesktopWindow(), hdc);
	DeleteObject(oldFont);

	pBuffPtr = pBuffer;
	pDataEndByte = pBuffer + buffSize;

	while (pBuffer < pDataEndByte)
	{
		POINTFX* apfx;
		POINTFX pfxA, pfxB, pfxC;
		WORD curveType, numPoints;
		BYTE* pContourStartByte, *pContourEndByte;

		///////////////////////////////////////////////////////////////////////////////////////

		pContourStartByte = pBuffer;

		pHeader = GetTTPolygonHeader(&pBuffer);
		pContourEndByte = pContourStartByte + pHeader->cb;		

		pGlyph->aContours[pGlyph->nContours].aPoints = NULL;
		pGlyph->aContours[pGlyph->nContours].nPoints = 0;

		pfxA = pHeader->pfxStart;
		jeGlyph3d_AddPointFIXED(pGlyph, pGlyph->nContours, &pfxA);

		while (pBuffer < pContourEndByte)
		{
			memcpy(&curveType, pBuffer, sizeof(WORD)), pBuffer += sizeof(WORD);
			memcpy(&numPoints, pBuffer, sizeof(WORD)), pBuffer += sizeof(WORD);
			assert(numPoints > 0);

			apfx = (POINTFX*)GetChunk((BYTE**)(&pBuffer), 
				numPoints * sizeof(POINTFX));

			assert(curveType == TT_PRIM_LINE || curveType == TT_PRIM_QSPLINE ||
				curveType == TT_PRIM_CSPLINE);
			
			if (curveType == TT_PRIM_LINE)
			{
				for (i = 0; i < numPoints; i ++)
				{
					jeGlyph3d_AddPointFIXED(pGlyph, pGlyph->nContours, &apfx[i]);
				}

				pfxA = apfx[i - 1];
			}
			
			// search MSDN with GGO_NATIVE to see where this delightful
			// chunk of code came from :)
			else if (curveType == TT_PRIM_QSPLINE)
			{
				float midx, midy;

				for (i = 0; i < numPoints - 1; i ++)
				{					
					pfxB = apfx[i];

					if (i < (numPoints - 2))
					{
						// compute float version of coordinates
						midx = (FIXEDToFloat(&pfxB.x) + FIXEDToFloat(&apfx[i + 1].x)) * 0.5f;
						midy = (FIXEDToFloat(&pfxB.y) + FIXEDToFloat(&apfx[i + 1].y)) * 0.5f;

						// convert back to fixed
						pfxC.x = FloatToFIXED((double)midx);
						pfxC.y = FloatToFIXED((double)midy);
					}
					else
					{
						pfxC = apfx[i + 1];
					}

					// compute spline points
					{
						float t;
						jeGlyph3d_Vec2f pa, pb, pc, q;

						pa.X = FIXEDToFloat(&pfxA.x); pa.Y = FIXEDToFloat(&pfxA.y);
						pb.X = FIXEDToFloat(&pfxB.x); pb.Y = FIXEDToFloat(&pfxB.y);
						pc.X = FIXEDToFloat(&pfxC.x); pc.Y = FIXEDToFloat(&pfxC.y);

						for (t = 0; t <= 1.f; t += tstep)
						{
							q.X = (pa.X - 2 * pb.X + pc.X) * jeFloat_Sqr(t) + (2 * (pb.X - pa.X)) * t + pa.X;
							q.Y = (pa.Y - 2 * pb.Y + pc.Y) * jeFloat_Sqr(t) + (2 * (pb.Y - pa.Y)) * t + pa.Y;

							jeGlyph3d_AddPointFloat(pGlyph, pGlyph->nContours, q.X, q.Y);
						}
					}

					pfxA = pfxC;
				}
			}

			else if (curveType == TT_PRIM_CSPLINE)
			{
				Log_Printf("jeGlyph3d_Create(): UNHANDLED CURVE TYPE [cspline]!\n");
				jeRam_Free(apfx);
				jeRam_Free(pHeader);
				goto CLEANUP;
			}

			jeRam_Free(apfx);
		}

		jeRam_Free(pHeader);
		pGlyph->nContours ++;
	}

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		jeGlyph3d_Contour_RemoveRedundantPoints(&pGlyph->aContours[i]);
		jeGlyph3d_ComputeContourEdgePoints(pGlyph, i);
		//jeGlyph3d_Contour_RemoveColinearEdges(&pGlyph->aContours[i]);
	}

	jeGlyph3d_ComputeYExtents(pGlyph);

CLEANUP:

	jeRam_Free(pBuffPtr);

	pGlyph->metricsInfo.width = (float)gm.gmBlackBoxX;
	pGlyph->metricsInfo.height = (float)gm.gmBlackBoxY;
	pGlyph->metricsInfo.xInc = (float)gm.gmCellIncX;
	pGlyph->metricsInfo.yInc = (float)gm.gmCellIncY;

	return pGlyph;
}

JETAPI void jeGlyph3d_Destroy(jeGlyph3d** ppGlyph)
{
	int i;

	assert(ppGlyph);
	assert(*ppGlyph);

	for (i = 0; i < (*ppGlyph)->nContours; i ++)
	{
		if ((*ppGlyph)->aContours[i].aPoints != NULL)
			jeRam_Free((*ppGlyph)->aContours[i].aPoints);
		if ((*ppGlyph)->aContours[i].aEdges != NULL)
			jeRam_Free((*ppGlyph)->aContours[i].aEdges);
	}

	// 

	// (*ppGlyph)->aBeveledContours[][] should be freed up

	for (i = 0; i < (*ppGlyph)->nTessGroups; i ++)
	{
		jeGlyph3d_TessGroup_FreeData(&(*ppGlyph)->aTessGroups[i]);
	}

	jeRam_Free(*ppGlyph);
}

///////////////////////////////////////////////////////////////////////////////////////
// fns

JETAPI uint32 jeGlyph3d_GetVal(jeGlyph3d* pGlyph)
{
	assert(pGlyph);

	return (uint32)pGlyph->val;
}

JETAPI void jeGlyph3d_GetMetricsInfo(jeGlyph3d* pGlyph, 
	jeGlyph3d_MetricsInfo* pMetricsInfo)
{
	assert(pGlyph);
	assert(pMetricsInfo);

	*pMetricsInfo = pGlyph->metricsInfo;
}

// helper fn used to compute a vertex normal for smoothing
void ComputeAverageNormal(jeGlyph3d_TriFace* aFaces, jeVec3d* pNormal,
	int i0, int i1, int i2)
{
	jeVec3d_Clear(pNormal);
	jeVec3d_Add(pNormal, &aFaces[i0].n, pNormal);
	jeVec3d_Add(pNormal, &aFaces[i1].n, pNormal);
	jeVec3d_Add(pNormal, &aFaces[i2].n, pNormal);
	jeVec3d_Normalize(pNormal);
}

///////////////////////////////////////////////////////////////////////////////////////
// generate a bone (with materials) from a glyph3d

JETAPI jeBoolean jeGlyph3d_GenBone(jeGlyph3d* pGlyph, jeBody* pBody, 
	int parentBoneIndex, int* pBoneIndex, const char* psBoneName, jeBoolean smoothOutline,
	const jeXForm3d* pXForm)
{
	jeVec3d* aVerts;
	jeGlyph3d_TriFace* aFaces;
	jeGlyph3d_TessGroup* pTessGroup;
	int i, j, c;
	jeBoolean glyphTop;
	int matIndex, groupIndex;

	float z, zStart, zEnd;

	///////////////////////////////////////////////////////////////////////////////////////

	assert(pGlyph);
	assert(psBoneName);
	assert(pBody);
	assert(pXForm);

	jeBody_AddBone(pBody, parentBoneIndex, psBoneName, pXForm, pBoneIndex);

	if (pGlyph->tess == JE_FALSE)
		pGlyph->nTessGroups = 0;

	groupIndex = 0;

	if (pGlyph->pBeveler != NULL)
	{
		// add beveled sides of the 3d glyph
		for (zEnd = 0.f, j = 0; j < pGlyph->pBeveler->nBevels; j ++)
		{
			pTessGroup = &pGlyph->aTessGroups[groupIndex];
			groupIndex ++;

			if (pGlyph->tess == JE_FALSE)
			{
				zStart = zEnd;
				zEnd -= pGlyph->pBeveler->aBevels[j].depth;

				if (j > 0)
				{
					for (i = 0; i < pGlyph->nContours; i ++)
					{
						jeGlyph3d_Contour_FreeData(&pGlyph->aBeveledContours[i][0]);
						jeGlyph3d_Contour_FreeData(&pGlyph->aBeveledContours[i][1]);
					}
				}

				jeGlyph3d_BevelGlyphOutline(pGlyph, j);
				jeGlyph3d_TessellateBeveledOutline(pGlyph, zStart, zEnd, pGlyph->nTessGroups);

				pGlyph->nTessGroups ++;
			}

			aVerts = pTessGroup->aTessVerts;
			aFaces = pTessGroup->aTessFaces;

			if (smoothOutline)
			{
				for (i = 0; i < pTessGroup->nTessFaces; i ++)
				{
					int ip2 = ((i - 2) + pTessGroup->nTessFaces) % pTessGroup->nTessFaces;
					int ip1 = ((i - 1) + pTessGroup->nTessFaces) % pTessGroup->nTessFaces;
					int in1 = (i + 1) % pTessGroup->nTessFaces;
					int in2 = (i + 2) % pTessGroup->nTessFaces;
					int in3 = (i + 3) % pTessGroup->nTessFaces;

					if (i & 1)
					{
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[0], ip2, ip1, i);
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[1], ip1, i, in1);
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[2], ip2, ip1, i);
					}
					else
					{
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[0], i, ip2, in1);
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[1], i, in2, in3);
						ComputeAverageNormal(aFaces, &aFaces[i].vertNormals[2], i, in1, in2);
					}
				}

				for (i = 0; i < pTessGroup->nTessFaces; i ++)
				{
					jeBody_AddFace(pBody, 
						&aVerts[aFaces[i].verts[0]], &aFaces[i].vertNormals[0], 0.f, 0.f, *pBoneIndex,
						&aVerts[aFaces[i].verts[1]], &aFaces[i].vertNormals[1], 0.f, 0.f, *pBoneIndex,
						&aVerts[aFaces[i].verts[2]], &aFaces[i].vertNormals[2], 0.f, 0.f, *pBoneIndex,
						j);
				}
			}
			else
			{
				for (i = 0; i < pTessGroup->nTessFaces; i ++)
				{
					jeBody_AddFace(pBody, 
						&aVerts[aFaces[i].verts[0]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
						&aVerts[aFaces[i].verts[1]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
						&aVerts[aFaces[i].verts[2]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
						j);
				}
			}

		} // j
	} // if beveler != NULL

	// add top and bottom bones

	for (c = 0; c < 2; c ++)
	{
		pTessGroup = &pGlyph->aTessGroups[groupIndex];
		groupIndex ++;

		if (pGlyph->tess == JE_FALSE)
		{
			if (c == 0)
			{
				glyphTop = JE_TRUE;
				matIndex = pGlyph->pBeveler->nBevels;
				z = 0.f;
			}
			else
			{
				glyphTop = JE_FALSE;
				matIndex = pGlyph->pBeveler->nBevels + 1;
				z = zEnd;
			}

			jeGlyph3d_SortEdges(pGlyph, glyphTop);
			jeGlyph3d_ScanConvertAndAddInteriorTris(pGlyph, z, glyphTop, pGlyph->nTessGroups);

			pGlyph->nTessGroups ++;
		}

		//jeGlyph3d_RemoveRedundantTessVerts(pGlyph, JE_GLYPH3D_VERT_TOL);
		aVerts = pTessGroup->aTessVerts;
		aFaces = pTessGroup->aTessFaces;

		for (i = 0; i < pTessGroup->nTessFaces; i ++)
		{
			jeBody_AddFace(pBody, 
				&aVerts[aFaces[i].verts[0]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
				&aVerts[aFaces[i].verts[1]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
				&aVerts[aFaces[i].verts[2]], &aFaces[i].n, 0.f, 0.f, *pBoneIndex,
				pGlyph->pBeveler->nBevels);
		}
	} // c

	for (i = 0; i < pGlyph->nContours; i ++)
	{
		jeGlyph3d_Contour_FreeData(&pGlyph->aBeveledContours[i][0]);
		jeGlyph3d_Contour_FreeData(&pGlyph->aBeveledContours[i][1]);
	}

	if (! pGlyph->tess)
	{
		Log_Printf("tessed glyph %u\n", pGlyph->val);
	}

	pGlyph->tess = JE_TRUE;

	return JE_TRUE;
}

///////////////////////////////////////////////////////////////////////////////////////
// bevel-related functions

/////////////////////////////////////////////////////////////////////////////////
// ctor / dtor

JETAPI jeGlyph3d_Beveler* jeGlyph3d_Beveler_Create(void)
{
	jeGlyph3d_Beveler* pBeveler;

	pBeveler = jeRam_Allocate(sizeof(jeGlyph3d_Beveler));
	if (pBeveler == NULL) return NULL;

	pBeveler->nBevels = 0;

	return pBeveler;
}

JETAPI void jeGlyph3d_Beveler_Destroy(jeGlyph3d_Beveler** ppBeveler)
{
	assert(ppBeveler);
	assert(*ppBeveler);

	jeRam_Free(*ppBeveler);
	*ppBeveler = NULL;
}

/////////////////////////////////////////////////////////////////////////////////
// accessors

JETAPI jeBoolean jeGlyph3d_Beveler_AddBevel(jeGlyph3d_Beveler* pBeveler, 
	float outline, float depth)
{
	assert(pBeveler);

	if (pBeveler->nBevels >= JE_GLYPH3D_MAX_NBEVELS)
	{
		Log_Printf("Reached max # bevels!\n");
		return JE_FALSE;
	}

	pBeveler->aBevels[pBeveler->nBevels].depth = depth;
	pBeveler->aBevels[pBeveler->nBevels].outline = outline;

	pBeveler->nBevels ++;

	return JE_TRUE;
}

JETAPI jeBoolean jeGlyph3d_Beveler_SubtractBevel(jeGlyph3d_Beveler* pBeveler)
{
	assert(pBeveler);

	if (pBeveler->nBevels <= 0)
		return JE_FALSE;

	pBeveler->nBevels --;

	return JE_TRUE;
}

JETAPI jeGlyph3d_Beveler_GetNBevels(const jeGlyph3d_Beveler* pBeveler)
{
	assert(pBeveler);

	return pBeveler->nBevels;
}

JETAPI const jeGlyph3d_BevelInfo* jeGlyph3d_Beveler_GetInfoAt(const jeGlyph3d_Beveler* pBeveler,
	int bevelIndex)
{
	assert(pBeveler);
	assert(bevelIndex >= 0 && bevelIndex < JE_GLYPH3D_MAX_NBEVELS);

	if (bevelIndex > pBeveler->nBevels) return NULL;

	return &pBeveler->aBevels[bevelIndex];
}

JETAPI jeBoolean jeGlyph3d_Beveler_SetAt(jeGlyph3d_Beveler* pBeveler,
	int bevelIndex, float outline, float depth)
{
	assert(pBeveler);
	assert(bevelIndex >= 0 && bevelIndex < JE_GLYPH3D_MAX_NBEVELS);

	if (bevelIndex > pBeveler->nBevels) return JE_FALSE;

	pBeveler->aBevels[bevelIndex].depth = depth;
	pBeveler->aBevels[bevelIndex].outline = outline;

	return JE_TRUE;
}
