/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 * Copyright (C) 2010-2011 Robert Beckebans <trebor_7@users.sourceforge.net>
 * Copyright (C) 2008 Oliver McFadden
 *
 * ET: Legacy
 * Copyright (C) 2012-2018 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy 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.
 *
 * ET: Legacy 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 ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL 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 source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file renderer2/tr_marks.c
 * @brief Polygon projection on the world polygons
 */

#include "tr_local.h"

#define MAX_VERTS_ON_POLY       64

#define MARKER_OFFSET           0

/**
 * @brief Out must have space for two more vertexes than in
 * @param[in] numInPoints
 * @param[in] inPoints
 * @param[out] numOutPoints
 * @param[out] outPoints
 * @param[in] normal
 * @param[in] dist
 * @param[in] epsilon
 */
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];
	int   sides[MAX_VERTS_ON_POLY + 4];
	int   counts[3];
	float dot;
	int   i, j;
	float *p1, *p2, *clip;
	float d;

	Com_Memset(dists, 0, MAX_VERTS_ON_POLY + 4);
	Com_Memset(sides, 0, MAX_VERTS_ON_POLY + 4);

	// 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;
		Com_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.f)
		{
			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)++;
	}
}

/**
 * @brief R_BoxSurfaces_r
 * @param[in,out] node
 * @param[in] mins
 * @param[in] maxs
 * @param[in,out] list
 * @param[in] listsize
 * @param[in,out] listlength
 * @param[in] dir
 */
void R_BoxSurfaces_r(bspNode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir)
{
	int          s, c;
	bspSurface_t *surf, **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];
		}
	}

	// add the individual surfaces
	mark = node->markSurfaces;
	c    = node->numMarkSurfaces;
	while (c--)
	{
		if (*listlength >= listsize)
		{
			break;
		}

		surf = *mark;

		// check if the surface has NOIMPACT or NOMARKS set
		if ((surf->shader->surfaceFlags & (SURF_NOIMPACT | SURF_NOMARKS)) || (surf->shader->contentFlags & CONTENTS_FOG))
		{
			surf->viewCount = tr.viewCountNoReset;
		}
		// 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, &((srfSurfaceFace_t *) surf->data)->plane);
			if (s == 1 || s == 2)
			{
				surf->viewCount = tr.viewCountNoReset;
			}
			else if (DotProduct(((srfSurfaceFace_t *) surf->data)->plane.normal, dir) > -0.5f)
			{
				// don't add faces that make sharp angles with the projection direction
				surf->viewCount = tr.viewCountNoReset;
			}
		}
		else if (*(surfaceType_t *) (surf->data) != SF_GRID
		         && *(surfaceType_t *) (surf->data) != SF_TRIANGLES)
		{
			surf->viewCount = tr.viewCountNoReset;
		}
		// check the viewCount because the surface may have
		// already been added if it spans multiple leafs
		if (surf->viewCount != tr.viewCountNoReset)
		{
			surf->viewCount   = tr.viewCountNoReset;
			list[*listlength] = (surfaceType_t *) surf->data;
			(*listlength)++;
		}
		mark++;
	}
}

/**
 * @brief R_AddMarkFragments
 * @param[in] numClipPoints
 * @param[in] clipPoints
 * @param[in] numPlanes
 * @param[in] normals
 * @param[in] dists
 * @param[in] maxPoints
 * @param[in] pointBuffer
 * @param maxFragments - unused
 * @param[in] fragmentBuffer
 * @param[in,out] returnedPoints
 * @param[in,out] returnedFragments
 * @param mins - unused
 * @param maxs - unused
 */
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;

#if defined(SSEVEC3_T)
	Com_Memcpy(pointBuffer + (*returnedPoints) * 4, clipPoints[pingPong], numClipPoints * sizeof(vec3_t));
#else
	Com_Memcpy(pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t));
#endif

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

/**
 * @brief R_MarkFragments
 * @param[in] numPoints
 * @param[in] points
 * @param[in] projection
 * @param[in] maxPoints
 * @param[in] pointBuffer
 * @param[in] maxFragments
 * @param[in] fragmentBuffer
 * @return
 */
int R_MarkFragments(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;
	srfSurfaceFace_t *face;
	srfGridMesh_t    *cv;
	srfTriangles_t   *trisurf;
	srfVert_t        *dv;
	srfTriangle_t    *tri;
	vec3_t           normal;
	vec3_t           projectionDir;
	vec3_t           v1, v2;

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

	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);
	//etl_assert(numsurfaces <= 64);
	//etl_assert(numsurfaces != 64);

	returnedPoints    = 0;
	returnedFragments = 0;

	for (i = 0; i < numsurfaces; i++)
	{
		if (*surfaces[i] == SF_GRID)
		{
			cv = (srfGridMesh_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.

					numClipPoints = 3;

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

					VectorCopy(dv[0].xyz, clipPoints[0][0]);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]);
					VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
					VectorCopy(dv[1].xyz, clipPoints[0][2]);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, 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.1f)
					{
						// 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]);
					VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]);
					VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
					VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
					VectorCopy(dv[cv->width + 1].xyz, clipPoints[0][2]);
					VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width + 1].normal, 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.05f)
					{
						// 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)
		{
			face = (srfSurfaceFace_t *) surfaces[i];

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

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

				/*
				   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.5) continue;
				   */

				// 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_noMarksOnTrisurfs->integer)
		{
			trisurf = (srfTriangles_t *) surfaces[i];

			for (k = 0, tri = trisurf->triangles; k < trisurf->numTriangles; k++, tri++)
			{
				for (j = 0; j < 3; j++)
				{
					VectorCopy(trisurf->verts[tri->indexes[j]].xyz, clipPoints[0][j]);
				}

				/*
				   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.5) continue;
				   */

				// 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;
}
