// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2024 by Sonic Team Junior.
// Copyright (C) 2009 by Stephen McGranahan.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  tables.c
/// \brief Lookup tables
///        Do not try to look them up :-).

// In the order of appearance:

// fixed_t finetangent[4096]   - Tangents LUT.
//  Should work with BAM fairly well (12 of 16bit, effectively, by shifting).

// fixed_t finesine[10240]     - Sine lookup.
//  Guess what, serves as cosine, too.
//  Remarkable thing is, how to use BAMs with this?

// fixed_t tantoangle[2049]    - ArcTan LUT,
//  Maps tan(angle) to angle fast. Gotta search.

#include "tables.h"

unsigned SlopeDiv(unsigned num, unsigned den)
{
	unsigned ans;
	num <<= (FINE_FRACBITS-FRACBITS);
	den <<= (FINE_FRACBITS-FRACBITS);
	if (den < 512)
		return SLOPERANGE;
	ans = (num<<3) / (den>>8);
	return ans <= SLOPERANGE ? ans : SLOPERANGE;
}

UINT64 SlopeDivEx(unsigned int num, unsigned int den)
{
	UINT64 ans;
	if (den < 512)
		return SLOPERANGE;
	ans = ((UINT64)num<<3)/(den>>8);
	return ans <= SLOPERANGE ? ans : SLOPERANGE;
}

fixed_t AngleFixed(angle_t af)
{
	angle_t wa = ANGLE_180;
	fixed_t wf = 180*FRACUNIT;
	fixed_t rf = 0*FRACUNIT;

	while (af)
	{
		while (af < wa)
		{
			wa /= 2;
			wf /= 2;
		}
		rf += wf;
		af -= wa;
	}

	return rf;
}

static FUNCMATH angle_t AngleAdj(const fixed_t fa, const fixed_t wf,
                                 angle_t ra)
{
	const angle_t adj = 0x77;
	const boolean fan = fa < 0;
	const fixed_t sl = FixedDiv(fa, wf*2);
	const fixed_t lb = fa % (wf*2);
	const fixed_t lo = (wf*2)-lb;

	if (ra == 0)
	{
		if (lb == 0)
		{
			ra = FixedMul(FRACUNIT/512, sl);
			if (ra > FRACUNIT/64)
				return InvAngle(ra);
			return ra;
		}
		else if (lb > 0)
			return InvAngle(FixedMul(lo*FRACUNIT, adj));
		else
			return InvAngle(FixedMul(lo*FRACUNIT, adj));
	}

	if (fan)
		return InvAngle(ra);
	else
		return ra;
}

angle_t FixedAngleC(fixed_t fa, fixed_t factor)
{
	angle_t wa = ANGLE_180;
	fixed_t wf = 180*FRACUNIT;
	angle_t ra = 0;
	const fixed_t cfa = fa;
	fixed_t cwf = wf;

	if (fa == 0)
		return 0;

	// -2,147,483,648 has no absolute value in a 32 bit signed integer
	// so this code _would_ infinite loop if passed it
	if (fa == INT32_MIN)
		return 0;

	if (factor == 0)
		return FixedAngle(fa);
	else if (factor > 0)
		cwf = wf = FixedMul(wf, factor);
	else if (factor < 0)
		cwf = wf = FixedDiv(wf, -factor);

	fa = abs(fa);

	while (fa)
	{
		while (fa < wf)
		{
			wa /= 2;
			wf /= 2;
		}
		ra = ra + wa;
		fa = fa - wf;
	}

	return AngleAdj(cfa, cwf, ra);
}

angle_t FixedAngle(fixed_t fa)
{
	angle_t wa = ANGLE_180;
	fixed_t wf = 180*FRACUNIT;
	angle_t ra = 0;
	const fixed_t cfa = fa;
	const fixed_t cwf = wf;

	if (fa == 0)
		return 0;

	// -2,147,483,648 has no absolute value in a 32 bit signed integer
	// so this code _would_ infinite loop if passed it
	if (fa == INT32_MIN)
		return 0;

	fa = abs(fa);

	while (fa)
	{
		while (fa < wf)
		{
			wa /= 2;
			wf /= 2;
		}
		ra = ra + wa;
		fa = fa - wf;
	}

	return AngleAdj(cfa, cwf, ra);
}


