1 /**
2  * @file
3  * @brief World query functions.
4  */
5 
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 Original file from Quake 2 v3.21: quake2-2.31/server/sv_world.c
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 "server.h"
30 #include "../common/qfiles.h"
31 
32 #define	AREA_DEPTH	4
33 
34 /**
35  * @brief Builds a uniformly subdivided tree for the given world size
36  * @sa SV_ClearWorld
37  * @sa SV_LinkEdict
38  */
SV_CreateWorldSector(int depth,const AABB & sBox)39 static worldSector_t* SV_CreateWorldSector (int depth, const AABB& sBox)
40 {
41 	if (sv->numWorldSectors >= lengthof(sv->worldSectors))
42 		Com_Error(ERR_DROP, "SV_CreateWorldSector: overflow");
43 
44 	worldSector_t* anode = &sv->worldSectors[sv->numWorldSectors];
45 	sv->numWorldSectors++;
46 
47 	anode->entities = nullptr;
48 
49 	if (depth == AREA_DEPTH) {
50 		anode->axis = LEAFNODE; /* end of tree */
51 		anode->children[0] = anode->children[1] = nullptr;
52 		return anode;
53 	}
54 
55 	vec3_t size;
56 	VectorSubtract(sBox.maxs, sBox.mins, size);
57 	if (size[0] > size[1])
58 		anode->axis = PLANE_X;
59 	else
60 		anode->axis = PLANE_Y;
61 
62 	anode->dist = 0.5f * (sBox.maxs[anode->axis] + sBox.mins[anode->axis]);
63 	AABB sBox1(sBox);
64 	AABB sBox2(sBox);
65 
66 	sBox1.maxs[anode->axis] = sBox2.mins[anode->axis] = anode->dist;
67 
68 	anode->children[0] = SV_CreateWorldSector(depth + 1, sBox2);
69 	anode->children[1] = SV_CreateWorldSector(depth + 1, sBox1);
70 
71 	return anode;
72 }
73 
74 /**
75  * @brief Clear physics interaction links
76  * @note Called after the world model has been loaded, before linking any entities
77  * @sa SV_SpawnServer
78  * @sa SV_CreateAreaNode
79  */
SV_ClearWorld(void)80 void SV_ClearWorld (void)
81 {
82 	SV_CreateWorldSector(0, sv->mapData.mapBox);
83 }
84 
SV_GetServerDataForEdict(const edict_t * ent)85 static inline sv_edict_t* SV_GetServerDataForEdict (const edict_t* ent)
86 {
87 	if (!ent || ent->number < 0 || ent->number >= lengthof(sv->edicts))
88 		Com_Error(ERR_DROP, "SV_GetServerDataForEdict: bad game ent");
89 
90 	return &sv->edicts[ent->number];
91 }
92 
93 /**
94  * @brief call before removing an entity, and before trying to move one, so it doesn't clip against itself
95  */
SV_UnlinkEdict(edict_t * ent)96 void SV_UnlinkEdict (edict_t* ent)
97 {
98 	sv_edict_t* sv_ent = SV_GetServerDataForEdict(ent);
99 	sv_edict_t* scan;
100 	worldSector_t* ws;
101 
102 	sv_ent->linked = false;
103 
104 	ws = sv_ent->worldSector;
105 	if (!ws)
106 		return;					/* not linked in anywhere */
107 
108 	sv_ent->worldSector = nullptr;
109 
110 	if (ws->entities == sv_ent) {
111 		ws->entities = sv_ent->nextEntityInWorldSector;
112 		return;
113 	}
114 
115 	for (scan = ws->entities; scan; scan = scan->nextEntityInWorldSector) {
116 		if (scan->nextEntityInWorldSector == sv_ent) {
117 			scan->nextEntityInWorldSector = sv_ent->nextEntityInWorldSector;
118 			return;
119 		}
120 	}
121 
122 	Com_Printf("WARNING: SV_UnlinkEntity: not found in worldSector\n");
123 }
124 
125 /**
126  * @brief Needs to be called any time an entity changes origin, mins, maxs,
127  * or solid. Automatically unlinks if needed. Sets ent->absmin and ent->absmax
128  * @sa SV_CreateAreaNode
129  */
SV_LinkEdict(edict_t * ent)130 void SV_LinkEdict (edict_t* ent)
131 {
132 	worldSector_t* node;
133 	sv_edict_t* sv_ent = SV_GetServerDataForEdict(ent);
134 
135 	if (sv_ent->worldSector)
136 		SV_UnlinkEdict(ent);	/* unlink from old position */
137 
138 	if (ent == svs.ge->edicts)
139 		return;					/* don't add the world */
140 
141 	if (!ent->inuse)
142 		return;
143 
144 	/* set the size */
145 	VectorSubtract(ent->maxs, ent->mins, ent->size);
146 
147 	/* increase the linkcount - even for none solids */
148 	ent->linkcount++;
149 
150 	CalculateMinsMaxs(ent->solid == SOLID_BSP ? ent->angles : vec3_origin, ent->mins, ent->maxs, ent->origin, ent->absmin, ent->absmax);
151 
152 	/* if not solid we have to set the abs mins/maxs above but don't really link it */
153 	if (ent->solid == SOLID_NOT)
154 		return;
155 
156 	/* find the first node that the ent's box crosses */
157 	node = sv->worldSectors;
158 	while (1) {
159 		/* end of tree */
160 		if (node->axis == LEAFNODE)
161 			break;
162 		if (ent->absmin[node->axis] > node->dist)
163 			node = node->children[0];
164 		else if (ent->absmax[node->axis] < node->dist)
165 			node = node->children[1];
166 		else
167 			break;				/* crosses the node */
168 	}
169 
170 	/* link it in */
171 	sv_ent->nextEntityInWorldSector = node->entities;
172 	node->entities = sv_ent;
173 
174 	sv_ent->linked = true;
175 	sv_ent->worldSector = node;
176 	sv_ent->ent = ent;
177 
178 	/* If this ent has a child, link it back in, too */
179 	if (ent->child) {
180 		VectorCopy(ent->absmin, ent->child->mins);
181 		VectorCopy(ent->absmax, ent->child->maxs);
182 
183 		/* expand the trigger box */
184 		ent->child->mins[0] -= (UNIT_SIZE / 2);
185 		ent->child->mins[1] -= (UNIT_SIZE / 2);
186 		ent->child->maxs[0] += (UNIT_SIZE / 2);
187 		ent->child->maxs[1] += (UNIT_SIZE / 2);
188 
189 		/* link child back into the world */
190 		SV_LinkEdict(ent->child);
191 	}
192 }
193 
194 /**
195  * @brief Checks whether the bounding box of the given edict will intersect with the given bbox
196  * @param[in] aabb the bounding box
197  * @param[in] ent The edict to check the intersection for
198  * @return @c true if intersect, @c false otherwise
199  */
SV_BoundingBoxesIntersect(const AABB & aabb,const edict_t * ent)200 static bool SV_BoundingBoxesIntersect (const AABB& aabb, const edict_t* ent)
201 {
202 	return aabb.doesIntersect(AABB(ent->absmin,ent->absmax));
203 }
204 
205 typedef struct {
206 	const float* areaMins, *areaMaxs;
207 	edict_t** areaEdictList;
208 	int areaEdictListCount, areaEdictListMaxCount;
209 } areaParms_t;
210 
211 /**
212  * @brief fills in a table of edict pointers with edicts that have bounding boxes
213  * that intersect the given area. It is possible for a non-axial bmodel
214  * to be returned that doesn't actually intersect the area on an exact test.
215  * @sa SV_AreaEdicts
216  */
SV_AreaEdicts_r(worldSector_t * node,areaParms_t * ap)217 static void SV_AreaEdicts_r (worldSector_t* node, areaParms_t* ap)
218 {
219 	sv_edict_t* check, *next;
220 
221 	for (check = node->entities; check; check = next) {
222 		next = check->nextEntityInWorldSector;
223 
224 		/* deactivated */
225 		if (check->ent->solid == SOLID_NOT)
226 			continue;
227 
228 		if (!check->ent->inuse)
229 			continue;
230 
231 		if (!SV_BoundingBoxesIntersect(AABB(ap->areaMins, ap->areaMaxs), check->ent))
232 			continue;			/* not touching */
233 
234 		if (ap->areaEdictListCount == ap->areaEdictListMaxCount) {
235 			Com_Printf("SV_AreaEdicts_r: MAXCOUNT\n");
236 			return;
237 		}
238 
239 		ap->areaEdictList[ap->areaEdictListCount] = check->ent;
240 		ap->areaEdictListCount++;
241 	}
242 
243 	if (node->axis == LEAFNODE)
244 		return;					/* terminal node - end of tree */
245 
246 	/* recurse down both sides */
247 	if (ap->areaMaxs[node->axis] > node->dist)
248 		SV_AreaEdicts_r(node->children[0], ap);
249 	if (ap->areaMins[node->axis] < node->dist)
250 		SV_AreaEdicts_r(node->children[1], ap);
251 }
252 
253 /**
254  * @sa SV_AreaEdicts_r
255  * @param[in] mins The mins of the bounding box
256  * @param[in] maxs The maxs of the bounding box
257  * @param[out] list The edict list that this trace is hitting
258  * @param[in] maxCount The size of the given @c list
259  * @return the number of pointers filled in
260  */
SV_AreaEdicts(const vec3_t mins,const vec3_t maxs,edict_t ** list,int maxCount)261 int SV_AreaEdicts (const vec3_t mins, const vec3_t maxs, edict_t** list, int maxCount)
262 {
263 	areaParms_t	ap;
264 
265 	ap.areaMins = mins;
266 	ap.areaMaxs = maxs;
267 	ap.areaEdictList = list;
268 	ap.areaEdictListCount = 0;
269 	ap.areaEdictListMaxCount = maxCount;
270 
271 	SV_AreaEdicts_r(sv->worldSectors, &ap);
272 
273 	return ap.areaEdictListCount;
274 }
275 
276 /** @brief Server side moveclip - see cmodel.c */
277 typedef struct moveclip_s {
278 	vec3_t boxmins, boxmaxs;	/**< enclose the test object along entire move */
279 	const float* mins, *maxs;	/**< size of the moving object */
280 	const float* start, *end;
281 	trace_t trace;
282 	const edict_t* passedict;
283 	int contentmask;
284 } moveclip_t;
285 
286 
287 /**
288  * @brief Returns a headnode that can be used for testing or clipping an
289  * object of mins/maxs size.
290  * Offset is filled in to contain the adjustment that must be added to the
291  * testing object's origin to get a point to use with the returned hull.
292  * @param[in] ent The edict to get the bmodel from (at least in case of SOLID_BSP)
293  * @param[out] tile The maptile the bmodel belongs, too (at least in case of SOLID_BSP)
294  * @param[out] rmaShift the shift vector in case of an RMA (needed for doors)
295  * @return The headnode for the edict
296  * @sa CL_HullForEntity
297  */
SV_HullForEntity(const edict_t * ent,int * tile,vec3_t rmaShift)298 static int SV_HullForEntity (const edict_t* ent, int* tile, vec3_t rmaShift)
299 {
300 	assert(ent->solid != SOLID_NOT);
301 	assert(ent->solid != SOLID_TRIGGER);
302 
303 	/* decide which clipping hull to use, based on the size */
304 	if (ent->solid == SOLID_BSP) {	/* explicit hulls in the BSP model */
305 		const cBspModel_t* model;
306 
307 		assert(ent->modelindex < MAX_MODELS);
308 
309 		model = sv->models[ent->modelindex];
310 		if (!model)
311 			Com_Error(ERR_FATAL, "SOLID_BSP with a non bsp model");
312 
313 		*tile = model->tile;
314 		VectorCopy(model->shift, rmaShift);
315 		assert(model->headnode < MAX_MAP_NODES);
316 		return model->headnode;
317 	}
318 
319 	/* create a temp hull from bounding box sizes */
320 	*tile = 0;
321 	VectorCopy(vec3_origin, rmaShift);
322 	return CM_HeadnodeForBox(sv->mapTiles.mapTiles[*tile], ent->mins, ent->maxs);
323 }
324 
325 
326 /**
327  * @sa SV_Trace
328  * @sa SV_AreaEdicts
329  * @sa CL_ClipMoveToLEs
330  */
SV_ClipMoveToEntities(moveclip_t * clip)331 static void SV_ClipMoveToEntities (moveclip_t* clip)
332 {
333 	int i;
334 	edict_t* touchlist[MAX_EDICTS];
335 	const float* angles;
336 	int headnode = 0;
337 	const int num = SV_AreaEdicts(clip->boxmins, clip->boxmaxs, touchlist, MAX_EDICTS);
338 
339 	/* be careful, it is possible to have an entity in this
340 	 * list removed before we get to it (killtriggered) */
341 	for (i = 0; i < num; i++) {
342 		vec3_t rmaShift;
343 		edict_t* touch = touchlist[i];
344 		int tile = 0;
345 
346 		if (touch->solid == SOLID_NOT || touch->solid == SOLID_TRIGGER)
347 			continue;
348 		if (touch == clip->passedict)
349 			continue;
350 
351 		if (clip->trace.allsolid)
352 			return;
353 
354 		if (clip->passedict) {
355 			if (touch->isParentship(clip->passedict))	/* check if one of them is the owner of the other */
356 				continue;		/* don't clip against own missiles or owner */
357 		}
358 
359 		/* might intersect, so do an exact clip */
360 		headnode = SV_HullForEntity(touch, &tile, rmaShift);
361 		if (headnode >= MAX_MAP_NODES)
362 			continue;
363 
364 		if (touch->solid != SOLID_BSP)
365 			angles = vec3_origin;	/* boxes don't rotate */
366 		else
367 			angles = touch->angles;
368 
369 		assert(headnode < MAX_MAP_NODES);
370 		trace_t trace = CM_HintedTransformedBoxTrace(sv->mapTiles.mapTiles[tile], clip->start, clip->end, AABB(clip->mins, clip->maxs), headnode,
371 				clip->contentmask, 0, touch->origin, angles, rmaShift, 1.0);
372 
373 #ifdef PARANOID
374 		Com_DPrintf(DEBUG_SERVER, "SV_ClipMoveToEntities: %i %i: (%i %i %i) (%i %i %i) (%i %i %i)\n", touch->number, touch->modelindex,
375 			(int)touch->mins[0], (int)touch->mins[1], (int)touch->mins[2],
376 			(int)touch->maxs[0], (int)touch->maxs[1], (int)touch->maxs[2],
377 			(int)touch->origin[0], (int)touch->origin[1], (int)touch->origin[2]);
378 #endif
379 
380 		if (trace.fraction < clip->trace.fraction) {
381 			bool oldStart;
382 
383 			/* make sure we keep a startsolid from a previous trace */
384 			oldStart = clip->trace.startsolid;
385 			trace.entNum = touch->number;
386 			clip->trace = trace;
387 			clip->trace.startsolid |= oldStart;
388 		} else if (trace.allsolid) {
389 			trace.entNum = touch->number;
390 			clip->trace = trace;
391 		} else if (trace.startsolid) {
392 			trace.entNum = touch->number;
393 			clip->trace.startsolid = true;
394 		}
395 	}
396 }
397 
398 /**
399  * @brief Returns the content flags for a given point
400  * @note Useful to determine whether an actor is e.g. inside of a water brush
401  * @sa CM_TestInLeaf
402  * @sa CM_TestBoxInBrush
403  * @sa CM_CompleteBoxTrace
404  */
SV_PointContents(const vec3_t p)405 int SV_PointContents (const vec3_t p)
406 {
407 	/* clip to all world levels */
408 	const trace_t trace = CM_CompleteBoxTrace(&sv->mapTiles, p, p, AABB(), TRACING_ALL_VISIBLE_LEVELS, MASK_ALL, 0);
409 	if (trace.fraction == 0)
410 		return trace.contentFlags;		/* blocked by the world */
411 	return 0;
412 }
413 
414 /**
415  * @brief calculates the bounding box for the whole trace
416  * @param[in] start The starting point of the trace
417  * @param[in] mins extents of the box we are moving through the world
418  * @param[in] maxs guess what
419  * @param[in] end The point where the trace should end
420  * @param[out] boxmins The lower bounds of the trace
421  * @param[out] boxmaxs The upper bounds
422  * @sa SV_Trace
423  */
SV_TraceBounds(const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,vec3_t boxmins,vec3_t boxmaxs)424 static void SV_TraceBounds (const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs)
425 {
426 #if 0
427 	/* debug to test against everything */
428 	boxmins[0] = boxmins[1] = boxmins[2] = -9999;
429 	boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999;
430 #else
431 	int i;
432 
433 	for (i = 0; i < 3; i++) {
434 		if (end[i] > start[i]) {
435 			boxmins[i] = start[i] + mins[i] - 1;
436 			boxmaxs[i] = end[i] + maxs[i] + 1;
437 		} else {
438 			boxmins[i] = end[i] + mins[i] - 1;
439 			boxmaxs[i] = start[i] + maxs[i] + 1;
440 		}
441 	}
442 #endif
443 }
444 
445 /**
446  * @brief Moves the given mins/maxs volume through the world from start to end.
447  * @note Passedict and edicts owned by passedict are explicitly not checked.
448  * @sa SV_TraceBounds
449  * @sa CL_Trace
450  * @param[in] start The starting position in the world for this trace
451  * @param[in] end The position in the world where this trace should stop
452  * @param[in] passedict is explicitly excluded from clipping checks (normally nullptr)
453  * if the entire move stays in a solid volume, trace.allsolid will be set,
454  * trace.startsolid will be set, and trace.fraction will be 0
455  * if the starting point is in a solid, it will be allowed to move out to an open area
456  * @param[in] contentmask brushes the trace should stop at (see MASK_*)
457  * @param[in] box The bounding box that is moved through the world
458  */
SV_Trace(const vec3_t start,const AABB & box,const vec3_t end,const edict_t * passedict,int contentmask)459 trace_t SV_Trace (const vec3_t start, const AABB &box, const vec3_t end, const edict_t* passedict, int contentmask)
460 {
461 	moveclip_t clip;
462 
463 	OBJZERO(clip);
464 
465 	/* clip to world - 0x1FF = all levels */
466 	clip.trace = CM_CompleteBoxTrace(&sv->mapTiles, start, end, box, TRACING_ALL_VISIBLE_LEVELS, contentmask, 0);
467 	/** @todo There is more than one world in case of a map assembly - use
468 	 * @c clip.trace.mapTile to get the correct one */
469 	clip.trace.entNum = 0; /* the first edict is the world */
470 	if (clip.trace.fraction == 0)
471 		return clip.trace;		/* blocked by the world */
472 
473 	clip.contentmask = contentmask;
474 	clip.start = start;
475 	clip.end = end;
476 	clip.mins = box.mins;
477 	clip.maxs = box.maxs;
478 	clip.passedict = passedict;
479 
480 	/* create the bounding box for the entire path traveled by the shot */
481 	SV_TraceBounds(start, clip.mins, clip.maxs, end, clip.boxmins, clip.boxmaxs);
482 
483 #if 0
484 	/* Output the trace bounds */
485 	Com_Printf("Trace: (%i, %i, %i) (%i, %i, %i)\n",
486 		(int) clip.boxmins[0], (int) clip.boxmins[1], (int) clip.boxmins[2],
487 		(int) clip.boxmaxs[0], (int) clip.boxmaxs[1], (int) clip.boxmaxs[2]);
488 #endif
489 
490 	/* clip to other solid entities */
491 	SV_ClipMoveToEntities(&clip);
492 
493 	return clip.trace;
494 }
495 
496 /**
497  * @brief Query the footstep sound for the given surface texture
498  * @sa Com_GetTerrainType
499  * @sa GenerateFootstepList
500  * @return either @c nullptr or the footstep sound filename if there is one assigned in the scripts
501  */
SV_GetFootstepSound(const char * texture)502 const char* SV_GetFootstepSound (const char* texture)
503 {
504 	const terrainType_t* t = Com_GetTerrainType(texture);
505 	return t ? t->footstepSound : nullptr;
506 }
507 
508 /**
509  * @brief Different terrain types might have different bounce fraction
510  * @sa Com_GetTerrainType
511  * @sa GenerateFootstepList
512  */
SV_GetBounceFraction(const char * texture)513 float SV_GetBounceFraction (const char* texture)
514 {
515 	const terrainType_t* t = Com_GetTerrainType(texture);
516 	return t ? t->bounceFraction : 1.0f;
517 }
518 
519 /**
520  * @brief Loads the mins/maxs for a md2 mesh model
521  * @param[in,out] mod The server side model struct to store the results in
522  * @param[in] buffer The mesh model buffer
523  */
SV_ModLoadAliasMD2Model(sv_model_t * mod,const byte * buffer)524 static void SV_ModLoadAliasMD2Model (sv_model_t* mod, const byte* buffer)
525 {
526 	const dMD2Model_t* md2 = (const dMD2Model_t*)buffer;
527 	const int num_frames = LittleLong(md2->num_frames);
528 	const int frameSize = LittleLong(md2->framesize);
529 	const dMD2Frame_t* frame = (const dMD2Frame_t*) ((const byte*) md2 + LittleLong(md2->ofs_frames) + mod->frame * frameSize);
530 	vec3_t scale, mins, maxs;
531 	int j;
532 
533 	if (mod->frame > num_frames)
534 		return;
535 
536 	for (j = 0; j < 3; j++) {
537 		scale[j] = LittleFloat(frame->scale[j]);
538 		mins[j] = LittleFloat(frame->translate[j]);
539 	}
540 
541 	VectorMA(mins, 255, scale, maxs);
542 	mod->aabb.add(mins);
543 	mod->aabb.add(maxs);
544 }
545 
546 /**
547  * @brief Loads the mins/maxs for a md3 mesh model
548  * @param[in,out] mod The server side model struct to store the results in
549  * @param[in] buffer The mesh model buffer
550  */
SV_ModLoadAliasMD3Model(sv_model_t * mod,const byte * buffer)551 static void SV_ModLoadAliasMD3Model (sv_model_t* mod, const byte* buffer)
552 {
553 	const dmd3_t* md3 = (const dmd3_t*)buffer;
554 	const dmd3frame_t* frame = (const dmd3frame_t*)((const byte*)md3 + LittleLong(md3->ofs_frames));
555 	const int num_frames = LittleLong(md3->num_frames);
556 	vec3_t mins, maxs;
557 	int j;
558 
559 	if (mod->frame > num_frames)
560 		return;
561 
562 	frame += mod->frame;
563 	for (j = 0; j < 3; j++) {
564 		mins[j] = LittleFloat(frame->mins[j]);
565 		maxs[j] = LittleFloat(frame->maxs[j]);
566 	}
567 	mod->aabb.add(mins);
568 	mod->aabb.add(maxs);
569 }
570 
571 /**
572  * @brief Loads the mins/maxs for a obj mesh model
573  * @param[in,out] mod The server side model struct to store the results in
574  * @param[in] buffer The mesh model buffer
575  * @param[in] bufferLength The mesh model buffer length
576  */
SV_ModLoadObjModel(sv_model_t * mod,const byte * buffer,int bufferLength)577 static void SV_ModLoadObjModel (sv_model_t* mod, const byte* buffer, int bufferLength)
578 {
579 	/** @todo implement me */
580 }
581 
582 /**
583  * @brief all supported model formats
584  * @sa modtype_t
585  */
586 static char const* const mod_extensions[] = {
587 	"md2", "md3", "obj", nullptr
588 };
589 
590 /**
591  * @brief Load the bounding box for the model on the serverside for pathfinding and clipping
592  * @param[in] model The relative model path to load the bounding box for
593  * @param[in] frame The frame to load the bounding box for
594  * @param[out] aabb The bounding box of the model - this is absolute to the worldorigin (0,0,0)
595  */
SV_LoadModelAABB(const char * model,int frame,AABB & aabb)596 bool SV_LoadModelAABB (const char* model, int frame, AABB& aabb)
597 {
598 	sv_model_t* mod;
599 	byte* buf = nullptr;
600 	unsigned int i;
601 	int modfilelen = 0;
602 
603 	if (model[0] == '\0')
604 		Com_Error(ERR_DROP, "SV_LoadModelAABB: nullptr model");
605 
606 	/* search the currently loaded models */
607 	for (i = 0, mod = sv->svModels; i < sv->numSVModels; i++, mod++)
608 		if (mod->frame == frame && Q_streq(mod->name, model)) {
609 			aabb.set(mod->aabb);
610 			return true;
611 		}
612 
613 	/* find a free model slot spot */
614 	for (i = 0, mod = sv->svModels; i < sv->numSVModels; i++, mod++) {
615 		if (!mod->name)
616 			break;				/* free spot */
617 	}
618 
619 	if (i == sv->numSVModels) {
620 		if (sv->numSVModels == MAX_MOD_KNOWN)
621 			Com_Error(ERR_DROP, "sv->numSVModels == MAX_MOD_KNOWN");
622 		sv->numSVModels++;
623 	}
624 
625 	VectorCopy(vec3_origin, aabb.mins);
626 	VectorCopy(vec3_origin, aabb.maxs);
627 
628 	/* load the file */
629 	if (Com_GetExtension(model) == nullptr) {
630 		char filename[MAX_QPATH];
631 
632 		for (i = 0; mod_extensions[i] != nullptr; i++) {
633 			Com_sprintf(filename, sizeof(filename), "%s.%s", model, mod_extensions[i]);
634 			modfilelen = FS_LoadFile(filename, &buf);
635 			if (buf) {
636 				break;
637 			}
638 		}
639 	} else {
640 		modfilelen = FS_LoadFile(model, &buf);
641 	}
642 
643 	if (!buf) {
644 		sv->numSVModels--;
645 		return false;
646 	}
647 
648 	OBJZERO(*mod);
649 	mod->name = Mem_PoolStrDup(model, com_genericPool, 0);
650 	mod->frame = frame;
651 	mod->aabb.clearBounds();
652 
653 	/* call the appropriate loader */
654 	switch (LittleLong(*(unsigned *) buf)) {
655 	case IDALIASHEADER:
656 		SV_ModLoadAliasMD2Model(mod, buf);
657 		break;
658 
659 	case IDMD3HEADER:
660 		SV_ModLoadAliasMD3Model(mod, buf);
661 		break;
662 
663 	default:
664 		if (!Q_strcasecmp(mod->name + strlen(mod->name) - 4, ".obj"))
665 			SV_ModLoadObjModel(mod, buf, modfilelen);
666 		else {
667 			FS_FreeFile(buf);
668 			return false;
669 		}
670 		break;
671 	}
672 
673 	aabb.set(mod->aabb);	/* to return the found values */
674 
675 	FS_FreeFile(buf);
676 	return true;
677 }
678