/* sv_world.c - world query functions Copyright (C) 2008 Uncle Mike 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 3 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. */ #include "common.h" #include "server.h" #include "const.h" #include "pm_local.h" #include "studio.h" typedef struct moveclip_s { vec3_t boxmins, boxmaxs; // enclose the test object along entire move float *mins, *maxs; // size of the moving object vec3_t mins2, maxs2; // size when clipping against mosnters const float *start, *end; edict_t *passedict; trace_t trace; int type; // move type qboolean ignoretrans; qboolean monsterclip; } moveclip_t; /* =============================================================================== HULL BOXES =============================================================================== */ static hull_t box_hull; static mclipnode_t box_clipnodes[6]; static mplane_t box_planes[6]; /* =================== SV_InitBoxHull Set up the planes and clipnodes so that the six floats of a bounding box can just be stored out and get a proper hull_t structure. =================== */ void SV_InitBoxHull( void ) { int i, side; box_hull.clipnodes = box_clipnodes; box_hull.planes = box_planes; box_hull.firstclipnode = 0; box_hull.lastclipnode = 5; for( i = 0; i < 6; i++ ) { box_clipnodes[i].planenum = i; side = i & 1; box_clipnodes[i].children[side] = CONTENTS_EMPTY; if( i != 5 ) box_clipnodes[i].children[side^1] = i + 1; else box_clipnodes[i].children[side^1] = CONTENTS_SOLID; box_planes[i].type = i>>1; box_planes[i].normal[i>>1] = 1; box_planes[i].signbits = 0; } } /* ==================== StudioPlayerBlend ==================== */ void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing *pBlend = (*pPitch * 3); if( *pBlend < pseqdesc->blendstart[0] ) { *pPitch -= pseqdesc->blendstart[0] / 3.0f; *pBlend = 0; } else if( *pBlend > pseqdesc->blendend[0] ) { *pPitch -= pseqdesc->blendend[0] / 3.0f; *pBlend = 255; } else { if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error *pBlend = 127; else *pBlend = 255.0f * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); *pPitch = 0; } } /* ==================== SV_CheckSphereIntersection check clients only ==================== */ qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end ) { int i, sequence; float radiusSquared; vec3_t traceOrg, traceDir; studiohdr_t *pstudiohdr; mstudioseqdesc_t *pseqdesc; model_t *mod; if( !FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) return true; if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL ) return true; if(( pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod )) == NULL ) return true; sequence = ent->v.sequence; if( sequence < 0 || sequence >= pstudiohdr->numseq ) sequence = 0; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; VectorCopy( start, traceOrg ); VectorSubtract( end, start, traceDir ); radiusSquared = 0.0f; for ( i = 0; i < 3; i++ ) radiusSquared += Q_max( fabs( pseqdesc->bbmin[i] ), fabs( pseqdesc->bbmax[i] )); return SphereIntersect( ent->v.origin, radiusSquared, traceOrg, traceDir ); } /* =================== SV_HullForBox To keep everything totally uniform, bounding boxes are turned into small BSP trees instead of being compared directly. =================== */ hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) { box_planes[0].dist = maxs[0]; box_planes[1].dist = mins[0]; box_planes[2].dist = maxs[1]; box_planes[3].dist = mins[1]; box_planes[4].dist = maxs[2]; box_planes[5].dist = mins[2]; return &box_hull; } /* ================== SV_HullAutoSelect select the apropriate hull automatically ================== */ hull_t *SV_HullAutoSelect( model_t *model, const vec3_t mins, const vec3_t maxs, const vec3_t size, vec3_t offset ) { float curdiff; float lastdiff = 999; int i, hullNumber = 0; // assume we fail vec3_t clip_size; hull_t *hull; // NOTE: this is not matched with hardcoded values in some cases... for( i = 0; i < MAX_MAP_HULLS; i++ ) { VectorSubtract( model->hulls[i].clip_maxs, model->hulls[i].clip_mins, clip_size ); curdiff = floor( VectorAvg( size )) - floor( VectorAvg( clip_size )); curdiff = fabs( curdiff ); if( curdiff < lastdiff ) { hullNumber = i; lastdiff = curdiff; } } // TraceHull stuff hull = &model->hulls[hullNumber]; // calculate an offset value to center the origin // NOTE: never get offset of drawing hull if( !hullNumber ) VectorCopy( hull->clip_mins, offset ); else VectorSubtract( hull->clip_mins, mins, offset ); return hull; } /* ================== SV_HullForBsp forcing to select BSP hull ================== */ hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset ) { hull_t *hull; model_t *model; vec3_t size; if( svgame.physFuncs.SV_HullForBsp != NULL ) { hull = svgame.physFuncs.SV_HullForBsp( ent, mins, maxs, offset ); if( hull ) return hull; } // decide which clipping hull to use, based on the size model = SV_ModelHandle( ent->v.modelindex ); if( !model || model->type != mod_brush ) Host_Error( "Entity %i (%s) SOLID_BSP with a non bsp model %s\n", NUM_FOR_EDICT( ent ), SV_ClassName( ent ), STRING( ent->v.model )); VectorSubtract( maxs, mins, size ); #ifdef RANDOM_HULL_NULLIZATION // author: The FiEctro hull = &model->hulls[COM_RandomLong( 0, 0 )]; #endif // g-cont: find a better method to detect quake-maps? if( FBitSet( world.flags, FWORLD_SKYSPHERE )) { // alternate hull select for quake maps if( size[0] < 3.0f || ent->v.solid == SOLID_PORTAL ) hull = &model->hulls[0]; else if( size[0] <= 32.0f ) hull = &model->hulls[1]; else hull = &model->hulls[2]; VectorSubtract( hull->clip_mins, mins, offset ); } else { if( size[0] <= 8.0f || ent->v.solid == SOLID_PORTAL ) { hull = &model->hulls[0]; VectorCopy( hull->clip_mins, offset ); } else { if( size[0] <= 36.0f ) { if( size[2] <= 36.0f ) hull = &model->hulls[3]; else hull = &model->hulls[1]; } else hull = &model->hulls[2]; VectorSubtract( hull->clip_mins, mins, offset ); } } VectorAdd( offset, ent->v.origin, offset ); return hull; } /* ================ SV_HullForEntity Returns a hull that can be used for testing or clipping an object of mins/maxs size. Offset is filled in to contain the adjustment that must be added to the testing object's origin to get a point to use with the returned hull. ================ */ hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset ) { hull_t *hull; vec3_t hullmins, hullmaxs; if( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) { if( ent->v.solid != SOLID_PORTAL ) { if( ent->v.movetype != MOVETYPE_PUSH && ent->v.movetype != MOVETYPE_PUSHSTEP ) Host_Error( "'%s' has SOLID_BSP without MOVETYPE_PUSH or MOVETYPE_PUSHSTEP\n", SV_ClassName( ent )); } hull = SV_HullForBsp( ent, mins, maxs, offset ); } else { // create a temp hull from bounding box sizes VectorSubtract( ent->v.mins, maxs, hullmins ); VectorSubtract( ent->v.maxs, mins, hullmaxs ); hull = SV_HullForBox( hullmins, hullmaxs ); VectorCopy( ent->v.origin, offset ); } return hull; } /* ==================== SV_HullForStudioModel ==================== */ hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes ) { qboolean useComplexHull; float scale = 0.5f; hull_t *hull = NULL; vec3_t size; model_t *mod; if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL ) { *numhitboxes = 1; return SV_HullForEntity( ent, mins, maxs, offset ); } VectorSubtract( maxs, mins, size ); useComplexHull = false; if( VectorIsNull( size ) && !FBitSet( svgame.globals->trace_flags, FTRACE_SIMPLEBOX )) { useComplexHull = true; if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) { if( sv_clienttrace.value == 0.0f ) { // so no way to trace studiomodels by hitboxes // use bbox instead useComplexHull = false; } else { scale = sv_clienttrace.value * 0.5f; VectorSet( size, 1.0f, 1.0f, 1.0f ); } } } if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ) || useComplexHull ) { VectorScale( size, scale, size ); VectorClear( offset ); if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) { studiohdr_t *pstudio; mstudioseqdesc_t *pseqdesc; byte controller[4]; byte blending[2]; vec3_t angles; int iBlend; pstudio = Mod_StudioExtradata( mod ); pseqdesc = (mstudioseqdesc_t *)((byte *)pstudio + pstudio->seqindex) + ent->v.sequence; VectorCopy( ent->v.angles, angles ); SV_StudioPlayerBlend( pseqdesc, &iBlend, &angles[PITCH] ); controller[0] = controller[1] = 0x7F; controller[2] = controller[3] = 0x7F; blending[0] = (byte)iBlend; blending[1] = 0; hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, angles, ent->v.origin, size, controller, blending, numhitboxes, ent ); } else { hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, ent->v.angles, ent->v.origin, size, ent->v.controller, ent->v.blending, numhitboxes, ent ); } } if( hull ) return hull; *numhitboxes = 1; return SV_HullForEntity( ent, mins, maxs, offset ); } /* =============================================================================== ENTITY AREA CHECKING =============================================================================== */ static int iTouchLinkSemaphore = 0; // prevent recursion when SV_TouchLinks is active areanode_t sv_areanodes[AREA_NODES]; static int sv_numareanodes; /* =============== SV_CreateAreaNode builds a uniformly subdivided tree for the given world size =============== */ areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs ) { areanode_t *anode; vec3_t size; vec3_t mins1, maxs1; vec3_t mins2, maxs2; anode = &sv_areanodes[sv_numareanodes++]; ClearLink( &anode->trigger_edicts ); ClearLink( &anode->solid_edicts ); ClearLink( &anode->portal_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.5f * ( 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 ) { int i; SV_InitBoxHull(); // for box testing // clear lightstyles for( i = 0; i < MAX_LIGHTSTYLES; i++ ) { sv.lightstyles[i].value = 256.0f; sv.lightstyles[i].time = 0.0f; } memset( sv_areanodes, 0, sizeof( sv_areanodes )); iTouchLinkSemaphore = 0; sv_numareanodes = 0; SV_CreateAreaNode( 0, sv.worldmodel->mins, sv.worldmodel->maxs ); } /* =============== SV_UnlinkEdict =============== */ void SV_UnlinkEdict( edict_t *ent ) { // not linked in anywhere if( !ent->area.prev ) return; RemoveLink( &ent->area ); ent->area.prev = NULL; ent->area.next = NULL; } /* ==================== SV_TouchLinks ==================== */ void SV_TouchLinks( edict_t *ent, areanode_t *node ) { link_t *l, *next; edict_t *touch; hull_t *hull; vec3_t test, offset; model_t *mod; // touch linked edicts for( l = node->trigger_edicts.next; l != &node->trigger_edicts; l = next ) { next = l->next; touch = EDICT_FROM_AREA( l ); if( svgame.physFuncs.SV_TriggerTouch != NULL ) { // user dll can override trigger checking (Xash3D extension) if( !svgame.physFuncs.SV_TriggerTouch( ent, touch )) continue; } else { if( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ? continue; if( touch->v.groupinfo && ent->v.groupinfo ) { if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, ent->v.groupinfo )) continue; if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, ent->v.groupinfo )) continue; } if( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax )) continue; mod = SV_ModelHandle( touch->v.modelindex ); // check brush triggers accuracy if( mod && mod->type == mod_brush ) { // force to select bsp-hull hull = SV_HullForBsp( touch, ent->v.mins, ent->v.maxs, offset ); // support for rotational triggers if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles )) { matrix4x4 matrix; Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f ); Matrix4x4_VectorITransform( matrix, ent->v.origin, test ); } else { // offset the test point appropriately for this hull. VectorSubtract( ent->v.origin, offset, test ); } // test hull for intersection with this model if( PM_HullPointContents( hull, hull->firstclipnode, test ) != CONTENTS_SOLID ) continue; } } // never touch the triggers when "playersonly" is active if( !sv.playersonly ) { svgame.globals->time = sv.time; svgame.dllFuncs.pfnTouch( touch, ent ); } } // recurse down both sides if( node->axis == -1 ) return; if( ent->v.absmax[node->axis] > node->dist ) SV_TouchLinks( ent, node->children[0] ); if( ent->v.absmin[node->axis] < node->dist ) SV_TouchLinks( ent, node->children[1] ); } /* =============== SV_FindTouchedLeafs =============== */ void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) { int sides; mleaf_t *leaf; if( node->contents == CONTENTS_SOLID ) return; // add an efrag if the node is a leaf if( node->contents < 0 ) { if( ent->num_leafs > ( MAX_ENT_LEAFS - 1 )) { // continue counting leafs, // so we know how many it's overrun ent->num_leafs = (MAX_ENT_LEAFS + 1); } else { leaf = (mleaf_t *)node; ent->leafnums[ent->num_leafs] = leaf->cluster; ent->num_leafs++; } return; } // NODE_MIXED sides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane ); if(( sides == 3 ) && ( *headnode == -1 )) *headnode = node - sv.worldmodel->nodes; // recurse down the contacted sides if( sides & 1 ) SV_FindTouchedLeafs( ent, node->children[0], headnode ); if( sides & 2 ) SV_FindTouchedLeafs( ent, node->children[1], headnode ); } /* =============== SV_LinkEdict =============== */ void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) { areanode_t *node; int headnode; if( ent->area.prev ) SV_UnlinkEdict( ent ); // unlink from old position if( ent == svgame.edicts ) return; // don't add the world if( !SV_IsValidEdict( ent )) return; // never add freed ents // set the abs box svgame.dllFuncs.pfnSetAbsBox( ent ); if( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment )) { memcpy( ent->leafnums, ent->v.aiment->leafnums, sizeof( ent->leafnums )); ent->num_leafs = ent->v.aiment->num_leafs; ent->headnode = ent->v.aiment->headnode; } else { // link to PVS leafs ent->num_leafs = 0; ent->headnode = -1; headnode = -1; if( ent->v.modelindex ) SV_FindTouchedLeafs( ent, sv.worldmodel->nodes, &headnode ); if( ent->num_leafs > MAX_ENT_LEAFS ) { memset( ent->leafnums, -1, sizeof( ent->leafnums )); ent->num_leafs = 0; // so we use headnode instead ent->headnode = headnode; } } // ignore non-solid bodies if( ent->v.solid == SOLID_NOT && ent->v.skin >= CONTENTS_EMPTY ) return; // find the first node that the ent's box crosses node = sv_areanodes; while( 1 ) { if( node->axis == -1 ) break; if( ent->v.absmin[node->axis] > node->dist ) node = node->children[0]; else if( ent->v.absmax[node->axis] < node->dist ) node = node->children[1]; else break; // crosses the node } // link it in if( ent->v.solid == SOLID_TRIGGER ) InsertLinkBefore( &ent->area, &node->trigger_edicts ); else if( ent->v.solid == SOLID_PORTAL ) InsertLinkBefore( &ent->area, &node->portal_edicts ); else InsertLinkBefore( &ent->area, &node->solid_edicts ); if( touch_triggers && !iTouchLinkSemaphore ) { iTouchLinkSemaphore = true; SV_TouchLinks( ent, sv_areanodes ); iTouchLinkSemaphore = false; } } /* =============================================================================== POINT TESTING IN HULLS =============================================================================== */ void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node ) { link_t *l, *next; edict_t *touch; hull_t *hull; vec3_t test, offset; model_t *mod; // get water edicts for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) { next = l->next; touch = EDICT_FROM_AREA( l ); if( touch->v.solid != SOLID_NOT ) // disabled ? continue; if( touch->v.groupinfo ) { if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, svs.groupmask )) continue; if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, svs.groupmask )) continue; } mod = SV_ModelHandle( touch->v.modelindex ); // only brushes can have special contents if( !mod || mod->type != mod_brush ) continue; if( !BoundsIntersect( origin, origin, touch->v.absmin, touch->v.absmax )) continue; // check water brushes accuracy hull = SV_HullForBsp( touch, vec3_origin, vec3_origin, offset ); // support for rotational water if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles )) { matrix4x4 matrix; Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f ); Matrix4x4_VectorITransform( matrix, origin, test ); } else { // offset the test point appropriately for this hull. VectorSubtract( origin, offset, test ); } // test hull for intersection with this model if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY ) continue; // compare contents ranking if( RankForContents( touch->v.skin ) > RankForContents( *pCont )) *pCont = touch->v.skin; // new content has more priority } // recurse down both sides if( node->axis == -1 ) return; if( origin[node->axis] > node->dist ) SV_WaterLinks( origin, pCont, node->children[0] ); if( origin[node->axis] < node->dist ) SV_WaterLinks( origin, pCont, node->children[1] ); } /* ============= SV_TruePointContents ============= */ int SV_TruePointContents( const vec3_t p ) { int cont; // sanity check if( !p ) return CONTENTS_NONE; // get base contents from world cont = PM_HullPointContents( &sv.worldmodel->hulls[0], 0, p ); // check all water entities SV_WaterLinks( p, &cont, sv_areanodes ); return cont; } /* ============= SV_PointContents ============= */ int SV_PointContents( const vec3_t p ) { int cont = SV_TruePointContents( p ); if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) cont = CONTENTS_WATER; return cont; } //=========================================================================== /* ============ SV_TestEntityPosition returns true if the entity is in solid currently ============ */ qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ) { qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; trace_t trace; if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) { // to avoid falling through tracktrain update client mins\maxs here if( FBitSet( ent->v.flags, FL_DUCKING )) SV_SetMinMaxSize( ent, svgame.pmove->player_mins[1], svgame.pmove->player_maxs[1], true ); else SV_SetMinMaxSize( ent, svgame.pmove->player_mins[0], svgame.pmove->player_maxs[0], true ); } trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip ); if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent )) { if( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker ) return trace.startsolid; return false; } return trace.startsolid; } /* =============================================================================== LINE TESTING IN HULLS =============================================================================== */ /* ================== SV_ClipMoveToEntity Handles selection or creation of a clipping hull, and offseting (and eventually rotation) of the end points ================== */ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) { hull_t *hull; model_t *model; vec3_t start_l, end_l; vec3_t offset, temp; int last_hitgroup; trace_t trace_hitbox; int i, j, hullcount; qboolean rotated, transform_bbox; matrix4x4 matrix; memset( trace, 0, sizeof( trace_t )); VectorCopy( end, trace->endpos ); trace->fraction = 1.0f; trace->allsolid = 1; model = SV_ModelHandle( ent->v.modelindex ); if( model && model->type == mod_studio ) { hull = SV_HullForStudioModel( ent, mins, maxs, offset, &hullcount ); } else { hull = SV_HullForEntity( ent, mins, maxs, offset ); hullcount = 1; } // rotate start and end into the models frame of reference if(( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) && !VectorIsNull( ent->v.angles )) rotated = true; else rotated = false; if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) { // keep untransformed bbox less than 45 degress or train on subtransit.bsp will stop working if(( check_angles( ent->v.angles[0] ) || check_angles( ent->v.angles[2] )) && !VectorIsNull( mins )) transform_bbox = true; else transform_bbox = false; } else transform_bbox = false; if( rotated ) { vec3_t out_mins, out_maxs; if( transform_bbox ) Matrix4x4_CreateFromEntity( matrix, ent->v.angles, ent->v.origin, 1.0f ); else Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f ); Matrix4x4_VectorITransform( matrix, start, start_l ); Matrix4x4_VectorITransform( matrix, end, end_l ); if( transform_bbox ) { World_TransformAABB( matrix, mins, maxs, out_mins, out_maxs ); VectorSubtract( hull->clip_mins, out_mins, offset ); // calc new local offset for( j = 0; j < 3; j++ ) { if( start_l[j] >= 0.0f ) start_l[j] -= offset[j]; else start_l[j] += offset[j]; if( end_l[j] >= 0.0f ) end_l[j] -= offset[j]; else end_l[j] += offset[j]; } } } else { VectorSubtract( start, offset, start_l ); VectorSubtract( end, offset, end_l ); } if( hullcount == 1 ) { PM_RecursiveHullCheck( hull, hull->firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)trace ); } else { last_hitgroup = 0; for( i = 0; i < hullcount; i++ ) { memset( &trace_hitbox, 0, sizeof( trace_t )); VectorCopy( end, trace_hitbox.endpos ); trace_hitbox.fraction = 1.0; trace_hitbox.allsolid = 1; PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox ); if( i == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace->fraction ) { if( trace->startsolid ) { *trace = trace_hitbox; trace->startsolid = true; } else *trace = trace_hitbox; last_hitgroup = i; } } trace->hitgroup = Mod_HitgroupForStudioHull( last_hitgroup ); } if( trace->fraction != 1.0f ) { // compute endpos (generic case) VectorLerp( start, trace->fraction, end, trace->endpos ); if( rotated ) { // transform plane VectorCopy( trace->plane.normal, temp ); Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); } else { trace->plane.dist = DotProduct( trace->endpos, trace->plane.normal ); } } if( trace->fraction < 1.0f || trace->startsolid ) trace->ent = ent; } /* ================== SV_PortalCSG a portal is flush with a world surface behind it. this causes problems. namely that we can't pass through the portal plane if the bsp behind it prevents out origin from getting through. so if the trace was clipped and ended infront of the portal, continue the trace to the edges of the portal cutout instead. ================== */ void SV_PortalCSG( edict_t *portal, const vec3_t trace_mins, const vec3_t trace_maxs, const vec3_t start, const vec3_t end, trace_t *trace ) { vec4_t planes[6]; //far, near, right, left, up, down int plane, k; vec3_t worldpos; float bestfrac; int hitplane; model_t *model; float portalradius; // only run this code if we impacted on the portal's parent. if( trace->fraction == 1.0f && !trace->startsolid ) return; // decide which clipping hull to use, based on the size model = SV_ModelHandle( portal->v.modelindex ); if( !model || model->type != mod_brush ) return; // make sure we use a sane valid position. if( trace->startsolid ) VectorCopy( start, worldpos ); else VectorCopy( trace->endpos, worldpos ); // determine the csg area. normals should be facing in AngleVectors( portal->v.angles, planes[1], planes[3], planes[5] ); VectorNegate(planes[1], planes[0]); VectorNegate(planes[3], planes[2]); VectorNegate(planes[5], planes[4]); portalradius = model->radius * 0.5f; planes[0][3] = DotProduct( portal->v.origin, planes[0] ) - (4.0f / 32.0f); planes[1][3] = DotProduct( portal->v.origin, planes[1] ) - (4.0f / 32.0f); //an epsilon beyond the portal planes[2][3] = DotProduct( portal->v.origin, planes[2] ) - portalradius; planes[3][3] = DotProduct( portal->v.origin, planes[3] ) - portalradius; planes[4][3] = DotProduct( portal->v.origin, planes[4] ) - portalradius; planes[5][3] = DotProduct( portal->v.origin, planes[5] ) - portalradius; // if we're actually inside the csg region for( plane = 0; plane < 6; plane++ ) { float d = DotProduct( worldpos, planes[plane] ); vec3_t nearest; for( k = 0; k < 3; k++ ) nearest[k] = (planes[plane][k]>=0) ? trace_maxs[k] : trace_mins[k]; // front plane gets further away with side if( !plane ) { planes[plane][3] -= DotProduct( nearest, planes[plane] ); } else if( plane > 1 ) { // side planes get nearer with size planes[plane][3] += 24; // DotProduct( nearest, planes[plane] ); } if( d - planes[plane][3] >= 0 ) continue; // endpos is inside else return; // end is already outside } // yup, we're inside, the trace shouldn't end where it actually did bestfrac = 1; hitplane = -1; for( plane = 0; plane < 6; plane++ ) { float ds = DotProduct( start, planes[plane] ) - planes[plane][3]; float de = DotProduct( end, planes[plane] ) - planes[plane][3]; float frac; if( ds >= 0 && de < 0 ) { frac = (ds) / (ds - de); if( frac < bestfrac ) { if( frac < 0 ) frac = 0; bestfrac = frac; hitplane = plane; } } } trace->startsolid = trace->allsolid = false; // if we cross the front of the portal, don't shorten the trace, // that will artificially clip us if( hitplane == 0 && trace->fraction > bestfrac ) return; // okay, elongate to clip to the portal hole properly. VectorLerp( start, bestfrac, end, trace->endpos ); trace->fraction = bestfrac; if( hitplane >= 0 ) { VectorCopy( planes[hitplane], trace->plane.normal ); trace->plane.dist = planes[hitplane][3]; if( hitplane == 1 ) trace->ent = portal; } } /* ================== SV_CustomClipMoveToEntity A part of physics engine implementation or custom physics implementation ================== */ void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) { // initialize custom trace memset( trace, 0, sizeof( trace_t )); VectorCopy( end, trace->endpos ); trace->allsolid = true; trace->fraction = 1.0f; if( svgame.physFuncs.ClipMoveToEntity != NULL ) { // do custom sweep test svgame.physFuncs.ClipMoveToEntity( ent, start, mins, maxs, end, trace ); } else { // function is missed, so we didn't hit anything trace->allsolid = false; } } /* ==================== SV_ClipToEntity generic clip function ==================== */ static qboolean SV_ClipToEntity( edict_t *touch, moveclip_t *clip ) { trace_t trace; model_t *mod; if( touch->v.groupinfo && SV_IsValidEdict( clip->passedict ) && clip->passedict->v.groupinfo != 0 ) { if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo )) return true; if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo )) return true; } if( touch == clip->passedict || touch->v.solid == SOLID_NOT ) return true; if( touch->v.solid == SOLID_TRIGGER ) Host_Error( "trigger in clipping list\n" ); // custom user filter if( svgame.dllFuncs2.pfnShouldCollide ) { if( !svgame.dllFuncs2.pfnShouldCollide( touch, clip->passedict )) return true; } // monsterclip filter (solid custom is a static or dynamic bodies) if( touch->v.solid == SOLID_BSP || touch->v.solid == SOLID_CUSTOM ) { // func_monsterclip works only with monsters that have same flag! if( FBitSet( touch->v.flags, FL_MONSTERCLIP ) && !clip->monsterclip ) return true; } else { // ignore all monsters but pushables if( clip->type == MOVE_NOMONSTERS && touch->v.movetype != MOVETYPE_PUSHSTEP ) return true; } mod = SV_ModelHandle( touch->v.modelindex ); if( mod && mod->type == mod_brush && clip->ignoretrans ) { // we ignore brushes with rendermode != kRenderNormal and without FL_WORLDBRUSH set if( touch->v.rendermode != kRenderNormal && !FBitSet( touch->v.flags, FL_WORLDBRUSH )) return true; } if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax )) return true; // aditional check to intersects clients with sphere if( touch->v.solid != SOLID_SLIDEBOX && !SV_CheckSphereIntersection( touch, clip->start, clip->end )) return true; // Xash3D extension if( SV_IsValidEdict( clip->passedict ) && clip->passedict->v.solid == SOLID_TRIGGER ) { // never collide items and player (because call "give" always stuck item in player // and total trace returns fail (old half-life bug) // items touch should be done in SV_TouchLinks not here if( FBitSet( touch->v.flags, FL_CLIENT|FL_FAKECLIENT )) return true; } // g-cont. make sure what size is really zero - check all the components if( SV_IsValidEdict( clip->passedict ) && !VectorIsNull( clip->passedict->v.size ) && VectorIsNull( touch->v.size )) return true; // points never interact // might intersect, so do an exact clip if( clip->trace.allsolid ) return false; if( SV_IsValidEdict( clip->passedict )) { if( touch->v.owner == clip->passedict ) return true; // don't clip against own missiles if( clip->passedict->v.owner == touch ) return true; // don't clip against owner } // make sure we don't hit the world if we're inside the portal if( touch->v.solid == SOLID_PORTAL ) SV_PortalCSG( touch, clip->mins, clip->maxs, clip->start, clip->end, &clip->trace ); if( touch->v.solid == SOLID_CUSTOM ) SV_CustomClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); else if( FBitSet( touch->v.flags, FL_MONSTER )) SV_ClipMoveToEntity( touch, clip->start, clip->mins2, clip->maxs2, clip->end, &trace ); else SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); clip->trace = World_CombineTraces( &clip->trace, &trace, touch ); return true; } /* ==================== SV_ClipToLinks Mins and maxs enclose the entire area swept by the move ==================== */ static void SV_ClipToLinks( areanode_t *node, moveclip_t *clip ) { link_t *l, *next; edict_t *touch; // touch linked edicts for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) { next = l->next; touch = EDICT_FROM_AREA( l ); if( !SV_ClipToEntity( touch, clip )) return; // trace.allsoild } // recurse down both sides if( node->axis == -1 ) return; if( clip->boxmaxs[node->axis] > node->dist ) SV_ClipToLinks( node->children[0], clip ); if( clip->boxmins[node->axis] < node->dist ) SV_ClipToLinks( node->children[1], clip ); } /* ==================== SV_ClipToPortals Mins and maxs enclose the entire area swept by the move ==================== */ static void SV_ClipToPortals( areanode_t *node, moveclip_t *clip ) { link_t *l, *next; edict_t *touch; // touch linked edicts for( l = node->portal_edicts.next; l != &node->portal_edicts; l = next ) { next = l->next; touch = EDICT_FROM_AREA( l ); if( !SV_ClipToEntity( touch, clip )) return; // trace.allsoild } // recurse down both sides if( node->axis == -1 ) return; if( clip->boxmaxs[node->axis] > node->dist ) SV_ClipToPortals( node->children[0], clip ); if( clip->boxmins[node->axis] < node->dist ) SV_ClipToPortals( node->children[1], clip ); } /* ==================== SV_ClipToWorldBrush Mins and maxs enclose the entire area swept by the move ==================== */ void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip ) { link_t *l, *next; edict_t *touch; trace_t trace; for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) { next = l->next; touch = EDICT_FROM_AREA( l ); if( touch->v.solid != SOLID_BSP || touch == clip->passedict || !( touch->v.flags & FL_WORLDBRUSH )) continue; if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax )) continue; if( clip->trace.allsolid ) return; SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); clip->trace = World_CombineTraces( &clip->trace, &trace, touch ); } // recurse down both sides if( node->axis == -1 ) return; if( clip->boxmaxs[node->axis] > node->dist ) SV_ClipToWorldBrush( node->children[0], clip ); if( clip->boxmins[node->axis] < node->dist ) SV_ClipToWorldBrush( node->children[1], clip ); } /* ================== SV_Move ================== */ trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip ) { moveclip_t clip; vec3_t trace_endpos; float trace_fraction; memset( &clip, 0, sizeof( moveclip_t )); SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace ); if( clip.trace.fraction != 0.0f ) { VectorCopy( clip.trace.endpos, trace_endpos ); trace_fraction = clip.trace.fraction; clip.trace.fraction = 1.0f; clip.start = start; clip.end = trace_endpos; clip.type = (type & 0xFF); clip.ignoretrans = type >> 8; clip.monsterclip = false; clip.passedict = (e) ? e : EDICT_NUM( 0 ); clip.mins = mins; clip.maxs = maxs; if( monsterclip && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) clip.monsterclip = true; if( clip.type == MOVE_MISSILE ) { VectorSet( clip.mins2, -15.0f, -15.0f, -15.0f ); VectorSet( clip.maxs2, 15.0f, 15.0f, 15.0f ); } else { VectorCopy( mins, clip.mins2 ); VectorCopy( maxs, clip.maxs2 ); } World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs ); SV_ClipToLinks( sv_areanodes, &clip ); SV_ClipToPortals( sv_areanodes, &clip ); clip.trace.fraction *= trace_fraction; svgame.globals->trace_ent = clip.trace.ent; } SV_CopyTraceToGlobal( &clip.trace ); return clip.trace; } trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) { return SV_Move( start, mins, maxs, end, type, e, false ); } /* ================== SV_MoveNoEnts ================== */ trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) { moveclip_t clip; vec3_t trace_endpos; float trace_fraction; memset( &clip, 0, sizeof( moveclip_t )); SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace ); if( clip.trace.fraction != 0.0f ) { VectorCopy( clip.trace.endpos, trace_endpos ); trace_fraction = clip.trace.fraction; clip.trace.fraction = 1.0f; clip.start = start; clip.end = trace_endpos; clip.type = (type & 0xFF); clip.ignoretrans = type >> 8; clip.monsterclip = false; clip.passedict = (e) ? e : EDICT_NUM( 0 ); clip.mins = mins; clip.maxs = maxs; VectorCopy( mins, clip.mins2 ); VectorCopy( maxs, clip.maxs2 ); World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs ); SV_ClipToWorldBrush( sv_areanodes, &clip ); SV_ClipToPortals( sv_areanodes, &clip ); clip.trace.fraction *= trace_fraction; svgame.globals->trace_ent = clip.trace.ent; } SV_CopyTraceToGlobal( &clip.trace ); return clip.trace; } /* ================== SV_TraceSurface find the face where the traceline hit assume pTextureEntity is valid ================== */ msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ) { matrix4x4 matrix; model_t *bmodel; hull_t *hull; vec3_t start_l, end_l; vec3_t offset; bmodel = SV_ModelHandle( ent->v.modelindex ); if( !bmodel || bmodel->type != mod_brush ) return NULL; hull = SV_HullForBsp( ent, vec3_origin, vec3_origin, offset ); VectorSubtract( start, offset, start_l ); VectorSubtract( end, offset, end_l ); // rotate start and end into the models frame of reference if( !VectorIsNull( ent->v.angles )) { Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f ); Matrix4x4_VectorITransform( matrix, start, start_l ); Matrix4x4_VectorITransform( matrix, end, end_l ); } return PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l ); } /* ================== SV_TraceTexture find the face where the traceline hit assume pTextureEntity is valid ================== */ const char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end ) { msurface_t *surf = SV_TraceSurface( ent, start, end ); if( !surf || !surf->texinfo || !surf->texinfo->texture ) return NULL; return surf->texinfo->texture->name; } /* ================== SV_MoveToss ================== */ trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ) { float gravity; vec3_t move, end; vec3_t original_origin; vec3_t original_velocity; vec3_t original_angles; vec3_t original_avelocity; trace_t trace; int i; VectorCopy( tossent->v.origin, original_origin ); VectorCopy( tossent->v.velocity, original_velocity ); VectorCopy( tossent->v.angles, original_angles ); VectorCopy( tossent->v.avelocity, original_avelocity ); gravity = tossent->v.gravity * svgame.movevars.gravity * 0.05f; for( i = 0; i < 200; i++ ) { SV_CheckVelocity( tossent ); tossent->v.velocity[2] -= gravity; VectorMA( tossent->v.angles, 0.05f, tossent->v.avelocity, tossent->v.angles ); VectorScale( tossent->v.velocity, 0.05f, move ); VectorAdd( tossent->v.origin, move, end ); trace = SV_Move( tossent->v.origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent, false ); VectorCopy( trace.endpos, tossent->v.origin ); if( trace.fraction < 1.0f ) break; } VectorCopy( original_origin, tossent->v.origin ); VectorCopy( original_velocity, tossent->v.velocity ); VectorCopy( original_angles, tossent->v.angles ); VectorCopy( original_avelocity, tossent->v.avelocity ); return trace; } /* =============================================================================== LIGHTING INFO =============================================================================== */ static vec3_t sv_pointColor; /* ================= SV_RecursiveLightPoint ================= */ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end ) { float front, back, scale, frac; int i, map, side, size; float ds, dt, s, t; int sample_size; msurface_t *surf; mtexinfo_t *tex; mextrasurf_t *info; color24 *lm; vec3_t mid; // didn't hit anything if( !node || node->contents < 0 ) return false; // calculate mid point front = PlaneDiff( start, node->plane ); back = PlaneDiff( end, node->plane ); side = front < 0.0f; if(( back < 0.0f ) == side ) return SV_RecursiveLightPoint( model, node->children[side], start, end ); frac = front / ( front - back ); VectorLerp( start, frac, end, mid ); // co down front side if( SV_RecursiveLightPoint( model, node->children[side], start, mid )) return true; // hit something if(( back < 0.0f ) == side ) return false;// didn't hit anything // check for impact on this node surf = model->surfaces + node->firstsurface; for( i = 0; i < node->numsurfaces; i++, surf++ ) { int smax, tmax; tex = surf->texinfo; info = surf->info; if( FBitSet( surf->flags, SURF_DRAWTILED )) continue; // no lightmaps s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3]; t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3]; if( s < info->lightmapmins[0] || t < info->lightmapmins[1] ) continue; ds = s - info->lightmapmins[0]; dt = t - info->lightmapmins[1]; if ( ds > info->lightextents[0] || dt > info->lightextents[1] ) continue; if( !surf->samples ) return true; sample_size = Mod_SampleSizeForFace( surf ); smax = (info->lightextents[0] / sample_size) + 1; tmax = (info->lightextents[1] / sample_size) + 1; ds /= sample_size; dt /= sample_size; VectorClear( sv_pointColor ); lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); size = smax * tmax; for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) { scale = sv.lightstyles[surf->styles[map]].value; sv_pointColor[0] += lm->r * scale; sv_pointColor[1] += lm->g * scale; sv_pointColor[2] += lm->b * scale; lm += size; // skip to next lightmap } return true; } // go down back side return SV_RecursiveLightPoint( model, node->children[!side], mid, end ); } void SV_RunLightStyles( void ) { int i, ofs; lightstyle_t *ls; float scale; scale = sv_lighting_modulate->value; // run lightstyles animation for( i = 0, ls = sv.lightstyles; i < MAX_LIGHTSTYLES; i++, ls++ ) { ls->time += sv.frametime; ofs = (ls->time * 10); if( ls->length == 0 ) ls->value = scale; // disable this light else if( ls->length == 1 ) ls->value = ( ls->map[0] / 12.0f ) * scale; else ls->value = ( ls->map[ofs % ls->length] / 12.0f ) * scale; } } /* ================== SV_SetLightStyle needs to get correct working SV_LightPoint ================== */ void SV_SetLightStyle( int style, const char* s, float f ) { int j, k; Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern )); sv.lightstyles[style].time = f; j = Q_strlen( s ); sv.lightstyles[style].length = j; for( k = 0; k < j; k++ ) sv.lightstyles[style].map[k] = (float)(s[k] - 'a'); if( sv.state != ss_active ) return; // tell the clients about changed lightstyle MSG_BeginServerCmd( &sv.reliable_datagram, svc_lightstyle ); MSG_WriteByte( &sv.reliable_datagram, style ); MSG_WriteString( &sv.reliable_datagram, sv.lightstyles[style].pattern ); MSG_WriteFloat( &sv.reliable_datagram, sv.lightstyles[style].time ); } /* ================== SV_GetLightStyle needs to get correct working SV_LightPoint ================== */ const char *SV_GetLightStyle( int style ) { if( style < 0 ) style = 0; if( style >= MAX_LIGHTSTYLES ) Host_Error( "SV_GetLightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); return sv.lightstyles[style].pattern; } /* ================== SV_LightForEntity grab the ambient lighting color for current point ================== */ int SV_LightForEntity( edict_t *pEdict ) { vec3_t start, end; if( FBitSet( pEdict->v.effects, EF_FULLBRIGHT ) || !sv.worldmodel->lightdata ) return 255; // player has more precision light level that come from client-side if( FBitSet( pEdict->v.flags, FL_CLIENT )) return pEdict->v.light_level; VectorCopy( pEdict->v.origin, start ); VectorCopy( pEdict->v.origin, end ); if( FBitSet( pEdict->v.effects, EF_INVLIGHT )) end[2] = start[2] + world.size[2]; else end[2] = start[2] - world.size[2]; VectorSet( sv_pointColor, 1.0f, 1.0f, 1.0f ); SV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end ); return VectorAvg( sv_pointColor ); }