#include "t_ftan.c"

#include "t_fsin.c"
fixed_t *finecosine = &finesine[FINEANGLES/4];

#include "t_tan2a.c"

#include "t_facon.c"


FUNCMATH angle_t FixedAcos(fixed_t x)
{
	if (-FRACUNIT > x || x >= FRACUNIT) return 0;
	return fineacon[((x<<(FINE_FRACBITS-FRACBITS)))+FRACUNIT];
}

//
// AngleBetweenVectors
//
// This checks to see if a point is inside the ranges of a polygon
//
angle_t FV2_AngleBetweenVectors(const vector2_t *Vector1, const vector2_t *Vector2)
{
	// Remember, above we said that the Dot Product of returns the cosine of the angle
	// between 2 vectors?  Well, that is assuming they are unit vectors (normalize vectors).
	// So, if we don't have a unit vector, then instead of just saying  arcCos(DotProduct(A, B))
	// We need to divide the dot product by the magnitude of the 2 vectors multiplied by each other.
	// Here is the equation:   arc cosine of (V . W / || V || * || W || )
	// the || V || means the magnitude of V.  This then cancels out the magnitudes dot product magnitudes.
	// But basically, if you have normalize vectors already, you can forget about the magnitude part.

	// Get the dot product of the vectors
	fixed_t dotProduct = FV2_Dot(Vector1, Vector2);

	// Get the product of both of the vectors magnitudes
	fixed_t vectorsMagnitude = FixedMul(FV2_Magnitude(Vector1), FV2_Magnitude(Vector2));

	// Return the arc cosine of the (dotProduct / vectorsMagnitude) which is the angle in RADIANS.
	return FixedAcos(FixedDiv(dotProduct, vectorsMagnitude));
}

angle_t FV3_AngleBetweenVectors(const vector3_t *Vector1, const vector3_t *Vector2)
{
	// Remember, above we said that the Dot Product of returns the cosine of the angle
	// between 2 vectors?  Well, that is assuming they are unit vectors (normalize vectors).
	// So, if we don't have a unit vector, then instead of just saying  arcCos(DotProduct(A, B))
	// We need to divide the dot product by the magnitude of the 2 vectors multiplied by each other.
	// Here is the equation:   arc cosine of (V . W / || V || * || W || )
	// the || V || means the magnitude of V.  This then cancels out the magnitudes dot product magnitudes.
	// But basically, if you have normalize vectors already, you can forget about the magnitude part.

	// Get the dot product of the vectors
	fixed_t dotProduct = FV3_Dot(Vector1, Vector2);

	// Get the product of both of the vectors magnitudes
	fixed_t vectorsMagnitude = FixedMul(FV3_Magnitude(Vector1), FV3_Magnitude(Vector2));

	// Return the arc cosine of the (dotProduct / vectorsMagnitude) which is the angle in RADIANS.
	return FixedAcos(FixedDiv(dotProduct, vectorsMagnitude));
}

