/*
===========================================================================

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).  

RTCW SP Source Code 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 3 of the License, or
(at your option) any later version.

RTCW SP Source Code 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 RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

// tr_marks.c -- polygon projection on the world polygons

#include "tr_local.h"
//#include "assert.h"

#define MAX_VERTS_ON_POLY       64

#define MARKER_OFFSET           0   // 1

// Ridah, just make these global to prevent having to add more paramaters, which add overhead
static vec3_t bestnormal;
static float bestdist;

/*
=============
R_ChopPolyBehindPlane

Out must have space for two more vertexes than in
=============
*/
#define SIDE_FRONT  0
#define SIDE_BACK   1
#define SIDE_ON     2
static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY],
								   int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY],
								   vec3_t normal, vec_t dist, vec_t epsilon ) {
	float dists[MAX_VERTS_ON_POLY + 4] = { 0 };
	int sides[MAX_VERTS_ON_POLY + 4] = { 0 };
	int counts[3];
	float dot;
	int i, j;
	float       *p1, *p2, *clip;
	float d;

	// don't clip if it might overflow
	if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) {
		*numOutPoints = 0;
		return;
	}

	counts[0] = counts[1] = counts[2] = 0;

	// determine sides for each point
	for ( i = 0 ; i < numInPoints ; i++ ) {
		dot = DotProduct( inPoints[i], normal );
		dot -= dist;
		dists[i] = dot;
		if ( dot > epsilon ) {
			sides[i] = SIDE_FRONT;
		} else if ( dot < -epsilon ) {
			sides[i] = SIDE_BACK;
		} else {
			sides[i] = SIDE_ON;
		}
		counts[sides[i]]++;
	}
	sides[i] = sides[0];
	dists[i] = dists[0];

	*numOutPoints = 0;

	if ( !counts[0] ) {
		return;
	}
	if ( !counts[1] ) {
		*numOutPoints = numInPoints;
		memcpy( outPoints, inPoints, numInPoints * sizeof( vec3_t ) );
		return;
	}

	for ( i = 0 ; i < numInPoints ; i++ ) {
		p1 = inPoints[i];
		clip = outPoints[ *numOutPoints ];

		if ( sides[i] == SIDE_ON ) {
			VectorCopy( p1, clip );
			( *numOutPoints )++;
			continue;
		}

		if ( sides[i] == SIDE_FRONT ) {
			VectorCopy( p1, clip );
			( *numOutPoints )++;
			clip = outPoints[ *numOutPoints ];
		}

		if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) {
			continue;
		}

		// generate a split point
		p2 = inPoints[ ( i + 1 ) % numInPoints ];

		d = dists[i] - dists[i + 1];
		if ( d == 0 ) {
			dot = 0;
		} else {
			dot = dists[i] / d;
		}

		// clip xyz

		for ( j = 0 ; j < 3 ; j++ ) {
			clip[j] = p1[j] + dot * ( p2[j] - p1[j] );
		}

		( *numOutPoints )++;
	}
}

