/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program 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.
**
**	This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : MathTest                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Tests/mathtest/raytest.cpp                   $*
 *                                                                                             *
 *                      $Author:: Greg_h                                                      $*
 *                                                                                             *
 *                     $Modtime:: 3/17/00 9:34a                                               $*
 *                                                                                             *
 *                    $Revision:: 10                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


#include "raytest.h"
#include "output.h"
#include "wwmath.h"
#include "tri.h"
#include "vector3.h"
#include "lineseg.h"
#include "aabox.h"
#include "obbox.h"
#include "colmath.h"
#include "p_timer.h"
#include <stdio.h>


void line_box_test(const LineSegClass & line,const OBBoxClass & box,CastResultStruct * res);

/************************************************************************************************
**
**  Ray - Triangle Test Cases
**
************************************************************************************************/

class RayTriTestClass
{
public:
	Vector3			P0;
	Vector3			P1;
	Vector3			V0;		
	Vector3			V1;
	Vector3			V2;
	Vector3			N;
	float				Fraction;
	bool				StartBad;
	LineSegClass	LineSeg;
	TriClass			Tri;

	RayTriTestClass
	(
		const Vector3 p0,					// p0 of ray
		const Vector3 p1,					// p1 of ray
		const Vector3 v0,					// v0 of triangle
		const Vector3 v1,					// v1 of triangle
		const Vector3 v2,					// v2 of triangle
		float frac,							// expected fraction
		bool sol								// expected start solid 
	) : 
		P0(p0),P1(p1),V0(v0),V1(v1),V2(v2),Fraction(frac),StartBad(sol),LineSeg(P0,P1)
	{
		Tri.V[0] = &V0;
		Tri.V[1] = &V1;
		Tri.V[2] = &V2;
		Tri.N = & N;
		Tri.Compute_Normal();
	}
};


RayTriTestClass RayTriTestCases[] = 
{
	RayTriTestClass
	(
		Vector3(0,0,0),
		Vector3(1,0,0),
		Vector3(1,1,1),
		Vector3(1,-1,1),
		Vector3(1,0,-1),
		1.0,
		false
	),
	RayTriTestClass				// ray going down +x, hitting a triangle at x=0.75
	(
		Vector3(0,0,0),		
		Vector3(1,0,0),
		Vector3(0.5,1,1),
		Vector3(1,-1,1),
		Vector3(0.75,0,-1),
		0.75,
		false
	),
	RayTriTestClass				// ray going down the -z axis hitting an x-y triangle
	(
		Vector3(0,0,5),			// ray start
		Vector3(0,0,-5),			// ray end
		Vector3(0,1,0),			// p0
		Vector3(-1,-1,0),			// p1
		Vector3(1,-1,0),			// p2
		0.5,
		false
	),
	RayTriTestClass				// ray going down the -z axis, hitting back of an x-y triangle
	(
		Vector3(0,0,5),			// ray start
		Vector3(0,0,-5),			// ray end
		Vector3(0,1,0),			// p0
		Vector3(1,-1,0),			// p1
		Vector3(-1,-1,0),			// p2
		0.5,
		false
	),
	RayTriTestClass				// ray going down the +x axis, hitting a y-z triangle
	(
		Vector3(0,0,0),			// ray start
		Vector3(5,0,0),			// ray end
		Vector3(2,0,1),			// p0
		Vector3(2,1,-1),			// p1
		Vector3(2,-1,-1),			// p2
		2.0f / 5.0f,
		false
	),
	RayTriTestClass				// ray going down the -z axis, hitting vertex 2 of the triangle
	(
		Vector3(0,0,5),			// ray start
		Vector3(0,0,-5),			// ray end
		Vector3(0,1,0),			// p0
		Vector3(0,0,0),			// p1
		Vector3(1,0,0),			// p2
		0.5f,
		false
	),

	RayTriTestClass				// ray going down the -z axis, hitting center of edge between p0 and p2
	(
		Vector3(0,0,5),			// ray start
		Vector3(0,0,-5),			// ray end
		Vector3(-1,1,0),			// p0
		Vector3(-1,-1,0),			// p1
		Vector3(1,-1,0),			// p2
		0.5f,
		false
	),
};

/************************************************************************************************
**
**  Ray - AABox Test Cases
**
************************************************************************************************/

class RayAABoxTestClass
{
public:
	Vector3			P0;
	Vector3			P1;
	AABoxClass		Box;
	float				Fraction;
	bool				StartBad;
	LineSegClass	LineSeg;
	TriClass			Tri;