//
// InsidePolygon
//
// This checks to see if a point is inside the ranges of a polygon
//
boolean FV2_InsidePolygon(const vector2_t *vIntersection, const vector2_t *Poly, const INT32 vertexCount)
{
	INT32 i;
	UINT64 Angle = 0;					// Initialize the angle
	vector2_t vA, vB;					// Create temp vectors

	// Just because we intersected the plane, doesn't mean we were anywhere near the polygon.
	// This functions checks our intersection point to make sure it is inside of the polygon.
	// This is another tough function to grasp at first, but let me try and explain.
	// It's a brilliant method really, what it does is create triangles within the polygon
	// from the intersection point.  It then adds up the inner angle of each of those triangles.
	// If the angles together add up to 360 degrees (or 2 * PI in radians) then we are inside!
	// If the angle is under that value, we must be outside of polygon.  To further
	// understand why this works, take a pencil and draw a perfect triangle.  Draw a dot in
	// the middle of the triangle.  Now, from that dot, draw a line to each of the vertices.
	// Now, we have 3 triangles within that triangle right?  Now, we know that if we add up
	// all of the angles in a triangle we get 360 right?  Well, that is kinda what we are doing,
	// but the inverse of that.  Say your triangle is an isosceles triangle, so add up the angles
	// and you will get 360 degree angles.  90 + 90 + 90 is 360.

	for (i = 0; i < vertexCount; i++)		// Go in a circle to each vertex and get the angle between
	{
		FV2_Point2Vec(&Poly[i], vIntersection, &vA);	// Subtract the intersection point from the current vertex
												// Subtract the point from the next vertex
		FV2_Point2Vec(&Poly[(i + 1) % vertexCount], vIntersection, &vB);

		Angle += FV2_AngleBetweenVectors(&vA, &vB);	// Find the angle between the 2 vectors and add them all up as we go along
	}

	// Now that we have the total angles added up, we need to check if they add up to 360 degrees.
	// Since we are using the dot product, we are working in radians, so we check if the angles
	// equals 2*PI.  We defined PI in 3DMath.h.  You will notice that we use a MATCH_FACTOR
	// in conjunction with our desired degree.  This is because of the inaccuracy when working
	// with floating point numbers.  It usually won't always be perfectly 2 * PI, so we need
	// to use a little twiddling.  I use .9999, but you can change this to fit your own desired accuracy.

	if(Angle >= ANGLE_MAX)	// If the angle is greater than 2 PI, (360 degrees)
		return 1; // The point is inside of the polygon

	return 0; // If you get here, it obviously wasn't inside the polygon.
}

boolean FV3_InsidePolygon(const vector3_t *vIntersection, const vector3_t *Poly, const INT32 vertexCount)
{
	INT32 i;
	UINT64 Angle = 0;					// Initialize the angle
	vector3_t vA, vB;					// Create temp vectors

	// Just because we intersected the plane, doesn't mean we were anywhere near the polygon.
	// This functions checks our intersection point to make sure it is inside of the polygon.
	// This is another tough function to grasp at first, but let me try and explain.
	// It's a brilliant method really, what it does is create triangles within the polygon
	// from the intersection point.  It then adds up the inner angle of each of those triangles.
	// If the angles together add up to 360 degrees (or 2 * PI in radians) then we are inside!
	// If the angle is under that value, we must be outside of polygon.  To further
	// understand why this works, take a pencil and draw a perfect triangle.  Draw a dot in
	// the middle of the triangle.  Now, from that dot, draw a line to each of the vertices.
	// Now, we have 3 triangles within that triangle right?  Now, we know that if we add up
	// all of the angles in a triangle we get 360 right?  Well, that is kinda what we are doing,
	// but the inverse of that.  Say your triangle is an isosceles triangle, so add up the angles
	// and you will get 360 degree angles.  90 + 90 + 90 is 360.

	for (i = 0; i < vertexCount; i++)		// Go in a circle to each vertex and get the angle between
	{
		FV3_Point2Vec(&Poly[i], vIntersection, &vA);	// Subtract the intersection point from the current vertex
												// Subtract the point from the next vertex
		FV3_Point2Vec(&Poly[(i + 1) % vertexCount], vIntersection, &vB);

		Angle += FV3_AngleBetweenVectors(&vA, &vB);	// Find the angle between the 2 vectors and add them all up as we go along
	}

	// Now that we have the total angles added up, we need to check if they add up to 360 degrees.
	// Since we are using the dot product, we are working in radians, so we check if the angles
	// equals 2*PI.  We defined PI in 3DMath.h.  You will notice that we use a MATCH_FACTOR
	// in conjunction with our desired degree.  This is because of the inaccuracy when working
	// with floating point numbers.  It usually won't always be perfectly 2 * PI, so we need
	// to use a little twiddling.  I use .9999, but you can change this to fit your own desired accuracy.

	if(Angle >= ANGLE_MAX)	// If the angle is greater than 2 PI, (360 degrees)
		return 1; // The point is inside of the polygon

	return 0; // If you get here, it obviously wasn't inside the polygon.
}

