| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- /*
- ** 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 : WWMath *
- * *
- * $Archive:: /Commando/Code/wwmath/colmathaabox.cpp $*
- * *
- * Author:: Greg Hjelstrom *
- * *
- * $Modtime:: 10/31/02 11:27a $*
- * *
- * $Revision:: 24 $*
- * *
- *---------------------------------------------------------------------------------------------*
- * Functions: *
- * CollisionMath::Intersection_Test -- Test intersection between two AABoxes *
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a sphere *
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a triangle *
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a line segment *
- * CollisionMath::Collide -- Collision test for a moving AABox and a plane *
- * aab_separation_test -- tests two AAB's for separation on an axis *
- * CollisionMath::Collide -- Collision test for two moving AABoxes *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- #include "colmath.h"
- #include "colmathinlines.h"
- #include "aaplane.h"
- #include "plane.h"
- #include "lineseg.h"
- #include "tri.h"
- #include "sphere.h"
- #include "aabox.h"
- #include "obbox.h"
- #include "wwdebug.h"
- /***********************************************************************************************
- * CollisionMath::Intersection_Test -- Test intersection between two AABoxes *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- bool CollisionMath::Intersection_Test(const AABoxClass & box,const AABoxClass & box2)
- {
- Vector3 dc = box2.Center - box.Center;
- if (box.Extent.X + box2.Extent.X < WWMath::Fabs(dc.X)) return false;
- if (box.Extent.Y + box2.Extent.Y < WWMath::Fabs(dc.Y)) return false;
- if (box.Extent.Z + box2.Extent.Z < WWMath::Fabs(dc.Z)) return false;
- return true;
- }
- /***********************************************************************************************
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a sphere *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const SphereClass & sphere)
- {
- // TODO: this function seems incorrect. Not currently using it though
- Vector3 dist = sphere.Center - box.Center;
- Vector3 extent;
- extent.X = box.Extent.X + sphere.Radius;
- extent.Y = box.Extent.Y + sphere.Radius;
- extent.Z = box.Extent.Z + sphere.Radius;
- //
- // Check to see if the sphere is completely outside the box
- //
- if (WWMath::Fabs(dist.X) > extent.X) return OUTSIDE;
- if (WWMath::Fabs(dist.Y) > extent.Y) return OUTSIDE;
- if (WWMath::Fabs(dist.Z) > extent.Z) return OUTSIDE;
- return INSIDE;
- }
- /***********************************************************************************************
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a line segment *
- * *
- * This function uses separating axes to determine whether an AABox and a line segment overlap *
- * I wrote it when we found that ray-casting was taking a huge amount of time in Renegade. *
- * Here are some statistics comparing this function to the previous function which used the *
- * same algorithm that Collide(box,ray) uses. *
- * *
- * collide algorithm (Gems) separating axis (this one) *
- * ----------------------------------------------------------------------------------------- *
- * number of tests 10000 10000 *
- * avg cycles 641 464 *
- * outside cycles 652 390 *
- * overlap cycles 610 683 *
- * *
- * The idea for this test is that it will early-exit when it can while the old test can't. *
- * When we are passing a ray down our culling trees, it is common to encounter boxes that *
- * are not intersecting the ray. The faster we can reject these, the better! *
- * *
- * INPUT: *
- * box - box to test *
- * line - line to test *
- * *
- * OUTPUT: *
- * overlap status of the line with respect to the box, either INSIDE, OUTSIDE, or OVERLAPPED *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const LineSegClass & line)
- {
- // If both endpoints are in, return INSIDE
- // If either was inside, return OVERLAPPED
- int inside_point_count = 0;
- if (CollisionMath::Overlap_Test(box,line.Get_P0()) == INSIDE) inside_point_count++;
- if (CollisionMath::Overlap_Test(box,line.Get_P1()) == INSIDE) inside_point_count++;
-
- if (inside_point_count == 2) {
-
- return INSIDE;
-
- } else if (inside_point_count == 1) {
-
- return OVERLAPPED;
-
- } else {
- // Here I'm using the separating axis theorem to test if the line-segment
- // and the box can be separated by a plane. Any two convex objects that are
- // not intersecting can be separated by a plane defined by either a
- // face normal from one of the objects or the cross-product of an edge from
- // each object. In the case of an axis-aligned box and a line-segment, we
- // have to check the three coordinate axes and the cross product between
- // each and the direction vector of the line segment.
- //
- // Here is the general test for an arbitrary axis:
- // -----------------------------------------------
- // box_proj = fabs(extent.X*axis.X) + fabs(extent.Y*axis.Y) + fabs(extent.Z*axis.Z)
- // p0_proj = fabs(dot_product(dp0,axis))
- // dp_proj = fabs(dot_product(dp,axis))
- // if (p0_proj > 0) {
- // if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
- // } else {
- // if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
- // }
- //
- // In practice, there are optimizations you can make on each of the axes that
- // we need to test (see below).
- float box_proj,p0_proj,dp_proj;
- Vector3 dp0;
- Vector3::Subtract(line.Get_P0(),box.Center,&dp0);
- // Project box and line onto the x-axis
- // Since I know the axis is the x-axis, just take the x components of each
- // vector instead of doing the dot-product. The projection of 'dp' is only
- // counted if it points towards the center of the box (i.e. it decreases the
- // chances of them being separated...)
- box_proj = box.Extent.X;
- p0_proj = dp0.X;
- dp_proj = line.Get_DP().X;
- if (p0_proj > 0) {
- if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
- } else {
- if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
- }
- // Project box and line onto the y-axis
- box_proj = box.Extent.Y;
- p0_proj = dp0.Y;
- dp_proj = line.Get_DP().Y;
- if (p0_proj > 0) {
- if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
- } else {
- if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
- }
- // Project box and line onto the z-axis
- box_proj = box.Extent.Z;
- p0_proj = dp0.Z;
- dp_proj = line.Get_DP().Z;
- if (p0_proj > 0) {
- if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
- } else {
- if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
- }
-
- // Project box and line onto (x cross line)
- // I'm manually optimizing the cross-product and taking advantage of the fact
- // that 'dp' will always project to zero for this axis.
- Vector3 axis;
- axis.Set(0,-line.Get_Dir().Z,line.Get_Dir().Y); // == (1,0,0) cross (x,y,z)
- box_proj = WWMath::Fabs(axis.Y*box.Extent.Y) + WWMath::Fabs(axis.Z*box.Extent.Z);
- p0_proj = Vector3::Dot_Product(axis,dp0);
- if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
- // Project box and line onto (y cross line)
- axis.Set(line.Get_Dir().Z,0,-line.Get_Dir().X); // == (0,1,0) cross (x,y,z)
- box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Z*box.Extent.Z);
- p0_proj = Vector3::Dot_Product(axis,dp0);
- if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
- // Project box and line onto (z cross line)
- axis.Set(-line.Get_Dir().Y,line.Get_Dir().X,0); // == (0,0,1) cross (x,y,z)
- box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Y*box.Extent.Y);
- p0_proj = Vector3::Dot_Product(axis,dp0);
- if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
- }
- return OVERLAPPED;
- }
- #if 0 // Alternate Overlap Test for AABox-Ray
- CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const LineSegClass & line)
- {
- // If both endpoints are in, return INSIDE
- // If either was inside, return OVERLAPPED
- int inside_point_count = 0;
- if (CollisionMath::Overlap_Test(box,line.Get_P0()) == INSIDE) inside_point_count++;
- if (CollisionMath::Overlap_Test(box,line.Get_P1()) == INSIDE) inside_point_count++;
-
- if (inside_point_count == 2) {
-
- return INSIDE;
-
- } else if (inside_point_count == 1) {
-
- return OVERLAPPED;
-
- } else {
- // Now we know that both points are outside of the box so we
- // have to detect if the ray penetrates the box.
- // I found this algorithm in one of the GEMS books...
- Vector3 boxmin,boxmax;
- Vector3::Subtract(box.Center,box.Extent,&boxmin);
- Vector3::Add(box.Center,box.Extent,&boxmax);
- float candidateplane[3]; // candidate intersection plane distance for each axis
- float maxt[3]; // t value along the ray for each axis
- Vector3 coord; // intersection point
- const int BOX_SIDE_NEGATIVE = -1;
- const int BOX_SIDE_MIDDLE = 0;
- const int BOX_SIDE_POSITIVE = 1;
- int quadrant[3];
- for (int i=0; i<3; i++) {
- if (line.Get_P0()[i] < boxmin[i]) {
- quadrant[i] = BOX_SIDE_NEGATIVE;
- candidateplane[i] = boxmin[i];
- } else if (line.Get_P0()[i] > boxmax[i]) {
- quadrant[i] = BOX_SIDE_POSITIVE;
- candidateplane[i] = boxmax[i];
- } else {
- quadrant[i] = BOX_SIDE_MIDDLE;
- }
- }
- // Calculate the 3 distances to the candidate planes
- for (i=0; i<3; i++) {
- if (quadrant[i] != BOX_SIDE_MIDDLE && line.Get_DP()[i] != 0.0f) {
- maxt[i] = (candidateplane[i] - line.Get_P0()[i]) / line.Get_DP()[i];
- } else {
- maxt[i] = -1.0f;
- }
- }
- // Get the largest of the maxt's for the final choice of intersection
- int intersection_plane = 0;
- for (i=1; i<3; i++) {
- if (maxt[i] > maxt[intersection_plane]) {
- intersection_plane = i;
- }
- }
- // If the ray is "in front" of all of the planes, return
- if (maxt[intersection_plane] < 0.0f) {
- return OUTSIDE;
- }
-
- // If the intersection is beyond the end of the ray, return
- if (maxt[intersection_plane] > 1.0f) {
- return OUTSIDE;
- }
- // Check if the ray is inside the box, i.e. on the two planes which
- // are not the intersection planes, the intersection point should
- // be between the min and max of the box.
- for (i=0; i<3; i++) {
-
- if (intersection_plane != i) {
-
- coord[i] = line.Get_P0()[i] + maxt[intersection_plane] * line.Get_DP()[i];
- if ((coord[i] < boxmin[i]) || (coord[i] > boxmax[i])) {
- return OUTSIDE; // ray is outside the box
- }
- } else {
- coord[i] = candidateplane[i];
- }
- }
- }
- return OVERLAPPED;
- }
- #endif // Alternate Overlap Test for AABox-Ray
- /***********************************************************************************************
- * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a triangle *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const TriClass & tri)
- {
- CastResultStruct res;
- CollisionMath::Collide(box,Vector3(0,0,0),tri,&res);
- return eval_overlap_collision(res);
- }
- /***********************************************************************************************
- * CollisionMath::Collide -- Collision test for a moving AABox and a plane *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- bool CollisionMath::Collide
- (
- const AABoxClass & box,
- const Vector3 & move_vector,
- const PlaneClass & plane,
- CastResultStruct * result
- )
- {
- float frac;
- float extent = box.Project_To_Axis(plane.N);
- float dist = Vector3::Dot_Product(plane.N,box.Center) + plane.D;
- float move = Vector3::Dot_Product(plane.N,move_vector);
- if (dist > extent) {
- if (dist + move > extent) {
- // entire move ok!
- frac = 1.0f;
- } else {
- // partial move allowed
- frac = (extent - dist) / move;
- }
- } else if (dist < -extent) {
- if (dist + move < -extent) {
- // entire move ok!
- frac = 1.0f;
- } else {
- // partial move allowed
- frac = (-extent - dist) / move;
- }
- } else {
- result->StartBad = true;
- result->Normal = plane.N;
- return true;
- }
- if (frac < result->Fraction) {
- result->Fraction = frac;
- result->Normal = plane.N;
- if (result->ComputeContactPoint) {
- Vector3 move_dir(move_vector);
- move_dir.Normalize();
- float move_extent = Vector3::Dot_Product(box.Extent,move_dir);
- result->ContactPoint = box.Center + result->Fraction * move_vector + move_extent * move_dir;
- }
- return true;
- }
- return false;
- }
- /*
- ** AABCollisionStruct
- ** Contains all of the intermediate and temporary values used by
- ** the set of functions used in detecting collisions for aab's
- */
- struct AABCollisionStruct
- {
- AABCollisionStruct(const AABoxClass &box0,const Vector3 &move0,const AABoxClass & box1,const Vector3 &move1) :
- StartBad(true), // Startbad is true until one of the axes clears it
- AxisId(-1), // AxisId will be the axis that allowed the longest move
- MaxFrac(0.0f), // MaxFrac is the longest allowed move so far
- Box0(box0),
- Box1(box1)
- {
- Vector3::Subtract(box1.Center,box0.Center,&C); // vector from center of box0 to center of box1
- Vector3::Subtract(move1,move0,&M); // move vector relative to stationary box0
- }
- bool StartBad; // Inital configuration is intersecting?
- float MaxFrac; // Longest move allowed so far
- int AxisId; // Last separating axis
- int Side; // which side of the interval
- Vector3 C; // Vector from the center0 to center1
- Vector3 M; // Move vector relative to stationary box0
-
- const AABoxClass & Box0;
- const AABoxClass & Box1;
- private:
- //not implemented
- AABCollisionStruct(const AABCollisionStruct&);
- AABCollisionStruct & operator = (const AABCollisionStruct&);
- };
- /***********************************************************************************************
- * aab_separation_test -- tests two AAB's for separation on an axis *
- * *
- * This function is very similar to the obb_separation_test. If a flaw is found in either, *
- * we should update the other... *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- static inline bool aab_separation_test
- (
- AABCollisionStruct & context,
- int axis
- )
- {
- // ra = box0 projection onto the axis
- // rb = box1 projection onto the axis
- // u0 = projected distance between the box centers at t0
- // u1 = projected distance between the box centers at t1
- float ra = context.Box0.Extent[axis];
- float rb = context.Box1.Extent[axis];
- float u0 = context.C[axis];
- float u1 = u0 + context.M[axis];
- float tmp;
- float rsum = ra+rb;
- if ( u0 + WWMATH_EPSILON > rsum ) {
- context.StartBad = false;
- if ( u1 > rsum ) {
- context.MaxFrac = 1.0f;
- return true;
- } else {
- tmp = (rsum-u0)/(u1-u0);
- if ( tmp > context.MaxFrac ) {
- context.MaxFrac = tmp;
- context.AxisId = axis;
- context.Side = +1;
- }
- }
- } else if ( u0 - WWMATH_EPSILON < -rsum ) {
- context.StartBad = false;
- if ( u1 < -rsum ) {
- context.MaxFrac = 1.0f;
- return true;
- } else {
- tmp = (-rsum-u0)/(u1-u0);
- if ( tmp > context.MaxFrac ) {
- context.MaxFrac = tmp;
- context.AxisId = axis;
- context.Side = -1;
- }
- }
- }
- return false;
- }
- /***********************************************************************************************
- * CollisionMath::Collide -- Collision test for two moving AABoxes *
- * *
- * this function sweeps two AABoxes and finds the first time of collision. *
- * *
- * INPUT: *
- * *
- * OUTPUT: *
- * *
- * WARNINGS: *
- * currently there is no parateter for the movement of the second box. the internal code *
- * can handle it though. *
- * *
- * HISTORY: *
- * 11/19/99 gth : Created. *
- *=============================================================================================*/
- bool CollisionMath::Collide(const AABoxClass & box,const Vector3 & move,const AABoxClass & box2,CastResultStruct * result)
- {
- /*
- ** Test the X-axis
- ** ra = box projection onto the axis
- ** rb = box2 projection onto the axis
- ** u0 = projected distance between the box centers at t0
- ** u1 = projected distance between the box centers at t1
- */
- AABCollisionStruct context(box,move,box2,Vector3(0,0,0));
- if (aab_separation_test(context,0)) {
- goto exit;
- }
- if (aab_separation_test(context,1)) {
- goto exit;
- }
-
- if (aab_separation_test(context,2)) {
- goto exit;
- }
- exit:
- if (context.StartBad) {
- result->StartBad = true;
- result->Fraction = 0.0f;
- return true;
- }
- if (context.MaxFrac < result->Fraction) {
- result->Fraction = context.MaxFrac;
- result->Normal.Set(0,0,0);
- result->Normal[context.AxisId] = -context.Side;
- if (result->ComputeContactPoint) {
- //WWASSERT(0); // TODO
- WWDEBUG_SAY(("AABox-AABox collision does not currently support contact point computation\r\n"));
- }
- return true;
- }
- return false;
- }
|