colmathaabox.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** 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 ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : WWMath *
  23. * *
  24. * $Archive:: /Commando/Code/wwmath/colmathaabox.cpp $*
  25. * *
  26. * Author:: Greg Hjelstrom *
  27. * *
  28. * $Modtime:: 10/31/02 11:27a $*
  29. * *
  30. * $Revision:: 24 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * CollisionMath::Intersection_Test -- Test intersection between two AABoxes *
  35. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a sphere *
  36. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a triangle *
  37. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a line segment *
  38. * CollisionMath::Collide -- Collision test for a moving AABox and a plane *
  39. * aab_separation_test -- tests two AAB's for separation on an axis *
  40. * CollisionMath::Collide -- Collision test for two moving AABoxes *
  41. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  42. #include "colmath.h"
  43. #include "colmathinlines.h"
  44. #include "aaplane.h"
  45. #include "plane.h"
  46. #include "lineseg.h"
  47. #include "tri.h"
  48. #include "sphere.h"
  49. #include "aabox.h"
  50. #include "obbox.h"
  51. #include "wwdebug.h"
  52. /***********************************************************************************************
  53. * CollisionMath::Intersection_Test -- Test intersection between two AABoxes *
  54. * *
  55. * INPUT: *
  56. * *
  57. * OUTPUT: *
  58. * *
  59. * WARNINGS: *
  60. * *
  61. * HISTORY: *
  62. * 11/19/99 gth : Created. *
  63. *=============================================================================================*/
  64. bool CollisionMath::Intersection_Test(const AABoxClass & box,const AABoxClass & box2)
  65. {
  66. Vector3 dc = box2.Center - box.Center;
  67. if (box.Extent.X + box2.Extent.X < WWMath::Fabs(dc.X)) return false;
  68. if (box.Extent.Y + box2.Extent.Y < WWMath::Fabs(dc.Y)) return false;
  69. if (box.Extent.Z + box2.Extent.Z < WWMath::Fabs(dc.Z)) return false;
  70. return true;
  71. }
  72. /***********************************************************************************************
  73. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a sphere *
  74. * *
  75. * INPUT: *
  76. * *
  77. * OUTPUT: *
  78. * *
  79. * WARNINGS: *
  80. * *
  81. * HISTORY: *
  82. * 11/19/99 gth : Created. *
  83. *=============================================================================================*/
  84. CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const SphereClass & sphere)
  85. {
  86. // TODO: this function seems incorrect. Not currently using it though
  87. Vector3 dist = sphere.Center - box.Center;
  88. Vector3 extent;
  89. extent.X = box.Extent.X + sphere.Radius;
  90. extent.Y = box.Extent.Y + sphere.Radius;
  91. extent.Z = box.Extent.Z + sphere.Radius;
  92. //
  93. // Check to see if the sphere is completely outside the box
  94. //
  95. if (WWMath::Fabs(dist.X) > extent.X) return OUTSIDE;
  96. if (WWMath::Fabs(dist.Y) > extent.Y) return OUTSIDE;
  97. if (WWMath::Fabs(dist.Z) > extent.Z) return OUTSIDE;
  98. return INSIDE;
  99. }
  100. /***********************************************************************************************
  101. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a line segment *
  102. * *
  103. * This function uses separating axes to determine whether an AABox and a line segment overlap *
  104. * I wrote it when we found that ray-casting was taking a huge amount of time in Renegade. *
  105. * Here are some statistics comparing this function to the previous function which used the *
  106. * same algorithm that Collide(box,ray) uses. *
  107. * *
  108. * collide algorithm (Gems) separating axis (this one) *
  109. * ----------------------------------------------------------------------------------------- *
  110. * number of tests 10000 10000 *
  111. * avg cycles 641 464 *
  112. * outside cycles 652 390 *
  113. * overlap cycles 610 683 *
  114. * *
  115. * The idea for this test is that it will early-exit when it can while the old test can't. *
  116. * When we are passing a ray down our culling trees, it is common to encounter boxes that *
  117. * are not intersecting the ray. The faster we can reject these, the better! *
  118. * *
  119. * INPUT: *
  120. * box - box to test *
  121. * line - line to test *
  122. * *
  123. * OUTPUT: *
  124. * overlap status of the line with respect to the box, either INSIDE, OUTSIDE, or OVERLAPPED *
  125. * *
  126. * WARNINGS: *
  127. * *
  128. * HISTORY: *
  129. * 11/19/99 gth : Created. *
  130. *=============================================================================================*/
  131. CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const LineSegClass & line)
  132. {
  133. // If both endpoints are in, return INSIDE
  134. // If either was inside, return OVERLAPPED
  135. int inside_point_count = 0;
  136. if (CollisionMath::Overlap_Test(box,line.Get_P0()) == INSIDE) inside_point_count++;
  137. if (CollisionMath::Overlap_Test(box,line.Get_P1()) == INSIDE) inside_point_count++;
  138. if (inside_point_count == 2) {
  139. return INSIDE;
  140. } else if (inside_point_count == 1) {
  141. return OVERLAPPED;
  142. } else {
  143. // Here I'm using the separating axis theorem to test if the line-segment
  144. // and the box can be separated by a plane. Any two convex objects that are
  145. // not intersecting can be separated by a plane defined by either a
  146. // face normal from one of the objects or the cross-product of an edge from
  147. // each object. In the case of an axis-aligned box and a line-segment, we
  148. // have to check the three coordinate axes and the cross product between
  149. // each and the direction vector of the line segment.
  150. //
  151. // Here is the general test for an arbitrary axis:
  152. // -----------------------------------------------
  153. // box_proj = fabs(extent.X*axis.X) + fabs(extent.Y*axis.Y) + fabs(extent.Z*axis.Z)
  154. // p0_proj = fabs(dot_product(dp0,axis))
  155. // dp_proj = fabs(dot_product(dp,axis))
  156. // if (p0_proj > 0) {
  157. // if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
  158. // } else {
  159. // if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
  160. // }
  161. //
  162. // In practice, there are optimizations you can make on each of the axes that
  163. // we need to test (see below).
  164. float box_proj,p0_proj,dp_proj;
  165. Vector3 dp0;
  166. Vector3::Subtract(line.Get_P0(),box.Center,&dp0);
  167. // Project box and line onto the x-axis
  168. // Since I know the axis is the x-axis, just take the x components of each
  169. // vector instead of doing the dot-product. The projection of 'dp' is only
  170. // counted if it points towards the center of the box (i.e. it decreases the
  171. // chances of them being separated...)
  172. box_proj = box.Extent.X;
  173. p0_proj = dp0.X;
  174. dp_proj = line.Get_DP().X;
  175. if (p0_proj > 0) {
  176. if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
  177. } else {
  178. if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
  179. }
  180. // Project box and line onto the y-axis
  181. box_proj = box.Extent.Y;
  182. p0_proj = dp0.Y;
  183. dp_proj = line.Get_DP().Y;
  184. if (p0_proj > 0) {
  185. if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
  186. } else {
  187. if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
  188. }
  189. // Project box and line onto the z-axis
  190. box_proj = box.Extent.Z;
  191. p0_proj = dp0.Z;
  192. dp_proj = line.Get_DP().Z;
  193. if (p0_proj > 0) {
  194. if (p0_proj > box_proj - WWMath::Min(dp_proj,0.0f)) return OUTSIDE;
  195. } else {
  196. if (-p0_proj > box_proj + WWMath::Max(dp_proj,0.0f)) return OUTSIDE;
  197. }
  198. // Project box and line onto (x cross line)
  199. // I'm manually optimizing the cross-product and taking advantage of the fact
  200. // that 'dp' will always project to zero for this axis.
  201. Vector3 axis;
  202. axis.Set(0,-line.Get_Dir().Z,line.Get_Dir().Y); // == (1,0,0) cross (x,y,z)
  203. box_proj = WWMath::Fabs(axis.Y*box.Extent.Y) + WWMath::Fabs(axis.Z*box.Extent.Z);
  204. p0_proj = Vector3::Dot_Product(axis,dp0);
  205. if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
  206. // Project box and line onto (y cross line)
  207. axis.Set(line.Get_Dir().Z,0,-line.Get_Dir().X); // == (0,1,0) cross (x,y,z)
  208. box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Z*box.Extent.Z);
  209. p0_proj = Vector3::Dot_Product(axis,dp0);
  210. if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
  211. // Project box and line onto (z cross line)
  212. axis.Set(-line.Get_Dir().Y,line.Get_Dir().X,0); // == (0,0,1) cross (x,y,z)
  213. box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Y*box.Extent.Y);
  214. p0_proj = Vector3::Dot_Product(axis,dp0);
  215. if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE;
  216. }
  217. return OVERLAPPED;
  218. }
  219. #if 0 // Alternate Overlap Test for AABox-Ray
  220. CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const LineSegClass & line)
  221. {
  222. // If both endpoints are in, return INSIDE
  223. // If either was inside, return OVERLAPPED
  224. int inside_point_count = 0;
  225. if (CollisionMath::Overlap_Test(box,line.Get_P0()) == INSIDE) inside_point_count++;
  226. if (CollisionMath::Overlap_Test(box,line.Get_P1()) == INSIDE) inside_point_count++;
  227. if (inside_point_count == 2) {
  228. return INSIDE;
  229. } else if (inside_point_count == 1) {
  230. return OVERLAPPED;
  231. } else {
  232. // Now we know that both points are outside of the box so we
  233. // have to detect if the ray penetrates the box.
  234. // I found this algorithm in one of the GEMS books...
  235. Vector3 boxmin,boxmax;
  236. Vector3::Subtract(box.Center,box.Extent,&boxmin);
  237. Vector3::Add(box.Center,box.Extent,&boxmax);
  238. float candidateplane[3]; // candidate intersection plane distance for each axis
  239. float maxt[3]; // t value along the ray for each axis
  240. Vector3 coord; // intersection point
  241. const int BOX_SIDE_NEGATIVE = -1;
  242. const int BOX_SIDE_MIDDLE = 0;
  243. const int BOX_SIDE_POSITIVE = 1;
  244. int quadrant[3];
  245. for (int i=0; i<3; i++) {
  246. if (line.Get_P0()[i] < boxmin[i]) {
  247. quadrant[i] = BOX_SIDE_NEGATIVE;
  248. candidateplane[i] = boxmin[i];
  249. } else if (line.Get_P0()[i] > boxmax[i]) {
  250. quadrant[i] = BOX_SIDE_POSITIVE;
  251. candidateplane[i] = boxmax[i];
  252. } else {
  253. quadrant[i] = BOX_SIDE_MIDDLE;
  254. }
  255. }
  256. // Calculate the 3 distances to the candidate planes
  257. for (i=0; i<3; i++) {
  258. if (quadrant[i] != BOX_SIDE_MIDDLE && line.Get_DP()[i] != 0.0f) {
  259. maxt[i] = (candidateplane[i] - line.Get_P0()[i]) / line.Get_DP()[i];
  260. } else {
  261. maxt[i] = -1.0f;
  262. }
  263. }
  264. // Get the largest of the maxt's for the final choice of intersection
  265. int intersection_plane = 0;
  266. for (i=1; i<3; i++) {
  267. if (maxt[i] > maxt[intersection_plane]) {
  268. intersection_plane = i;
  269. }
  270. }
  271. // If the ray is "in front" of all of the planes, return
  272. if (maxt[intersection_plane] < 0.0f) {
  273. return OUTSIDE;
  274. }
  275. // If the intersection is beyond the end of the ray, return
  276. if (maxt[intersection_plane] > 1.0f) {
  277. return OUTSIDE;
  278. }
  279. // Check if the ray is inside the box, i.e. on the two planes which
  280. // are not the intersection planes, the intersection point should
  281. // be between the min and max of the box.
  282. for (i=0; i<3; i++) {
  283. if (intersection_plane != i) {
  284. coord[i] = line.Get_P0()[i] + maxt[intersection_plane] * line.Get_DP()[i];
  285. if ((coord[i] < boxmin[i]) || (coord[i] > boxmax[i])) {
  286. return OUTSIDE; // ray is outside the box
  287. }
  288. } else {
  289. coord[i] = candidateplane[i];
  290. }
  291. }
  292. }
  293. return OVERLAPPED;
  294. }
  295. #endif // Alternate Overlap Test for AABox-Ray
  296. /***********************************************************************************************
  297. * CollisionMath::Overlap_Test -- Tests overlap between an AABox and a triangle *
  298. * *
  299. * INPUT: *
  300. * *
  301. * OUTPUT: *
  302. * *
  303. * WARNINGS: *
  304. * *
  305. * HISTORY: *
  306. * 11/19/99 gth : Created. *
  307. *=============================================================================================*/
  308. CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const TriClass & tri)
  309. {
  310. CastResultStruct res;
  311. CollisionMath::Collide(box,Vector3(0,0,0),tri,&res);
  312. return eval_overlap_collision(res);
  313. }
  314. /***********************************************************************************************
  315. * CollisionMath::Collide -- Collision test for a moving AABox and a plane *
  316. * *
  317. * INPUT: *
  318. * *
  319. * OUTPUT: *
  320. * *
  321. * WARNINGS: *
  322. * *
  323. * HISTORY: *
  324. * 11/19/99 gth : Created. *
  325. *=============================================================================================*/
  326. bool CollisionMath::Collide
  327. (
  328. const AABoxClass & box,
  329. const Vector3 & move_vector,
  330. const PlaneClass & plane,
  331. CastResultStruct * result
  332. )
  333. {
  334. float frac;
  335. float extent = box.Project_To_Axis(plane.N);
  336. float dist = Vector3::Dot_Product(plane.N,box.Center) + plane.D;
  337. float move = Vector3::Dot_Product(plane.N,move_vector);
  338. if (dist > extent) {
  339. if (dist + move > extent) {
  340. // entire move ok!
  341. frac = 1.0f;
  342. } else {
  343. // partial move allowed
  344. frac = (extent - dist) / move;
  345. }
  346. } else if (dist < -extent) {
  347. if (dist + move < -extent) {
  348. // entire move ok!
  349. frac = 1.0f;
  350. } else {
  351. // partial move allowed
  352. frac = (-extent - dist) / move;
  353. }
  354. } else {
  355. result->StartBad = true;
  356. result->Normal = plane.N;
  357. return true;
  358. }
  359. if (frac < result->Fraction) {
  360. result->Fraction = frac;
  361. result->Normal = plane.N;
  362. if (result->ComputeContactPoint) {
  363. Vector3 move_dir(move_vector);
  364. move_dir.Normalize();
  365. float move_extent = Vector3::Dot_Product(box.Extent,move_dir);
  366. result->ContactPoint = box.Center + result->Fraction * move_vector + move_extent * move_dir;
  367. }
  368. return true;
  369. }
  370. return false;
  371. }
  372. /*
  373. ** AABCollisionStruct
  374. ** Contains all of the intermediate and temporary values used by
  375. ** the set of functions used in detecting collisions for aab's
  376. */
  377. struct AABCollisionStruct
  378. {
  379. AABCollisionStruct(const AABoxClass &box0,const Vector3 &move0,const AABoxClass & box1,const Vector3 &move1) :
  380. StartBad(true), // Startbad is true until one of the axes clears it
  381. AxisId(-1), // AxisId will be the axis that allowed the longest move
  382. MaxFrac(0.0f), // MaxFrac is the longest allowed move so far
  383. Box0(box0),
  384. Box1(box1)
  385. {
  386. Vector3::Subtract(box1.Center,box0.Center,&C); // vector from center of box0 to center of box1
  387. Vector3::Subtract(move1,move0,&M); // move vector relative to stationary box0
  388. }
  389. bool StartBad; // Inital configuration is intersecting?
  390. float MaxFrac; // Longest move allowed so far
  391. int AxisId; // Last separating axis
  392. int Side; // which side of the interval
  393. Vector3 C; // Vector from the center0 to center1
  394. Vector3 M; // Move vector relative to stationary box0
  395. const AABoxClass & Box0;
  396. const AABoxClass & Box1;
  397. private:
  398. //not implemented
  399. AABCollisionStruct(const AABCollisionStruct&);
  400. AABCollisionStruct & operator = (const AABCollisionStruct&);
  401. };
  402. /***********************************************************************************************
  403. * aab_separation_test -- tests two AAB's for separation on an axis *
  404. * *
  405. * This function is very similar to the obb_separation_test. If a flaw is found in either, *
  406. * we should update the other... *
  407. * *
  408. * INPUT: *
  409. * *
  410. * OUTPUT: *
  411. * *
  412. * WARNINGS: *
  413. * *
  414. * HISTORY: *
  415. * 11/19/99 gth : Created. *
  416. *=============================================================================================*/
  417. static inline bool aab_separation_test
  418. (
  419. AABCollisionStruct & context,
  420. int axis
  421. )
  422. {
  423. // ra = box0 projection onto the axis
  424. // rb = box1 projection onto the axis
  425. // u0 = projected distance between the box centers at t0
  426. // u1 = projected distance between the box centers at t1
  427. float ra = context.Box0.Extent[axis];
  428. float rb = context.Box1.Extent[axis];
  429. float u0 = context.C[axis];
  430. float u1 = u0 + context.M[axis];
  431. float tmp;
  432. float rsum = ra+rb;
  433. if ( u0 + WWMATH_EPSILON > rsum ) {
  434. context.StartBad = false;
  435. if ( u1 > rsum ) {
  436. context.MaxFrac = 1.0f;
  437. return true;
  438. } else {
  439. tmp = (rsum-u0)/(u1-u0);
  440. if ( tmp > context.MaxFrac ) {
  441. context.MaxFrac = tmp;
  442. context.AxisId = axis;
  443. context.Side = +1;
  444. }
  445. }
  446. } else if ( u0 - WWMATH_EPSILON < -rsum ) {
  447. context.StartBad = false;
  448. if ( u1 < -rsum ) {
  449. context.MaxFrac = 1.0f;
  450. return true;
  451. } else {
  452. tmp = (-rsum-u0)/(u1-u0);
  453. if ( tmp > context.MaxFrac ) {
  454. context.MaxFrac = tmp;
  455. context.AxisId = axis;
  456. context.Side = -1;
  457. }
  458. }
  459. }
  460. return false;
  461. }
  462. /***********************************************************************************************
  463. * CollisionMath::Collide -- Collision test for two moving AABoxes *
  464. * *
  465. * this function sweeps two AABoxes and finds the first time of collision. *
  466. * *
  467. * INPUT: *
  468. * *
  469. * OUTPUT: *
  470. * *
  471. * WARNINGS: *
  472. * currently there is no parateter for the movement of the second box. the internal code *
  473. * can handle it though. *
  474. * *
  475. * HISTORY: *
  476. * 11/19/99 gth : Created. *
  477. *=============================================================================================*/
  478. bool CollisionMath::Collide(const AABoxClass & box,const Vector3 & move,const AABoxClass & box2,CastResultStruct * result)
  479. {
  480. /*
  481. ** Test the X-axis
  482. ** ra = box projection onto the axis
  483. ** rb = box2 projection onto the axis
  484. ** u0 = projected distance between the box centers at t0
  485. ** u1 = projected distance between the box centers at t1
  486. */
  487. AABCollisionStruct context(box,move,box2,Vector3(0,0,0));
  488. if (aab_separation_test(context,0)) {
  489. goto exit;
  490. }
  491. if (aab_separation_test(context,1)) {
  492. goto exit;
  493. }
  494. if (aab_separation_test(context,2)) {
  495. goto exit;
  496. }
  497. exit:
  498. if (context.StartBad) {
  499. result->StartBad = true;
  500. result->Fraction = 0.0f;
  501. return true;
  502. }
  503. if (context.MaxFrac < result->Fraction) {
  504. result->Fraction = context.MaxFrac;
  505. result->Normal.Set(0,0,0);
  506. result->Normal[context.AxisId] = -context.Side;
  507. if (result->ComputeContactPoint) {
  508. //WWASSERT(0); // TODO
  509. WWDEBUG_SAY(("AABox-AABox collision does not currently support contact point computation\r\n"));
  510. }
  511. return true;
  512. }
  513. return false;
  514. }