/* -------------------------------------------------------------------------------

Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant 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.

GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

----------------------------------------------------------------------------------

This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."

------------------------------------------------------------------------------- */



/* marker */
#define DECALS_C



/* dependencies */
#include "q3map2.h"



#define MAX_PROJECTORS		1024

typedef struct decalProjector_s
{
	shaderInfo_t   *si;
	vec3_t          mins, maxs;
	vec3_t          center;
	float           radius, radius2;
	int             numPlanes;	/* either 5 or 6, for quad or triangle projectors */
	vec4_t          planes[6];
	vec4_t          texMat[2];
}
decalProjector_t;

static int      numProjectors = 0;
static decalProjector_t projectors[MAX_PROJECTORS];

static int      numDecalSurfaces = 0;

static vec3_t   entityOrigin;



/*
DVectorNormalize()
normalizes a vector, returns the length, operates using doubles
*/

typedef double  dvec_t;
typedef dvec_t  dvec3_t[3];

dvec_t DVectorNormalize(dvec3_t in, dvec3_t out)
{
	dvec_t          len, ilen;


	len = (dvec_t) sqrt(in[0] * in[0] + in[1] * in[1] + in[2] * in[2]);
	if(len == 0.0)
	{
		VectorClear(out);
		return 0.0;
	}

	ilen = 1.0 / len;
	out[0] = in[0] * ilen;
	out[1] = in[1] * ilen;
	out[2] = in[2] * ilen;

	return len;
}



/*
MakeTextureMatrix()
generates a texture projection matrix for a triangle
returns qfalse if a texture matrix cannot be created
*/

#define Vector2Subtract(a,b,c)	((c)[ 0 ] = (a)[ 0 ] - (b)[ 0 ], (c)[ 1 ] = (a)[ 1 ] - (b)[ 1 ])

