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