1 /**
2 * @file
3 * @brief model loading and grid oriented movement and scanning
4 * @note collision detection code (server side)
5 */
6
7 /*
8 Copyright (C) 1997-2001 Id Software, Inc.
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
19 See the GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25 */
26
27 #include "common.h"
28 #include "tracing.h"
29
30 /*
31 ===============================================================================
32 GAME RELATED TRACING USING ENTITIES
33 ===============================================================================
34 */
35
36 /**
37 * @brief Calculates the bounding box for the given bsp model
38 * @param[in] model The model to calculate the bbox for
39 * @param[out] mins The maxs of the bbox
40 * @param[out] maxs The mins of the bbox
41 */
CM_CalculateBoundingBox(const cBspModel_t * model,vec3_t mins,vec3_t maxs)42 static void CM_CalculateBoundingBox (const cBspModel_t* model, vec3_t mins, vec3_t maxs)
43 {
44 /* Quickly calculate the bounds of this model to see if they can overlap. */
45 VectorAdd(model->origin, model->mins, mins);
46 VectorAdd(model->origin, model->maxs, maxs);
47 if (VectorNotEmpty(model->angles)) {
48 vec3_t acenter, aoffset;
49 const float offset = std::max(std::max(fabs(mins[0] - maxs[0]), fabs(mins[1] - maxs[1])), fabs(mins[2] - maxs[2])) / 2.0;
50 VectorCenterFromMinsMaxs(mins, maxs, acenter);
51 VectorSet(aoffset, offset, offset, offset);
52 VectorAdd(acenter, aoffset, maxs);
53 VectorSubtract(acenter, aoffset, mins);
54 }
55 }
56
57 /**
58 * @brief A quick test if the trace might hit the inline model
59 * @param[in] start The position to start the trace.
60 * @param[in] stop The position where the trace ends.
61 * @param[in] model The entity to check
62 * @return true - the line isn't anywhere near the model
63 */
CM_LineMissesModel(const vec3_t start,const vec3_t stop,const cBspModel_t * model)64 static bool CM_LineMissesModel (const vec3_t start, const vec3_t stop, const cBspModel_t* model)
65 {
66 vec3_t amins, amaxs;
67 CM_CalculateBoundingBox(model, amins, amaxs);
68 /* If the bounds of the extents box and the line do not overlap, then skip tracing this model. */
69 if ((start[0] > amaxs[0] && stop[0] > amaxs[0])
70 || (start[1] > amaxs[1] && stop[1] > amaxs[1])
71 || (start[2] > amaxs[2] && stop[2] > amaxs[2])
72 || (start[0] < amins[0] && stop[0] < amins[0])
73 || (start[1] < amins[1] && stop[1] < amins[1])
74 || (start[2] < amins[2] && stop[2] < amins[2]))
75 return true;
76
77 return false;
78 }
79
80
81 /**
82 * @param[in] tile Tile to check (normally 0 - except in assembled maps)
83 * @param[in] start trace start vector
84 * @param[in] end trace end vector
85 * @param[in] traceBox The box we shove through the world
86 * @param[in] headnode if < 0 we are in a leaf node
87 * @param[in] contentmask content flags the trace should stop at (see MASK_*)
88 * @param[in] brushrejects brushes the trace should ignore (see MASK_*)
89 * @param[in] origin center for rotating objects
90 * @param[in] angles current rotation status (in degrees) for rotating objects
91 * @param[in] rmaShift how much the object was shifted by the RMA process (needed for doors)
92 * @param[in] fraction The furthest distance needed to trace before we stop.
93 * @brief Handles offseting and rotation of the end points for moving and rotating entities
94 * @sa CM_BoxTrace
95 */
CM_HintedTransformedBoxTrace(MapTile & tile,const vec3_t start,const vec3_t end,const AABB & traceBox,const int headnode,const int contentmask,const int brushrejects,const vec3_t origin,const vec3_t angles,const vec3_t rmaShift,const float fraction)96 trace_t CM_HintedTransformedBoxTrace (MapTile &tile, const vec3_t start, const vec3_t end, const AABB &traceBox, const int headnode, const int contentmask, const int brushrejects, const vec3_t origin, const vec3_t angles, const vec3_t rmaShift, const float fraction)
97 {
98 vec3_t start_l, end_l;
99 vec3_t forward, right, up;
100 vec3_t temp;
101 bool rotated;
102
103 /* subtract origin offset */
104 VectorSubtract(start, origin, start_l);
105 VectorSubtract(end, origin, end_l);
106
107 /* rotate start and end into the models frame of reference */
108 if (headnode != tile.box_headnode && VectorNotEmpty(angles)) {
109 rotated = true;
110 } else {
111 rotated = false;
112 }
113
114 if (rotated) {
115 AngleVectors(angles, forward, right, up);
116
117 VectorCopy(start_l, temp);
118 start_l[0] = DotProduct(temp, forward);
119 start_l[1] = -DotProduct(temp, right);
120 start_l[2] = DotProduct(temp, up);
121
122 VectorCopy(end_l, temp);
123 end_l[0] = DotProduct(temp, forward);
124 end_l[1] = -DotProduct(temp, right);
125 end_l[2] = DotProduct(temp, up);
126 }
127
128 /* When tracing through a model, we want to use the nodes, planes etc. as calculated by ufo2map.
129 * But nodes and planes have been shifted in case of an RMA. At least for doors we need to undo the shift. */
130 if (VectorNotEmpty(origin)) { /* only doors seem to have their origin set */
131 VectorAdd(start_l, rmaShift, start_l); /* undo the shift */
132 VectorAdd(end_l, rmaShift, end_l);
133 }
134
135 /* sweep the box through the model */
136 trace_t trace = TR_BoxTrace(&tile, start_l, end_l, traceBox, headnode, contentmask, brushrejects, fraction);
137 trace.mapTile = tile.idx;
138
139 if (rotated && trace.fraction != 1.0) {
140 vec3_t a;
141 /** @todo figure out how to do this with existing angles */
142 VectorNegate(angles, a);
143 AngleVectors(a, forward, right, up);
144
145 VectorCopy(trace.plane.normal, temp);
146 trace.plane.normal[0] = DotProduct(temp, forward);
147 trace.plane.normal[1] = -DotProduct(temp, right);
148 trace.plane.normal[2] = DotProduct(temp, up);
149 }
150
151 VectorInterpolation(start, end, trace.fraction, trace.endpos);
152
153 return trace;
154 }
155
156 /**
157 * @brief To keep everything totally uniform, bounding boxes are turned into small
158 * BSP trees instead of being compared directly.
159 */
CM_HeadnodeForBox(MapTile & tile,const vec3_t mins,const vec3_t maxs)160 int32_t CM_HeadnodeForBox (MapTile &tile, const vec3_t mins, const vec3_t maxs)
161 {
162 tile.box_planes[0].dist = maxs[0];
163 tile.box_planes[1].dist = -maxs[0];
164 tile.box_planes[2].dist = mins[0];
165 tile.box_planes[3].dist = -mins[0];
166 tile.box_planes[4].dist = maxs[1];
167 tile.box_planes[5].dist = -maxs[1];
168 tile.box_planes[6].dist = mins[1];
169 tile.box_planes[7].dist = -mins[1];
170 tile.box_planes[8].dist = maxs[2];
171 tile.box_planes[9].dist = -maxs[2];
172 tile.box_planes[10].dist = mins[2];
173 tile.box_planes[11].dist = -mins[2];
174
175 assert(tile.box_headnode < MAX_MAP_NODES);
176 return tile.box_headnode;
177 }
178
179 /* TRACING FUNCTIONS */
180
181 /**
182 * @brief Checks traces against the world and all inline models
183 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
184 * @param[in] start The position to start the trace.
185 * @param[in] stop The position where the trace ends.
186 * @param[in] levelmask
187 * @param[in] entlist The local models list
188 * @sa TR_TestLine
189 * @sa CM_InlineModel
190 * @sa CM_TransformedBoxTrace
191 * @return true - hit something
192 * @return false - hit nothing
193 */
CM_EntTestLine(mapTiles_t * mapTiles,const vec3_t start,const vec3_t stop,const int levelmask,const char ** entlist)194 bool CM_EntTestLine (mapTiles_t* mapTiles, const vec3_t start, const vec3_t stop, const int levelmask, const char** entlist)
195 {
196 const char** name;
197
198 /* trace against world first */
199 if (TR_TestLine(mapTiles, start, stop, levelmask))
200 /* We hit the world, so we didn't make it anyway... */
201 return true;
202
203 /* no local models, so we made it. */
204 if (!entlist)
205 return false;
206
207 for (name = entlist; *name; name++) {
208 const cBspModel_t* model;
209 /* check whether this is really an inline model */
210 if (*name[0] != '*')
211 Com_Error(ERR_DROP, "name in the inlineList is no inline model: '%s'", *name);
212 model = CM_InlineModel(mapTiles, *name);
213 assert(model);
214 if (model->headnode >= mapTiles->mapTiles[model->tile].numnodes + 6)
215 continue;
216
217 /* check if we can safely exclude that the trace can hit the model */
218 if (CM_LineMissesModel(start, stop, model))
219 continue;
220
221 const trace_t trace = CM_HintedTransformedBoxTrace(mapTiles->mapTiles[model->tile], start, stop, AABB(),
222 model->headnode, MASK_VISIBILILITY, 0, model->origin, model->angles, model->shift, 1.0);
223 /* if we started the trace in a wall */
224 /* or the trace is not finished */
225 if (trace.startsolid || trace.fraction < 1.0)
226 return true;
227 }
228
229 /* not blocked */
230 return false;
231 }
232
233 /**
234 * @brief Checks traces against the world and all inline models, gives the hit position back
235 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
236 * @param[in] start The position to start the trace.
237 * @param[in] stop The position where the trace ends.
238 * @param[out] end The position where the line hits a object or the stop position if nothing is in the line
239 * @param[in] levelmask
240 * @param[in] entlist The local models list
241 * @sa TR_TestLineDM
242 * @sa CM_TransformedBoxTrace
243 */
CM_EntTestLineDM(mapTiles_t * mapTiles,const vec3_t start,const vec3_t stop,vec3_t end,const int levelmask,const char ** entlist)244 bool CM_EntTestLineDM (mapTiles_t* mapTiles, const vec3_t start, const vec3_t stop, vec3_t end, const int levelmask, const char** entlist)
245 {
246 const char** name;
247 bool blocked;
248 float fraction = 2.0f;
249
250 /* trace against world first */
251 blocked = TR_TestLineDM(mapTiles, start, stop, end, levelmask);
252 if (!entlist)
253 return blocked;
254
255 for (name = entlist; *name; name++) {
256 const cBspModel_t* model;
257 /* check whether this is really an inline model */
258 if (*name[0] != '*') {
259 /* Let's see what the data looks like... */
260 Com_Error(ERR_DROP, "name in the inlineList is no inline model: '%s' (inlines: %p, name: %p)",
261 *name, (void*)entlist, (void*)name);
262 }
263 model = CM_InlineModel(mapTiles, *name);
264 assert(model);
265 if (model->headnode >= mapTiles->mapTiles[model->tile].numnodes + 6)
266 continue;
267
268 /* check if we can safely exclude that the trace can hit the model */
269 if (CM_LineMissesModel(start, stop, model))
270 continue;
271
272 const trace_t trace = CM_HintedTransformedBoxTrace(mapTiles->mapTiles[model->tile], start, end, AABB(),
273 model->headnode, MASK_ALL, 0, model->origin, model->angles, vec3_origin, fraction);
274 /* if we started the trace in a wall */
275 if (trace.startsolid) {
276 VectorCopy(start, end);
277 return true;
278 }
279 /* trace not finished */
280 if (trace.fraction < fraction) {
281 blocked = true;
282 fraction = trace.fraction;
283 VectorCopy(trace.endpos, end);
284 }
285 }
286
287 /* return result */
288 return blocked;
289 }
290
291 /**
292 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
293 * @param[in] start trace start vector
294 * @param[in] end trace end vector
295 * @param[in] box The box we move through the world
296 * @param[in] levelmask Selects which submodels get scanned.
297 * @param[in] brushmask brushes the trace should stop at (see MASK_*)
298 * @param[in] brushreject brushes the trace should ignore (see MASK_*)
299 * @brief Traces all submodels in all tiles. Used by ufo and ufo_ded.
300 */
CM_CompleteBoxTrace(mapTiles_t * mapTiles,const vec3_t start,const vec3_t end,const AABB & box,int levelmask,int brushmask,int brushreject)301 trace_t CM_CompleteBoxTrace (mapTiles_t* mapTiles, const vec3_t start, const vec3_t end, const AABB &box, int levelmask, int brushmask, int brushreject)
302 {
303 int tile, i;
304 vec3_t smin, smax, emin, emax, wpmins, wpmaxs;
305 const vec3_t offset = {UNIT_SIZE / 2, UNIT_SIZE / 2, UNIT_HEIGHT / 2};
306
307 trace_t tr;
308 tr.fraction = 2.0f;
309
310 /* Prep the mins and maxs */
311 for (i = 0; i < 3; i++) {
312 smin[i] = start[i] + std::min(box.mins[i], box.maxs[i]);
313 smax[i] = start[i] + std::max(box.mins[i], box.maxs[i]);
314 emin[i] = end[i] + std::min(box.mins[i], box.maxs[i]);
315 emax[i] = end[i] + std::max(box.mins[i], box.maxs[i]);
316 }
317
318 /* trace against all loaded map tiles */
319 for (tile = 0; tile < mapTiles->numTiles; tile++) {
320 MapTile &myTile = mapTiles->mapTiles[tile];
321 PosToVec(myTile.wpMins, wpmins);
322 VectorSubtract(wpmins, offset, wpmins);
323 PosToVec(myTile.wpMaxs, wpmaxs);
324 VectorAdd(wpmaxs, offset, wpmaxs);
325 /* If the trace is completely outside of the tile, then skip it. */
326 if (smax[0] < wpmins[0] && emax[0] < wpmins[0])
327 continue;
328 if (smax[1] < wpmins[1] && emax[1] < wpmins[1])
329 continue;
330 if (smax[2] < wpmins[2] && emax[2] < wpmins[2])
331 continue;
332 if (smin[0] > wpmaxs[0] && emin[0] > wpmaxs[0])
333 continue;
334 if (smin[1] > wpmaxs[1] && emin[1] > wpmaxs[1])
335 continue;
336 if (smin[2] > wpmaxs[2] && emin[2] > wpmaxs[2])
337 continue;
338 trace_t newtr = TR_TileBoxTrace(&myTile, start, end, box, levelmask, brushmask, brushreject);
339 newtr.mapTile = tile;
340
341 /* memorize the trace with the minimal fraction */
342 if (newtr.fraction == 0.0)
343 return newtr;
344 if (newtr.fraction < tr.fraction)
345 tr = newtr;
346 }
347 return tr;
348 }
349
350
351 /**
352 * @brief Performs box traces against the world and all inline models, gives the hit position back
353 * @param[in] mapTiles List of tiles the current (RMA-)map is composed of
354 * @param[in] traceLine The start/stop position of the trace.
355 * @param[in] traceBox The minimum/maximum extents of the collision box that is projected.
356 * @param[in] levelmask A mask of the game levels to trace against. Mask 0x100 filters clips.
357 * @param[in] brushmask Any brush detected must at least have one of these contents.
358 * @param[in] brushreject Any brush detected with any of these contents will be ignored.
359 * @param[in] list The local models list (a local model has a name starting with * followed by the model number)
360 * @return a trace_t with the information of the closest brush intersected.
361 * @sa CM_CompleteBoxTrace
362 * @sa CM_HintedTransformedBoxTrace
363 */
CM_EntCompleteBoxTrace(mapTiles_t * mapTiles,const Line & traceLine,const AABB * traceBox,int levelmask,int brushmask,int brushreject,const char ** list)364 trace_t CM_EntCompleteBoxTrace (mapTiles_t* mapTiles, const Line &traceLine, const AABB *traceBox, int levelmask, int brushmask, int brushreject, const char** list)
365 {
366 AABB lineBox(*traceBox);
367 lineBox.shift(traceLine.start); /* the traceBox in starting position */
368 AABB lineBoxTemp(*traceBox);
369 lineBoxTemp.shift(traceLine.stop); /* in end position */
370 lineBox.add(lineBoxTemp); /* bounding box for the whole trace */
371 /* Now lineBox specifies the whole volume to be traced through. */
372
373 /* reconstruct a levelmask */
374 vec_t minZ = lineBox.mins[2];
375 vec_t maxZ = lineBox.maxs[2];
376 int newLevelMask = 0x100; /* this bit is for the cliplevels */
377 for (int i = 0; i < 8; i++) {
378 vec_t lower = i * UNIT_HEIGHT; /* the height bounds of the level */
379 vec_t upper = (i + 1) * UNIT_HEIGHT;
380 if (minZ > upper || maxZ < lower)
381 continue;
382 newLevelMask |= (1 << i);
383 }
384
385 /* trace against world first */
386 const trace_t tr = CM_CompleteBoxTrace(mapTiles, traceLine.start, traceLine.stop, *traceBox, newLevelMask, brushmask, brushreject);
387 if (!list || tr.fraction == 0.0)
388 return tr;
389
390 trace_t trace = tr;
391 for (const char** name = list; *name; name++) {
392 /* check whether this is really an inline model */
393 if (*name[0] != '*')
394 Com_Error(ERR_DROP, "name in the inlineList is no inline model: '%s'", *name);
395 const cBspModel_t* model = CM_InlineModel(mapTiles, *name);
396 assert(model);
397 if (model->headnode >= mapTiles->mapTiles[model->tile].numnodes + 6)
398 continue;
399
400 vec3_t amins, amaxs;
401 /* Quickly calculate the bounds of this model to see if they can overlap. */
402 CM_CalculateBoundingBox(model, amins, amaxs);
403
404 /* If the bounds of the extents box and the line do not overlap, then skip tracing this model. */
405 AABB modelBox(amins, amaxs);
406 if (!lineBox.doesIntersect(modelBox))
407 continue;
408
409 const trace_t newtr = CM_HintedTransformedBoxTrace(mapTiles->mapTiles[model->tile], traceLine.start, traceLine.stop, *traceBox,
410 model->headnode, brushmask, brushreject, model->origin, model->angles, model->shift, trace.fraction);
411
412 /* memorize the trace with the minimal fraction */
413 if (newtr.fraction == 0.0)
414 return newtr;
415 if (newtr.fraction < trace.fraction)
416 trace = newtr;
417 }
418 return trace;
419 }
420