static qboolean MakeTextureMatrix(decalProjector_t * dp, vec4_t projection, bspDrawVert_t * a, bspDrawVert_t * b,
								  bspDrawVert_t * c)
{
	int             i, j;
	double          bb, s, t, d;
	dvec3_t         pa, pb, pc;
	dvec3_t         bary, xyz;
	dvec3_t         vecs[3], axis[3], lengths;


	/* project triangle onto plane of projection */
	d = DotProduct(a->xyz, projection) - projection[3];
	VectorMA(a->xyz, -d, projection, pa);
	d = DotProduct(b->xyz, projection) - projection[3];
	VectorMA(b->xyz, -d, projection, pb);
	d = DotProduct(c->xyz, projection) - projection[3];
	VectorMA(c->xyz, -d, projection, pc);

	/* two methods */
#if 1
	{
		/* old code */

		/* calculate barycentric basis for the triangle */
		bb = (b->st[0] - a->st[0]) * (c->st[1] - a->st[1]) - (c->st[0] - a->st[0]) * (b->st[1] - a->st[1]);
		if(fabs(bb) < 0.00000001)
			return qfalse;

		/* calculate texture origin */
#if 0
		s = 0.0;
		t = 0.0;
		bary[0] = ((b->st[0] - s) * (c->st[1] - t) - (c->st[0] - s) * (b->st[1] - t)) / bb;
		bary[1] = ((c->st[0] - s) * (a->st[1] - t) - (a->st[0] - s) * (c->st[1] - t)) / bb;
		bary[2] = ((a->st[0] - s) * (b->st[1] - t) - (b->st[0] - s) * (a->st[1] - t)) / bb;

		origin[0] = bary[0] * pa[0] + bary[1] * pb[0] + bary[2] * pc[0];
		origin[1] = bary[0] * pa[1] + bary[1] * pb[1] + bary[2] * pc[1];
		origin[2] = bary[0] * pa[2] + bary[1] * pb[2] + bary[2] * pc[2];
#endif

		/* calculate s vector */
		s = a->st[0] + 1.0;
		t = a->st[1] + 0.0;
		bary[0] = ((b->st[0] - s) * (c->st[1] - t) - (c->st[0] - s) * (b->st[1] - t)) / bb;
		bary[1] = ((c->st[0] - s) * (a->st[1] - t) - (a->st[0] - s) * (c->st[1] - t)) / bb;
		bary[2] = ((a->st[0] - s) * (b->st[1] - t) - (b->st[0] - s) * (a->st[1] - t)) / bb;

		xyz[0] = bary[0] * pa[0] + bary[1] * pb[0] + bary[2] * pc[0];
		xyz[1] = bary[0] * pa[1] + bary[1] * pb[1] + bary[2] * pc[1];
		xyz[2] = bary[0] * pa[2] + bary[1] * pb[2] + bary[2] * pc[2];

		//% VectorSubtract( xyz, origin, vecs[ 0 ] );
		VectorSubtract(xyz, pa, vecs[0]);

		/* calculate t vector */
		s = a->st[0] + 0.0;
		t = a->st[1] + 1.0;
		bary[0] = ((b->st[0] - s) * (c->st[1] - t) - (c->st[0] - s) * (b->st[1] - t)) / bb;
		bary[1] = ((c->st[0] - s) * (a->st[1] - t) - (a->st[0] - s) * (c->st[1] - t)) / bb;
		bary[2] = ((a->st[0] - s) * (b->st[1] - t) - (b->st[0] - s) * (a->st[1] - t)) / bb;

		xyz[0] = bary[0] * pa[0] + bary[1] * pb[0] + bary[2] * pc[0];
		xyz[1] = bary[0] * pa[1] + bary[1] * pb[1] + bary[2] * pc[1];
		xyz[2] = bary[0] * pa[2] + bary[1] * pb[2] + bary[2] * pc[2];

		//% VectorSubtract( xyz, origin, vecs[ 1 ] );
		VectorSubtract(xyz, pa, vecs[1]);

		/* calculate r vector */
		VectorScale(projection, -1.0, vecs[2]);

		/* calculate transform axis */
		for(i = 0; i < 3; i++)
			lengths[i] = DVectorNormalize(vecs[i], axis[i]);
		for(i = 0; i < 2; i++)
			for(j = 0; j < 3; j++)
				dp->texMat[i][j] = lengths[i] > 0.0 ? (axis[i][j] / lengths[i]) : 0.0;
		//% dp->texMat[ i ][ j ] = fabs( vecs[ i ][ j ] ) > 0.0 ? (1.0 / vecs[ i ][ j ]) : 0.0;
		//% dp->texMat[ i ][ j ] = axis[ i ][ j ] > 0.0 ? (1.0 / axis[ i ][ j ]) : 0.0;

		/* calculalate translation component */
		dp->texMat[0][3] = a->st[0] - DotProduct(a->xyz, dp->texMat[0]);
		dp->texMat[1][3] = a->st[1] - DotProduct(a->xyz, dp->texMat[1]);
	}
#else
	{
		int             k;
		dvec3_t         origin, deltas[3];
		double          texDeltas[3][2];
		double          delta, texDelta;


		/* new code */

		/* calculate deltas */
		VectorSubtract(pa, pb, deltas[0]);
		VectorSubtract(pa, pc, deltas[1]);
		VectorSubtract(pb, pc, deltas[2]);
		Vector2Subtract(a->st, b->st, texDeltas[0]);
		Vector2Subtract(a->st, c->st, texDeltas[1]);
		Vector2Subtract(b->st, c->st, texDeltas[2]);

		/* walk st */
		for(i = 0; i < 2; i++)
		{
			/* walk xyz */
			for(j = 0; j < 3; j++)
			{
				/* clear deltas */
				delta = 0.0;
				texDelta = 0.0;

				/* walk deltas */
				for(k = 0; k < 3; k++)
				{
					if(fabs(deltas[k][j]) > delta && fabs(texDeltas[k][i]) > texDelta)
					{
						delta = deltas[k][j];
						texDelta = texDeltas[k][i];
					}
				}

				/* set texture matrix component */
				if(fabs(delta) > 0.0)
					dp->texMat[i][j] = texDelta / delta;
				else
					dp->texMat[i][j] = 0.0;
			}

			/* set translation component */
			dp->texMat[i][3] = a->st[i] - DotProduct(pa, dp->texMat[i]);
		}
	}
#endif

	/* debug code */
#if 1
	Sys_Printf("Mat: [ %f %f %f %f ] [ %f %f %f %f ] Theta: %f (%f)\n",
			   dp->texMat[0][0], dp->texMat[0][1], dp->texMat[0][2], dp->texMat[0][3],
			   dp->texMat[1][0], dp->texMat[1][1], dp->texMat[1][2], dp->texMat[1][3],
			   RAD2DEG(acos(DotProduct(dp->texMat[0], dp->texMat[1]))), RAD2DEG(acos(DotProduct(axis[0], axis[1]))));

	Sys_Printf("XYZ: %f %f %f ST: %f %f ST(t): %f %f\n",
			   a->xyz[0], a->xyz[1], a->xyz[2],
			   a->st[0], a->st[1],
			   DotProduct(a->xyz, dp->texMat[0]) + dp->texMat[0][3], DotProduct(a->xyz, dp->texMat[1]) + dp->texMat[1][3]);
#endif

	/* test texture matrix */
	s = DotProduct(a->xyz, dp->texMat[0]) + dp->texMat[0][3];
	t = DotProduct(a->xyz, dp->texMat[1]) + dp->texMat[1][3];
	if(fabs(s - a->st[0]) > 0.01 || fabs(t - a->st[1]) > 0.01)
	{
		Sys_Printf("Bad texture matrix! (A) (%f, %f) != (%f, %f)\n", s, t, a->st[0], a->st[1]);
		//% return qfalse;
	}
	s = DotProduct(b->xyz, dp->texMat[0]) + dp->texMat[0][3];
	t = DotProduct(b->xyz, dp->texMat[1]) + dp->texMat[1][3];
	if(fabs(s - b->st[0]) > 0.01 || fabs(t - b->st[1]) > 0.01)
	{
		Sys_Printf("Bad texture matrix! (B) (%f, %f) != (%f, %f)\n", s, t, b->st[0], b->st[1]);
		//% return qfalse;
	}
	s = DotProduct(c->xyz, dp->texMat[0]) + dp->texMat[0][3];
	t = DotProduct(c->xyz, dp->texMat[1]) + dp->texMat[1][3];
	if(fabs(s - c->st[0]) > 0.01 || fabs(t - c->st[1]) > 0.01)
	{
		Sys_Printf("Bad texture matrix! (C) (%f, %f) != (%f, %f)\n", s, t, c->st[0], c->st[1]);
		//% return qfalse;
	}

	/* disco */
	return qtrue;
}



