diff options
Diffstat (limited to 'src/server/sv_world.c')
-rw-r--r-- | src/server/sv_world.c | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/src/server/sv_world.c b/src/server/sv_world.c new file mode 100644 index 0000000..d40cc21 --- /dev/null +++ b/src/server/sv_world.c @@ -0,0 +1,692 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// world.c -- world query functions + +#include "server.h" + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { + if ( ent->r.bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + if ( ent->r.svFlags & SVF_CAPSULE ) { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); +} + + + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +typedef struct worldSector_s { + int axis; // -1 = leaf node + float dist; + struct worldSector_s *children[2]; + svEntity_t *entities; +} worldSector_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + + +/* +=============== +SV_SectorList_f +=============== +*/ +void SV_SectorList_f( void ) { + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for ( i = 0 ; i < AREA_NODES ; i++ ) { + sec = &sv_worldSectors[i]; + + c = 0; + for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { + c++; + } + Com_Printf( "sector %i: %i entities\n", i, c ); + } +} + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + 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_CreateworldSector (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) { + clipHandle_t h; + vec3_t mins, maxs; + + Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( sharedEntity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->r.linked = qfalse; + + ws = ent->worldSector; + if ( !ws ) { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if ( ws->entities == ent ) { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { + if ( scan->nextEntityInWorldSector == ent ) { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); +} + + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity( sharedEntity_t *gEnt ) { + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + ent = SV_SvEntityForGentity( gEnt ); + + if ( ent->worldSector ) { + SV_UnlinkEntity( gEnt ); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if ( gEnt->r.bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-gEnt->r.mins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z maxs can be negative... + k = (gEnt->r.maxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + gEnt->s.solid = (k<<16) | (j<<8) | i; + } else { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { + // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + for (i=0 ; i<3 ; i++) { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); + VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + //get all leafs, including solids + num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, + leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if ( !num_leafs ) { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for (i=0 ; i<num_leafs ; i++) { + area = CM_LeafArea (leafs[i]); + if (area != -1) { + // doors may legally straggle two areas, + // but nothing should evern need more than that + if (ent->areanum != -1 && ent->areanum != area) { + if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { + Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", + gEnt->s.number, + gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); + } + ent->areanum2 = area; + } else { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for (i=0 ; i < num_leafs ; i++) { + cluster = CM_LeafCluster( leafs[i] ); + if ( cluster != -1 ) { + ent->clusternums[ent->numClusters++] = cluster; + if ( ent->numClusters == MAX_ENT_CLUSTERS ) { + break; + } + } + } + + // store off a last cluster if we need to + if ( i != num_leafs ) { + ent->lastCluster = CM_LeafCluster( lastLeaf ); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while (1) + { + if (node->axis == -1) + break; + if ( gEnt->r.absmin[node->axis] > node->dist) + node = node->children[0]; + else if ( gEnt->r.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->r.linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +typedef struct { + const float *mins; + const float *maxs; + int *list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + sharedEntity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->r.absmin[0] > ap->maxs[0] + || gcheck->r.absmin[1] > ap->maxs[1] + || gcheck->r.absmin[2] > ap->maxs[2] + || gcheck->r.absmax[0] < ap->mins[0] + || gcheck->r.absmax[1] < ap->mins[1] + || gcheck->r.absmax[2] < ap->mins[2]) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_Printf ("SV_AreaEntities: MAXCOUNT\n"); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + ap->count++; + } + + if (node->axis == -1) { + return; // terminal node + } + + // recurse down both sides + if ( ap->maxs[node->axis] > node->dist ) { + SV_AreaEntities_r ( node->children[0], ap ); + } + if ( ap->mins[node->axis] < node->dist ) { + SV_AreaEntities_r ( node->children[1], ap ); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + + + +//=========================================================================== + + +typedef struct { + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + traceType_t collisionType; +} moveclip_t; + + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, traceType_t type ) { + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum( entityNum ); + + Com_Memset(trace, 0, sizeof(trace_t)); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( contentmask & touch->r.contents ) ) { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( trace, (float *)start, (float *)end, + (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, type); + + if ( trace->fraction < 1 ) { + trace->entityNum = touch->s.number; + } +} + + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +void SV_ClipMoveToEntities( moveclip_t *clip ) { + int i, num; + int touchlist[MAX_GENTITIES]; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace; + clipHandle_t clipHandle; + float *origin, *angles; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passOwnerNum == ENTITYNUM_NONE ) { + passOwnerNum = -1; + } + } else { + passOwnerNum = -1; + } + + for ( i=0 ; i<num ; i++ ) { + if ( clip->trace.allsolid ) { + return; + } + touch = SV_GentityNum( touchlist[i] ); + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if ( touchlist[i] == clip->passEntityNum ) { + continue; // don't clip against the pass entity + } + if ( touch->r.ownerNum == clip->passEntityNum ) { + continue; // don't clip against own missiles + } + if ( touch->r.ownerNum == passOwnerNum ) { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( clip->contentmask & touch->r.contents ) ) { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->collisionType); + + if ( trace.allsolid ) { + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } else if ( trace.startsolid ) { + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) { + qboolean oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } + } +} + + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, traceType_t type ) { + moveclip_t clip; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); + + // clip to world + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, type ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; + clip.start = start; +// VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy( end, clip.end ); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.collisionType = type; + + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + for ( i=0 ; i<3 ; i++ ) { + if ( end[i] > 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; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities ( &clip ); + + *results = clip.trace; +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // get base contents from world + contents = CM_PointContents( p, 0 ); + + // or in contents from all the other entities + num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); + + for ( i=0 ; i<num ; i++ ) { + if ( touch[i] == passEntityNum ) { + continue; + } + hit = SV_GentityNum( touch[i] ); + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( hit ); + angles = hit->s.angles; + if ( !hit->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); + + contents |= c2; + } + + return contents; +} + + |