/*
=================
R_BoxSurfaces_r

=================
*/
void R_BoxSurfaces_r( mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir ) {

	int s, c;
	msurface_t	*surf;
	int *mark;

	// do the tail recursion in a loop
	while ( node->contents == -1 ) {
		s = BoxOnPlaneSide( mins, maxs, node->plane );
		if ( s == 1 ) {
			node = node->children[0];
		} else if ( s == 2 ) {
			node = node->children[1];
		} else {
			R_BoxSurfaces_r( node->children[0], mins, maxs, list, listsize, listlength, dir );
			node = node->children[1];
		}
	}

	// Ridah, don't mark alpha surfaces
	if ( node->contents & CONTENTS_TRANSLUCENT ) {
		return;
	}

	// add the individual surfaces
	mark = tr.world->marksurfaces + node->firstmarksurface;
	c = node->nummarksurfaces;
	while ( c-- ) {
		int *surfViewCount;
		//
		if ( *listlength >= listsize ) {
			break;
		}
		//
		surfViewCount = &tr.world->surfacesViewCount[*mark];
		surf = tr.world->surfaces + *mark;
		// check if the surface has NOIMPACT or NOMARKS set
		if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) )
				|| ( surf->shader->contentFlags & CONTENTS_FOG ) ) {
			*surfViewCount = tr.viewCount;
		}
		// extra check for surfaces to avoid list overflows
		else if ( *( surf->data ) == SF_FACE ) {
			// the face plane should go through the box
			s = BoxOnPlaneSide( mins, maxs, &surf->cullinfo.plane );
			if ( s == 1 || s == 2 ) {
				*surfViewCount = tr.viewCount;
			} else if (DotProduct(surf->cullinfo.plane.normal, dir) < -0.5) {
				// don't add faces that make sharp angles with the projection direction
				*surfViewCount = tr.viewCount;
			}
		}
		else if (*(surf->data) != SF_GRID &&
			*(surf->data) != SF_TRIANGLES)
			*surfViewCount = tr.viewCount;
		// check the viewCount because the surface may have
		// already been added if it spans multiple leafs
		if (*surfViewCount != tr.viewCount) {
			*surfViewCount = tr.viewCount;
			list[*listlength] = surf->data;
			( *listlength )++;
		}
		mark++;
	}
}

/*
=================
R_AddMarkFragments

=================
*/
void R_AddMarkFragments( int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY],
						 int numPlanes, vec3_t *normals, float *dists,
						 int maxPoints, vec3_t pointBuffer,
						 int maxFragments, markFragment_t *fragmentBuffer,
						 int *returnedPoints, int *returnedFragments,
						 vec3_t mins, vec3_t maxs ) {
	int pingPong, i;
	markFragment_t  *mf;

	// chop the surface by all the bounding planes of the to be projected polygon
	pingPong = 0;

	for ( i = 0 ; i < numPlanes ; i++ ) {

		R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong],
							   &numClipPoints, clipPoints[!pingPong],
							   normals[i], dists[i], 0.5 );
		pingPong ^= 1;
		if ( numClipPoints == 0 ) {
			break;
		}
	}
	// completely clipped away?
	if ( numClipPoints == 0 ) {
		return;
	}

	// add this fragment to the returned list
	if ( numClipPoints + ( *returnedPoints ) > maxPoints ) {
		return; // not enough space for this polygon
	}
	/*
	// all the clip points should be within the bounding box
	for ( i = 0 ; i < numClipPoints ; i++ ) {
		int j;
		for ( j = 0 ; j < 3 ; j++ ) {
			if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break;
			if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break;
		}
		if (j < 3) break;
	}
	if (i < numClipPoints) return;
	*/

	mf = fragmentBuffer + ( *returnedFragments );
	mf->firstPoint = ( *returnedPoints );
	mf->numPoints = numClipPoints;
	//memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) );
	for ( i = 0; i < numClipPoints; i++ ) {
		VectorCopy( clipPoints[pingPong][i], (float *)pointBuffer + 5 * ( *returnedPoints + i ) );
	}

	( *returnedPoints ) += numClipPoints;
	( *returnedFragments )++;
}