/*
TransformDecalProjector()
transforms a decal projector
note: non-normalized axes will screw up the plane transform
*/

static void TransformDecalProjector(decalProjector_t * in, vec3_t axis[3], vec3_t origin, decalProjector_t * out)
{
	int             i;


	/* copy misc stuff */
	out->si = in->si;
	out->numPlanes = in->numPlanes;

	/* translate bounding box and sphere (note: rotated projector bounding box will be invalid!) */
	VectorSubtract(in->mins, origin, out->mins);
	VectorSubtract(in->maxs, origin, out->maxs);
	VectorSubtract(in->center, origin, out->center);
	out->radius = in->radius;
	out->radius2 = in->radius2;

	/* translate planes */
	for(i = 0; i < in->numPlanes; i++)
	{
		out->planes[i][0] = DotProduct(in->planes[i], axis[0]);
		out->planes[i][1] = DotProduct(in->planes[i], axis[1]);
		out->planes[i][2] = DotProduct(in->planes[i], axis[2]);
		out->planes[i][3] = in->planes[i][3] - DotProduct(out->planes[i], origin);
	}

	/* translate texture matrix */
	for(i = 0; i < 2; i++)
	{
		out->texMat[i][0] = DotProduct(in->texMat[i], axis[0]);
		out->texMat[i][1] = DotProduct(in->texMat[i], axis[1]);
		out->texMat[i][2] = DotProduct(in->texMat[i], axis[2]);
		out->texMat[i][3] = in->texMat[i][3] + DotProduct(out->texMat[i], origin);
	}
}



/*
MakeDecalProjector()
creates a new decal projector from a triangle
*/