//
// IntersectedPolygon
//
// This checks if a line is intersecting a polygon
//
boolean FV3_IntersectedPolygon(const vector3_t *vPoly, const vector3_t *vLine, const INT32 vertexCount, vector3_t *collisionPoint)
{
	vector3_t vNormal, vIntersection;
	fixed_t originDistance = 0*FRACUNIT;


	// First we check to see if our line intersected the plane.  If this isn't true
	// there is no need to go on, so return false immediately.
	// We pass in address of vNormal and originDistance so we only calculate it once

	if(!FV3_IntersectedPlane(vPoly, vLine,   &vNormal,   &originDistance))
		return false;

	// Now that we have our normal and distance passed back from IntersectedPlane(),
	// we can use it to calculate the intersection point.  The intersection point
	// is the point that actually is ON the plane.  It is between the line.  We need
	// this point test next, if we are inside the polygon.  To get the I-Point, we
	// give our function the normal of the plane, the points of the line, and the originDistance.

	FV3_IntersectionPoint(&vNormal, vLine, originDistance, &vIntersection);

	// Now that we have the intersection point, we need to test if it's inside the polygon.
	// To do this, we pass in :
	// (our intersection point, the polygon, and the number of vertices our polygon has)

	if(FV3_InsidePolygon(&vIntersection, vPoly, vertexCount))
	{
		if (collisionPoint != NULL) // Optional - load the collision point.
		{
			collisionPoint->x = vIntersection.x;
			collisionPoint->y = vIntersection.y;
			collisionPoint->z = vIntersection.z;
		}
		return true; // We collided!
	}

	// If we get here, we must have NOT collided
	return false;
}

//
// RotateVector
//
// Rotates a vector around another vector
//
void FV3_Rotate(vector3_t *rotVec, const vector3_t *axisVec, const angle_t angle)
{
	// Rotate the point (x,y,z) around the vector (u,v,w)
	fixed_t ux = FixedMul(axisVec->x, rotVec->x);
	fixed_t uy = FixedMul(axisVec->x, rotVec->y);
	fixed_t uz = FixedMul(axisVec->x, rotVec->z);
	fixed_t vx = FixedMul(axisVec->y, rotVec->x);
	fixed_t vy = FixedMul(axisVec->y, rotVec->y);
	fixed_t vz = FixedMul(axisVec->y, rotVec->z);
	fixed_t wx = FixedMul(axisVec->z, rotVec->x);
	fixed_t wy = FixedMul(axisVec->z, rotVec->y);
	fixed_t wz = FixedMul(axisVec->z, rotVec->z);
	fixed_t sa = FINESINE(angle);
	fixed_t ca = FINECOSINE(angle);
	fixed_t ua = ux+vy+wz;
	fixed_t ax = FixedMul(axisVec->x,ua);
	fixed_t ay = FixedMul(axisVec->y,ua);
	fixed_t az = FixedMul(axisVec->z,ua);
	fixed_t xs = FixedMul(axisVec->x,axisVec->x);
	fixed_t ys = FixedMul(axisVec->y,axisVec->y);
	fixed_t zs = FixedMul(axisVec->z,axisVec->z);
	fixed_t bx = FixedMul(rotVec->x,ys+zs);
	fixed_t by = FixedMul(rotVec->y,xs+zs);
	fixed_t bz = FixedMul(rotVec->z,xs+ys);
	fixed_t cx = FixedMul(axisVec->x,vy+wz);
	fixed_t cy = FixedMul(axisVec->y,ux+wz);
	fixed_t cz = FixedMul(axisVec->z,ux+vy);
	fixed_t dx = FixedMul(bx-cx, ca);
	fixed_t dy = FixedMul(by-cy, ca);
	fixed_t dz = FixedMul(bz-cz, ca);
	fixed_t ex = FixedMul(vz-wy, sa);
	fixed_t ey = FixedMul(wx-uz, sa);
	fixed_t ez = FixedMul(uy-vx, sa);

	rotVec->x = ax+dx+ex;
	rotVec->y = ay+dy+ey;
	rotVec->z = az+dz+ez;
}