/*
=================
R_OldMarkFragments

=================
*/
int R_OldMarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
						int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) {
	int numsurfaces, numPlanes;
	int i, j, k, m, n;
	surfaceType_t   *surfaces[64];
	vec3_t mins, maxs;
	int returnedFragments;
	int returnedPoints;
	vec3_t normals[MAX_VERTS_ON_POLY + 2];
	float dists[MAX_VERTS_ON_POLY + 2];
	vec3_t clipPoints[2][MAX_VERTS_ON_POLY];
	int numClipPoints;
	float           *v;
	srfBspSurface_t	*cv;
	glIndex_t		*tri;
	srfVert_t		*dv;
	vec3_t normal;
	vec3_t projectionDir;
	vec3_t v1, v2;

	if (numPoints <= 0) {
		return 0;
	}

	//increment view count for double check prevention
	tr.viewCount++;

	//
	VectorNormalize2( projection, projectionDir );
	// find all the brushes that are to be considered
	ClearBounds( mins, maxs );
	for ( i = 0 ; i < numPoints ; i++ ) {
		vec3_t temp;

		AddPointToBounds( points[i], mins, maxs );
		VectorAdd( points[i], projection, temp );
		AddPointToBounds( temp, mins, maxs );
		// make sure we get all the leafs (also the one(s) in front of the hit surface)
		VectorMA( points[i], -20, projectionDir, temp );
		AddPointToBounds( temp, mins, maxs );
	}

	if ( numPoints > MAX_VERTS_ON_POLY ) {
		numPoints = MAX_VERTS_ON_POLY;
	}
	// create the bounding planes for the to be projected polygon
	for ( i = 0 ; i < numPoints ; i++ ) {
		VectorSubtract( points[( i + 1 ) % numPoints], points[i], v1 );
		VectorAdd( points[i], projection, v2 );
		VectorSubtract( points[i], v2, v2 );
		CrossProduct( v1, v2, normals[i] );
		VectorNormalizeFast( normals[i] );
		dists[i] = DotProduct( normals[i], points[i] );
	}
	// add near and far clipping planes for projection
	VectorCopy( projectionDir, normals[numPoints] );
	dists[numPoints] = DotProduct( normals[numPoints], points[0] ) - 32;
	VectorCopy( projectionDir, normals[numPoints + 1] );
	VectorInverse( normals[numPoints + 1] );
	dists[numPoints + 1] = DotProduct( normals[numPoints + 1], points[0] ) - 20;
	numPlanes = numPoints + 2;

	numsurfaces = 0;
	R_BoxSurfaces_r( tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir );
	//assert(numsurfaces <= 64);
	//assert(numsurfaces != 64);

	returnedPoints = 0;
	returnedFragments = 0;

	for ( i = 0 ; i < numsurfaces ; i++ ) {

		if ( *surfaces[i] == SF_GRID ) {

			cv = (srfBspSurface_t *) surfaces[i];
			for ( m = 0 ; m < cv->height - 1 ; m++ ) {
				for ( n = 0 ; n < cv->width - 1 ; n++ ) {
					// We triangulate the grid and chop all triangles within
					// the bounding planes of the to be projected polygon.
					// LOD is not taken into account, not such a big deal though.
					//
					// It's probably much nicer to chop the grid itself and deal
					// with this grid as a normal SF_GRID surface so LOD will
					// be applied. However the LOD of that chopped grid must
					// be synced with the LOD of the original curve.
					// One way to do this; the chopped grid shares vertices with
					// the original curve. When LOD is applied to the original
					// curve the unused vertices are flagged. Now the chopped curve
					// should skip the flagged vertices. This still leaves the
					// problems with the vertices at the chopped grid edges.
					//
					// To avoid issues when LOD applied to "hollow curves" (like
					// the ones around many jump pads) we now just add a 2 unit
					// offset to the triangle vertices.
					// The offset is added in the vertex normal vector direction
					// so all triangles will still fit together.
					// The 2 unit offset should avoid pretty much all LOD problems.
					vec3_t fNormal;

					numClipPoints = 3;

					dv = cv->verts + m * cv->width + n;

					VectorCopy( dv[0].xyz, clipPoints[0][0] );
					R_VaoUnpackNormal(fNormal, dv[0].normal);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
					VectorCopy( dv[cv->width].xyz, clipPoints[0][1] );
					R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
					VectorCopy( dv[1].xyz, clipPoints[0][2] );
					R_VaoUnpackNormal(fNormal, dv[1].normal);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]);
					// check the normal of this triangle
					VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 );
					VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 );
					CrossProduct( v1, v2, normal );
					VectorNormalizeFast( normal );
					if ( DotProduct( normal, projectionDir ) < -0.1 ) {
						// add the fragments of this triangle
						R_AddMarkFragments( numClipPoints, clipPoints,
											numPlanes, normals, dists,
											maxPoints, pointBuffer,
											maxFragments, fragmentBuffer,
											&returnedPoints, &returnedFragments, mins, maxs );

						if ( returnedFragments == maxFragments ) {
							return returnedFragments;   // not enough space for more fragments
						}
					}

					VectorCopy( dv[1].xyz, clipPoints[0][0] );
					R_VaoUnpackNormal(fNormal, dv[1].normal);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
					VectorCopy( dv[cv->width].xyz, clipPoints[0][1] );
					R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
					VectorCopy( dv[cv->width + 1].xyz, clipPoints[0][2] );
					R_VaoUnpackNormal(fNormal, dv[cv->width + 1].normal);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]);
					// check the normal of this triangle
					VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 );
					VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 );
					CrossProduct( v1, v2, normal );
					VectorNormalizeFast( normal );
					if ( DotProduct( normal, projectionDir ) < -0.05 ) {
						// add the fragments of this triangle
						R_AddMarkFragments( numClipPoints, clipPoints,
											numPlanes, normals, dists,
											maxPoints, pointBuffer,
											maxFragments, fragmentBuffer,
											&returnedPoints, &returnedFragments, mins, maxs );

						if ( returnedFragments == maxFragments ) {
							return returnedFragments;   // not enough space for more fragments
						}
					}
				}
			}
		} else if ( *surfaces[i] == SF_FACE )     {

			srfBspSurface_t *surf = ( srfBspSurface_t * ) surfaces[i];

			// check the normal of this face
			if (DotProduct(surf->cullPlane.normal, projectionDir) > -0.5) {
				continue;
			}

			for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
			{
				for(j = 0; j < 3; j++)
				{
					v = surf->verts[tri[j]].xyz;
					VectorMA(v, MARKER_OFFSET, surf->cullPlane.normal, clipPoints[0][j]);
				}
				// add the fragments of this face
				R_AddMarkFragments( 3, clipPoints,
									numPlanes, normals, dists,
									maxPoints, pointBuffer,
									maxFragments, fragmentBuffer,
									&returnedPoints, &returnedFragments, mins, maxs );
				if ( returnedFragments == maxFragments ) {
					return returnedFragments;   // not enough space for more fragments
				}
			}
		}
		else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) {

			srfBspSurface_t *surf = (srfBspSurface_t *) surfaces[i];

			for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
			{
				for(j = 0; j < 3; j++)
				{
					vec3_t fNormal;
					v = surf->verts[tri[j]].xyz;
					R_VaoUnpackNormal(fNormal, surf->verts[tri[j]].normal);
					VectorMA(v, MARKER_OFFSET, fNormal, clipPoints[0][j]);
				}

				// add the fragments of this face
				R_AddMarkFragments(3, clipPoints,
								   numPlanes, normals, dists,
								   maxPoints, pointBuffer,
								   maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs);
				if(returnedFragments == maxFragments)
				{
					return returnedFragments;	// not enough space for more fragments
				}
			}
		}
	}
	return returnedFragments;
}