static int MakeDecalProjector(shaderInfo_t * si, vec4_t projection, float distance, int numVerts, bspDrawVert_t ** dv)
{
	int             i, j;
	decalProjector_t *dp;
	vec3_t          xyz;


	/* dummy check */
	if(numVerts != 3 && numVerts != 4)
		return -1;

	/* limit check */
	if(numProjectors >= MAX_PROJECTORS)
	{
		Sys_Printf("WARNING: MAX_PROJECTORS (%d) exceeded, no more decal projectors available.\n", MAX_PROJECTORS);
		return -2;
	}

	/* create a new projector */
	dp = &projectors[numProjectors];
	memset(dp, 0, sizeof(*dp));

	/* basic setup */
	dp->si = si;
	dp->numPlanes = numVerts + 2;

	/* make texture matrix */
	if(!MakeTextureMatrix(dp, projection, dv[0], dv[1], dv[2]))
		return -1;

	/* bound the projector */
	ClearBounds(dp->mins, dp->maxs);
	for(i = 0; i < numVerts; i++)
	{
		AddPointToBounds(dv[i]->xyz, dp->mins, dp->maxs);
		VectorMA(dv[i]->xyz, distance, projection, xyz);
		AddPointToBounds(xyz, dp->mins, dp->maxs);
	}

	/* make the bounding sphere */
	VectorAdd(dp->mins, dp->maxs, dp->center);
	VectorScale(dp->center, 0.5f, dp->center);
	VectorSubtract(dp->maxs, dp->center, xyz);
	dp->radius = VectorLength(xyz);
	dp->radius2 = dp->radius * dp->radius;

	/* make the front plane */
	if(!PlaneFromPoints(dp->planes[0], dv[0]->xyz, dv[1]->xyz, dv[2]->xyz, qtrue))
		return -1;

	/* make the back plane */
	VectorSubtract(vec3_origin, dp->planes[0], dp->planes[1]);
	VectorMA(dv[0]->xyz, distance, projection, xyz);
	dp->planes[1][3] = DotProduct(xyz, dp->planes[1]);

	/* make the side planes */
	for(i = 0; i < numVerts; i++)
	{
		j = (i + 1) % numVerts;
		VectorMA(dv[i]->xyz, distance, projection, xyz);
		if(!PlaneFromPoints(dp->planes[i + 2], dv[j]->xyz, dv[i]->xyz, xyz, qtrue))
			return -1;
	}

	/* return ok */
	numProjectors++;
	return numProjectors - 1;
}



/*
ProcessDecals()
finds all decal entities and creates decal projectors
*/

#define PLANAR_EPSILON	0.5f

void ProcessDecals(void)
{
	int             i, j, x, y, pw[5], r, iterations;
	float           distance;
	vec4_t          projection, plane;
	vec3_t          origin, target, delta;
	entity_t       *e, *e2;
	parseMesh_t    *p;
	mesh_t         *mesh, *subdivided;
	bspDrawVert_t  *dv[4];
	const char     *value;


	/* note it */
	Sys_FPrintf(SYS_VRB, "--- ProcessDecals ---\n");

	/* walk entity list */
	for(i = 0; i < numEntities; i++)
	{
		/* get entity */
		e = &entities[i];
		value = ValueForKey(e, "classname");
		if(Q_stricmp(value, "_decal"))
			continue;

		/* any patches? */
		if(e->patches == NULL)
		{
			Sys_Printf("WARNING: Decal entity without any patch meshes, ignoring.\n");
			e->epairs = NULL;	/* fixme: leak! */
			continue;
		}

		/* find target */
		value = ValueForKey(e, "target");
		e2 = FindTargetEntity(value);

		/* no target? */
		if(e2 == NULL)
		{
			Sys_Printf("WARNING: Decal entity without a valid target, ignoring.\n");
			continue;
		}

		/* walk entity patches */
		for(p = e->patches; p != NULL; p = e->patches)
		{
			/* setup projector */
			if(VectorCompare(e->origin, vec3_origin))
			{
				VectorAdd(p->eMins, p->eMaxs, origin);
				VectorScale(origin, 0.5f, origin);
			}
			else
				VectorCopy(e->origin, origin);

			VectorCopy(e2->origin, target);
			VectorSubtract(target, origin, delta);

			/* setup projection plane */
			distance = VectorNormalize2(delta, projection);
			projection[3] = DotProduct(origin, projection);

			/* create projectors */
			if(distance > 0.125f)
			{
				/* tesselate the patch */
				iterations = IterationsForCurve(p->longestCurve, patchSubdivisions);
				subdivided = SubdivideMesh2(p->mesh, iterations);

				/* fit it to the curve and remove colinear verts on rows/columns */
				PutMeshOnCurve(*subdivided);
				mesh = RemoveLinearMeshColumnsRows(subdivided);
				FreeMesh(subdivided);

				/* offset by projector origin */
				for(j = 0; j < (mesh->width * mesh->height); j++)
					VectorAdd(mesh->verts[j].xyz, e->origin, mesh->verts[j].xyz);

				/* iterate through the mesh quads */
				for(y = 0; y < (mesh->height - 1); y++)
				{
					for(x = 0; x < (mesh->width - 1); x++)
					{
						/* set indexes */
						pw[0] = x + (y * mesh->width);
						pw[1] = x + ((y + 1) * mesh->width);
						pw[2] = x + 1 + ((y + 1) * mesh->width);
						pw[3] = x + 1 + (y * mesh->width);
						pw[4] = x + (y * mesh->width);	/* same as pw[ 0 ] */

						/* set radix */
						r = (x + y) & 1;

						/* get drawverts */
						dv[0] = &mesh->verts[pw[r + 0]];
						dv[1] = &mesh->verts[pw[r + 1]];
						dv[2] = &mesh->verts[pw[r + 2]];
						dv[3] = &mesh->verts[pw[r + 3]];

						/* planar? (nuking this optimization as it doesn't work on non-rectangular quads) */
						plane[0] = 0.0f;	/* stupid msvc */
						/*if(PlaneFromPoints(plane, dv[0]->xyz, dv[1]->xyz, dv[2]->xyz, qtrue) &&
						   fabs(DotProduct(dv[1]->xyz, plane) - plane[3]) <= PLANAR_EPSILON)
						{
							/ * make a quad projector * /
							MakeDecalProjector(p->shaderInfo, projection, distance, 4, dv);
						}
						else*/
						{
							/* make first triangle */
							MakeDecalProjector(p->shaderInfo, projection, distance, 3, dv);

							/* make second triangle */
							dv[1] = dv[2];
							dv[2] = dv[3];
							MakeDecalProjector(p->shaderInfo, projection, distance, 3, dv);
						}
					}
				}

				/* clean up */
				free(mesh);
			}

			/* remove patch from entity (fixme: leak!) */
			e->patches = p->next;

			/* push patch to worldspawn (enable this to debug projectors) */
#if 0
			p->next = entities[0].patches;
			entities[0].patches = p;
#endif
		}
	}

	/* emit some stats */
	Sys_FPrintf(SYS_VRB, "%9d decal projectors\n", numProjectors);
}