	RayAABoxTestClass
	(
		const Vector3 p0,					// p0 of ray
		const Vector3 p1,					// p1 of ray
		const Vector3 center,			// center of the box
		const Vector3 extent,			// extent of the box
		float frac,							// expected fraction
		bool sol								// expected start solid 
	) : 
		P0(p0),P1(p1),Box(center,extent),Fraction(frac),StartBad(sol),LineSeg(P0,P1)
	{
	}
};

RayAABoxTestClass RayAABoxTestCases[] = 
{
	RayAABoxTestClass						
	(
		Vector3(5,0,0),					// p0 of ray
		Vector3(0,0,0),					// p1 of ray
		Vector3(0,0,0),					// center of the box
		Vector3(1,1,1),					// extent of the box
		4.0f / 5.0f,						// expected fraction
		false
	),
	RayAABoxTestClass						
	(
		Vector3(-2,-5,0),					// p0 of ray
		Vector3(0,0,0),					// p1 of ray
		Vector3(0,0,0),					// center of the box
		Vector3(2,2,2),					// extent of the box
		3.0f / 5.0f,						// expected fraction
		false
	),
	RayAABoxTestClass						
	(
		Vector3(-2,5,0),					// p0 of ray
		Vector3(0,0,0),					// p1 of ray
		Vector3(0,0,0),					// center of the box
		Vector3(10,1,1),					// extent of the box
		4.0f / 5.0f,						// expected fraction
		false
	),
	RayAABoxTestClass						// ray just clips the corner of the box (in x-y plane)
	(
		Vector3(2,0,0),					// p0 of ray
		Vector3(0,2,0),					// p1 of ray
		Vector3(0,0,0),					// center of the box
		Vector3(1,1,1),					// extent of the box
		0.5f,									// expected fraction
		false
	),
	RayAABoxTestClass						// ray misses box
	(
		Vector3(1.01f,-3,0),				// p0 of ray
		Vector3(1.01f,3,0),				// p1 of ray
		Vector3(0,0,0),					// center of the box
		Vector3(1,1,1),					// extent of the box
		1.0f,									// expected fraction
		false
	),
};


