colmathline.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. ** Command & Conquer Generals Zero Hour(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:: /VSS_Sync/wwmath/colmathline.cpp $*
  25. * *
  26. * Author:: Greg Hjelstrom *
  27. * *
  28. * $Modtime:: 8/29/01 10:25p $*
  29. * *
  30. * $Revision:: 10 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "colmath.h"
  36. #include "aaplane.h"
  37. #include "plane.h"
  38. #include "lineseg.h"
  39. #include "tri.h"
  40. #include "sphere.h"
  41. #include "aabox.h"
  42. #include "obbox.h"
  43. #include "wwdebug.h"
  44. /*
  45. ** Structure used in the line->box test. There was a lot of common code between the axis-
  46. ** aligned and oriented box tests so I package all of the truely relevant information into
  47. ** this struct and pass it into a function local to this module. In the case of oriented
  48. ** boxes, the ray must be transformed into the box's coordinate system prior to the call
  49. ** and the normal is calculated slightly differently.
  50. */
  51. struct BoxTestStruct
  52. {
  53. Vector3 Min;
  54. Vector3 Max;
  55. Vector3 P0;
  56. Vector3 DP;
  57. float Fraction;
  58. bool Inside;
  59. int Axis;
  60. int Side;
  61. };
  62. /*
  63. ** Enumeration which can be used to categorize a point with respect to the projection
  64. ** of a box onto an axis. The point will either be to the negative side of the span, in the
  65. ** span, or on the positive side of the span.
  66. */
  67. enum BoxSideType {
  68. BOX_SIDE_NEGATIVE = 0,
  69. BOX_SIDE_POSITIVE = 1,
  70. BOX_SIDE_MIDDLE = 2
  71. };
  72. /*
  73. ** Table of normals for an axis aligned box.
  74. ** access like this:
  75. **
  76. ** _box_normal[AXIS][SIDE]
  77. **
  78. ** <AXIS> is 0,1,2 meaning x,y,z
  79. ** <SIDE> is BOX_SIDE_NEGATIVE or BOX_SIDE_POSITIVE
  80. */
  81. static Vector3 _box_normal[3][2] =
  82. {
  83. // plane = 0 (x axis)
  84. {
  85. Vector3(-1,0,0), // Left
  86. Vector3(1,0,0) // Right
  87. },
  88. // plane = 1 (y axis)
  89. {
  90. Vector3(0,-1,0),
  91. Vector3(0,1,0)
  92. },
  93. // plane = 2 (z axis)
  94. {
  95. Vector3(0,0,-1),
  96. Vector3(0,0,1)
  97. }
  98. };
  99. /*
  100. ** Local function prototypes
  101. */
  102. inline bool Test_Aligned_Box(BoxTestStruct * test);
  103. bool CollisionMath::Collide(const LineSegClass & line,const AAPlaneClass & plane,CastResultStruct * result)
  104. {
  105. float num,den,t;
  106. den = line.Get_DP()[plane.Normal];
  107. /*
  108. ** Check if line is parallel to this plane
  109. */
  110. if (den == 0.0f) {
  111. return false;
  112. }
  113. num = -(line.Get_P0()[plane.Normal] - plane.Dist);
  114. t = num/den;
  115. /*
  116. ** If t is not between 0 and 1, the line containing the segment intersects
  117. ** the plane but the segment does not
  118. */
  119. if ((t < 0.0f) || (t > 1.0f)) {
  120. return false;
  121. }
  122. /*
  123. ** Ok, we hit the plane!
  124. */
  125. if (t < result->Fraction) {
  126. result->Fraction = t;
  127. result->Normal.Set(0,0,0);
  128. result->Normal[plane.Normal] = 1.0f;
  129. return true;
  130. }
  131. return false;
  132. }
  133. bool CollisionMath::Collide(const LineSegClass & line,const PlaneClass & plane,CastResultStruct * result)
  134. {
  135. float num,den,t;
  136. den = Vector3::Dot_Product(plane.N,line.Get_DP());
  137. /*
  138. ** If the denominator is zero, the ray is parallel to the plane
  139. */
  140. if (den == 0.0f) {
  141. return false;
  142. }
  143. num = -(Vector3::Dot_Product(plane.N,line.Get_P0()) - plane.D);
  144. t = num/den;
  145. /*
  146. ** If t is not between 0 and 1, the line containing the segment intersects
  147. ** the plane but the segment does not
  148. */
  149. if ((t < 0.0f) || (t > 1.0f)) {
  150. return false;
  151. }
  152. /*
  153. ** Ok, we hit the plane!
  154. */
  155. if (t < result->Fraction) {
  156. result->Fraction = t;
  157. result->Normal = plane.N;
  158. /*
  159. ** if user is asking for the point, compute it.
  160. */
  161. if (result->ComputeContactPoint) {
  162. result->ContactPoint = line.Get_P0() + result->Fraction * line.Get_DP();
  163. }
  164. return true;
  165. }
  166. return false;
  167. }
  168. bool CollisionMath::Collide(const LineSegClass & line,const TriClass & tri,CastResultStruct * res)
  169. {
  170. TRACK_COLLISION_RAY_TRI;
  171. /*
  172. ** Compute intersection of the line with the plane of the polygon
  173. */
  174. PlaneClass plane(*tri.N,*tri.V[0]);
  175. Vector3 ipoint;
  176. float num,den,t;
  177. den = Vector3::Dot_Product(plane.N,line.Get_DP());
  178. /*
  179. ** If the denominator is zero, the ray is parallel to the plane
  180. */
  181. if (den == 0.0f) {
  182. return false;
  183. }
  184. num = -(Vector3::Dot_Product(plane.N,line.Get_P0()) - plane.D);
  185. t = num/den;
  186. /*
  187. ** If t is not between 0 and 1, the line containing the segment intersects
  188. ** the plane but the segment does not
  189. */
  190. if ((t < 0.0f) || (t > 1.0f)) {
  191. return false;
  192. }
  193. ipoint = line.Get_P0() + t*line.Get_DP();
  194. /*
  195. ** Check if this point is inside the triangle
  196. */
  197. if (!tri.Contains_Point(ipoint)) {
  198. return false;
  199. }
  200. /*
  201. ** Ok, we hit the triangle, set the collision results
  202. */
  203. if (t < res->Fraction) {
  204. res->Fraction = t;
  205. res->Normal = plane.N;
  206. if (res->ComputeContactPoint) {
  207. res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
  208. }
  209. TRACK_COLLISION_RAY_TRI_HIT;
  210. return true;
  211. }
  212. return false;
  213. }
  214. bool CollisionMath::Collide(const LineSegClass & line,const SphereClass & sphere,CastResultStruct * res)
  215. {
  216. // this game from graphics gems 1, page 388
  217. // intersection of a ray with a sphere
  218. Vector3 dc = sphere.Center - line.Get_P0();
  219. float clen = Vector3::Dot_Product((dc) , line.Get_Dir());
  220. float disc = (sphere.Radius * sphere.Radius) - (dc.Length2() - clen*clen);
  221. if (disc < 0.0f) {
  222. return false;
  223. } else {
  224. float d = WWMath::Sqrt(disc);
  225. float frac = (clen - d) / line.Get_Length();
  226. if (frac<0.0f)
  227. frac = (clen + d) / line.Get_Length();
  228. if (frac<0.0f) return false;
  229. if (frac < res->Fraction) {
  230. res->Fraction = frac;
  231. Vector3 p = line.Get_P0() + (clen - d)*line.Get_Dir();
  232. Vector3 norm = p - sphere.Center;
  233. norm /= sphere.Radius;
  234. res->Normal = norm;
  235. if (res->ComputeContactPoint) {
  236. res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
  237. }
  238. return true;
  239. }
  240. }
  241. return false;
  242. }
  243. bool CollisionMath::Collide(const LineSegClass & line,const AABoxClass & box,CastResultStruct * res)
  244. {
  245. // set up the test struct
  246. BoxTestStruct test;
  247. test.Min = box.Center - box.Extent;
  248. test.Max = box.Center + box.Extent;
  249. test.P0 = line.Get_P0();
  250. test.DP = line.Get_DP();
  251. // check ray against the box, exit if the ray totally missed the box,
  252. if (!Test_Aligned_Box(&test)) {
  253. return false;
  254. }
  255. // if ray starts inside the box, note that fact and bail.
  256. if (test.Inside) {
  257. res->StartBad = true;
  258. return true;
  259. }
  260. // Now, if this intersection is before any current intersection
  261. // that we've found, install our intersection
  262. if (test.Fraction < res->Fraction) {
  263. res->Fraction = test.Fraction;
  264. assert(test.Side != BOX_SIDE_MIDDLE);
  265. res->Normal = _box_normal[test.Axis][test.Side];
  266. if (res->ComputeContactPoint) {
  267. res->ContactPoint = line.Get_P0() + res->Fraction * line.Get_DP();
  268. }
  269. return true;
  270. }
  271. return false;
  272. }
  273. bool CollisionMath::Collide(const LineSegClass & line,const OBBoxClass & box,CastResultStruct * result)
  274. {
  275. // set up the test struct
  276. BoxTestStruct test;
  277. test.Min = box.Center - box.Extent;
  278. test.Max = box.Center + box.Extent;
  279. test.P0 = (box.Basis.Transpose() * (line.Get_P0() - box.Center)) + box.Center;
  280. test.DP = box.Basis.Transpose() * line.Get_DP();
  281. // check ray against the box, exit if the ray totally missed the box,
  282. if (!Test_Aligned_Box(&test)) {
  283. return false;
  284. }
  285. // if ray starts inside the box, don't collide
  286. if (test.Inside) {
  287. result->StartBad = true;
  288. return true;
  289. }
  290. // Now, if this intersection is before any current intersection
  291. // that we've found, install our intersection
  292. if (test.Fraction < result->Fraction) {
  293. result->Fraction = test.Fraction;
  294. assert(test.Side != BOX_SIDE_MIDDLE);
  295. switch (test.Axis) {
  296. case 0:
  297. result->Normal.Set(box.Basis[0][0],box.Basis[1][0],box.Basis[2][0]);
  298. break;
  299. case 1:
  300. result->Normal.Set(box.Basis[0][1],box.Basis[1][1],box.Basis[2][1]);
  301. break;
  302. case 2:
  303. result->Normal.Set(box.Basis[0][2],box.Basis[1][2],box.Basis[2][2]);
  304. break;
  305. }
  306. if (test.Side == BOX_SIDE_NEGATIVE) {
  307. result->Normal = -result->Normal;
  308. }
  309. if (result->ComputeContactPoint) {
  310. result->ContactPoint = line.Get_P0() + result->Fraction * line.Get_DP();
  311. }
  312. return true;
  313. }
  314. return false;
  315. }
  316. /***********************************************************************************************
  317. * Test_Aligned_Box -- used as the guts of the Box intersection tests *
  318. * *
  319. * INPUT: *
  320. * *
  321. * OUTPUT: *
  322. * *
  323. * WARNINGS: *
  324. * *
  325. * HISTORY: *
  326. * 4/20/98 GTH : Created. *
  327. *=============================================================================================*/
  328. inline bool Test_Aligned_Box(BoxTestStruct * test)
  329. {
  330. int i;
  331. // candidate intersection plane distance for each axis
  332. float candidateplane[3];
  333. // t value along the ray for each axis
  334. float maxt[3];
  335. // intersection point
  336. Vector3 coord;
  337. // which "side" of the box for each axis
  338. int quadrant[3];
  339. bool inside = true;
  340. for (i=0; i<3; i++) {
  341. if (test->P0[i] < test->Min[i]) {
  342. quadrant[i] = BOX_SIDE_NEGATIVE;
  343. candidateplane[i] = test->Min[i];
  344. inside = false;
  345. } else if (test->P0[i] > test->Max[i]) {
  346. quadrant[i] = BOX_SIDE_POSITIVE;
  347. candidateplane[i] = test->Max[i];
  348. inside = false;
  349. } else {
  350. quadrant[i] = BOX_SIDE_MIDDLE;
  351. }
  352. }
  353. /*
  354. ** Ray started out inside the box...
  355. */
  356. if (inside) {
  357. test->Fraction = 0.0f;
  358. test->Inside = true;
  359. return true;
  360. }
  361. /*
  362. ** Calculate the 3 distances to the candidate planes
  363. */
  364. for (i=0; i<3; i++) {
  365. if (quadrant[i] != BOX_SIDE_MIDDLE && test->DP[i] != 0.0f) {
  366. maxt[i] = (candidateplane[i] - test->P0[i]) / test->DP[i];
  367. } else {
  368. maxt[i] = -1.0f;
  369. }
  370. }
  371. /*
  372. ** Get the largest of the maxt's for the final choice of intersection
  373. */
  374. int intersection_plane = 0;
  375. for (i=1; i<3; i++) {
  376. if (maxt[i] > maxt[intersection_plane]) {
  377. intersection_plane = i;
  378. }
  379. }
  380. /*
  381. ** If the ray is "in front" of all of the planes, return
  382. */
  383. if (maxt[intersection_plane] < 0.0f) {
  384. return false;
  385. }
  386. /*
  387. ** Check if the ray is inside the box, i.e. on the two planes which
  388. ** are not the intersection planes, the intersection point should
  389. ** be between the min and max of the box.
  390. */
  391. for (i=0; i<3; i++) {
  392. if (intersection_plane != i) {
  393. coord[i] = test->P0[i] + maxt[intersection_plane] * test->DP[i];
  394. if ((coord[i] < test->Min[i]) || (coord[i] > test->Max[i])) {
  395. return false; // ray is outside the box
  396. }
  397. } else {
  398. coord[i] = candidateplane[i];
  399. }
  400. }
  401. /*
  402. ** Fill in the intersection values
  403. */
  404. test->Fraction = maxt[intersection_plane];
  405. test->Inside = false;
  406. test->Axis = intersection_plane;
  407. test->Side = quadrant[intersection_plane];
  408. return true;
  409. }