/*
ProjectDecalOntoWinding()
projects a decal onto a winding
*/

static void ProjectDecalOntoWinding(decalProjector_t * dp, mapDrawSurface_t * ds, winding_t * w)
{
	int             i, j;
	float           d, d2, alpha;
	winding_t      *front, *back;
	mapDrawSurface_t *ds2;
	bspDrawVert_t  *dv;
	vec4_t          plane;


	/* dummy check */
	if(w->numpoints < 3)
	{
		FreeWinding(w);
		return;
	}

	/* offset by entity origin */
	for(i = 0; i < w->numpoints; i++)
		VectorAdd(w->p[i], entityOrigin, w->p[i]);

	/* make a plane from the winding */
	if(!PlaneFromPoints(plane, w->p[0], w->p[1], w->p[2], qtrue))
	{
		FreeWinding(w);
		return;
	}

	/* backface check */
	d = DotProduct(dp->planes[0], plane);
	if(d < -0.0001f)
	{
		FreeWinding(w);
		return;
	}

	/* walk list of planes */
	for(i = 0; i < dp->numPlanes; i++)
	{
		/* chop winding by the plane */
		ClipWindingEpsilon(w, dp->planes[i], dp->planes[i][3], 0.0625f, &front, &back);
		FreeWinding(w);

		/* lose the front fragment */
		if(front != NULL)
			FreeWinding(front);

		/* if nothing left in back, then bail */
		if(back == NULL)
			return;

		/* reset winding */
		w = back;
	}

	/* nothing left? */
	if(w == NULL || w->numpoints < 3)
		return;

	/* add to counts */
	numDecalSurfaces++;

	/* make a new surface */
	ds2 = AllocDrawSurface(SURFACE_DECAL);

	/* set it up */
	ds2->entityNum = ds->entityNum;
	ds2->castShadows = ds->castShadows;
	ds2->recvShadows = ds->recvShadows;
	ds2->shaderInfo = dp->si;
	ds2->fogNum = ds->fogNum;	/* why was this -1? */
	ds2->lightmapScale = ds->lightmapScale;
	ds2->shadeAngleDegrees = ds->shadeAngleDegrees;
	ds2->numVerts = w->numpoints;
	ds2->verts = safe_malloc(ds2->numVerts * sizeof(*ds2->verts));
	memset(ds2->verts, 0, ds2->numVerts * sizeof(*ds2->verts));

	/* set vertexes */
	for(i = 0; i < ds2->numVerts; i++)
	{
		/* get vertex */
		dv = &ds2->verts[i];

		/* set alpha */
		d = DotProduct(w->p[i], dp->planes[0]) - dp->planes[0][3];
		d2 = DotProduct(w->p[i], dp->planes[1]) - dp->planes[1][3];
		alpha = 255.0f * d2 / (d + d2);
		if(alpha > 255)
			alpha = 255;
		else if(alpha < 0)
			alpha = 0;

		/* set misc */
		VectorSubtract(w->p[i], entityOrigin, dv->xyz);
		VectorCopy(plane, dv->normal);
		dv->st[0] = DotProduct(dv->xyz, dp->texMat[0]) + dp->texMat[0][3];
		dv->st[1] = DotProduct(dv->xyz, dp->texMat[1]) + dp->texMat[1][3];

		/* set color */
		for(j = 0; j < MAX_LIGHTMAPS; j++)
		{
			dv->lightColor[j][0] = 255;
			dv->lightColor[j][1] = 255;
			dv->lightColor[j][2] = 255;
			dv->lightColor[j][3] = alpha;
		}
	}
}



