484 lines
13 KiB
C++
484 lines
13 KiB
C++
/*
|
|
** 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:: /VSS_Sync/wwmath/colmathline.cpp $*
|
|
* *
|
|
* Author:: Greg Hjelstrom *
|
|
* *
|
|
* $Modtime:: 8/29/01 10:25p $*
|
|
* *
|
|
* $Revision:: 10 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
#include "colmath.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"
|
|
|
|
|
|
/*
|
|
** Structure used in the line->box test. There was a lot of common code between the axis-
|
|
** aligned and oriented box tests so I package all of the truely relevant information into
|
|
** this struct and pass it into a function local to this module. In the case of oriented
|
|
** boxes, the ray must be transformed into the box's coordinate system prior to the call
|
|
** and the normal is calculated slightly differently.
|
|
*/
|
|
struct BoxTestStruct
|
|
{
|
|
Vector3 Min;
|
|
Vector3 Max;
|
|
Vector3 P0;
|
|
Vector3 DP;
|
|
float Fraction;
|
|
bool Inside;
|
|
int Axis;
|
|
int Side;
|
|
};
|
|
|
|
/*
|
|
** Enumeration which can be used to categorize a point with respect to the projection
|
|
** of a box onto an axis. The point will either be to the negative side of the span, in the
|
|
** span, or on the positive side of the span.
|
|
*/
|
|
enum BoxSideType {
|
|
BOX_SIDE_NEGATIVE = 0,
|
|
BOX_SIDE_POSITIVE = 1,
|
|
BOX_SIDE_MIDDLE = 2
|
|
};
|
|
|
|
|
|
/*
|
|
** Table of normals for an axis aligned box.
|
|
** access like this:
|
|
**
|
|
** _box_normal[AXIS][SIDE]
|
|
**
|
|
** <AXIS> is 0,1,2 meaning x,y,z
|
|
** <SIDE> is BOX_SIDE_NEGATIVE or BOX_SIDE_POSITIVE
|
|
*/
|
|
static Vector3 _box_normal[3][2] =
|
|
{
|
|
// plane = 0 (x axis)
|
|
{
|
|
Vector3(-1,0,0), // Left
|
|
Vector3(1,0,0) // Right
|
|
},
|
|
// plane = 1 (y axis)
|
|
{
|
|
Vector3(0,-1,0),
|
|
Vector3(0,1,0)
|
|
},
|
|
// plane = 2 (z axis)
|
|
{
|
|
Vector3(0,0,-1),
|
|
Vector3(0,0,1)
|
|
}
|
|
};
|
|
|
|
/*
|
|
** Local function prototypes
|
|
*/
|
|
inline bool Test_Aligned_Box(BoxTestStruct * test);
|
|
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const AAPlaneClass & plane,CastResultStruct * result)
|
|
{
|
|
float num,den,t;
|
|
|
|
den = line.Get_DP()[plane.Normal];
|
|
|
|
/*
|
|
** Check if line is parallel to this plane
|
|
*/
|
|
if (den == 0.0f) {
|
|
return false;
|
|
}
|
|
|
|
num = -(line.Get_P0()[plane.Normal] - plane.Dist);
|
|
t = num/den;
|
|
|
|
/*
|
|
** If t is not between 0 and 1, the line containing the segment intersects
|
|
** the plane but the segment does not
|
|
*/
|
|
if ((t < 0.0f) || (t > 1.0f)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Ok, we hit the plane!
|
|
*/
|
|
if (t < result->Fraction) {
|
|
result->Fraction = t;
|
|
result->Normal.Set(0,0,0);
|
|
result->Normal[plane.Normal] = 1.0f;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const PlaneClass & plane,CastResultStruct * result)
|
|
{
|
|
float num,den,t;
|
|
den = Vector3::Dot_Product(plane.N,line.Get_DP());
|
|
|
|
/*
|
|
** If the denominator is zero, the ray is parallel to the plane
|
|
*/
|
|
if (den == 0.0f) {
|
|
return false;
|
|
}
|
|
|
|
num = -(Vector3::Dot_Product(plane.N,line.Get_P0()) - plane.D);
|
|
t = num/den;
|
|
|
|
/*
|
|
** If t is not between 0 and 1, the line containing the segment intersects
|
|
** the plane but the segment does not
|
|
*/
|
|
if ((t < 0.0f) || (t > 1.0f)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Ok, we hit the plane!
|
|
*/
|
|
if (t < result->Fraction) {
|
|
result->Fraction = t;
|
|
result->Normal = plane.N;
|
|
|
|
/*
|
|
** if user is asking for the point, compute it.
|
|
*/
|
|
if (result->ComputeContactPoint) {
|
|
result->ContactPoint = line.Get_P0() + result->Fraction * line.Get_DP();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const TriClass & tri,CastResultStruct * res)
|
|
{
|
|
TRACK_COLLISION_RAY_TRI;
|
|
|
|
/*
|
|
** Compute intersection of the line with the plane of the polygon
|
|
*/
|
|
PlaneClass plane(*tri.N,*tri.V[0]);
|
|
Vector3 ipoint;
|
|
float num,den,t;
|
|
|
|
den = Vector3::Dot_Product(plane.N,line.Get_DP());
|
|
|
|
/*
|
|
** If the denominator is zero, the ray is parallel to the plane
|
|
*/
|
|
if (den == 0.0f) {
|
|
return false;
|
|
}
|
|
num = -(Vector3::Dot_Product(plane.N,line.Get_P0()) - plane.D);
|
|
t = num/den;
|
|
|
|
/*
|
|
** If t is not between 0 and 1, the line containing the segment intersects
|
|
** the plane but the segment does not
|
|
*/
|
|
if ((t < 0.0f) || (t > 1.0f)) {
|
|
return false;
|
|
}
|
|
|
|
ipoint = line.Get_P0() + t*line.Get_DP();
|
|
|
|
/*
|
|
** Check if this point is inside the triangle
|
|
*/
|
|
if (!tri.Contains_Point(ipoint)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Ok, we hit the triangle, set the collision results
|
|
*/
|
|
if (t < res->Fraction) {
|
|
res->Fraction = t;
|
|
res->Normal = plane.N;
|
|
if (res->ComputeContactPoint) {
|
|
res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
|
|
}
|
|
TRACK_COLLISION_RAY_TRI_HIT;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const SphereClass & sphere,CastResultStruct * res)
|
|
{
|
|
// this game from graphics gems 1, page 388
|
|
// intersection of a ray with a sphere
|
|
Vector3 dc = sphere.Center - line.Get_P0();
|
|
float clen = Vector3::Dot_Product((dc) , line.Get_Dir());
|
|
float disc = (sphere.Radius * sphere.Radius) - (dc.Length2() - clen*clen);
|
|
if (disc < 0.0f) {
|
|
return false;
|
|
} else {
|
|
float d = WWMath::Sqrt(disc);
|
|
float frac = (clen - d) / line.Get_Length();
|
|
if (frac<0.0f)
|
|
frac = (clen + d) / line.Get_Length();
|
|
if (frac<0.0f) return false;
|
|
if (frac < res->Fraction) {
|
|
|
|
res->Fraction = frac;
|
|
|
|
Vector3 p = line.Get_P0() + (clen - d)*line.Get_Dir();
|
|
Vector3 norm = p - sphere.Center;
|
|
norm /= sphere.Radius;
|
|
|
|
res->Normal = norm;
|
|
if (res->ComputeContactPoint) {
|
|
res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const AABoxClass & box,CastResultStruct * res)
|
|
{
|
|
// set up the test struct
|
|
BoxTestStruct test;
|
|
test.Min = box.Center - box.Extent;
|
|
test.Max = box.Center + box.Extent;
|
|
test.P0 = line.Get_P0();
|
|
test.DP = line.Get_DP();
|
|
|
|
// check ray against the box, exit if the ray totally missed the box,
|
|
if (!Test_Aligned_Box(&test)) {
|
|
return false;
|
|
}
|
|
|
|
// if ray starts inside the box, note that fact and bail.
|
|
if (test.Inside) {
|
|
res->StartBad = true;
|
|
return true;
|
|
}
|
|
|
|
// Now, if this intersection is before any current intersection
|
|
// that we've found, install our intersection
|
|
if (test.Fraction < res->Fraction) {
|
|
res->Fraction = test.Fraction;
|
|
assert(test.Side != BOX_SIDE_MIDDLE);
|
|
res->Normal = _box_normal[test.Axis][test.Side];
|
|
if (res->ComputeContactPoint) {
|
|
res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CollisionMath::Collide(const LineSegClass & line,const OBBoxClass & box,CastResultStruct * result)
|
|
{
|
|
// set up the test struct
|
|
BoxTestStruct test;
|
|
test.Min = box.Center - box.Extent;
|
|
test.Max = box.Center + box.Extent;
|
|
|
|
test.P0 = (box.Basis.Transpose() * (line.Get_P0() - box.Center)) + box.Center;
|
|
test.DP = box.Basis.Transpose() * line.Get_DP();
|
|
|
|
// check ray against the box, exit if the ray totally missed the box,
|
|
if (!Test_Aligned_Box(&test)) {
|
|
return false;
|
|
}
|
|
|
|
// if ray starts inside the box, don't collide
|
|
if (test.Inside) {
|
|
result->StartBad = true;
|
|
return true;
|
|
}
|
|
|
|
// Now, if this intersection is before any current intersection
|
|
// that we've found, install our intersection
|
|
if (test.Fraction < result->Fraction) {
|
|
result->Fraction = test.Fraction;
|
|
assert(test.Side != BOX_SIDE_MIDDLE);
|
|
|
|
switch (test.Axis) {
|
|
case 0:
|
|
result->Normal.Set(box.Basis[0][0],box.Basis[1][0],box.Basis[2][0]);
|
|
break;
|
|
|
|
case 1:
|
|
result->Normal.Set(box.Basis[0][1],box.Basis[1][1],box.Basis[2][1]);
|
|
break;
|
|
|
|
case 2:
|
|
result->Normal.Set(box.Basis[0][2],box.Basis[1][2],box.Basis[2][2]);
|
|
break;
|
|
}
|
|
|
|
if (test.Side == BOX_SIDE_NEGATIVE) {
|
|
result->Normal = -result->Normal;
|
|
}
|
|
if (result->ComputeContactPoint) {
|
|
result->ContactPoint = line.Get_P0() + result->Fraction * line.Get_DP();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Test_Aligned_Box -- used as the guts of the Box intersection tests *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 4/20/98 GTH : Created. *
|
|
*=============================================================================================*/
|
|
inline bool Test_Aligned_Box(BoxTestStruct * test)
|
|
{
|
|
int i;
|
|
|
|
// candidate intersection plane distance for each axis
|
|
float candidateplane[3];
|
|
|
|
// t value along the ray for each axis
|
|
float maxt[3];
|
|
|
|
// intersection point
|
|
Vector3 coord;
|
|
|
|
// which "side" of the box for each axis
|
|
int quadrant[3];
|
|
bool inside = true;
|
|
|
|
for (i=0; i<3; i++) {
|
|
if (test->P0[i] < test->Min[i]) {
|
|
quadrant[i] = BOX_SIDE_NEGATIVE;
|
|
candidateplane[i] = test->Min[i];
|
|
inside = false;
|
|
} else if (test->P0[i] > test->Max[i]) {
|
|
quadrant[i] = BOX_SIDE_POSITIVE;
|
|
candidateplane[i] = test->Max[i];
|
|
inside = false;
|
|
} else {
|
|
quadrant[i] = BOX_SIDE_MIDDLE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Ray started out inside the box...
|
|
*/
|
|
if (inside) {
|
|
test->Fraction = 0.0f;
|
|
test->Inside = true;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
** Calculate the 3 distances to the candidate planes
|
|
*/
|
|
for (i=0; i<3; i++) {
|
|
if (quadrant[i] != BOX_SIDE_MIDDLE && test->DP[i] != 0.0f) {
|
|
maxt[i] = (candidateplane[i] - test->P0[i]) / test->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 false;
|
|
}
|
|
|
|
/*
|
|
** 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] = test->P0[i] + maxt[intersection_plane] * test->DP[i];
|
|
|
|
if ((coord[i] < test->Min[i]) || (coord[i] > test->Max[i])) {
|
|
|
|
return false; // ray is outside the box
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
coord[i] = candidateplane[i];
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Fill in the intersection values
|
|
*/
|
|
test->Fraction = maxt[intersection_plane];
|
|
test->Inside = false;
|
|
test->Axis = intersection_plane;
|
|
test->Side = quadrant[intersection_plane];
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|