1 /**
2 * @file
3 * @brief model tracing and bounding
4 * @note collision detection code
5 */
6
7 /*
8 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
9
10 Copyright (C) 1997-2001 Id Software, Inc.
11
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20
21 See the GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27 */
28
29 #include "tracing.h"
30 #include "common.h"
31
32 /* TR_TILE_TYPE and TR_PLANE_TYPE are defined in tracing.h */
33 #if defined(COMPILE_MAP)
34 #define TR_NODE_TYPE dBspNode_t
35 #define TR_LEAF_TYPE dBspLeaf_t
36 #define TR_BRUSHSIDE_TYPE dBspBrushSide_t
37 #elif defined(COMPILE_UFO)
38 #define TR_NODE_TYPE cBspNode_t
39 #define TR_LEAF_TYPE cBspLeaf_t
40 #define TR_BRUSHSIDE_TYPE cBspBrushSide_t
41 #else
42 #error Either COMPILE_MAP or COMPILE_UFO must be defined in order for tracing.c to work.
43 #endif
44 /** @note all the above types are declared in typedefs.h */
45
46 /* This attempts to make the box tracing code thread safe. */
47 typedef struct boxtrace_s {
48 vec3_t start, end;
49 vec3_t mins, maxs;
50 vec3_t absmins, absmaxs;
51 vec3_t extents;
52
53 trace_t trace;
54 uint32_t contents; /**< content flags to match again - MASK_ALL to match everything */
55 uint32_t rejects; /**< content flags that should be rejected in a trace - ignored when MASK_ALL is given as content flags */
56 bool ispoint; /* optimized case */
57
58 TR_TILE_TYPE *tile;
59 } boxtrace_t;
60
61 /** @note For multi-check avoidance.
62 * @todo not thread safe */
63 static int checkcount;
64
65 /*
66 ===============================================================================
67 TRACING NODES
68 ===============================================================================
69 */
70
71 /**
72 * @brief Converts the disk node structure into the efficient tracing structure for LineTraces
73 */
TR_MakeTracingNode(TR_TILE_TYPE * tile,tnode_t ** tnode,int32_t nodenum)74 static void TR_MakeTracingNode (TR_TILE_TYPE *tile, tnode_t** tnode, int32_t nodenum)
75 {
76 tnode_t* t; /* the tracing node to build */
77 TR_PLANE_TYPE *plane;
78 int i;
79 TR_NODE_TYPE *node; /* the node we are investigating */
80
81 t = (*tnode)++;
82
83 node = tile->nodes + nodenum;
84 #ifdef COMPILE_UFO
85 plane = node->plane;
86 #else
87 plane = tile->planes + node->planenum;
88 #endif
89
90 t->type = plane->type;
91 VectorCopy(plane->normal, t->normal);
92 t->dist = plane->dist;
93
94 for (i = 0; i < 2; i++) {
95 if (node->children[i] < 0) { /* is it a leaf ? */
96 const int32_t leafnum = -(node->children[i]) - 1;
97 const TR_LEAF_TYPE *leaf = &tile->leafs[leafnum];
98 const uint32_t contentFlags = leaf->contentFlags & ~(1 << 31);
99 if ((contentFlags & (CONTENTS_SOLID | MASK_CLIP)) && !(contentFlags & CONTENTS_PASSABLE))
100 t->children[i] = -node->children[i] | (1 << 31); /* mark as 'blocking' */
101 else
102 t->children[i] = (1 << 31); /* mark as 'empty leaf' */
103 } else { /* not a leaf */
104 t->children[i] = *tnode - tile->tnodes;
105 if (t->children[i] > tile->numnodes) {
106 Com_Printf("Exceeded allocated memory for tracing structure (%i > %i)\n",
107 t->children[i], tile->numnodes);
108 }
109 TR_MakeTracingNode(tile, tnode, node->children[i]); /* recurse further down the tree */
110 }
111 }
112 }
113
114 /**
115 * @sa CMod_LoadNodes
116 * @sa R_ModLoadNodes
117 */
TR_BuildTracingNode_r(TR_TILE_TYPE * tile,tnode_t ** tnode,int32_t nodenum,int level)118 void TR_BuildTracingNode_r (TR_TILE_TYPE *tile, tnode_t** tnode, int32_t nodenum, int level)
119 {
120 assert(nodenum < tile->numnodes + 6); /* +6 => bbox */
121
122 /**
123 * We are checking for a leaf in the tracing node. For ufo2map, planenum == PLANENUMLEAF.
124 * For the game, plane will be nullptr.
125 */
126 #ifdef COMPILE_UFO
127 if (!tile->nodes[nodenum].plane) {
128 #else
129 if (tile->nodes[nodenum].planenum == PLANENUM_LEAF) {
130 #endif
131 TR_NODE_TYPE *n;
132 tnode_t* t;
133 vec3_t c0maxs, c1mins;
134 int i;
135
136 n = &tile->nodes[nodenum];
137
138 /* alloc new node */
139 t = (*tnode)++;
140
141 /* no leafs here */
142 if (n->children[0] < 0 || n->children[1] < 0)
143 #ifdef COMPILE_UFO
144 Com_Error(ERR_DROP, "Unexpected leaf");
145 #else
146 Sys_Error("Unexpected leaf");
147 #endif
148
149 VectorCopy(tile->nodes[n->children[0]].maxs, c0maxs);
150 VectorCopy(tile->nodes[n->children[1]].mins, c1mins);
151
152 #if 0
153 Com_Printf("(%i %i : %i %i) (%i %i : %i %i)\n",
154 (int)tile->nodes[n->children[0]].mins[0], (int)tile->nodes[n->children[0]].mins[1],
155 (int)tile->nodes[n->children[0]].maxs[0], (int)tile->nodes[n->children[0]].maxs[1],
156 (int)tile->nodes[n->children[1]].mins[0], (int)tile->nodes[n->children[1]].mins[1],
157 (int)tile->nodes[n->children[1]].maxs[0], (int)tile->nodes[n->children[1]].maxs[1]);
158 #endif
159
160 for (i = 0; i < 2; i++)
161 if (c0maxs[i] <= c1mins[i]) {
162 /* create a separation plane */
163 t->type = i;
164 VectorSet(t->normal, i, (i ^ 1), 0);
165 t->dist = (c0maxs[i] + c1mins[i]) / 2;
166
167 t->children[1] = *tnode - tile->tnodes;
168 TR_BuildTracingNode_r(tile, tnode, n->children[0], level);
169 t->children[0] = *tnode - tile->tnodes;
170 TR_BuildTracingNode_r(tile, tnode, n->children[1], level);
171 return;
172 }
173
174 /* can't construct such a separation plane */
175 t->type = PLANE_NONE;
176
177 for (i = 0; i < 2; i++) {
178 t->children[i] = *tnode - tile->tnodes;
179 TR_BuildTracingNode_r(tile, tnode, n->children[i], level);
180 }
181 } else {
182 /* Make a lookup table */
183 tile->cheads[tile->numcheads].cnode = nodenum;
184 tile->cheads[tile->numcheads].level = level;
185 tile->numcheads++;
186 assert(tile->numcheads <= MAX_MAP_NODES);
187 /* Make the tracing node. */
188 TR_MakeTracingNode(tile, tnode, nodenum);
189 }
190 }
191
192
193 /*
194 ===============================================================================
195 LINE TRACING - TEST FOR BRUSH PRESENCE
196 ===============================================================================
197 */
198
199
200 /**
201 * @param[in] tile The map tile containing the structures to be traced.
202 * @param[in] nodenum Node index
203 * @param[in] start The position to start the trace.
204 * @param[in] end The position where the trace ends.
205 * @return zero if the line is not blocked, else a positive value
206 * @sa TR_TestLineDist_r
207 * @sa CM_TestLine
208 */
209 int TR_TestLine_r (TR_TILE_TYPE *tile, int32_t nodenum, const vec3_t start, const vec3_t end)
210 {
211 tnode_t* tnode;
212 float front, back;
213 int r;
214
215 /* negative numbers indicate leaf nodes. Empty leaf nodes are marked as (1 << 31).
216 * Turning off that bit makes us return 0 or the positive node number to indicate blocking. */
217 if (nodenum & (1 << 31))
218 return nodenum & ~(1 << 31);
219
220 tnode = &tile->tnodes[nodenum];
221 assert(tnode);
222 switch (tnode->type) {
223 case PLANE_X:
224 case PLANE_Y:
225 case PLANE_Z:
226 front = start[tnode->type] - tnode->dist;
227 back = end[tnode->type] - tnode->dist;
228 break;
229 case PLANE_NONE:
230 r = TR_TestLine_r(tile, tnode->children[0], start, end);
231 if (r)
232 return r;
233 return TR_TestLine_r(tile, tnode->children[1], start, end);
234 default:
235 front = DotProduct(start, tnode->normal) - tnode->dist;
236 back = DotProduct(end, tnode->normal) - tnode->dist;
237 break;
238 }
239
240 if (front >= -ON_EPSILON && back >= -ON_EPSILON)
241 return TR_TestLine_r(tile, tnode->children[0], start, end);
242 else if (front < ON_EPSILON && back < ON_EPSILON)
243 return TR_TestLine_r(tile, tnode->children[1], start, end);
244 else {
245 const int side = front < 0;
246 const float frac = front / (front - back);
247 vec3_t mid;
248
249 VectorInterpolation(start, end, frac, mid);
250
251 r = TR_TestLine_r(tile, tnode->children[side], start, mid);
252 if (r)
253 return r;
254 return TR_TestLine_r(tile, tnode->children[!side], mid, end);
255 }
256 }
257
258
259 /**
260 * @brief Tests to see if a line intersects any brushes in a tile.
261 * @param[in] tile The map tile containing the structures to be traced.
262 * @param[in] start The position to start the trace.
263 * @param[in] end The position where the trace ends.
264 * @param[in] levelmask
265 * @return true if the line is blocked
266 * @note This function uses levels and levelmasks. The levels are as following:
267 * 0-255: brushes are assigned to a level based on their assigned viewing levels. A brush with
268 * no levels assigned will be stuck in 0, a brush viewable from all 8 levels will be in 255, and
269 * so on. Each brush will only appear in one level.
270 * 256: weaponclip-level
271 * 257: actorclip-level
272 *
273 * The levelmask is used to determine which levels AND which, if either, clip to trace through.
274 * The mask bits are as follows:
275 * 0x0FF: Level bits. If any bits are set then a brush's level ANDed with the levelmask, then
276 * that level is traced. It could possibly be used to speed up traces.
277 * 0x100: Actorclip bit. If this bit is set, the actorclip level will be traced.
278 * 0x200: Weaponclip bit. If this bit is set, the weaponclip level will be traced.
279 */
280 static bool TR_TileTestLine (TR_TILE_TYPE *tile, const vec3_t start, const vec3_t end, const int levelmask)
281 {
282 const int corelevels = (levelmask & TL_FLAG_REGULAR_LEVELS);
283 int i;
284
285 /* loop over all theads */
286 for (i = 0; i < tile->numtheads; i++) {
287 const int level = tile->theadlevel[i];
288 if (level && corelevels && !(level & corelevels))
289 continue;
290 if (level == LEVEL_LIGHTCLIP) /* lightclips are only used in ufo2map, and it does not use this function */
291 continue;
292 if (level == LEVEL_ACTORCLIP && !(levelmask & TL_FLAG_ACTORCLIP))
293 continue;
294 if (level == LEVEL_WEAPONCLIP && !(levelmask & TL_FLAG_WEAPONCLIP))
295 continue;
296 if (TR_TestLine_r(tile, tile->thead[i], start, end))
297 return true;
298 }
299 return false;
300 }
301
302 /**
303 * @brief Checks traces against the world
304 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
305 * @param[in] start The position to start the trace.
306 * @param[in] end The position where the trace ends.
307 * @param[in] levelmask Indicates which special levels, if any, to include in the trace.
308 * @note Special levels are LEVEL_ACTORCLIP and LEVEL_WEAPONCLIP.
309 * @sa TR_TestLine_r
310 * @return false if not blocked
311 */
312 bool TR_TestLine (mapTiles_t* mapTiles, const vec3_t start, const vec3_t end, const int levelmask)
313 {
314 int tile;
315
316 for (tile = 0; tile < mapTiles->numTiles; tile++) {
317 if (TR_TileTestLine(&mapTiles->mapTiles[tile], start, end, levelmask))
318 return true;
319 }
320 return false;
321 }
322
323
324 /*
325 ===============================================================================
326 LINE TRACING - TEST FOR BRUSH LOCATION
327 ===============================================================================
328 */
329
330
331 /**
332 * @param[in] tile The map tile containing the structures to be traced.
333 * @param[in] nodenum Node index
334 * @param[in] start The position to start the trace.
335 * @param[in] end The position where the trace ends.
336 * @param[in,out] tr_end used to hold the point on a line that an obstacle is encountered.
337 * @sa TR_TestLine_r
338 * @sa TR_TestLineDM
339 */
340 static int TR_TestLineDist_r (TR_TILE_TYPE *tile, int32_t nodenum, const vec3_t start, const vec3_t end, vec3_t tr_end)
341 {
342 tnode_t* tnode;
343 float front, back;
344 vec3_t mid;
345 float frac;
346 int side;
347 int r;
348
349 if (nodenum & (1 << 31)) {
350 r = nodenum & ~(1 << 31);
351 if (r)
352 VectorCopy(start, tr_end);
353 return r; /* leaf node */
354 }
355
356 tnode = &tile->tnodes[nodenum];
357 assert(tnode);
358 switch (tnode->type) {
359 case PLANE_X:
360 case PLANE_Y:
361 case PLANE_Z:
362 front = start[tnode->type] - tnode->dist;
363 back = end[tnode->type] - tnode->dist;
364 break;
365 case PLANE_NONE:
366 r = TR_TestLineDist_r(tile, tnode->children[0], start, end, tr_end);
367 if (r)
368 VectorCopy(tr_end, mid);
369 side = TR_TestLineDist_r(tile, tnode->children[1], start, end, tr_end);
370 if (side && r) {
371 if (VectorNearer(mid, tr_end, start)) {
372 VectorCopy(mid, tr_end);
373 return r;
374 } else {
375 return side;
376 }
377 }
378
379 if (r) {
380 VectorCopy(mid, tr_end);
381 return r;
382 }
383
384 return side;
385 default:
386 front = (start[0] * tnode->normal[0] + start[1] * tnode->normal[1] + start[2] * tnode->normal[2]) - tnode->dist;
387 back = (end[0] * tnode->normal[0] + end[1] * tnode->normal[1] + end[2] * tnode->normal[2]) - tnode->dist;
388 break;
389 }
390
391 if (front >= -ON_EPSILON && back >= -ON_EPSILON)
392 return TR_TestLineDist_r(tile, tnode->children[0], start, end, tr_end);
393
394 if (front < ON_EPSILON && back < ON_EPSILON)
395 return TR_TestLineDist_r(tile, tnode->children[1], start, end, tr_end);
396
397 side = front < 0;
398
399 frac = front / (front - back);
400
401 VectorInterpolation(start, end, frac, mid);
402
403 r = TR_TestLineDist_r(tile, tnode->children[side], start, mid, tr_end);
404 if (r)
405 return r;
406 return TR_TestLineDist_r(tile, tnode->children[!side], mid, end, tr_end);
407 }
408
409 /**
410 * @brief Checks traces against the world, gives hit position back
411 * @param[in] tile The map tile containing the structures to be traced.
412 * @param[in] start The position to start the trace.
413 * @param[in] end The position where the trace ends.
414 * @param[out] hit The position where the trace hits a object or the 'end' position if nothing is in the line.
415 * @param[in] levelmask The bitmask of the levels (1<<[0-7]) to trace for
416 * @sa TR_TestLineDM
417 * @sa CL_ActorMouseTrace
418 * @return false if no connection between start and end - 1 otherwise
419 */
420 static bool TR_TileTestLineDM (TR_TILE_TYPE *tile, const vec3_t start, const vec3_t end, vec3_t hit, const int levelmask)
421 {
422 #ifdef COMPILE_MAP
423 const int corelevels = (levelmask & TL_FLAG_REGULAR_LEVELS);
424 #endif
425 vec3_t tr_end;
426 int i;
427
428 VectorCopy(end, hit);
429
430 for (i = 0; i < tile->numtheads; i++) {
431 #ifdef COMPILE_MAP
432 const int level = tile->theadlevel[i];
433 if (level && corelevels && !(level & levelmask))
434 continue;
435 /* if (level == LEVEL_LIGHTCLIP)
436 continue;*/
437 if (level == LEVEL_ACTORCLIP && !(levelmask & TL_FLAG_ACTORCLIP))
438 continue;
439 if (level == LEVEL_WEAPONCLIP && !(levelmask & TL_FLAG_WEAPONCLIP))
440 continue;
441 #endif
442 if (TR_TestLineDist_r(tile, tile->thead[i], start, end, tr_end))
443 if (VectorNearer(tr_end, hit, start))
444 VectorCopy(tr_end, hit);
445 }
446
447 if (VectorCompareEps(hit, end, EQUAL_EPSILON))
448 return false;
449 else
450 return true;
451 }
452
453
454 /**
455 * @brief Checks traces against the world, gives hit position back
456 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
457 * @param[in] start The position to start the trace.
458 * @param[in] end The position where the trace ends.
459 * @param[out] hit The position where the trace hits a object or the 'end' position if nothing is in the line.
460 * @param[in] levelmask Indicates which special levels, if any, to include in the trace.
461 * @sa TR_TestLineDM
462 * @sa CL_ActorMouseTrace
463 * @return false if no connection between start and end - 1 otherwise
464 */
465 bool TR_TestLineDM (mapTiles_t* mapTiles, const vec3_t start, const vec3_t end, vec3_t hit, const int levelmask)
466 {
467 int tile;
468 vec3_t t_end;
469
470 VectorCopy(end, hit);
471 VectorCopy(end, t_end);
472
473 for (tile = 0; tile < mapTiles->numTiles; tile++) {
474 if (TR_TileTestLineDM(&mapTiles->mapTiles[tile], start, end, t_end, levelmask)) {
475 if (VectorNearer(t_end, hit, start))
476 VectorCopy(t_end, hit);
477 }
478 }
479
480 if (VectorCompareEps(hit, end, EQUAL_EPSILON))
481 return false;
482 else
483 return true;
484 }
485
486
487 /*
488 ===============================================================================
489 BOX TRACING
490 ===============================================================================
491 */
492
493
494 /**
495 * @brief Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH
496 */
497 int TR_BoxOnPlaneSide (const vec3_t mins, const vec3_t maxs, const TR_PLANE_TYPE *plane)
498 {
499 int side, i;
500 vec3_t corners[2];
501 vec_t dist1, dist2;
502
503 /* axial planes are easy */
504 if (AXIAL(plane)) {
505 side = 0;
506 if (maxs[plane->type] > plane->dist + PLANESIDE_EPSILON)
507 side |= PSIDE_FRONT;
508 if (mins[plane->type] < plane->dist - PLANESIDE_EPSILON)
509 side |= PSIDE_BACK;
510 return side;
511 }
512
513 /* create the proper leading and trailing verts for the box */
514
515 for (i = 0; i < 3; i++) {
516 if (plane->normal[i] < 0) {
517 corners[0][i] = mins[i];
518 corners[1][i] = maxs[i];
519 } else {
520 corners[1][i] = mins[i];
521 corners[0][i] = maxs[i];
522 }
523 }
524
525 dist1 = DotProduct(plane->normal, corners[0]) - plane->dist;
526 dist2 = DotProduct(plane->normal, corners[1]) - plane->dist;
527 side = 0;
528 if (dist1 >= PLANESIDE_EPSILON)
529 side = PSIDE_FRONT;
530 if (dist2 < PLANESIDE_EPSILON)
531 side |= PSIDE_BACK;
532
533 return side;
534 }
535
536 typedef struct leaf_check_s {
537 int leaf_count, leaf_maxcount;
538 int32_t* leaf_list;
539 int32_t leaf_topnode;
540 } leaf_check_t;
541
542 /**
543 * @brief Fills in a list of all the leafs touched
544 * call with topnode set to the headnode, returns with topnode
545 * set to the first node that splits the box
546 */
547 static void TR_BoxLeafnums_r (boxtrace_t* traceData, int32_t nodenum, leaf_check_t* lc)
548 {
549 TR_TILE_TYPE *myTile = traceData->tile;
550
551 while (1) {
552 if (nodenum <= LEAFNODE) {
553 if (lc->leaf_count >= lc->leaf_maxcount) {
554 /* Com_Printf("CM_BoxLeafnums_r: overflow\n"); */
555 return;
556 }
557 lc->leaf_list[lc->leaf_count++] = -1 - nodenum;
558 return;
559 }
560
561 assert(nodenum < myTile->numnodes + 6); /* +6 => bbox */
562 TR_NODE_TYPE *node = &myTile->nodes[nodenum];
563
564 TR_PLANE_TYPE *plane;
565 #ifdef COMPILE_UFO
566 plane = node->plane;
567 #else
568 plane = myTile->planes + node->planenum;
569 #endif
570
571 int s = TR_BoxOnPlaneSide(traceData->absmins, traceData->absmaxs, plane);
572 if (s == PSIDE_FRONT)
573 nodenum = node->children[0];
574 else if (s == PSIDE_BACK)
575 nodenum = node->children[1];
576 else { /* go down both */
577 if (lc->leaf_topnode == LEAFNODE)
578 lc->leaf_topnode = nodenum;
579 TR_BoxLeafnums_r(traceData, node->children[0], lc);
580 nodenum = node->children[1];
581 }
582 }
583 }
584
585 /**
586 * @param[in] traceData both parameters and results of the trace
587 * @param[in] headnode if < 0 we are in a leaf node
588 */
589 static int TR_BoxLeafnums_headnode (boxtrace_t* traceData, int32_t* list, int listsize, int32_t headnode, int32_t* topnode)
590 {
591 leaf_check_t lc;
592 lc.leaf_list = list;
593 lc.leaf_count = 0;
594 lc.leaf_maxcount = listsize;
595 lc.leaf_topnode = LEAFNODE;
596
597 assert(headnode < traceData->tile->numnodes + 6); /* +6 => bbox */
598 TR_BoxLeafnums_r(traceData, headnode, &lc);
599
600 if (topnode)
601 *topnode = lc.leaf_topnode;
602
603 return lc.leaf_count;
604 }
605
606
607 /**
608 * @param[in,out] traceData both parameters and results of the trace
609 * @param[in] brush the brush that is being examined
610 * @param[in] leaf the leafnode the brush that is being examined belongs to
611 * @brief This function checks to see if any sides of a brush intersect the line from p1 to p2 or are located within
612 * the perpendicular bounding box from mins to maxs originating from the line. It also check to see if the line
613 * originates from inside the brush, terminates inside the brush, or is completely contained within the brush.
614 */
615 static void TR_ClipBoxToBrush (boxtrace_t* traceData, cBspBrush_t* brush, TR_LEAF_TYPE *leaf)
616 {
617 int i, j;
618 TR_PLANE_TYPE *clipplane;
619 int clipplanenum;
620 float dist;
621 float enterfrac, leavefrac;
622 vec3_t ofs;
623 bool getout, startout;
624 #ifdef COMPILE_UFO
625 TR_BRUSHSIDE_TYPE *leadside = nullptr;
626 #endif
627 TR_TILE_TYPE *myTile = traceData->tile;
628
629 enterfrac = -1.0;
630 leavefrac = 1.0;
631 clipplane = nullptr;
632
633 if (!brush || !brush->numsides)
634 return;
635
636 getout = false;
637 startout = false;
638 clipplanenum = 0;
639
640 for (i = 0; i < brush->numsides; i++) {
641 TR_BRUSHSIDE_TYPE *side = &myTile->brushsides[brush->firstbrushside + i];
642 #ifdef COMPILE_UFO
643 TR_PLANE_TYPE *plane = side->plane;
644 #else
645 TR_PLANE_TYPE *plane = myTile->planes + side->planenum;
646 #endif
647
648 /** @todo special case for axial */
649 if (!traceData->ispoint) { /* general box case */
650 /* push the plane out appropriately for mins/maxs */
651 for (j = 0; j < 3; j++) {
652 if (plane->normal[j] < 0)
653 ofs[j] = traceData->maxs[j];
654 else
655 ofs[j] = traceData->mins[j];
656 }
657 dist = DotProduct(ofs, plane->normal);
658 dist = plane->dist - dist;
659 } else { /* special point case */
660 dist = plane->dist;
661 }
662
663 float d1 = DotProduct(traceData->start, plane->normal) - dist;
664 float d2 = DotProduct(traceData->end, plane->normal) - dist;
665
666 if (d2 > 0)
667 getout = true; /* endpoint is not in solid */
668 if (d1 > 0)
669 startout = true; /* startpoint is not in solid */
670
671 /* if completely in front of face, no intersection */
672 if (d1 > 0 && d2 >= d1)
673 return;
674
675 if (d1 <= 0 && d2 <= 0)
676 continue;
677
678 /* crosses face */
679 if (d1 > d2) { /* enter */
680 const float f = (d1 - DIST_EPSILON) / (d1 - d2);
681 if (f > enterfrac) {
682 enterfrac = f;
683 clipplane = plane;
684 #ifdef COMPILE_MAP
685 clipplanenum = side->planenum;
686 #else
687 leadside = side;
688 #endif
689 }
690 } else { /* leave */
691 const float f = (d1 + DIST_EPSILON) / (d1 - d2);
692 if (f < leavefrac)
693 leavefrac = f;
694 }
695 }
696
697 if (!startout) { /* original point was inside brush */
698 traceData->trace.startsolid = true;
699 if (!getout)
700 traceData->trace.allsolid = true;
701 traceData->trace.leafnum = leaf - myTile->leafs;
702 return;
703 }
704 if (enterfrac < leavefrac) {
705 if (enterfrac > -1 && enterfrac < traceData->trace.fraction) {
706 if (enterfrac < 0)
707 enterfrac = 0;
708 traceData->trace.fraction = enterfrac;
709 traceData->trace.plane = *clipplane;
710 traceData->trace.planenum = clipplanenum;
711 #ifdef COMPILE_UFO
712 traceData->trace.surface = leadside->surface;
713 #endif
714 traceData->trace.contentFlags = brush->contentFlags;
715 traceData->trace.leafnum = leaf - myTile->leafs;
716 }
717 }
718 }
719
720 /**
721 * @sa CM_TraceToLeaf
722 */
723 static void TR_TestBoxInBrush (boxtrace_t* traceData, cBspBrush_t* brush)
724 {
725 int i, j;
726 TR_PLANE_TYPE *plane;
727 vec3_t ofs;
728 TR_TILE_TYPE *myTile = traceData->tile;
729
730 if (!brush || !brush->numsides)
731 return;
732
733 for (i = 0; i < brush->numsides; i++) {
734 TR_BRUSHSIDE_TYPE *side = &myTile->brushsides[brush->firstbrushside + i];
735 #ifdef COMPILE_UFO
736 plane = side->plane;
737 #else
738 plane = myTile->planes + side->planenum;
739 #endif
740
741 /** @todo special case for axial */
742 /* general box case */
743 /* push the plane out appropriately for mins/maxs */
744 for (j = 0; j < 3; j++) {
745 if (plane->normal[j] < 0)
746 ofs[j] = traceData->maxs[j];
747 else
748 ofs[j] = traceData->mins[j];
749 }
750 float dist = DotProduct(ofs, plane->normal);
751 dist = plane->dist - dist;
752
753 float d1 = DotProduct(traceData->start, plane->normal) - dist;
754
755 /* if completely in front of face, no intersection */
756 if (d1 > 0)
757 return;
758 }
759
760 /* inside this brush */
761 traceData->trace.startsolid = traceData->trace.allsolid = true;
762 traceData->trace.fraction = 0;
763 traceData->trace.contentFlags = brush->contentFlags;
764 }
765
766
767 /**
768 * @param[in] traceData both parameters and results of the trace
769 * @param[in] leafnum the leaf index that we are looking in for a hit against
770 * @sa CM_ClipBoxToBrush
771 * @sa CM_TestBoxInBrush
772 * @sa CM_RecursiveHullCheck
773 * @brief This function checks if the specified leaf matches any mask specified in traceData.contents. and does
774 * not contain any mask specified in traceData.rejects If so, each brush in the leaf is examined to see if it
775 * is intersected by the line drawn in TR_RecursiveHullCheck or is within the bounding box set in trace_mins and
776 * trace_maxs with the origin on the line.
777 */
778 static void TR_TraceToLeaf (boxtrace_t* traceData, int32_t leafnum)
779 {
780 int k;
781 TR_LEAF_TYPE *leaf;
782 TR_TILE_TYPE *myTile = traceData->tile;
783
784 assert(leafnum > LEAFNODE);
785 assert(leafnum <= myTile->numleafs);
786
787 leaf = &myTile->leafs[leafnum];
788
789 if (traceData->contents != MASK_ALL && (!(leaf->contentFlags & traceData->contents) || (leaf->contentFlags & traceData->rejects)))
790 return;
791
792 /* trace line against all brushes in the leaf */
793 for (k = 0; k < leaf->numleafbrushes; k++) {
794 const int brushnum = myTile->leafbrushes[leaf->firstleafbrush + k];
795 cBspBrush_t* b = &myTile->brushes[brushnum];
796
797 if (b->checkcount == checkcount)
798 continue; /* already checked this brush in another leaf */
799 b->checkcount = checkcount;
800
801 if (traceData->contents != MASK_ALL && (!(b->contentFlags & traceData->contents) || (b->contentFlags & traceData->rejects)))
802 continue;
803
804 TR_ClipBoxToBrush(traceData, b, leaf);
805 if (!traceData->trace.fraction)
806 return;
807 }
808 }
809
810
811 /**
812 * @sa CM_TestBoxInBrush
813 */
814 static void TR_TestInLeaf (boxtrace_t* traceData, int32_t leafnum)
815 {
816 int k;
817 const TR_LEAF_TYPE *leaf;
818 TR_TILE_TYPE *myTile = traceData->tile;
819
820 assert(leafnum > LEAFNODE);
821 assert(leafnum <= myTile->numleafs);
822
823 leaf = &myTile->leafs[leafnum];
824 /* If this leaf contains no flags we need to look for, then skip it. */
825 if (!(leaf->contentFlags & traceData->contents)) /* || (leaf->contentFlags & traceData->rejects) */
826 return;
827
828 /* trace line against all brushes in the leaf */
829 for (k = 0; k < leaf->numleafbrushes; k++) {
830 const int brushnum = myTile->leafbrushes[leaf->firstleafbrush + k];
831 cBspBrush_t* b = &myTile->brushes[brushnum];
832 if (b->checkcount == checkcount)
833 continue; /* already checked this brush in another leaf */
834 b->checkcount = checkcount;
835
836 if (!(traceData->contents && (b->contentFlags & traceData->contents)) || (b->contentFlags & traceData->rejects))
837 continue;
838 TR_TestBoxInBrush(traceData, b);
839 if (!traceData->trace.fraction)
840 return;
841 }
842 }
843
844
845 /**
846 * @param[in] traceData both parameters and results of the trace
847 * @param[in] nodenum the node index that we are looking in for a hit
848 * @param[in] p1f based on the original line, the fraction traveled to reach the start vector
849 * @param[in] p2f based on the original line, the fraction traveled to reach the end vector
850 * @param[in] p1 start vector
851 * @param[in] p2 end vector
852 * @sa CM_BoxTrace
853 * @sa CM_TraceToLeaf
854 * @brief This recursive function traces through the bsp tree looking to see if a brush can be
855 * found that intersects the line from p1 to p2, including a bounding box (plane) offset that
856 * is perpendicular to the line. If the node of the tree is a leaf, the leaf is checked. If
857 * not, it is determined which side(s) of the tree need to be traversed, and this function is
858 * called again. The bounding box mentioned earlier is set in TR_BoxTrace, and propagated
859 * using trace_extents. Trace_extents is specifically how far from the line a bsp node needs
860 * to be in order to be included or excluded in the search.
861 */
862 static void TR_RecursiveHullCheck (boxtrace_t* traceData, int32_t nodenum, float p1f, float p2f, const vec3_t p1, const vec3_t p2)
863 {
864 TR_NODE_TYPE *node;
865 TR_PLANE_TYPE *plane;
866 float t1, t2, offset;
867 float frac, frac2;
868 int side;
869 float midf;
870 vec3_t mid;
871 TR_TILE_TYPE *myTile = traceData->tile;
872
873 if (traceData->trace.fraction <= p1f)
874 return; /* already hit something nearer */
875
876 /* if < 0, we are in a leaf node */
877 if (nodenum <= LEAFNODE) {
878 TR_TraceToLeaf(traceData, LEAFNODE - nodenum);
879 return;
880 }
881
882 assert(nodenum < MAX_MAP_NODES);
883
884 /* find the point distances to the seperating plane
885 * and the offset for the size of the box */
886 node = myTile->nodes + nodenum;
887
888 #ifdef COMPILE_UFO
889 plane = node->plane;
890 #else
891 assert(node->planenum < MAX_MAP_PLANES);
892 plane = myTile->planes + node->planenum;
893 #endif
894
895 if (AXIAL(plane)) {
896 const int type = plane->type;
897 t1 = p1[type] - plane->dist;
898 t2 = p2[type] - plane->dist;
899 offset = traceData->extents[type];
900 } else {
901 t1 = DotProduct(plane->normal, p1) - plane->dist;
902 t2 = DotProduct(plane->normal, p2) - plane->dist;
903 if (traceData->ispoint)
904 offset = 0;
905 else
906 offset = fabs(traceData->extents[0] * plane->normal[0])
907 + fabs(traceData->extents[1] * plane->normal[1])
908 + fabs(traceData->extents[2] * plane->normal[2]);
909 }
910
911 /* see which sides we need to consider */
912 if (t1 >= offset && t2 >= offset) {
913 TR_RecursiveHullCheck(traceData, node->children[0], p1f, p2f, p1, p2);
914 return;
915 } else if (t1 < -offset && t2 < -offset) {
916 TR_RecursiveHullCheck(traceData, node->children[1], p1f, p2f, p1, p2);
917 return;
918 }
919
920 /* put the crosspoint DIST_EPSILON pixels on the near side */
921 /** @note t1 and t2 refer to the distance the endpoints of the line are from the bsp dividing plane for this node.
922 * Additionally, frac and frac2 are the fractions of the CURRENT PIECE of the line that was being tested, and will
923 * add to (approximately) 1. When midf is calculated, frac and frac2 are converted to reflect the fraction of the
924 * WHOLE line being traced. However, the interpolated vector is based on the CURRENT endpoints so uses frac and
925 * frac2 to find the actual splitting point.
926 */
927 if (t1 < t2) {
928 const float idist = 1.0 / (t1 - t2);
929 side = 1;
930 frac2 = (t1 + offset + DIST_EPSILON) * idist;
931 frac = (t1 - offset + DIST_EPSILON) * idist;
932 } else if (t1 > t2) {
933 const float idist = 1.0 / (t1 - t2);
934 side = 0;
935 frac2 = (t1 - offset - DIST_EPSILON) * idist;
936 frac = (t1 + offset + DIST_EPSILON) * idist;
937 } else {
938 side = 0;
939 frac = 1;
940 frac2 = 0;
941 }
942
943 /* move up to the node */
944 if (frac < 0)
945 frac = 0;
946 else if (frac > 1)
947 frac = 1;
948
949 midf = p1f + (p2f - p1f) * frac;
950 VectorInterpolation(p1, p2, frac, mid);
951 TR_RecursiveHullCheck(traceData, node->children[side], p1f, midf, p1, mid);
952
953 /* go past the node */
954 if (frac2 < 0)
955 frac2 = 0;
956 else if (frac2 > 1)
957 frac2 = 1;
958
959 midf = p1f + (p2f - p1f) * frac2;
960 VectorInterpolation(p1, p2, frac2, mid);
961 TR_RecursiveHullCheck(traceData, node->children[side ^ 1], midf, p2f, mid, p2);
962 }
963
964 /**
965 * @brief This function traces a line from start to end. It returns a trace_t indicating what portion of the line
966 * can be traveled from start to end before hitting a brush that meets the criteria in brushmask. The point that
967 * this line intersects that brush is also returned.
968 * There is a special case when start and end are the same vector. In this case, the bounding box formed by mins
969 * and maxs offset from start is examined for any brushes that meet the criteria. The first brush found inside
970 * the bounding box is returned.
971 * There is another special case when mins and maxs are both origin vectors (0, 0, 0). In this case, the
972 * @param[in] start trace start vector
973 * @param[in] end trace end vector
974 * @param[in] traceBox The box we shove through the world
975 * @param[in] tile Tile to check (normally 0 - except in assembled maps)
976 * @param[in] headnode if < 0 we are in a leaf node
977 * @param[in] contentmask brushes the trace should stop at (see MASK_*)
978 * @param[in] brushreject brushes the trace should ignore (see MASK_*)
979 * @param[in] fraction The furthest distance needed to trace before we stop.
980 * @sa TR_RecursiveHullCheck
981 * @sa TR_BoxLeafnums_headnode
982 */
983 trace_t TR_BoxTrace (TR_TILE_TYPE *tile, const vec3_t start, const vec3_t end, const AABB &traceBox, const int headnode, const int contentmask, const int brushreject, const float fraction)
984 {
985 vec3_t offset, amins, amaxs, astart, aend;
986 boxtrace_t traceData;
987
988 checkcount++; /* for multi-check avoidance */
989
990 #ifdef COMPILE_UFO
991 if (headnode >= tile->numnodes + 6)
992 Com_Error(ERR_DROP, "headnode (%i) is out of bounds: %i", headnode, tile->numnodes + 6);
993 #else
994 assert(headnode < tile->numnodes + 6); /* +6 => bbox */
995 #endif
996
997 /* fill in a default trace */
998 traceData.trace.init();
999 traceData.trace.fraction = std::min(fraction, 1.0f); /* Use 1 or fraction, whichever is lower. */
1000 traceData.trace.surface = nullptr;
1001
1002 if (!tile->numnodes) /* map not loaded */
1003 return traceData.trace;
1004
1005 /* Optimize the trace by moving the line to be traced across into the origin of the box trace. */
1006 /* Calculate the offset needed to center the trace about the line */
1007 traceBox.getCenter(offset);
1008
1009 /* Now remove the offset from bmin and bmax (effectively centering the trace box about the origin of the line)
1010 * and add the offset to the trace line (effectively repositioning the trace box at the desired coordinates) */
1011 VectorSubtract(traceBox.mins, offset, amins);
1012 VectorSubtract(traceBox.maxs, offset, amaxs);
1013 VectorAdd(start, offset, astart);
1014 VectorAdd(end, offset, aend);
1015
1016 traceData.contents = contentmask;
1017 traceData.rejects = brushreject;
1018 traceData.tile = tile;
1019 VectorCopy(astart, traceData.start);
1020 VectorCopy(aend, traceData.end);
1021 VectorCopy(amins, traceData.mins);
1022 VectorCopy(amaxs, traceData.maxs);
1023
1024 /* check for position test special case */
1025 if (VectorEqual(astart, aend)) {
1026 int32_t leafs[MAX_LEAFS];
1027 int numleafs;
1028 int32_t topnode;
1029 int i;
1030
1031 VectorAdd(astart, amaxs, traceData.absmaxs);
1032 VectorAdd(astart, amins, traceData.absmins);
1033 /* Removed because if creates a buffer- is this needed?
1034 for (i = 0; i < 3; i++) {
1035 / * expand the box by 1 * /
1036 traceData.absmins[i] -= 1;
1037 traceData.absmaxs[i] += 1;
1038 }
1039 */
1040
1041 numleafs = TR_BoxLeafnums_headnode(&traceData, leafs, MAX_LEAFS, headnode, &topnode);
1042 for (i = 0; i < numleafs; i++) {
1043 TR_TestInLeaf(&traceData, leafs[i]);
1044 if (traceData.trace.allsolid)
1045 break;
1046 }
1047 VectorCopy(start, traceData.trace.endpos);
1048 return traceData.trace;
1049 }
1050
1051 /* check for point special case */
1052 if (VectorEmpty(amins) && VectorEmpty(amaxs)) {
1053 traceData.ispoint = true;
1054 VectorClear(traceData.extents);
1055 } else {
1056 traceData.ispoint = false;
1057 VectorCopy(amaxs, traceData.extents);
1058 }
1059
1060 /* general sweeping through world */
1061 /** @todo Would Interpolating aend to traceData.fraction and passing traceData.fraction instead of 1.0 make this faster? */
1062 TR_RecursiveHullCheck(&traceData, headnode, 0.0, 1.0, astart, aend);
1063
1064 if (traceData.trace.fraction >= 1.0) {
1065 VectorCopy(aend, traceData.trace.endpos);
1066 } else {
1067 VectorInterpolation(traceData.start, traceData.end, traceData.trace.fraction, traceData.trace.endpos);
1068 }
1069 /* Now un-offset the end position. */
1070 VectorSubtract(traceData.trace.endpos, offset, traceData.trace.endpos);
1071 return traceData.trace;
1072 }
1073
1074 /**
1075 * @brief Traces all submodels in the specified tile. Provides for a short
1076 * circuit if the trace tries to move past fraction to save time.
1077 * @param[in] myTile The tile being traced
1078 * @param[in] start trace start vector
1079 * @param[in] end trace end vector
1080 * @param[in] aabb The box we are moving through the world
1081 * @param[in] levelmask Selects which submodels get scanned.
1082 * @param[in] brushmask brushes the trace should stop at (see MASK_*)
1083 * @param[in] brushreject brushes the trace should ignore (see MASK_*)
1084 */
1085 trace_t TR_TileBoxTrace (TR_TILE_TYPE *myTile, const vec3_t start, const vec3_t end, const AABB &aabb, const int levelmask, const int brushmask, const int brushreject)
1086 {
1087 int i;
1088 cBspHead_t* h;
1089 trace_t tr;
1090
1091 /* ensure that the first trace is set in every case */
1092 tr.fraction = 2.0f;
1093
1094 /* trace against all loaded map tiles */
1095 for (i = 0, h = myTile->cheads; i < myTile->numcheads; i++, h++) {
1096 /* This code uses levelmask to limit by maplevel. Supposedly maplevels 1-255
1097 * are bitmasks of game levels 1-8. 0 is a special case repeat of 255.
1098 * However a levelmask including 0x100 is usually included so the CLIP levels are
1099 * examined. */
1100 if (h->level && h->level <= LEVEL_LASTVISIBLE && levelmask && !(h->level & levelmask))
1101 continue;
1102
1103 assert(h->cnode < myTile->numnodes + 6); /* +6 => bbox */
1104 const trace_t newtr = TR_BoxTrace(myTile, start, end, aabb, h->cnode, brushmask, brushreject, tr.fraction);
1105
1106 /* memorize the trace with the minimal fraction */
1107 if (newtr.fraction == 0.0)
1108 return newtr;
1109 if (newtr.fraction < tr.fraction)
1110 tr = newtr;
1111 }
1112 return tr;
1113 }
1114
1115 #ifdef COMPILE_MAP
1116
1117 /**
1118 * @param[in] start trace start vector
1119 * @param[in] end trace end vector
1120 * @param[in] mins box mins
1121 * @param[in] maxs box maxs
1122 * @param[in] levelmask Selects which submodels get scanned.
1123 * @param[in] brushmask brushes the trace should stop at (see MASK_*)
1124 * @param[in] brushreject brushes the trace should ignore (see MASK_*)
1125 * @brief Traces all submodels in the first tile. Used by ufo2map.
1126 */
1127 trace_t TR_SingleTileBoxTrace (mapTiles_t* mapTiles, const Line &traceLine, const AABB* traceBox, const int levelmask, const int brushmask, const int brushreject)
1128 {
1129 /* Trace the whole line against the first tile. */
1130 trace_t tr = TR_TileBoxTrace(&mapTiles->mapTiles[0], traceLine.start, traceLine.stop, *traceBox, levelmask, brushmask, brushreject);
1131 tr.mapTile = 0;
1132 return tr;
1133 }
1134 #endif
1135