/* ** 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 . */ /*********************************************************************************************** *** 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 : WWMath * * * * $Archive:: /Commando/Code/Tests/mathtest/obboxtest.cpp $* * * * Author:: Greg Hjelstrom * * * * $Modtime:: 12/06/00 9:33a $* * * * $Revision:: 8 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * test_obb_tri -- performs some "hard-coded" obb-tri collision tests * * brute_force_cast_obb_tri -- binary-search method of finding the collision time for obb-tr * * brute_force_obb_tri_test -- collide random obb's into random tri's * * test_obb_obb -- performs some "hard-coded" obb-obb collision tests * * brute_force_cast_obb_obb -- brute force function to verify collision of two obb's * * brute_force_obb_obb_test -- collide random obb's together * * Test_OBBoxes -- run all of the obb-obb and obb-tri tests * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "obboxtest.h" #include "output.h" #include "vector3.h" #include "tri.h" #include "obbox.h" #include "wwmath.h" #include "colmath.h" #include "p_timer.h" #include #include #include //#define MANUAL_DEBUGGING static Matrix3 _RotateZ45 ( (float)WWMATH_SQRT2/2.0f, -(float)WWMATH_SQRT2/2.0f, 0.0f, (float)WWMATH_SQRT2/2.0f, (float)WWMATH_SQRT2/2.0f, 0.0f, 0.0f, 0.0f, 1.0f ); static Matrix3 _RotateZ90 ( 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); /********************************************************************* ** ** OBBoxTriTestClass ** Test Data for OBBox->Triangle collision ** *********************************************************************/ class OBBoxTriTestClass { public: OBBoxClass Box; Vector3 BoxMove; Vector3 V0; Vector3 V1; Vector3 V2; Vector3 N; TriClass Tri; float Fraction; bool StartBad; OBBoxTriTestClass ( const Vector3 &c, // center of box const Vector3 &e, // extent of box const Matrix3 &b, // basis of box const Vector3 &m, // move for the box 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 ) { BoxMove = m; V0 = v0; V1 = v1; V2 = v2; Fraction = frac; StartBad = sol; /* ** Initialize the triangle */ Tri.V[0] = &V0; Tri.V[1] = &V1; Tri.V[2] = &V2; Tri.N = & N; Tri.Compute_Normal(); /* ** Initialize the box */ Box.Center = c; Box.Extent = e; Box.Basis = b; } }; OBBoxTriTestClass Test0 ( Vector3(0,0,0), // box starting at origin Vector3(2,1,1), // extent is 2 units along x, 1 y, 1 z Matrix3(1), Vector3(1,0,0), // moving 5 along x axis Vector3(6,-3,-1), // triangle crossing x and y extent but not colliding Vector3(8,-1,2), Vector3(9,0,-1), 1.0f, false ); OBBoxTriTestClass Test1 ( Vector3(3,0,0), Vector3(1,2,1), Matrix3(1), Vector3(0,-2,0), Vector3(1,-3,0), Vector3(2,-3,5), Vector3(6,-3,1), 0.5f, false ); OBBoxTriTestClass Test2 ( Vector3(-3.5,-1.5,0), // sweeping a 3x3 box along pos x and neg y Vector3(1.5,1.5,1.5), Matrix3(1), Vector3(4,-4,0), Vector3(-4,-4,-1), // into a polygon in y-z plane Vector3(-2,-4,5), Vector3(0,-4,1), 0.25f, // should only move 25% false ); OBBoxTriTestClass Test3 ( Vector3(-3.5,-1.5,0) + 0.25f * Vector3(4,-4,0), Vector3(1.5,1.5,1.5), // starting at end of test2's move, should be 0.0 but not StartBad! Matrix3(1), Vector3(4,-4,0), Vector3(-4,-4,-1), // into a polygon in y-z plane Vector3(-2,-4,5), Vector3(0,-4,1), 0.0f, false ); OBBoxTriTestClass Test4 ( Vector3(-3.5f,-1.5f,0), // Same as test 2 Vector3(1.5f,1.5f,1.5f), Matrix3(1), Vector3(4,-4,0), Vector3(-9,-4,-1), // into a polygon in y-z plane *but* just barely not in the way Vector3(-8,-4,5), Vector3(-4.9f,-4,0), 1.0f, // should move 100% false ); OBBoxTriTestClass Test5 ( Vector3(-3.5,-1.5,0), // Same as test 3 with box brushing polygon vertex. Vector3(1.5,1.5,1.5), Matrix3(1), Vector3(4,-4,0), Vector3(-9,-4,-1), // into a polygon in y-z plane just touching path of box Vector3(-8,-4,5), Vector3(-4,-4,0), 1.0f, // should move 100% false ); OBBoxTriTestClass Test6 ( Vector3(-3.5f,-1.5f,0), // Same as test 3 with box brushing polygon vertex. Vector3(1.5f,1.5f,1.5f), Matrix3(1), Vector3(4,-4,0), Vector3(-9,-4,-1), // into a polygon in y-z plane just barely hitting it Vector3(-8,-4,5), Vector3(-3.999f,-4,0), // (-4,-4,0) would just "touch" (see test5) 0.25f, // should move 25% false ); OBBoxTriTestClass Test7 ( Vector3(0,0,0), // This is a case where the box starts out intersecting Vector3(5,5,5), Matrix3(1), Vector3(4,4,0), Vector3(1,4,-1), Vector3(2,4,5), Vector3(5,4,0), 0.0f, true ); OBBoxTriTestClass Test8 ( Vector3(-2.5,2,0), // center Vector3(1.5,1,1), // extent Matrix3(1), // basis Vector3(3,0,0), // move Vector3(1,2,0), // v0 Vector3(3,4,5), // v1 Vector3(4,5,-1), // v2 0.66666667f, false ); OBBoxTriTestClass Test9 ( Vector3(0,0,0), // Box with diagonal y-z length of 1, rotated 45 about z Vector3(WWMATH_SQRT2/2.0,WWMATH_SQRT2/2.0,1), _RotateZ45, Vector3(4,0,0), Vector3(3,2,-1), // triangle blocking the move at x=3 (hitting back side) Vector3(3,0,1), Vector3(3,-2,-1), 0.5f, // hitting another box edge-to-face halfway through the move false ); OBBoxTriTestClass * OBBoxTriTestCases[] = { &Test0, &Test1, &Test2, &Test3, &Test4, &Test5, &Test6, &Test7, &Test8, &Test9 }; /*********************************************************************************************** * test_obb_tri -- performs some "hard-coded" obb-tri collision tests * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ void test_obb_tri(void) { Print_Title("Testing OBBox->Triangle collision."); /* ** Test the sweep function with a bunch of boxes and triangles */ CastResultStruct result; result.ComputeContactPoint = true; int numtests = sizeof(OBBoxTriTestCases)/sizeof(OBBoxTriTestClass *); unsigned cycles; unsigned totalcycles = 0; // prime the cache OBBoxTriTestClass * testcase = OBBoxTriTestCases[9]; cycles = Get_CPU_Clock(); CollisionMath::Collide( testcase->Box, testcase->BoxMove, testcase->Tri, Vector3(0,0,0), &result); cycles = Get_CPU_Clock() - cycles; // now time and test the routine for (int i=0; iBox, testcase->BoxMove, testcase->Tri, Vector3(0,0,0), &result); cycles = Get_CPU_Clock() - cycles; totalcycles += cycles; if ((WWMath::Fabs(testcase->Fraction - result.Fraction) > WWMATH_EPSILON) || (testcase->StartBad != result.StartBad)) { printf("test: %3d fraction: %8.6f cycles: %12d \tfailed!\n",i,result.Fraction,cycles); } else { printf("test: %3d fraction: %8.6f cycles: %12d \tpassed...\n",i,result.Fraction,cycles); } } printf("average cycles: %d\n",totalcycles / numtests); printf("\n"); /* ** Test a box moving down the z-axis to a polygon, then moving along ** the x-axis along the surface of the polygon. */ OBBoxClass testbox; Vector3 move; Vector3 v0,v1,v2,n; TriClass testtri; v0.Set(0,1,0); v1.Set(-1,-1,0); v2.Set(1,1,0); testtri.V[0] = &v0; testtri.V[1] = &v1; testtri.V[2] = &v2; testtri.N = &n; testtri.Compute_Normal(); testbox.Center.Set(0,0,2.363f); testbox.Extent.Set(1,1,1); move.Set(0,0,-5.0f); CastResultStruct cres; cres.StartBad = 0; cres.Fraction = 1.0f; CollisionMath::Collide(testbox,move,testtri,Vector3(0,0,0),&cres); printf("fraction = %f\n",cres.Fraction); testbox.Center += cres.Fraction * move; move.Set(1.0f,1.0f,0.0f); cres.StartBad = 0; cres.Fraction = 1.0f; CollisionMath::Collide(testbox,move,testtri,Vector3(0,0,0),&cres); printf("fraction = %f\n",cres.Fraction); testbox.Center += cres.Fraction * move; /* ** Try to get a timing of Oriented_Box_Intersects_Tri versus ** CollisionMath::Intersect */ const int REPEAT = 50; cycles = Get_CPU_Clock(); for (int j=0;jBox,testcase->Tri); } cycles = Get_CPU_Clock() - cycles; printf("CollisionMath::Intersect - average cycles: %d\n",cycles / REPEAT); printf("\n"); cycles = Get_CPU_Clock(); for (j=0;jBox,testcase->Tri); } cycles = Get_CPU_Clock() - cycles; printf("Oriented_Box_Intersects_Tri - average cycles: %d\n",cycles / REPEAT); printf("\n"); } /*********************************************************************************************** * brute_force_cast_obb_tri -- binary-search method of finding the collision time for obb-tri * * * * This function doesn't really work in the general case. Only when the endpoint of the move * * is guaranteed to be inside the triangle * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ float brute_force_cast_obb_tri ( const OBBoxClass & box, const Vector3 & move, const TriClass & tri ) { float istart = 0.0f; float iend = 1.0f; while (iend - istart > WWMATH_EPSILON/2.0f) { float icenter = (iend + istart) / 2.0f; OBBoxClass testbox = box; testbox.Center = box.Center + icenter*move; if (Oriented_Box_Intersects_Tri(testbox,tri)) { iend = icenter; } else { istart = icenter; } } return (iend + istart) / 2.0f; } /*********************************************************************************************** * brute_force_obb_tri_test -- collide random obb's into random tri's * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ void brute_force_obb_tri_test(int test_count) { Print_Title("Brute Force Testing OBBox->Tri collision."); Vector3 v[3]; Vector3 n; OBBoxClass box; TriClass tri; Vector3 move; tri.V[0] = &v[0]; tri.V[1] = &v[1]; tri.V[2] = &v[2]; tri.N = &n; int fail_count = 0; int startbad_count = 0; int startbad_fail_count = 0; int bad_points = 0; float min_fraction = 1.0f; float max_fraction = 0.0f; float avg_fraction = 0.0f; float avg_fraction_error = 0.0f; int fraction_error_count = 0; float max_error = 0.0f; for (int i=0; i max_fraction) max_fraction = result.Fraction; // verify that the fraction is correct float realfrac = brute_force_cast_obb_tri(box,move,tri); if (fabs(realfrac - result.Fraction) > WWMATH_EPSILON) { success = false; float error = fabs(realfrac - result.Fraction); avg_fraction_error += error; fraction_error_count++; if (error > max_error) max_error = error; } // verify that they are not intersecting at the end of the move // if the allowed move is smaller than epsilon, we skip this and don't move if (result.Fraction > WWMATH_EPSILON) { OBBoxClass box2 = box; CastResultStruct second_result; box2.Center += /*0.9999f **/ result.Fraction * move; CollisionMath::Collide(box2,move,tri,Vector3(0,0,0),&second_result); if (second_result.StartBad) success = false; if ((result.Fraction < 1.0f) && (second_result.Fraction > 0.01f)) success = false; } // if something failed, do the test again to let the programmer step through... if (!success) { fail_count++; CastResultStruct redo_result; redo_result.ComputeContactPoint = true; CollisionMath::Collide(box,move,tri,Vector3(0,0,0),&redo_result); } } } printf("\n"); int passes = test_count - (startbad_fail_count + fail_count); printf("Passed %d tests out of %d tests.\n",passes,test_count); printf("StartBad tests: %d failures: %d\n",startbad_count,startbad_fail_count); if (fraction_error_count > 0) { avg_fraction_error /= (float)fraction_error_count; } avg_fraction /= (float)(test_count - startbad_count); printf("Largest Fraction: %f\n",max_fraction); printf("Smallest Fraction: %f\n",min_fraction); printf("Average Fraction: %f\n",avg_fraction); printf("Average Error: %f\n",avg_fraction_error); printf("Biggest Error: %f\n",max_error); } /********************************************************************* ** ** OBBoxTestClass ** Data for testing OBBox->OBBox collision detection ** *********************************************************************/ class OBBoxTestClass { public: OBBoxClass Box0; Vector3 Move0; OBBoxClass Box1; Vector3 Move1; float Fraction; bool StartBad; OBBoxTestClass ( const Vector3 & c0, // center of box0 const Vector3 & e0, // extent of box0 const Matrix3 & b0, // basis of box0 const Vector3 & m0, // move for box0 const Vector3 & c1, // center of box1 const Vector3 & e1, // extent of box1 const Matrix3 & b1, // basis of box1 const Vector3 & m1, // move for box1 float frac, // expected fraction bool sol // expected start solid ) : Box0(c0,e0,b0), Move0(m0), Box1(c1,e1,b1), Move1(m1), Fraction(frac), StartBad(sol) { } }; OBBoxTestClass BTest0 ( Vector3(0,0,0), // center Vector3(4,0,0), // extent Matrix3(1), // basis Vector3(4,0,0), // move Vector3(6,0,0), // center Vector3(1,1,1), // extent Matrix3(1), // basis Vector3(0,0,0), // move 0.25f, false ); OBBoxTestClass BTest1 ( Vector3(-3.5f,-1.5f,0.0f), Vector3(1.5f,1.5f,1.5f), Matrix3(1), Vector3(4,-4,0), Vector3(-5.1,-5,0), Vector3(1,1,1), Matrix3(1), Vector3(0,0,0), 1.0f, // should just barely go by (touches) false ); OBBoxTestClass BTest2 ( Vector3(3,0,0), Vector3(7,1,1), Matrix3(1), Vector3(4,-4,0), Vector3(9.5,0,0), Vector3(1,6,1), Matrix3(1), Vector3(0,0,0), 0.0f, // startbad true ); OBBoxTestClass BTest3 ( Vector3(0,0,0), // Box with diagonal y-z length of 1, rotated 45 about z Vector3(WWMATH_SQRT2/2.0,WWMATH_SQRT2/2.0,1), _RotateZ45, Vector3(4,0,0), Vector3(4,0,0), // axis-aligned box blocking the move along the x-axis Vector3(1,3,1), Matrix3(1), Vector3(0,0,0), 0.5f, // hitting another box edge-to-face halfway through the move false ); OBBoxTestClass BTest4 ( Vector3(0,0,0), // Box with diagonal y-z length of 1, rotated 45 about z Vector3(WWMATH_SQRT2/2.0,WWMATH_SQRT2/2.0,1), _RotateZ45, Vector3(0,4,0), Vector3(0,4,0), // axis-aligned box blocking the move along the x-axis Vector3(3,1,1), Matrix3(1), Vector3(0,0,0), 0.5f, // hitting another box edge-to-face halfway through the move false ); OBBoxTestClass BTest5 ( Vector3(0,0,0), // Box with diagonal y-z length of 1, rotated 45 about z Vector3(WWMATH_SQRT2/2.0,WWMATH_SQRT2/2.0,1), _RotateZ45, Vector3(0,-4,0), Vector3(0,-4,0), // axis-aligned box blocking the move along the x-axis Vector3(3,1,1), Matrix3(1), Vector3(0,0,0), 0.5f, // hitting another box edge-to-face halfway through the move false ); OBBoxTestClass * OBBoxTestCases[] = { &BTest0, &BTest1, &BTest2, &BTest3, &BTest4, &BTest5 }; /*********************************************************************************************** * test_obb_obb -- performs some "hard-coded" obb-obb collision tests * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ void test_obb_obb(void) { Print_Title("Testing OBBox->OBBox collision."); CastResultStruct result; result.ComputeContactPoint = true; int numtests = sizeof(OBBoxTestCases)/sizeof(OBBoxTestClass *); unsigned cycles; unsigned totalcycles = 0; // prime the cache OBBoxTestClass * testcase = OBBoxTestCases[4]; cycles = Get_CPU_Clock(); CollisionMath::Collide( testcase->Box0, testcase->Move0, testcase->Box1, testcase->Move1, &result); cycles = Get_CPU_Clock() - cycles; // now time and test the routine for (int i=0; iBox0, testcase->Move0, testcase->Box1, testcase->Move1, &result); cycles = Get_CPU_Clock() - cycles; totalcycles += cycles; if ((WWMath::Fabs(testcase->Fraction - result.Fraction) > WWMATH_EPSILON) || (testcase->StartBad != result.StartBad)) { printf("test: %5d\tcycles: %12d \t\tfailed!\n",i,cycles); } else { printf("test: %5d\tcycles: %12d \t\tpassed...\n",i,cycles); } } printf("average cycles: %d\n",totalcycles / numtests); printf("\n"); /* ** Try to get an accurate timing, run the same (startbad) test ** 100 times... */ const int REPEAT=50; cycles = Get_CPU_Clock(); for (int j=0;jBox0, testcase->Move0, testcase->Box1, testcase->Move1, &result); } cycles = Get_CPU_Clock() - cycles; printf("average cycles: %d\n",cycles / REPEAT); printf("\n"); /* ** Now, comparing Oriented_Boxes_Intersect with CollisionMath::Intersect */ cycles = Get_CPU_Clock(); for (j=0;jBox0,testcase->Box1); } cycles = Get_CPU_Clock() - cycles; printf("CollisionMath::Intersect - average cycles: %d\n",cycles / REPEAT); printf("\n"); cycles = Get_CPU_Clock(); for (j=0;jBox0,testcase->Box1); } cycles = Get_CPU_Clock() - cycles; printf("Oriented_Boxes_Intersect - average cycles: %d\n",cycles / REPEAT); printf("\n"); } /*********************************************************************************************** * brute_force_cast_obb_obb -- brute force function to verify collision of two obb's * * * * This function doesn't really work in the general case. Only when the endpoint of the move * * is guaranteed to be inside the other box... * * * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ float brute_force_cast_obb_obb ( const OBBoxClass & box0, const Vector3 & move0, const OBBoxClass & box1 ) { float istart = 0.0f; float iend = 1.0f; while (iend - istart > WWMATH_EPSILON) { float icenter = (iend + istart) / 2.0f; OBBoxClass testbox = box0; testbox.Center = box0.Center + icenter*move0; if (Oriented_Boxes_Intersect(testbox,box1)) { iend = icenter; } else { istart = icenter; } } return (iend + istart) / 2.0f; } /*********************************************************************************************** * brute_force_obb_obb_test -- collide random obb's together * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ void brute_force_obb_obb_test(int test_count) { Print_Title("Brute Force Testing OBBox->OBBox collision."); OBBoxClass box0; OBBoxClass box1; Vector3 move0; int fail_count = 0; int startbad_count = 0; int startbad_fail_count = 0; float avg_fraction = 0.0f; float avg_fraction_error = 0.0f; int fraction_error_count = 0; float max_error = 0.0f; float min_fraction = 1.0f; float max_fraction = 0.0f; for (int i=0; i max_fraction) max_fraction = result.Fraction; // verify that the fraction is correct float realfrac = brute_force_cast_obb_obb(box0,move0,box1); if (fabs(realfrac - result.Fraction) > WWMATH_EPSILON) { success = false; float error = fabs(realfrac - result.Fraction); avg_fraction_error += error; fraction_error_count++; if (error > max_error) max_error = error; } // verify that they are not intersecting now // if the allowed move is smaller than epsilon, we skip this and don't move if (result.Fraction > WWMATH_EPSILON) { OBBoxClass box2 = box0; box2.Center += (1.0f - WWMATH_EPSILON) * result.Fraction * move0; CastResultStruct second_result; CollisionMath::Collide(box2,move0,box1,Vector3(0,0,0),&second_result); if (second_result.StartBad) { success = false; #ifdef MANUAL_DEBUGGING _asm int 0x03; while (!success) { CastResultStruct move_result; CollisionMath::Collide(box0,move0,box1,Vector3(0,0,0),&move_result); second_result.Reset(); CollisionMath::Collide(box2,move0,box1,Vector3(0,0,0),&second_result); } #endif } if ((result.Fraction < 1.0f) && (second_result.Fraction > 0.01f)) success = false; } // if something failed, do the test again to let the programmer step through... if (!success) { #ifdef MANUAL_DEBUGGING _asm int 0x03; while (!success) { fail_count++; CastResultStruct redo_result; redo_result.ComputePoint = true; CollisionMath::Collide(box0,move0,box1,Vector3(0,0,0),&redo_result); } #endif printf("x"); } else { printf("."); } } } printf("\n"); int passes = test_count - (startbad_fail_count + fail_count); printf("Passed %d tests out of %d tests.\n",passes,test_count); printf("StartBad tests: %d failures: %d\n",startbad_count,startbad_fail_count); if (fraction_error_count) { avg_fraction_error /= (float)fraction_error_count; } avg_fraction /= (float)(test_count - startbad_count); printf("Largest Fraction: %f\n",max_fraction); printf("Smallest Fraction: %f\n",min_fraction); printf("Average Fraction: %f\n",avg_fraction); printf("Average Error: %f\n",avg_fraction_error); printf("Biggest Error: %f\n",max_error); } /*********************************************************************************************** * Test_OBBoxes -- run all of the obb-obb and obb-tri tests * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/8/99 GTH : Created. * *=============================================================================================*/ void Test_OBBoxes(void) { const int TESTCOUNT = 100; srand(time(NULL)); test_obb_tri(); test_obb_obb(); brute_force_obb_tri_test(TESTCOUNT); brute_force_obb_obb_test(TESTCOUNT); }