/*
=================
R_MarkFragments

=================
*/
int R_MarkFragments( int orientation, const vec3_t *points, const vec3_t projection,
					 int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) {
	int numsurfaces, numPlanes;
	int i, j, k, m, n;
	surfaceType_t   *surfaces[4096];
	vec3_t mins, maxs;
	int returnedFragments;
	int returnedPoints;
	vec3_t normals[MAX_VERTS_ON_POLY + 2];
	float dists[MAX_VERTS_ON_POLY + 2];
	vec3_t clipPoints[2][MAX_VERTS_ON_POLY];
	int numClipPoints;
	float           *v;
	srfBspSurface_t	*cv;
	glIndex_t		*tri;
	srfVert_t		*dv;
	vec3_t normal;
	vec3_t projectionDir;
	vec3_t v1, v2;
	float radius;
	vec3_t center;		// center of original mark
	int numPoints = 4;	// Ridah, we were only ever passing in 4, so I made this local and used the parameter for the orientation
	qboolean oldMapping = qfalse;

	if (numPoints <= 0) {
		return 0;
	}

	//increment view count for double check prevention
	tr.viewCount++;

	// RF, negative maxFragments means we want original mapping
	if ( maxFragments < 0 ) {
		maxFragments = -maxFragments;
		oldMapping = qtrue;
	}

	VectorClear( center );
	for ( i = 0 ; i < numPoints ; i++ ) {
		VectorAdd( points[i], center, center );
	}
	VectorScale( center, 1.0 / numPoints, center );
	//
	radius = VectorNormalize2( projection, projectionDir ) / 2.0;
	bestdist = 0;
	VectorNegate( projectionDir, bestnormal );
	// find all the brushes that are to be considered
	ClearBounds( mins, maxs );
	for ( i = 0 ; i < numPoints ; i++ ) {
		vec3_t temp;

		AddPointToBounds( points[i], mins, maxs );
		VectorMA( points[i], 1 * ( 1 + oldMapping * radius * 4 ), projection, temp );
		AddPointToBounds( temp, mins, maxs );
		// make sure we get all the leafs (also the one(s) in front of the hit surface)
		VectorMA( points[i], -20 * ( 1.0 + (float)oldMapping * ( radius / 20.0 ) * 4 ), projectionDir, temp );
		AddPointToBounds( temp, mins, maxs );
	}

	if ( numPoints > MAX_VERTS_ON_POLY ) {
		numPoints = MAX_VERTS_ON_POLY;
	}
	// create the bounding planes for the to be projected polygon
	for ( i = 0 ; i < numPoints ; i++ ) {
		VectorSubtract( points[( i + 1 ) % numPoints], points[i], v1 );
		VectorAdd( points[i], projection, v2 );
		VectorSubtract( points[i], v2, v2 );
		CrossProduct( v1, v2, normals[i] );
		VectorNormalize( normals[i] );
		dists[i] = DotProduct( normals[i], points[i] );
	}
	// add near and far clipping planes for projection
	VectorCopy( projectionDir, normals[numPoints] );
	dists[numPoints] = DotProduct( normals[numPoints], points[0] ) - radius * ( 1 + oldMapping * 10 );
	VectorCopy( projectionDir, normals[numPoints + 1] );
	VectorInverse( normals[numPoints + 1] );
	dists[numPoints + 1] = DotProduct( normals[numPoints + 1], points[0] ) - radius * ( 1 + oldMapping * 10 );
	numPlanes = numPoints + 2;

	numsurfaces = 0;
	R_BoxSurfaces_r( tr.world->nodes, mins, maxs, surfaces, 4096, &numsurfaces, projectionDir );

	returnedPoints = 0;
	returnedFragments = 0;

	// find the closest surface to center the decal there, and wrap around other surfaces
	if ( !oldMapping ) {
		VectorNegate( bestnormal, bestnormal );
	}

	for ( i = 0 ; i < numsurfaces ; i++ ) {

		if ( *surfaces[i] == SF_GRID ) {

			cv = (srfBspSurface_t *) surfaces[i];
			for ( m = 0 ; m < cv->height - 1 ; m++ ) {
				for ( n = 0 ; n < cv->width - 1 ; n++ ) {
					// We triangulate the grid and chop all triangles within
					// the bounding planes of the to be projected polygon.
					// LOD is not taken into account, not such a big deal though.
					//
					// It's probably much nicer to chop the grid itself and deal
					// with this grid as a normal SF_GRID surface so LOD will
					// be applied. However the LOD of that chopped grid must
					// be synced with the LOD of the original curve.
					// One way to do this; the chopped grid shares vertices with
					// the original curve. When LOD is applied to the original
					// curve the unused vertices are flagged. Now the chopped curve
					// should skip the flagged vertices. This still leaves the
					// problems with the vertices at the chopped grid edges.
					//
					// To avoid issues when LOD applied to "hollow curves" (like
					// the ones around many jump pads) we now just add a 2 unit
					// offset to the triangle vertices.
					// The offset is added in the vertex normal vector direction
					// so all triangles will still fit together.
					// The 2 unit offset should avoid pretty much all LOD problems.
					vec3_t fNormal;

					numClipPoints = 3;

					dv = cv->verts + m * cv->width + n;

					VectorCopy( dv[0].xyz, clipPoints[0][0] );
					R_VaoUnpackNormal(fNormal, dv[0].normal);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
					VectorCopy( dv[cv->width].xyz, clipPoints[0][1] );
					R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
					VectorCopy( dv[1].xyz, clipPoints[0][2] );
					R_VaoUnpackNormal(fNormal, dv[1].normal);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]);
					// check the normal of this triangle
					VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 );
					VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 );
					CrossProduct( v1, v2, normal );
					VectorNormalize( normal );
					if ( DotProduct( normal, projectionDir ) < -0.1 ) {
						// add the fragments of this triangle
						R_AddMarkFragments( numClipPoints, clipPoints,
											numPlanes, normals, dists,
											maxPoints, pointBuffer,
											maxFragments, fragmentBuffer,
											&returnedPoints, &returnedFragments, mins, maxs );

						if ( returnedFragments == maxFragments ) {
							return returnedFragments;   // not enough space for more fragments
						}
					}

					VectorCopy( dv[1].xyz, clipPoints[0][0] );
					R_VaoUnpackNormal(fNormal, dv[1].normal);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
					VectorCopy( dv[cv->width].xyz, clipPoints[0][1] );
					R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
					VectorCopy( dv[cv->width + 1].xyz, clipPoints[0][2] );
					R_VaoUnpackNormal(fNormal, dv[cv->width + 1].normal);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]);
					// check the normal of this triangle
					VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 );
					VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 );
					CrossProduct( v1, v2, normal );
					VectorNormalize( normal );
					if ( DotProduct( normal, projectionDir ) < -0.05 ) {
						// add the fragments of this triangle
						R_AddMarkFragments( numClipPoints, clipPoints,
											numPlanes, normals, dists,
											maxPoints, pointBuffer,
											maxFragments, fragmentBuffer,
											&returnedPoints, &returnedFragments, mins, maxs );

						if ( returnedFragments == maxFragments ) {
							return returnedFragments;   // not enough space for more fragments
						}
					}
				}
			}
		} else if ( *surfaces[i] == SF_FACE )     {
			extern float VectorDistance( vec3_t v1, vec3_t v2 );
			vec3_t axis[3];
			float texCoordScale, dot;
			vec3_t originalPoints[4];
			vec3_t newCenter, delta;
			int oldNumPoints;
			float epsilon = 0.5;
			// duplicated so we don't mess with the original clips for the curved surfaces
			vec3_t lnormals[MAX_VERTS_ON_POLY + 2];
			float ldists[MAX_VERTS_ON_POLY + 2];
			vec3_t lmins, lmaxs;

			srfBspSurface_t *surf = ( srfBspSurface_t * ) surfaces[i];

			if ( !oldMapping ) {

				// Ridah, create a new clip box such that this decal surface is mapped onto
				// the current surface without distortion. To find the center of the new clip box,
				// we project the center of the original impact center out along the projection vector,
				// onto the current surface

				// find the center of the new decal
				dot = DotProduct( center, surf->cullPlane.normal );
				dot -= surf->cullPlane.dist;
				// check the normal of this face
				if ( dot < -epsilon && DotProduct(surf->cullPlane.normal, projectionDir) >= 0.01 ) {
					continue;
				} else if ( fabs( dot ) > radius ) {
					continue;
				}
				// if the impact point is behind the surface, subtract the projection, otherwise add it
				VectorMA( center, -dot, bestnormal, newCenter );

				// recalc dot from the offset position
				dot = DotProduct( newCenter, surf->cullPlane.normal );
				dot -= surf->cullPlane.dist;
				VectorMA( newCenter, -dot, surf->cullPlane.normal, newCenter );

				VectorMA( newCenter, MARKER_OFFSET, surf->cullPlane.normal, newCenter );

				// create the texture axis
				VectorNormalize2( surf->cullPlane.normal, axis[0] );
				PerpendicularVector( axis[1], axis[0] );
				RotatePointAroundVector( axis[2], axis[0], axis[1], (float)orientation );
				CrossProduct( axis[0], axis[2], axis[1] );

				texCoordScale = 0.5 * 1.0 / radius;

				// create the full polygon
				for ( j = 0 ; j < 3 ; j++ ) {
					originalPoints[0][j] = newCenter[j] - radius * axis[1][j] - radius * axis[2][j];
					originalPoints[1][j] = newCenter[j] + radius * axis[1][j] - radius * axis[2][j];
					originalPoints[2][j] = newCenter[j] + radius * axis[1][j] + radius * axis[2][j];
					originalPoints[3][j] = newCenter[j] - radius * axis[1][j] + radius * axis[2][j];
				}

				ClearBounds( lmins, lmaxs );

				// create the bounding planes for the to be projected polygon
				for ( j = 0 ; j < 4 ; j++ ) {
					AddPointToBounds( originalPoints[j], lmins, lmaxs );

					VectorSubtract( originalPoints[( j + 1 ) % numPoints], originalPoints[j], v1 );
					VectorSubtract( originalPoints[j], surf->cullPlane.normal, v2 );
					VectorSubtract( originalPoints[j], v2, v2 );
					CrossProduct( v1, v2, lnormals[j] );
					VectorNormalize( lnormals[j] );
					ldists[j] = DotProduct( lnormals[j], originalPoints[j] );
				}
				numPlanes = numPoints;

				// done.

				for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
				{
					for(j = 0; j < 3; j++)
					{
						v = surf->verts[tri[j]].xyz;
						VectorMA(v, MARKER_OFFSET, surf->cullPlane.normal, clipPoints[0][j]);
					}

					oldNumPoints = returnedPoints;

					// add the fragments of this face
					R_AddMarkFragments( 3, clipPoints,
										numPlanes, lnormals, ldists,
										maxPoints, pointBuffer,
										maxFragments, fragmentBuffer,
										&returnedPoints, &returnedFragments, lmins, lmaxs );

					if ( oldNumPoints != returnedPoints ) {
						// flag this surface as already having computed ST's
						fragmentBuffer[returnedFragments - 1].numPoints *= -1;

						// Ridah, calculate ST's
						for ( j = 0 ; j < ( returnedPoints - oldNumPoints ) ; j++ ) {
							VectorSubtract( (float *)pointBuffer + 5 * ( oldNumPoints + j ), newCenter, delta );
							*( (float *)pointBuffer + 5 * ( oldNumPoints + j ) + 3 ) = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale;
							*( (float *)pointBuffer + 5 * ( oldNumPoints + j ) + 4 ) = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale;
						}
					}

					if ( returnedFragments == maxFragments ) {
						return returnedFragments;   // not enough space for more fragments
					}
				}

			} else {    // old mapping

				// check the normal of this face
				//if (DotProduct(surf->cullPlane.normal, projectionDir) > 0.0) {
				//	continue;
				//}

				for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
				{
					for(j = 0; j < 3; j++)
					{
						v = surf->verts[tri[j]].xyz;
						VectorMA(v, MARKER_OFFSET, surf->cullPlane.normal, clipPoints[0][j]);
					}
					// add the fragments of this face
					R_AddMarkFragments( 3, clipPoints,
										numPlanes, normals, dists,
										maxPoints, pointBuffer,
										maxFragments, fragmentBuffer,
										&returnedPoints, &returnedFragments, mins, maxs );
					if ( returnedFragments == maxFragments ) {
						return returnedFragments;   // not enough space for more fragments
					}
				}

			}

		}
		else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) {

			srfBspSurface_t *surf = (srfBspSurface_t *) surfaces[i];

			for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
			{
				for(j = 0; j < 3; j++)
				{
					vec3_t fNormal;
					v = surf->verts[tri[j]].xyz;
					R_VaoUnpackNormal(fNormal, surf->verts[tri[j]].normal);
					VectorMA(v, MARKER_OFFSET, fNormal, clipPoints[0][j]);
				}

				// add the fragments of this face
				R_AddMarkFragments(3, clipPoints,
								   numPlanes, normals, dists,
								   maxPoints, pointBuffer,
								   maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs);
				if(returnedFragments == maxFragments)
				{
					return returnedFragments;	// not enough space for more fragments
				}
			}
		}
	}
	return returnedFragments;
}

