366 lines
11 KiB
C
366 lines
11 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2008 ©
|
|
// cl_physics.c - client physic and prediction
|
|
//=======================================================================
|
|
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include "matrix_lib.h"
|
|
#include "const.h"
|
|
|
|
/*
|
|
===================
|
|
CL_CheckPredictionError
|
|
===================
|
|
*/
|
|
void CL_CheckPredictionError( void )
|
|
{
|
|
int frame;
|
|
int delta[3];
|
|
int len;
|
|
|
|
if( !cl_predict->integer ) return;
|
|
|
|
// calculate the last usercmd_t we sent that the server has processed
|
|
frame = cls.netchan.incoming_acknowledged;
|
|
frame &= (CMD_BACKUP-1);
|
|
|
|
// compare what the server returned with what we had predicted it to be
|
|
VectorSubtract (cl.frame.ps.origin, cl.predicted_origins[frame], delta);
|
|
|
|
// save the prediction error for interpolation
|
|
len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]);
|
|
if( len > 640 ) // 80 world units
|
|
{ // a teleport or something
|
|
VectorClear (cl.prediction_error);
|
|
}
|
|
else
|
|
{
|
|
if (cl_showmiss->value && (delta[0] || delta[1] || delta[2]))
|
|
Msg ("prediction miss on %i: %i\n", cl.frame.serverframe, delta[0] + delta[1] + delta[2]);
|
|
|
|
VectorCopy (cl.frame.ps.origin, cl.predicted_origins[frame]);
|
|
|
|
// save for error itnerpolation
|
|
VectorCopy( delta, cl.prediction_error );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
LINE TESTING IN HULLS
|
|
|
|
===============================================================================
|
|
*/
|
|
int CL_ContentsMask( const edict_t *passedict )
|
|
{
|
|
if( passedict )
|
|
{
|
|
if( passedict->v.flags & FL_MONSTER )
|
|
return MASK_MONSTERSOLID;
|
|
else if( passedict->v.flags & FL_CLIENT )
|
|
return MASK_PLAYERSOLID;
|
|
else if( passedict->v.solid == SOLID_TRIGGER )
|
|
return CONTENTS_SOLID|CONTENTS_BODY;
|
|
return MASK_SOLID;
|
|
}
|
|
return MASK_SOLID;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_Trace
|
|
==================
|
|
*/
|
|
trace_t CL_Trace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, edict_t *passedict, int contentsmask )
|
|
{
|
|
vec3_t hullmins, hullmaxs;
|
|
int i, bodycontents;
|
|
bool pointtrace;
|
|
edict_t *traceowner, *touch;
|
|
trace_t trace;
|
|
vec3_t clipboxmins, clipboxmaxs; // bounding box of entire move area
|
|
vec3_t clipmins, clipmaxs; // size of the moving object
|
|
vec3_t clipmins2, clipmaxs2; // size when clipping against monsters
|
|
vec3_t clipstart, clipend; // start and end origin of move
|
|
trace_t cliptrace; // trace results
|
|
matrix4x4 matrix, imatrix; // matrices to transform into/out of other entity's space
|
|
cmodel_t *model; // model of other entity
|
|
int numtouchedicts = 0; // list of entities to test for collisions
|
|
edict_t *touchedicts[MAX_EDICTS];
|
|
|
|
VectorCopy( start, clipstart );
|
|
VectorCopy( end, clipend );
|
|
VectorCopy( mins, clipmins );
|
|
VectorCopy( maxs, clipmaxs );
|
|
VectorCopy( mins, clipmins2 );
|
|
VectorCopy( maxs, clipmaxs2 );
|
|
|
|
// clip to world
|
|
pe->ClipToWorld( &cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, contentsmask );
|
|
cliptrace.startstuck = cliptrace.startsolid;
|
|
if( cliptrace.startsolid || cliptrace.fraction < 1 )
|
|
cliptrace.ent = (edict_t *)EDICT_NUM( 0 );
|
|
if( type == MOVE_WORLDONLY ) return cliptrace;
|
|
|
|
if( type == MOVE_MISSILE )
|
|
{
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
clipmins2[i] -= 15;
|
|
clipmaxs2[i] += 15;
|
|
}
|
|
}
|
|
|
|
// get adjusted box for bmodel collisions if the world is q1bsp or hlbsp
|
|
VectorCopy( clipmins, hullmins );
|
|
VectorCopy( clipmaxs, hullmaxs );
|
|
|
|
// create the bounding box of the entire move
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1;
|
|
clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1;
|
|
}
|
|
|
|
// if the passedict is world, make it NULL (to avoid two checks each time)
|
|
if( passedict == EDICT_NUM( 0 )) passedict = NULL;
|
|
// figure out whether this is a point trace for comparisons
|
|
pointtrace = VectorCompare( clipmins, clipmaxs );
|
|
// precalculate passedict's owner edict pointer for comparisons
|
|
if( passedict && passedict->v.owner )
|
|
traceowner = passedict->v.owner;
|
|
else traceowner = NULL;
|
|
|
|
// clip to entities
|
|
// because this uses World_EntitiestoBox, we know all entity boxes overlap
|
|
// the clip region, so we can skip culling checks in the loop below
|
|
numtouchedicts = 0;// FIXME: CL_AreaEdicts( clipboxmins, clipboxmaxs, touchedicts, host.max_edicts );
|
|
if( numtouchedicts > host.max_edicts )
|
|
{
|
|
// this never happens
|
|
MsgDev( D_WARN, "CL_AreaEdicts returned %i edicts, max was %i\n", numtouchedicts, host.max_edicts );
|
|
numtouchedicts = host.max_edicts;
|
|
}
|
|
for( i = 0; i < numtouchedicts; i++ )
|
|
{
|
|
touch = touchedicts[i];
|
|
if( touch->v.solid < SOLID_BBOX ) continue;
|
|
if( type == MOVE_NOMONSTERS && touch->v.solid != SOLID_BSP )
|
|
continue;
|
|
|
|
if( passedict )
|
|
{
|
|
// don't clip against self
|
|
if( passedict == touch ) continue;
|
|
// don't clip owned entities against owner
|
|
if( traceowner == touch ) continue;
|
|
// don't clip owner against owned entities
|
|
if( passedict == touch->v.owner ) continue;
|
|
// don't clip points against points (they can't collide)
|
|
if( pointtrace && VectorCompare( touch->v.mins, touch->v.maxs ) && (type != MOVE_MISSILE || !(touch->v.flags & FL_MONSTER)))
|
|
continue;
|
|
}
|
|
|
|
bodycontents = CONTENTS_BODY;
|
|
|
|
// might interact, so do an exact clip
|
|
model = NULL;
|
|
if( touch->v.solid == SOLID_BSP || type == MOVE_HITMODEL )
|
|
{
|
|
uint modelindex = (uint)touch->v.modelindex;
|
|
// if the modelindex is 0, it shouldn't be SOLID_BSP!
|
|
if( modelindex > 0 && modelindex < MAX_MODELS )
|
|
model = cl.models[touch->v.modelindex];
|
|
}
|
|
if( model ) Matrix4x4_CreateFromEntity( matrix, touch->v.origin[0], touch->v.origin[1], touch->v.origin[2], touch->v.angles[0], touch->v.angles[1], touch->v.angles[2], 1 );
|
|
else Matrix4x4_CreateTranslate( matrix, touch->v.origin[0], touch->v.origin[1], touch->v.origin[2] );
|
|
Matrix4x4_Invert_Simple( imatrix, matrix );
|
|
if( touch->v.flags & FL_MONSTER )
|
|
pe->ClipToGenericEntity(&trace, model, touch->v.mins, touch->v.maxs, bodycontents, matrix, imatrix, clipstart, clipmins2, clipmaxs2, clipend, contentsmask );
|
|
else pe->ClipToGenericEntity(&trace, model, touch->v.mins, touch->v.maxs, bodycontents, matrix, imatrix, clipstart, clipmins, clipmaxs, clipend, contentsmask );
|
|
pe->CombineTraces( &cliptrace, &trace, (edict_t *)touch, touch->v.solid == SOLID_BSP );
|
|
}
|
|
return cliptrace;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_CheckVelocity
|
|
================
|
|
*/
|
|
void CL_CheckVelocity( edict_t *ent )
|
|
{
|
|
int i;
|
|
float wishspeed;
|
|
|
|
// bound velocity
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
if(IS_NAN(ent->v.velocity[i]))
|
|
{
|
|
MsgDev( D_INFO, "Got a NaN velocity on entity #%i (%s)\n", NUM_FOR_EDICT( ent ), STRING( ent->v.classname ));
|
|
ent->v.velocity[i] = 0;
|
|
}
|
|
if (IS_NAN(ent->v.origin[i]))
|
|
{
|
|
MsgDev( D_INFO, "Got a NaN origin on entity #%i (%s)\n", NUM_FOR_EDICT( ent ), STRING( ent->v.classname ));
|
|
ent->v.origin[i] = 0;
|
|
}
|
|
}
|
|
|
|
// LordHavoc: max velocity fix, inspired by Maddes's source fixes, but this is faster
|
|
wishspeed = DotProduct( ent->v.velocity, ent->v.velocity );
|
|
if( wishspeed > ( clgame.maxVelocity * clgame.maxVelocity ))
|
|
{
|
|
wishspeed = clgame.maxVelocity / com.sqrt( wishspeed );
|
|
ent->v.velocity[0] *= wishspeed;
|
|
ent->v.velocity[1] *= wishspeed;
|
|
ent->v.velocity[2] *= wishspeed;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_ClipMoveToEntities
|
|
|
|
====================
|
|
*/
|
|
void CL_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr )
|
|
{
|
|
/*
|
|
for( i = 0; i < cl.frame.num_entities; i++ )
|
|
{
|
|
num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
|
|
ent = &cl_parse_entities[num];
|
|
|
|
if(!ent->solid) continue;
|
|
if(ent->number == cl.playernum + 1) continue;
|
|
|
|
if( ent->solid == SOLID_BMODEL )
|
|
{
|
|
// special value for bmodel
|
|
cmodel = cl.model_clip[ent->modelindex];
|
|
if(!cmodel) continue;
|
|
angles = ent->angles;
|
|
}
|
|
else
|
|
{ // encoded bbox
|
|
x = (ent->solid & 255);
|
|
zd = ((ent->solid>>8) & 255);
|
|
zu = ((ent->solid>>16) & 255) - 32;
|
|
|
|
bmins[0] = bmins[1] = -x;
|
|
bmaxs[0] = bmaxs[1] = x;
|
|
bmins[2] = -zd;
|
|
bmaxs[2] = zu;
|
|
angles = ent->angles;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
CL_PMTrace
|
|
================
|
|
*/
|
|
void CL_PMTrace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr )
|
|
{
|
|
*tr = CL_Trace( start, mins, maxs, end, MOVE_NORMAL, NULL, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY );
|
|
}
|
|
|
|
int CL_PointContents( const vec3_t point )
|
|
{
|
|
// get world supercontents at this point
|
|
if( cl.worldmodel && cl.worldmodel->PointContents )
|
|
return cl.worldmodel->PointContents( point, cl.worldmodel );
|
|
return 0;
|
|
}
|
|
|
|
bool CL_AmbientLevel( const vec3_t point, float *volumes )
|
|
{
|
|
// get world supercontents at this point
|
|
if( cl.worldmodel && cl.worldmodel->AmbientLevel )
|
|
return cl.worldmodel->AmbientLevel( point, volumes, cl.worldmodel );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_PredictMovement
|
|
|
|
Sets cl.predicted_origin and cl.predicted_angles
|
|
=================
|
|
*/
|
|
void CL_PredictMovement (void)
|
|
{
|
|
int ack, current;
|
|
int frame;
|
|
int oldframe;
|
|
entvars_t pmove;
|
|
usercmd_t *cmd;
|
|
int i;
|
|
float step;
|
|
float oldz;
|
|
|
|
if( cls.state != ca_active ) return;
|
|
if( cl_paused->value ) return;
|
|
|
|
pmove = EDICT_NUM( cl.playernum + 1 )->v;
|
|
|
|
if( !cl_predict->value || cl.frame.ps.ed_flags & ESF_NO_PREDICTION )
|
|
{
|
|
// just set angles
|
|
for( i = 0; i < 3; i++ )
|
|
cl.predicted_angles[i] = cl.viewangles[i] + SHORT2ANGLE( cl.frame.ps.delta_angles[i] );
|
|
return;
|
|
}
|
|
|
|
ack = cls.netchan.incoming_acknowledged;
|
|
current = cls.netchan.outgoing_sequence;
|
|
|
|
// if we are too far out of date, just freeze
|
|
if( current - ack >= CMD_BACKUP )
|
|
{
|
|
if( cl_showmiss->value )
|
|
Msg( "exceeded CMD_BACKUP\n" );
|
|
return;
|
|
}
|
|
|
|
// SCR_DebugGraph (current - ack - 1, COLOR_0);
|
|
|
|
frame = 0;
|
|
|
|
// run frames
|
|
while( ++ack < current )
|
|
{
|
|
frame = ack & (CMD_BACKUP-1);
|
|
cmd = &cl.cmds[frame];
|
|
|
|
pe->PlayerMove( &pmove, cmd, NULL, true );
|
|
|
|
// save for debug checking
|
|
VectorCopy( pmove.origin, cl.predicted_origins[frame] );
|
|
}
|
|
|
|
oldframe = (ack-2) & (CMD_BACKUP-1);
|
|
|
|
if( pmove.flags & FL_ONGROUND )
|
|
{
|
|
oldz = cl.predicted_origins[oldframe][2];
|
|
step = pmove.origin[2] - oldz;
|
|
if( step > 63 && step < 160 )
|
|
{
|
|
cl.predicted_step = step;
|
|
cl.predicted_step_time = cls.realtime - cls.frametime * 500;
|
|
}
|
|
}
|
|
|
|
// copy results out for rendering
|
|
VectorCopy( pmove.origin, cl.predicted_origin );
|
|
VectorCopy( pmove.viewangles, cl.predicted_angles );
|
|
}
|