/*
ProjectDecalOntoFace()
projects a decal onto a brushface surface
*/

static void ProjectDecalOntoFace(decalProjector_t * dp, mapDrawSurface_t * ds)
{
	vec4_t          plane;
	float           d;
	winding_t      *w;


	/* dummy check */
	if(ds->sideRef == NULL || ds->sideRef->side == NULL)
		return;

	/* backface check */
	if(ds->planar)
	{
		VectorCopy(mapplanes[ds->planeNum].normal, plane);
		plane[3] = mapplanes[ds->planeNum].dist + DotProduct(plane, entityOrigin);
		d = DotProduct(dp->planes[0], plane);
		if(d < -0.0001f)
			return;
	}

	/* generate decal */
	w = WindingFromDrawSurf(ds);
	ProjectDecalOntoWinding(dp, ds, w);
}



/*
ProjectDecalOntoPatch()
projects a decal onto a patch surface
*/

static void ProjectDecalOntoPatch(decalProjector_t * dp, mapDrawSurface_t * ds)
{
	int             x, y, pw[5], r, iterations;
	vec4_t          plane;
	float           d;
	mesh_t          src, *mesh, *subdivided;
	winding_t      *w;


	/* backface check */
	if(ds->planar)
	{
		VectorCopy(mapplanes[ds->planeNum].normal, plane);
		plane[3] = mapplanes[ds->planeNum].dist + DotProduct(plane, entityOrigin);
		d = DotProduct(dp->planes[0], plane);
		if(d < -0.0001f)
			return;
	}

	/* tesselate the patch */
	src.width = ds->patchWidth;
	src.height = ds->patchHeight;
	src.verts = ds->verts;
	iterations = IterationsForCurve(ds->longestCurve, patchSubdivisions);
	subdivided = SubdivideMesh2(src, iterations);

	/* fit it to the curve and remove colinear verts on rows/columns */
	PutMeshOnCurve(*subdivided);
	mesh = RemoveLinearMeshColumnsRows(subdivided);
	FreeMesh(subdivided);

	/* iterate through the mesh quads */
	for(y = 0; y < (mesh->height - 1); y++)
	{
		for(x = 0; x < (mesh->width - 1); x++)
		{
			/* set indexes */
			pw[0] = x + (y * mesh->width);
			pw[1] = x + ((y + 1) * mesh->width);
			pw[2] = x + 1 + ((y + 1) * mesh->width);
			pw[3] = x + 1 + (y * mesh->width);
			pw[4] = x + (y * mesh->width);	/* same as pw[ 0 ] */

			/* set radix */
			r = (x + y) & 1;

			/* generate decal for first triangle */
			w = AllocWinding(3);
			w->numpoints = 3;
			VectorCopy(mesh->verts[pw[r + 0]].xyz, w->p[0]);
			VectorCopy(mesh->verts[pw[r + 1]].xyz, w->p[1]);
			VectorCopy(mesh->verts[pw[r + 2]].xyz, w->p[2]);
			ProjectDecalOntoWinding(dp, ds, w);

			/* generate decal for second triangle */
			w = AllocWinding(3);
			w->numpoints = 3;
			VectorCopy(mesh->verts[pw[r + 0]].xyz, w->p[0]);
			VectorCopy(mesh->verts[pw[r + 2]].xyz, w->p[1]);
			VectorCopy(mesh->verts[pw[r + 3]].xyz, w->p[2]);
			ProjectDecalOntoWinding(dp, ds, w);
		}
	}

	/* clean up */
	free(mesh);
}



