summaryrefslogtreecommitdiff
path: root/src/sv_world.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sv_world.c')
-rw-r--r--src/sv_world.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/sv_world.c b/src/sv_world.c
new file mode 100644
index 0000000..e3c40c9
--- /dev/null
+++ b/src/sv_world.c
@@ -0,0 +1,652 @@
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+// world.c -- world query functions
+
+#include "sv_local.h"
+
+/*
+===============================================================================
+
+ENTITY AREA CHECKING
+
+FIXME: this use of "area" is different from the bsp file use
+===============================================================================
+*/
+
+typedef struct areanode_s {
+ int axis; // -1 = leaf node
+ float dist;
+ struct areanode_s *children[2];
+ list_t trigger_edicts;
+ list_t solid_edicts;
+} areanode_t;
+
+#define AREA_DEPTH 4
+#define AREA_NODES 32
+
+static areanode_t sv_areanodes[AREA_NODES];
+static int sv_numareanodes;
+
+static float *area_mins, *area_maxs;
+static edict_t **area_list;
+static int area_count, area_maxcount;
+static int area_type;
+
+/*
+===============
+SV_CreateAreaNode
+
+Builds a uniformly subdivided tree for the given world size
+===============
+*/
+static areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs) {
+ areanode_t *anode;
+ vec3_t size;
+ vec3_t mins1, maxs1, mins2, maxs2;
+
+ anode = &sv_areanodes[sv_numareanodes];
+ sv_numareanodes++;
+
+ List_Init (&anode->trigger_edicts);
+ List_Init (&anode->solid_edicts);
+
+ if (depth == AREA_DEPTH)
+ {
+ anode->axis = -1;
+ anode->children[0] = anode->children[1] = NULL;
+ return anode;
+ }
+
+ VectorSubtract (maxs, mins, size);
+ if (size[0] > size[1])
+ anode->axis = 0;
+ else
+ anode->axis = 1;
+
+ anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);
+ VectorCopy (mins, mins1);
+ VectorCopy (mins, mins2);
+ VectorCopy (maxs, maxs1);
+ VectorCopy (maxs, maxs2);
+
+ maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
+
+ anode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2);
+ anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1);
+
+ return anode;
+}
+
+/*
+===============
+SV_ClearWorld
+
+===============
+*/
+void SV_ClearWorld( void ) {
+ mmodel_t *cm;
+ edict_t *ent;
+ int i;
+
+ memset( sv_areanodes, 0, sizeof( sv_areanodes ) );
+ sv_numareanodes = 0;
+
+ if( sv.cm.cache ) {
+ cm = &sv.cm.cache->models[0];
+ SV_CreateAreaNode( 0, cm->mins, cm->maxs );
+ }
+
+ // make sure all entities are unlinked
+ for( i = 0; i < ge->max_edicts; i++ ) {
+ ent = EDICT_NUM( i );
+ ent->area.prev = ent->area.next = NULL;
+ }
+}
+
+
+/*
+===============
+SV_LinkEdict
+
+General purpose routine shared between game DLL and MVD code.
+Links entity to PVS leafs.
+===============
+*/
+void SV_LinkEdict( cm_t *cm, edict_t *ent ) {
+ mleaf_t *leafs[MAX_TOTAL_ENT_LEAFS];
+ int clusters[MAX_TOTAL_ENT_LEAFS];
+ int num_leafs;
+ int i, j;
+ int area;
+ mnode_t *topnode;
+
+ // set the size
+ VectorSubtract (ent->maxs, ent->mins, ent->size);
+
+ // set the abs box
+ if (ent->solid == SOLID_BSP &&
+ (ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2]) )
+ { // expand for rotation
+ float max, v;
+ int i;
+
+ max = 0;
+ for (i=0 ; i<3 ; i++) {
+ v = Q_fabs( ent->mins[i]);
+ if (v > max)
+ max = v;
+ v = Q_fabs( ent->maxs[i]);
+ if (v > max)
+ max = v;
+ }
+ for (i=0 ; i<3 ; i++)
+ {
+ ent->absmin[i] = ent->s.origin[i] - max;
+ ent->absmax[i] = ent->s.origin[i] + max;
+ }
+ }
+ else
+ { // normal
+ VectorAdd (ent->s.origin, ent->mins, ent->absmin);
+ VectorAdd (ent->s.origin, ent->maxs, ent->absmax);
+ }
+
+ // because movement is clipped an epsilon away from an actual edge,
+ // we must fully check even when bounding boxes don't quite touch
+ ent->absmin[0] -= 1;
+ ent->absmin[1] -= 1;
+ ent->absmin[2] -= 1;
+ ent->absmax[0] += 1;
+ ent->absmax[1] += 1;
+ ent->absmax[2] += 1;
+
+// link to PVS leafs
+ ent->num_clusters = 0;
+ ent->areanum = 0;
+ ent->areanum2 = 0;
+
+ //get all leafs, including solids
+ num_leafs = CM_BoxLeafs( cm, ent->absmin, ent->absmax,
+ leafs, MAX_TOTAL_ENT_LEAFS, &topnode);
+
+ // set areas
+ for (i=0 ; i<num_leafs ; i++)
+ {
+ clusters[i] = CM_LeafCluster (leafs[i]);
+ area = CM_LeafArea (leafs[i]);
+ if (area)
+ { // doors may legally straggle two areas,
+ // but nothing should evern need more than that
+ if (ent->areanum && ent->areanum != area)
+ {
+ if (ent->areanum2 && ent->areanum2 != area && sv.state == ss_loading)
+ Com_DPrintf ("Object touching 3 areas at %f %f %f\n",
+ ent->absmin[0], ent->absmin[1], ent->absmin[2]);
+ ent->areanum2 = area;
+ }
+ else
+ ent->areanum = area;
+ }
+ }
+
+ if (num_leafs >= MAX_TOTAL_ENT_LEAFS)
+ { // assume we missed some leafs, and mark by headnode
+ ent->num_clusters = -1;
+ ent->headnode = CM_NumNode( cm, topnode );
+ }
+ else
+ {
+ ent->num_clusters = 0;
+ for (i=0 ; i<num_leafs ; i++)
+ {
+ if (clusters[i] == -1)
+ continue; // not a visible leaf
+ for (j=0 ; j<i ; j++)
+ if (clusters[j] == clusters[i])
+ break;
+ if (j == i)
+ {
+ if (ent->num_clusters == MAX_ENT_CLUSTERS)
+ { // assume we missed some leafs, and mark by headnode
+ ent->num_clusters = -1;
+ ent->headnode = CM_NumNode( cm, topnode );
+ break;
+ }
+
+ ent->clusternums[ent->num_clusters++] = clusters[i];
+ }
+ }
+ }
+}
+
+void PF_UnlinkEdict (edict_t *ent) {
+ if (!ent->area.prev)
+ return; // not linked in anywhere
+ List_Remove (&ent->area);
+ ent->area.prev = ent->area.next = NULL;
+}
+
+static int SV_CalcSolid( vec3_t mins, vec3_t maxs ) {
+ int i, j, k;
+
+ // assume that x/y are equal and symetric
+ i = maxs[0] / 8;
+ clamp( i, 1, 31 );
+
+ // z is not symetric
+ j = ( -mins[2] ) / 8;
+ clamp( j, 1, 31 );
+
+ // and z maxs can be negative...
+ k = ( maxs[2] + 32 ) / 8;
+ clamp( k, 1, 63 );
+
+ return ( k << 10 ) | ( j << 5 ) | i;
+}
+
+static int SV_CalcSolid32( vec3_t mins, vec3_t maxs ) {
+ int i, j, k;
+
+ // assume that x/y are equal and symetric
+ i = maxs[0];
+ clamp( i, 1, 255 );
+
+ // z is not symetric
+ j = -mins[2];
+ clamp( j, 1, 255 );
+
+ // and z maxs can be negative...
+ k = maxs[2] + 32768;
+ clamp( k, 1, 65535 );
+
+ return ( k << 16 ) | ( j << 8 ) | i;
+}
+
+void PF_LinkEdict (edict_t *ent) {
+ areanode_t *node;
+ server_entity_t *sent;
+ int entnum;
+
+ if (ent->area.prev)
+ PF_UnlinkEdict (ent); // unlink from old position
+
+ if (ent == ge->edicts)
+ return; // don't add the world
+
+ if (!ent->inuse) {
+ Com_DPrintf( "%s: entity %d is not in use\n", __func__, NUM_FOR_EDICT( ent ) );
+ return;
+ }
+
+ if( !sv.cm.cache ) {
+ return;
+ }
+
+ entnum = NUM_FOR_EDICT( ent );
+ sent = &sv.entities[entnum];
+
+ // encode the size into the entity_state for client prediction
+ switch( ent->solid ) {
+ case SOLID_BBOX:
+ if( ( ent->svflags & SVF_DEADMONSTER ) || VectorCompare( ent->mins, ent->maxs ) ) {
+ ent->s.solid = 0;
+ sent->solid32 = 0;
+ } else {
+ ent->s.solid = SV_CalcSolid( ent->mins, ent->maxs );
+ sent->solid32 = SV_CalcSolid32( ent->mins, ent->maxs );
+ }
+ break;
+ case SOLID_BSP:
+ ent->s.solid = 31; // a solid_bbox will never create this value
+ sent->solid32 = 31; // FIXME: use 255?
+ break;
+ default:
+ ent->s.solid = 0;
+ sent->solid32 = 0;
+ break;
+ }
+
+ SV_LinkEdict( &sv.cm, ent );
+
+ // if first time, make sure old_origin is valid
+ if (!ent->linkcount) {
+ VectorCopy (ent->s.origin, ent->s.old_origin);
+ }
+ ent->linkcount++;
+
+ if (ent->solid == SOLID_NOT)
+ return;
+
+// find the first node that the ent's box crosses
+ node = sv_areanodes;
+ while (1)
+ {
+ if (node->axis == -1)
+ break;
+ if (ent->absmin[node->axis] > node->dist)
+ node = node->children[0];
+ else if (ent->absmax[node->axis] < node->dist)
+ node = node->children[1];
+ else
+ break; // crosses the node
+ }
+
+ // link it in
+ if (ent->solid == SOLID_TRIGGER)
+ List_Append( &node->trigger_edicts, &ent->area );
+ else
+ List_Append( &node->solid_edicts, &ent->area );
+}
+
+
+/*
+====================
+SV_AreaEdicts_r
+
+====================
+*/
+static void SV_AreaEdicts_r (areanode_t *node) {
+ list_t *start;
+ edict_t *check;
+
+ // touch linked edicts
+ if (area_type == AREA_SOLID)
+ start = &node->solid_edicts;
+ else
+ start = &node->trigger_edicts;
+
+ LIST_FOR_EACH( edict_t, check, start, area ) {
+ if (check->solid == SOLID_NOT)
+ continue; // deactivated
+ if (check->absmin[0] > area_maxs[0]
+ || check->absmin[1] > area_maxs[1]
+ || check->absmin[2] > area_maxs[2]
+ || check->absmax[0] < area_mins[0]
+ || check->absmax[1] < area_mins[1]
+ || check->absmax[2] < area_mins[2])
+ continue; // not touching
+
+ if (area_count == area_maxcount) {
+ Com_WPrintf ("SV_AreaEdicts: MAXCOUNT\n");
+ return;
+ }
+
+ area_list[area_count] = check;
+ area_count++;
+ }
+
+ if (node->axis == -1)
+ return; // terminal node
+
+ // recurse down both sides
+ if ( area_maxs[node->axis] > node->dist )
+ SV_AreaEdicts_r ( node->children[0] );
+ if ( area_mins[node->axis] < node->dist )
+ SV_AreaEdicts_r ( node->children[1] );
+}
+
+/*
+================
+SV_AreaEdicts
+================
+*/
+int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **list,
+ int maxcount, int areatype)
+{
+ area_mins = mins;
+ area_maxs = maxs;
+ area_list = list;
+ area_count = 0;
+ area_maxcount = maxcount;
+ area_type = areatype;
+
+ SV_AreaEdicts_r (sv_areanodes);
+
+ return area_count;
+}
+
+
+//===========================================================================
+
+/*
+================
+SV_HullForEntity
+
+Returns a headnode that can be used for testing or clipping an
+object of mins/maxs size.
+================
+*/
+static mnode_t *SV_HullForEntity( edict_t *ent ) {
+ mmodel_t *model;
+
+ if( ent->solid == SOLID_BSP ) {
+ int index = ent->s.modelindex - 1;
+
+ // explicit hulls in the BSP model
+ if( index <= 0 || index >= sv.cm.cache->nummodels ) {
+ Com_Error( ERR_DROP, "%s: inline model %d out of range",
+ __func__, index );
+ }
+
+ model = &sv.cm.cache->models[index];
+ return model->headnode;
+ }
+
+ // create a temp hull from bounding box sizes
+ return CM_HeadnodeForBox( ent->mins, ent->maxs );
+}
+
+/*
+=============
+SV_PointContents
+=============
+*/
+int SV_PointContents (vec3_t p)
+{
+ edict_t *touch[MAX_EDICTS], *hit;
+ int i, num;
+ int contents, c2;
+ mnode_t *headnode;
+
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "%s: no map loaded", __func__ );
+ }
+
+ // get base contents from world
+ contents = CM_PointContents (p, sv.cm.cache->nodes);
+
+ // or in contents from all the other entities
+ num = SV_AreaEdicts (p, p, touch, MAX_EDICTS, AREA_SOLID);
+
+ for (i=0 ; i<num ; i++)
+ {
+ hit = touch[i];
+
+ // might intersect, so do an exact clip
+ headnode = SV_HullForEntity (hit);
+
+ c2 = CM_TransformedPointContents (p, headnode,
+ hit->s.origin, hit->s.angles);
+
+ contents |= c2;
+ }
+
+ return contents;
+}
+
+
+
+typedef struct {
+ vec3_t boxmins, boxmaxs;// enclose the test object along entire move
+ vec_t *mins, *maxs; // size of the moving object
+ vec_t *start, *end;
+ trace_t *trace;
+ edict_t *passedict;
+ int contentmask;
+} moveclip_t;
+
+/*
+====================
+SV_ClipMoveToEntities
+
+====================
+*/
+static void SV_ClipMoveToEntities( moveclip_t *clip ) {
+ int i, num;
+ edict_t *touchlist[MAX_EDICTS], *touch;
+ trace_t trace;
+ mnode_t *headnode;
+
+ num = SV_AreaEdicts (clip->boxmins, clip->boxmaxs, touchlist
+ , MAX_EDICTS, AREA_SOLID);
+
+ // be careful, it is possible to have an entity in this
+ // list removed before we get to it (killtriggered)
+ for (i=0 ; i<num ; i++)
+ {
+ touch = touchlist[i];
+ if (touch->solid == SOLID_NOT)
+ continue;
+ if (touch == clip->passedict)
+ continue;
+ if (clip->trace->allsolid)
+ return;
+ if (clip->passedict)
+ {
+ if (touch->owner == clip->passedict)
+ continue; // don't clip against own missiles
+ if (clip->passedict->owner == touch)
+ continue; // don't clip against owner
+ }
+
+ if ( !(clip->contentmask & CONTENTS_DEADMONSTER)
+ && (touch->svflags & SVF_DEADMONSTER) )
+ continue;
+
+ // might intersect, so do an exact clip
+ headnode = SV_HullForEntity (touch);
+
+ CM_TransformedBoxTrace (&trace, clip->start, clip->end,
+ clip->mins, clip->maxs, headnode, clip->contentmask,
+ touch->s.origin, touch->s.angles);
+
+ CM_ClipEntity( clip->trace, &trace, touch );
+ }
+}
+
+
+/*
+==================
+SV_TraceBounds
+==================
+*/
+static void SV_TraceBounds (moveclip_t *clip) {
+ int i;
+
+ for (i=0 ; i<3 ; i++) {
+ if (clip->end[i] > clip->start[i]) {
+ clip->boxmins[i] = clip->start[i] + clip->mins[i] - 1;
+ clip->boxmaxs[i] = clip->end[i] + clip->maxs[i] + 1;
+ } else {
+ clip->boxmins[i] = clip->end[i] + clip->mins[i] - 1;
+ clip->boxmaxs[i] = clip->start[i] + clip->maxs[i] + 1;
+ }
+ }
+}
+
+/*
+==================
+SV_Trace
+
+Moves the given mins/maxs volume through the world from start to end.
+
+Passedict and edicts owned by passedict are explicitly not checked.
+==================
+*/
+trace_t *SV_Trace( trace_t *trace,
+ vec3_t start,
+ vec3_t mins,
+ vec3_t maxs,
+ vec3_t end,
+ edict_t *passedict,
+ int contentmask )
+{
+ moveclip_t clip;
+
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "%s: no map loaded", __func__ );
+ }
+
+ if( ++sv.tracecount > 10000 ) {
+ Com_EPrintf( "%s: game DLL caught in infinite loop!\n", __func__ );
+ memset( trace, 0, sizeof( *trace ) );
+ trace->fraction = 1;
+ trace->ent = ge->edicts;
+ VectorCopy( end, trace->endpos );
+ sv.tracecount = 0;
+ return trace;
+ }
+
+ if (!mins)
+ mins = vec3_origin;
+ if (!maxs)
+ maxs = vec3_origin;
+
+ // clip to world
+ CM_BoxTrace( trace, start, end, mins, maxs,
+ sv.cm.cache->nodes, contentmask );
+ trace->ent = ge->edicts;
+ if (trace->fraction == 0) {
+ return trace; // blocked by the world
+ }
+
+ memset( &clip, 0, sizeof( clip ) );
+ clip.trace = trace;
+ clip.contentmask = contentmask;
+ clip.start = start;
+ clip.end = end;
+ clip.mins = mins;
+ clip.maxs = maxs;
+ clip.passedict = passedict;
+
+ // create the bounding box of the entire move
+ SV_TraceBounds( &clip );
+
+ // clip to other solid entities
+ SV_ClipMoveToEntities( &clip );
+
+ return trace;
+}
+
+/*
+==================
+SV_Trace_Native
+
+Variant of SV_Trace for native game ABI
+==================
+*/
+trace_t SV_Trace_Native (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passedict, int contentmask) {
+ trace_t trace;
+
+ SV_Trace( &trace, start, mins, maxs, end, passedict, contentmask );
+
+ return trace;
+}
+