void Test_Rays(void)
{
	CastResultStruct result;
	CastResultStruct result_check;
	volatile unsigned cycles;
	int numtests;
	int i;

	/*
	** Test the Cast_Ray function with a bunch of ray-triangle pairs.
	*/
	Print_Title("Testing ray-triangle intersection.");

	numtests = sizeof(RayTriTestCases)/sizeof(RayTriTestClass);
	for (i=0; i<numtests; i++) {
		
		RayTriTestClass * testcase = &(RayTriTestCases[i]);

		result.Fraction = 1.0;
		result.StartBad = false;
		result.Normal.Set(0,0,0);

		cycles = Get_CPU_Clock();
		CollisionMath::Collide(testcase->LineSeg,testcase->Tri,&result);
		cycles = Get_CPU_Clock() - cycles;
	
		printf("Ray -> Tri cycles: %d\n",cycles);
		
		CHECK(i,(fabs(testcase->Fraction - result.Fraction) < 0.001f));
	}

	/*
	** Test the Cast_Ray function with a bunch of ray-aabox pairs.
	*/
	Print_Title("Testing ray-aabox intersection.");

	numtests = sizeof(RayAABoxTestCases)/sizeof(RayAABoxTestClass);
	for (i=0; i<numtests; i++) {
		
		RayAABoxTestClass * testcase = &(RayAABoxTestCases[i]);

		result.Fraction = 1.0;
		result.StartBad = false;
		result.Normal.Set(0,0,0);

		cycles = Get_CPU_Clock();
		CollisionMath::Collide(testcase->LineSeg,testcase->Box,&result);
		cycles = Get_CPU_Clock() - cycles;
	
		printf("Ray -> AABox cycles: %d\n",cycles);
		
		CHECK(i,(fabs(testcase->Fraction - result.Fraction) < 0.001f));
	}

	/*
	** Test the Cast_Ray function on some random oriented boxes
	*/
	Print_Title("Testing ray-obbox intersection.");
	for (i=0; i<30; i++) {

		result.Fraction = 1.0;
		result.StartBad = false;
		result.Normal.Set(0,0,0);

		// create a random box
		OBBoxClass box;
		box.Init_Random(1.0f,2.75f);

		// create a random line
		LineSegClass line;
		line.Set_Random(Vector3(-3,-3,-3),Vector3(3,3,3));

		// use the ray-box test
		cycles = Get_CPU_Clock();
		CollisionMath::Collide(line,box,&result);
		cycles = Get_CPU_Clock() - cycles;
		
		// double-check the result
		result_check.Fraction = 1.0f;
		result_check.StartBad = false;
		result_check.Normal.Set(0,0,0);
		line_box_test(line,box,&result_check);

		// print what happened
		bool passed = false;
		if (fabs(result_check.Fraction - result.Fraction) < 0.001f) {
			passed = true;
		}

		printf("test %3d  cycles: %6d\tfraction: %1.8f\t",i,cycles,result.Fraction);
		if (passed) {
			printf("passed\n");
		} else if (result.StartBad) {
			printf("(start inside)\n");
		} else {
			printf("<<failed!>>\n");
		}
	}


	/*
	** Test the Overlap_Test function on some random AABoxes
	*/
	int total_tests = 0;
	int total_cycles = 0;
	int outside_tests = 0;
	int outside_cycles = 0;
	int overlap_tests = 0;
	int overlap_cycles = 0;
	
	CollisionMath::OverlapType overlap;
	Print_Title("Testing ray-aabox overlap.");
	for (i=0; i<10000; i++) {

		// create a random box
		AABoxClass box;
		box.Init_Random(-1.0f,1.0f,1.0f,2.0f);

		// create a random line
		const float DIMENSION = 5.0f;
		LineSegClass line;
		line.Set_Random(Vector3(-DIMENSION,-DIMENSION,-DIMENSION),Vector3(DIMENSION,DIMENSION,DIMENSION));

		// use the overlap_test
		cycles = Get_CPU_Clock();
		overlap = CollisionMath::Overlap_Test(box,line);
		cycles = Get_CPU_Clock() - cycles;
		
		// print what happened
		printf("test %3d  cycles: %6d\tfraction: %1.8f\t",i,cycles,result.Fraction);
		if (overlap == CollisionMath::OUTSIDE) {
			printf("outside\n");
		} else if (overlap == CollisionMath::INSIDE) {
			printf("inside\n");
		} else {
			printf("overlapped\n");
		}
		
		// stats
		total_tests++;
		total_cycles += cycles;
		if (overlap == CollisionMath::OUTSIDE) {
			outside_tests++;
			outside_cycles += cycles;
		} else {
			overlap_tests++;
			overlap_cycles += cycles;
		}
	}

	printf("Total Tests:              %d\n",total_tests);
	printf("Average Cycles:           %f\n",(float)total_cycles / (float)total_tests);
	printf("Outside Tests:            %d\n",outside_tests);
	printf("Average Outside Cycles:   %f\n",(float)outside_cycles / (float)outside_tests);
	printf("Overlap Tests:            %d\n",overlap_tests);
	printf("Average Overlap Cycles:   %f\n",(float)overlap_cycles / (float)overlap_tests);

}




void line_box_test(const LineSegClass & line,const OBBoxClass & box,CastResultStruct * res)
{
	Vector3 point[8];

	point[0] = box.Center + box.Basis * Vector3( box.Extent.X, box.Extent.Y, box.Extent.Z);
	point[1] = box.Center + box.Basis * Vector3(-box.Extent.X, box.Extent.Y, box.Extent.Z);
	point[2] = box.Center + box.Basis * Vector3(-box.Extent.X,-box.Extent.Y, box.Extent.Z);
	point[3] = box.Center + box.Basis * Vector3( box.Extent.X,-box.Extent.Y, box.Extent.Z);

	point[4] = box.Center + box.Basis * Vector3( box.Extent.X, box.Extent.Y,-box.Extent.Z);
	point[5] = box.Center + box.Basis * Vector3(-box.Extent.X, box.Extent.Y,-box.Extent.Z);
	point[6] = box.Center + box.Basis * Vector3(-box.Extent.X,-box.Extent.Y,-box.Extent.Z);
	point[7] = box.Center + box.Basis * Vector3( box.Extent.X,-box.Extent.Y,-box.Extent.Z);

	static int triverts[12][3] = 
	{
		{ 0,1,2 },
		{ 0,2,3 },
		{ 4,5,1 },
		{ 4,1,0 },
		{ 1,5,6 },
		{ 1,6,2 },
		{ 3,2,6 },
		{ 3,6,7 },
		{ 4,0,3 },
		{ 4,3,7 },
		{ 4,7,6 },
		{ 4,6,5 }
	};

	// first, check if ray starts inside box?
	
	// now, check for collision with each triangle
	for (int i=0; i<12; i++) {
		
		TriClass tri;
		Vector3 normal;

		tri.V[0] = &point[triverts[i][0]];
		tri.V[1] = &point[triverts[i][1]];
		tri.V[2] = &point[triverts[i][2]];
		tri.N = &normal;
		tri.Compute_Normal();

		CollisionMath::Collide(line,tri,res);
	}
}