/*
ProjectDecalOntoTriangles()
projects a decal onto a triangle surface
*/

static void ProjectDecalOntoTriangles(decalProjector_t * dp, mapDrawSurface_t * ds)
{
	int             i;
	vec4_t          plane;
	float           d;
	winding_t      *w;

#if 0
	/* triangle surfaces without shaders don't get marks by default */
	if(ds->type == SURFACE_TRIANGLES && ds->shaderInfo->shaderText == NULL)
		return;
#endif

	/* backface check */
	if(ds->planar)
	{
		VectorCopy(mapplanes[ds->planeNum].normal, plane);
		plane[3] = mapplanes[ds->planeNum].dist + DotProduct(plane, entityOrigin);
		d = DotProduct(dp->planes[0], plane);
		if(d < -0.0001f)
			return;
	}

	/* iterate through triangles */
	for(i = 0; i < ds->numIndexes; i += 3)
	{
		/* generate decal */
		w = AllocWinding(3);
		w->numpoints = 3;
		VectorCopy(ds->verts[ds->indexes[i]].xyz, w->p[0]);
		VectorCopy(ds->verts[ds->indexes[i + 1]].xyz, w->p[1]);
		VectorCopy(ds->verts[ds->indexes[i + 2]].xyz, w->p[2]);
		ProjectDecalOntoWinding(dp, ds, w);
	}
}



/*
MakeEntityDecals()
projects decals onto world surfaces
*/

void MakeEntityDecals(entity_t * e)
{
	int             i, j, k, f, fOld, start;
	decalProjector_t dp;
	mapDrawSurface_t *ds;
	vec3_t          identityAxis[3] = { {1, 0, 0}, {0, 1, 0}, {0, 0, 1} };


	/* note it */
	Sys_FPrintf(SYS_VRB, "--- MakeEntityDecals ---\n");

	/* set entity origin */
	VectorCopy(e->origin, entityOrigin);

	/* transform projector instead of geometry */
	VectorClear(entityOrigin);

	/* init pacifier */
	fOld = -1;
	start = I_FloatTime();

	/* walk the list of decal projectors */
	for(i = 0; i < numProjectors; i++)
	{
		/* print pacifier */
		f = 10 * i / numProjectors;
		if(f != fOld)
		{
			fOld = f;
			Sys_FPrintf(SYS_VRB, "%d...", f);
		}

		/* get projector */
		TransformDecalProjector(&projectors[i], identityAxis, e->origin, &dp);

		/* walk the list of surfaces in the entity */
		for(j = e->firstDrawSurf; j < numMapDrawSurfs; j++)
		{
			/* get surface */
			ds = &mapDrawSurfs[j];
			if(ds->numVerts <= 0)
				continue;

			/* ignore autosprite or nomarks */
			if(ds->shaderInfo->autosprite || (ds->shaderInfo->compileFlags & C_NOMARKS))
				continue;

			/* bounds check */
			for(k = 0; k < 3; k++)
				if(ds->mins[k] >= (dp.center[k] + dp.radius) || ds->maxs[k] <= (dp.center[k] - dp.radius))
					break;
			if(k < 3)
				continue;

			/* switch on type */
			switch (ds->type)
			{
				case SURFACE_FACE:
					ProjectDecalOntoFace(&dp, ds);
					break;

				case SURFACE_PATCH:
					ProjectDecalOntoPatch(&dp, ds);
					break;

				case SURFACE_TRIANGLES:
				case SURFACE_FORCED_META:
				case SURFACE_META:
					ProjectDecalOntoTriangles(&dp, ds);
					break;

				default:
					break;
			}
		}
	}

	/* print time */
	Sys_FPrintf(SYS_VRB, " (%d)\n", (int)(I_FloatTime() - start));

	/* emit some stats */
	Sys_FPrintf(SYS_VRB, "%9d decal surfaces\n", numDecalSurfaces);
}
