e9788fa52444ca58f9a981e418cad4fa770e6709
[xonotic/darkplaces.git] / cl_collision.c
1
2 #include "quakedef.h"
3 #include "cl_collision.h"
4
5 #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
6 float CL_SelectTraceLine(const vec3_t start, const vec3_t pEnd, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent)
7 #else
8 float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent)
9 #endif
10 {
11         float maxfrac, maxrealfrac;
12         int n;
13         entity_render_t *ent;
14         float tracemins[3], tracemaxs[3];
15         trace_t trace;
16         float tempnormal[3], starttransformed[3], endtransformed[3];
17 #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
18         vec3_t end;
19         vec_t len;
20
21         if(!VectorCompare(start, pEnd))
22         {
23                 // TRICK: make the trace 1 qu longer!
24                 VectorSubtract(pEnd, start, end);
25                 len = VectorNormalizeLength(end);
26                 VectorAdd(pEnd, end, end);
27         }
28         else
29                 VectorCopy(pEnd, end);
30 #endif
31
32         memset (&trace, 0 , sizeof(trace_t));
33         trace.fraction = 1;
34         trace.realfraction = 1;
35         VectorCopy (end, trace.endpos);
36
37         if (hitent)
38                 *hitent = 0;
39         if (cl.worldmodel && cl.worldmodel->TraceBox)
40                 cl.worldmodel->TraceBox(cl.worldmodel, 0, &trace, start, vec3_origin, vec3_origin, end, SUPERCONTENTS_SOLID);
41
42         if (normal)
43                 VectorCopy(trace.plane.normal, normal);
44         maxfrac = trace.fraction;
45         maxrealfrac = trace.realfraction;
46
47         tracemins[0] = min(start[0], end[0]);
48         tracemaxs[0] = max(start[0], end[0]);
49         tracemins[1] = min(start[1], end[1]);
50         tracemaxs[1] = max(start[1], end[1]);
51         tracemins[2] = min(start[2], end[2]);
52         tracemaxs[2] = max(start[2], end[2]);
53
54         // look for embedded bmodels
55         for (n = 0;n < cl.num_entities;n++)
56         {
57                 if (!cl.entities_active[n])
58                         continue;
59                 ent = &cl.entities[n].render;
60                 if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs))
61                         continue;
62                 if (!ent->model || !ent->model->TraceBox)
63                         continue;
64                 if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer)
65                         continue;
66                 // if transparent and not selectable, skip entity
67                 if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST))))
68                         continue;
69                 if (ent == ignoreent)
70                         continue;
71                 Matrix4x4_Transform(&ent->inversematrix, start, starttransformed);
72                 Matrix4x4_Transform(&ent->inversematrix, end, endtransformed);
73                 Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, SUPERCONTENTS_SOLID, 0, NULL);
74                 if(!VectorCompare(start, pEnd))
75                         Collision_ShortenTrace(&trace, len / (len + 1), pEnd);
76                 if (maxrealfrac < trace.realfraction)
77                         continue;
78
79                 ent->model->TraceBox(ent->model, ent->frameblend[0].subframe, &trace, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID);
80
81                 if (maxrealfrac > trace.realfraction)
82                 {
83                         if (hitent)
84                                 *hitent = n;
85                         maxfrac = trace.fraction;
86                         maxrealfrac = trace.realfraction;
87                         if (normal)
88                         {
89                                 VectorCopy(trace.plane.normal, tempnormal);
90                                 Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal);
91                         }
92                 }
93         }
94         maxfrac = bound(0, maxfrac, 1);
95         maxrealfrac = bound(0, maxrealfrac, 1);
96         //if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__);
97         if (impact)
98                 VectorLerp(start, maxfrac, end, impact);
99         return maxfrac;
100 }
101
102 void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius)
103 {
104         // FIXME: check multiple brush models
105         if (cl.worldmodel && cl.worldmodel->brush.FindNonSolidLocation)
106                 cl.worldmodel->brush.FindNonSolidLocation(cl.worldmodel, in, out, radius);
107 }
108
109 dp_model_t *CL_GetModelByIndex(int modelindex)
110 {
111         if(!modelindex)
112                 return NULL;
113         if (modelindex < 0)
114         {
115                 modelindex = -(modelindex+1);
116                 if (modelindex < MAX_MODELS)
117                         return cl.csqc_model_precache[modelindex];
118         }
119         else
120         {
121                 if(modelindex < MAX_MODELS)
122                         return cl.model_precache[modelindex];
123         }
124         return NULL;
125 }
126
127 dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed)
128 {
129         if (!ed || ed->priv.server->free)
130                 return NULL;
131         return CL_GetModelByIndex((int)ed->fields.client->modelindex);
132 }
133
134 void CL_LinkEdict(prvm_edict_t *ent)
135 {
136         vec3_t mins, maxs;
137
138         if (ent == prog->edicts)
139                 return;         // don't add the world
140
141         if (ent->priv.server->free)
142                 return;
143
144         // set the abs box
145
146         if (ent->fields.client->solid == SOLID_BSP)
147         {
148                 dp_model_t *model = CL_GetModelByIndex( (int)ent->fields.client->modelindex );
149                 if (model == NULL)
150                 {
151                         Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent));
152
153                         model = CL_GetModelByIndex( 0 );
154                 }
155
156                 if( model != NULL )
157                 {
158                         if (!model->TraceBox && developer.integer >= 1)
159                                 Con_Printf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent));
160
161                         if (ent->fields.client->angles[0] || ent->fields.client->angles[2] || ent->fields.client->avelocity[0] || ent->fields.client->avelocity[2])
162                         {
163                                 VectorAdd(ent->fields.client->origin, model->rotatedmins, mins);
164                                 VectorAdd(ent->fields.client->origin, model->rotatedmaxs, maxs);
165                         }
166                         else if (ent->fields.client->angles[1] || ent->fields.client->avelocity[1])
167                         {
168                                 VectorAdd(ent->fields.client->origin, model->yawmins, mins);
169                                 VectorAdd(ent->fields.client->origin, model->yawmaxs, maxs);
170                         }
171                         else
172                         {
173                                 VectorAdd(ent->fields.client->origin, model->normalmins, mins);
174                                 VectorAdd(ent->fields.client->origin, model->normalmaxs, maxs);
175                         }
176                 }
177                 else
178                 {
179                         // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily
180                         VectorAdd(ent->fields.client->origin, ent->fields.client->mins, mins);
181                         VectorAdd(ent->fields.client->origin, ent->fields.client->maxs, maxs);
182                 }
183         }
184         else
185         {
186                 VectorAdd(ent->fields.client->origin, ent->fields.client->mins, mins);
187                 VectorAdd(ent->fields.client->origin, ent->fields.client->maxs, maxs);
188         }
189
190         VectorCopy(mins, ent->fields.client->absmin);
191         VectorCopy(maxs, ent->fields.client->absmax);
192
193         World_LinkEdict(&cl.world, ent, ent->fields.client->absmin, ent->fields.client->absmax);
194 }
195
196 int CL_GenericHitSuperContentsMask(const prvm_edict_t *passedict)
197 {
198         prvm_eval_t *val;
199         if (passedict)
200         {
201                 val = PRVM_EDICTFIELDVALUE(passedict, prog->fieldoffsets.dphitcontentsmask);
202                 if (val && val->_float)
203                         return (int)val->_float;
204                 else if (passedict->fields.client->solid == SOLID_SLIDEBOX)
205                 {
206                         if ((int)passedict->fields.client->flags & FL_MONSTER)
207                                 return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP;
208                         else
209                                 return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP;
210                 }
211                 else if (passedict->fields.client->solid == SOLID_CORPSE)
212                         return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;
213                 else if (passedict->fields.client->solid == SOLID_TRIGGER)
214                         return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;
215                 else
216                         return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE;
217         }
218         else
219                 return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE;
220 }
221
222 /*
223 ==================
224 CL_Move
225 ==================
226 */
227 #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
228 trace_t CL_Move(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities)
229 #else
230 trace_t CL_Move(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities)
231 #endif
232 {
233         vec3_t hullmins, hullmaxs;
234         int i, bodysupercontents;
235         int passedictprog;
236         qboolean pointtrace;
237         prvm_edict_t *traceowner, *touch;
238         trace_t trace;
239         // bounding box of entire move area
240         vec3_t clipboxmins, clipboxmaxs;
241         // size of the moving object
242         vec3_t clipmins, clipmaxs;
243         // size when clipping against monsters
244         vec3_t clipmins2, clipmaxs2;
245         // start and end origin of move
246         vec3_t clipstart, clipend;
247         // trace results
248         trace_t cliptrace;
249         // matrices to transform into/out of other entity's space
250         matrix4x4_t matrix, imatrix;
251         // model of other entity
252         dp_model_t *model;
253         // list of entities to test for collisions
254         int numtouchedicts;
255         prvm_edict_t *touchedicts[MAX_EDICTS];
256 #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
257         vec3_t end;
258         vec_t len;
259
260         if(!VectorCompare(start, pEnd))
261         {
262                 // TRICK: make the trace 1 qu longer!
263                 VectorSubtract(pEnd, start, end);
264                 len = VectorNormalizeLength(end);
265                 VectorAdd(pEnd, end, end);
266         }
267         else
268                 VectorCopy(pEnd, end);
269 #endif
270
271         if (hitnetworkentity)
272                 *hitnetworkentity = 0;
273
274         VectorCopy(start, clipstart);
275         VectorCopy(end, clipend);
276         VectorCopy(mins, clipmins);
277         VectorCopy(maxs, clipmaxs);
278         VectorCopy(mins, clipmins2);
279         VectorCopy(maxs, clipmaxs2);
280 #if COLLISIONPARANOID >= 3
281         Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]);
282 #endif
283
284         // clip to world
285         Collision_ClipToWorld(&cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask);
286         cliptrace.bmodelstartsolid = cliptrace.startsolid;
287         if (cliptrace.startsolid || cliptrace.fraction < 1)
288                 cliptrace.ent = prog ? prog->edicts : NULL;
289         if (type == MOVE_WORLDONLY)
290                 goto finished;
291
292         if (type == MOVE_MISSILE)
293         {
294                 // LordHavoc: modified this, was = -15, now -= 15
295                 for (i = 0;i < 3;i++)
296                 {
297                         clipmins2[i] -= 15;
298                         clipmaxs2[i] += 15;
299                 }
300         }
301
302         // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp
303         if (cl.worldmodel && cl.worldmodel->brush.RoundUpToHullSize)
304                 cl.worldmodel->brush.RoundUpToHullSize(cl.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs);
305         else
306         {
307                 VectorCopy(clipmins, hullmins);
308                 VectorCopy(clipmaxs, hullmaxs);
309         }
310
311         // create the bounding box of the entire move
312         for (i = 0;i < 3;i++)
313         {
314                 clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1;
315                 clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1;
316         }
317
318         // debug override to test against everything
319         if (sv_debugmove.integer)
320         {
321                 clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999;
322                 clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] =  999999999;
323         }
324
325         // if the passedict is world, make it NULL (to avoid two checks each time)
326         // this checks prog because this function is often called without a CSQC
327         // VM context
328         if (prog == NULL || passedict == prog->edicts)
329                 passedict = NULL;
330         // precalculate prog value for passedict for comparisons
331         passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0;
332         // figure out whether this is a point trace for comparisons
333         pointtrace = VectorCompare(clipmins, clipmaxs);
334         // precalculate passedict's owner edict pointer for comparisons
335         traceowner = passedict ? PRVM_PROG_TO_EDICT(passedict->fields.client->owner) : NULL;
336
337         // collide against network entities
338         if (hitnetworkbrushmodels)
339         {
340                 for (i = 0;i < cl.num_brushmodel_entities;i++)
341                 {
342                         entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render;
343                         if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs))
344                                 continue;
345                         Collision_ClipToGenericEntity(&trace, ent->model, ent->frameblend[0].subframe, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, mins, maxs, end, hitsupercontentsmask);
346                         if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
347                                 *hitnetworkentity = cl.brushmodel_entities[i];
348                         Collision_CombineTraces(&cliptrace, &trace, NULL, true);
349                 }
350         }
351
352         // collide against player entities
353         if (hitnetworkplayers)
354         {
355                 vec3_t origin, entmins, entmaxs;
356                 matrix4x4_t entmatrix, entinversematrix;
357
358                 if(gamemode == GAME_NEXUIZ)
359                 {
360                         // don't hit network players, if we are a nonsolid player
361                         if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616)
362                                 goto skipnetworkplayers;
363                 }
364
365                 for (i = 1;i <= cl.maxclients;i++)
366                 {
367                         entity_render_t *ent = &cl.entities[i].render;
368
369                         // don't hit ourselves
370                         if (i == cl.playerentity)
371                                 continue;
372
373                         // don't hit players that don't exist
374                         if (!cl.scores[i-1].name[0])
375                                 continue;
376
377                         if(gamemode == GAME_NEXUIZ)
378                         {
379                                 // don't hit spectators or nonsolid players
380                                 if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616)
381                                         continue;
382                         }
383
384                         Matrix4x4_OriginFromMatrix(&ent->matrix, origin);
385                         VectorAdd(origin, cl.playerstandmins, entmins);
386                         VectorAdd(origin, cl.playerstandmaxs, entmaxs);
387                         if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs))
388                                 continue;
389                         Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]);
390                         Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]);
391                         Collision_ClipToGenericEntity(&trace, NULL, 0, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, mins, maxs, end, hitsupercontentsmask);
392                         if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
393                                 *hitnetworkentity = i;
394                         Collision_CombineTraces(&cliptrace, &trace, NULL, false);
395                 }
396
397 skipnetworkplayers:
398                 ;
399         }
400
401         // clip to entities
402         // because this uses World_EntitiestoBox, we know all entity boxes overlap
403         // the clip region, so we can skip culling checks in the loop below
404         // note: if prog is NULL then there won't be any linked entities
405         numtouchedicts = 0;
406         if (hitcsqcentities && prog != NULL)
407         {
408                 numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
409                 if (numtouchedicts > MAX_EDICTS)
410                 {
411                         // this never happens
412                         Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
413                         numtouchedicts = MAX_EDICTS;
414                 }
415         }
416         for (i = 0;i < numtouchedicts;i++)
417         {
418                 touch = touchedicts[i];
419
420                 if (touch->fields.client->solid < SOLID_BBOX)
421                         continue;
422                 if (type == MOVE_NOMONSTERS && touch->fields.client->solid != SOLID_BSP)
423                         continue;
424
425                 if (passedict)
426                 {
427                         // don't clip against self
428                         if (passedict == touch)
429                                 continue;
430                         // don't clip owned entities against owner
431                         if (traceowner == touch)
432                                 continue;
433                         // don't clip owner against owned entities
434                         if (passedictprog == touch->fields.client->owner)
435                                 continue;
436                         // don't clip points against points (they can't collide)
437                         if (pointtrace && VectorCompare(touch->fields.client->mins, touch->fields.client->maxs) && (type != MOVE_MISSILE || !((int)touch->fields.client->flags & FL_MONSTER)))
438                                 continue;
439                 }
440
441                 bodysupercontents = touch->fields.client->solid == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY;
442
443                 // might interact, so do an exact clip
444                 model = NULL;
445                 if ((int) touch->fields.client->solid == SOLID_BSP || type == MOVE_HITMODEL)
446                 {
447                         unsigned int modelindex = (unsigned int)touch->fields.client->modelindex;
448                         // if the modelindex is 0, it shouldn't be SOLID_BSP!
449                         if (modelindex > 0 && modelindex < MAX_MODELS)
450                                 model = cl.model_precache[(int)touch->fields.client->modelindex];
451                 }
452                 if (model)
453                         Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.client->origin[0], touch->fields.client->origin[1], touch->fields.client->origin[2], touch->fields.client->angles[0], touch->fields.client->angles[1], touch->fields.client->angles[2], 1);
454                 else
455                         Matrix4x4_CreateTranslate(&matrix, touch->fields.client->origin[0], touch->fields.client->origin[1], touch->fields.client->origin[2]);
456                 Matrix4x4_Invert_Simple(&imatrix, &matrix);
457                 if ((int)touch->fields.client->flags & FL_MONSTER)
458                         Collision_ClipToGenericEntity(&trace, model, (int) touch->fields.client->frame, touch->fields.client->mins, touch->fields.client->maxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask);
459                 else
460                         Collision_ClipToGenericEntity(&trace, model, (int) touch->fields.client->frame, touch->fields.client->mins, touch->fields.client->maxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask);
461
462                 if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
463                         *hitnetworkentity = 0;
464                 Collision_CombineTraces(&cliptrace, &trace, (void *)touch, touch->fields.client->solid == SOLID_BSP);
465         }
466
467 finished:
468 #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
469         if(!VectorCompare(start, pEnd))
470                 Collision_ShortenTrace(&cliptrace, len / (len + 1), pEnd);
471 #endif
472         return cliptrace;
473 }