#define M(row,col) dest->m[row * 4 + col]
matrix_t *FM_Rotate(matrix_t *dest, angle_t angle, fixed_t x, fixed_t y, fixed_t z)
{
	const fixed_t sinA = FINESINE(angle>>ANGLETOFINESHIFT);
	const fixed_t cosA = FINECOSINE(angle>>ANGLETOFINESHIFT);
	const fixed_t invCosA = FRACUNIT - cosA;
	vector3_t nrm;
	fixed_t xSq, ySq, zSq;
	fixed_t sx, sy, sz;
	fixed_t sxy, sxz, syz;

	nrm.x = x;
	nrm.y = y;
	nrm.z = z;
	FV3_Normalize(&nrm);

	x = nrm.x;
	y = nrm.y;
	z = nrm.z;

	xSq = FixedMul(x, FixedMul(invCosA,x));
	ySq = FixedMul(y, FixedMul(invCosA,y));
	zSq = FixedMul(z, FixedMul(invCosA,z));

	sx = FixedMul(sinA, x);
	sy = FixedMul(sinA, y);
	sz = FixedMul(sinA, z);

	sxy = FixedMul(x, FixedMul(invCosA,y));
	sxz = FixedMul(x, FixedMul(invCosA,z));
	syz = FixedMul(y, FixedMul(invCosA,z));


	M(0, 0) = xSq + cosA;
	M(1, 0) = sxy - sz;
	M(2, 0) = sxz + sy;
	M(3, 0) = 0;

	M(0, 1) = sxy + sz;
	M(1, 1) = ySq + cosA;
	M(2, 1) = syz - sx;
	M(3, 1) = 0;

	M(0, 2) = sxz - sy;
	M(1, 2) = syz + sx;
	M(2, 2) = zSq + cosA;
	M(3, 2) = 0;

	M(0, 3) = 0;
	M(1, 3) = 0;
	M(2, 3) = 0;
	M(3, 3) = FRACUNIT;

	return dest;
}


matrix_t *FM_RotateX(matrix_t *dest, angle_t rad)
{
	const angle_t fa = rad>>ANGLETOFINESHIFT;
	const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);

	M(0, 0) = FRACUNIT;
	M(0, 1) = 0;
	M(0, 2) = 0;
	M(0, 3) = 0;
	M(1, 0) = 0;
	M(1, 1) = cosrad;
	M(1, 2) = sinrad;
	M(1, 3) = 0;
	M(2, 0) = 0;
	M(2, 1) = -sinrad;
	M(2, 2) = cosrad;
	M(2, 3) = 0;
	M(3, 0) = 0;
	M(3, 1) = 0;
	M(3, 2) = 0;
	M(3, 3) = FRACUNIT;

	return dest;
}

matrix_t *FM_RotateY(matrix_t *dest, angle_t rad)
{
	const angle_t fa = rad>>ANGLETOFINESHIFT;
	const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);

	M(0, 0) = cosrad;
	M(0, 1) = 0;
	M(0, 2) = -sinrad;
	M(0, 3) = 0;
	M(1, 0) = 0;
	M(1, 1) = FRACUNIT;
	M(1, 2) = 0;
	M(1, 3) = 0;
	M(2, 0) = sinrad;
	M(2, 1) = 0;
	M(2, 2) = cosrad;
	M(2, 3) = 0;
	M(3, 0) = 0;
	M(3, 1) = 0;
	M(3, 2) = 0;
	M(3, 3) =  FRACUNIT;

	return dest;
}

matrix_t *FM_RotateZ(matrix_t *dest, angle_t rad)
{
	const angle_t fa = rad>>ANGLETOFINESHIFT;
	const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);

	M(0, 0) = cosrad;
	M(0, 1) = sinrad;
	M(0, 2) = 0;
	M(0, 3) = 0;
	M(1, 0) = -sinrad;
	M(1, 1) = cosrad;
	M(1, 2) = 0;
	M(1, 3) = 0;
	M(2, 0) = 0;
	M(2, 1) = 0;
	M(2, 2) = FRACUNIT;
	M(2, 3) = 0;
	M(3, 0) = 0;
	M(3, 1) = 0;
	M(3, 2) = 0;
	M(3, 3) = FRACUNIT;

	return dest;
}

#undef M
