dhewm3-sdk/game/physics/Physics_Player.cpp
Daniel Gibson 2bd963b986 Fix player's clipModel->axis when loading savegame, fixes #328
idClipModel::axis is an idMat3 rotation matrix.
Usually it's an identity matrix, but if the player is pushed around by
an idPush entity it's modified and apparently can (wrongly) remain
modified, possibly when saving while idPush is active.
This seems to happen sometimes on the crashing elevator in game/delta1.
The fix/workaround is to reset it to mat3_identity when loading a
savegame.
2021-02-22 05:22:36 +01:00

2058 lines
53 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code 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.
Doom 3 Source Code 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "sys/platform.h"
#include "gamesys/SysCvar.h"
#include "Entity.h"
#include "physics/Physics_Player.h"
CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player )
END_CLASS
// movement parameters
const float PM_STOPSPEED = 100.0f;
const float PM_SWIMSCALE = 0.5f;
const float PM_LADDERSPEED = 100.0f;
const float PM_STEPSCALE = 1.0f;
const float PM_ACCELERATE = 10.0f;
const float PM_AIRACCELERATE = 1.0f;
const float PM_WATERACCELERATE = 4.0f;
const float PM_FLYACCELERATE = 8.0f;
const float PM_FRICTION = 6.0f;
const float PM_AIRFRICTION = 0.0f;
const float PM_WATERFRICTION = 1.0f;
const float PM_FLYFRICTION = 3.0f;
const float PM_NOCLIPFRICTION = 12.0f;
const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes
const float OVERCLIP = 1.001f;
// movementFlags
const int PMF_DUCKED = 1; // set when ducking
const int PMF_JUMPED = 2; // set when the player jumped this frame
const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame
const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame
const int PMF_JUMP_HELD = 16; // set when jump button is held down
const int PMF_TIME_LAND = 32; // movementTime is time before rejump
const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time
const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump
const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK);
int c_pmove = 0;
/*
============
idPhysics_Player::CmdScale
Returns the scale factor to apply to cmd movements
This allows the clients to use axial -127 to 127 values for all directions
without getting a sqrt(2) distortion in speed.
============
*/
float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const {
int max;
float total;
float scale;
int forwardmove;
int rightmove;
int upmove;
forwardmove = cmd.forwardmove;
rightmove = cmd.rightmove;
// since the crouch key doubles as downward movement, ignore downward movement when we're on the ground
// otherwise crouch speed will be lower than specified
if ( walking ) {
upmove = 0;
} else {
upmove = cmd.upmove;
}
max = abs( forwardmove );
if ( abs( rightmove ) > max ) {
max = abs( rightmove );
}
if ( abs( upmove ) > max ) {
max = abs( upmove );
}
if ( !max ) {
return 0.0f;
}
total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove );
scale = (float) playerSpeed * max / ( 127.0f * total );
return scale;
}
/*
==============
idPhysics_Player::Accelerate
Handles user intended acceleration
==============
*/
void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) {
#if 1
// q2 style
float addspeed, accelspeed, currentspeed;
currentspeed = current.velocity * wishdir;
addspeed = wishspeed - currentspeed;
if (addspeed <= 0) {
return;
}
accelspeed = accel * frametime * wishspeed;
if (accelspeed > addspeed) {
accelspeed = addspeed;
}
current.velocity += accelspeed * wishdir;
#else
// proper way (avoids strafe jump maxspeed bug), but feels bad
idVec3 wishVelocity;
idVec3 pushDir;
float pushLen;
float canPush;
wishVelocity = wishdir * wishspeed;
pushDir = wishVelocity - current.velocity;
pushLen = pushDir.Normalize();
canPush = accel * frametime * wishspeed;
if (canPush > pushLen) {
canPush = pushLen;
}
current.velocity += canPush * pushDir;
#endif
}
/*
==================
idPhysics_Player::SlideMove
Returns true if the velocity was clipped in some way
==================
*/
#define MAX_CLIP_PLANES 5
bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) {
int i, j, k, pushFlags;
int bumpcount, numbumps, numplanes;
float d, time_left, into, totalMass;
idVec3 dir, planes[MAX_CLIP_PLANES];
idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity;
trace_t trace, stepTrace, downTrace;
bool nearGround, stepped, pushed;
numbumps = 4;
primal_velocity = current.velocity;
if ( gravity ) {
endVelocity = current.velocity + gravityVector * frametime;
current.velocity = ( current.velocity + endVelocity ) * 0.5f;
primal_velocity = endVelocity;
if ( groundPlane ) {
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
}
}
else {
endVelocity = current.velocity;
}
time_left = frametime;
// never turn against the ground plane
if ( groundPlane ) {
numplanes = 1;
planes[0] = groundTrace.c.normal;
} else {
numplanes = 0;
}
// never turn against original velocity
planes[numplanes] = current.velocity;
planes[numplanes].Normalize();
numplanes++;
for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) {
// calculate position we are trying to move to
end = current.origin + time_left * current.velocity;
// see if we can make it there
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
time_left -= time_left * trace.fraction;
current.origin = trace.endpos;
// if moved the entire distance
if ( trace.fraction >= 1.0f ) {
break;
}
stepped = pushed = false;
// if we are allowed to step up
if ( stepUp ) {
nearGround = groundPlane | ladder;
if ( !nearGround ) {
// trace down to see if the player is near the ground
// step checking when near the ground allows the player to move up stairs smoothly while jumping
stepEnd = current.origin + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL );
}
// may only step up if near the ground or on a ladder
if ( nearGround ) {
// step up
stepEnd = current.origin - maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
// trace along velocity
stepEnd = downTrace.endpos + time_left * current.velocity;
gameLocal.clip.Translation( stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
// step down
stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) {
// if moved the entire distance
if ( stepTrace.fraction >= 1.0f ) {
time_left = 0;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_UP;
current.velocity *= PM_STEPSCALE;
break;
}
// if the move is further when stepping up
if ( stepTrace.fraction > trace.fraction ) {
time_left -= time_left * stepTrace.fraction;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_UP;
current.velocity *= PM_STEPSCALE;
trace = stepTrace;
stepped = true;
}
}
}
}
// if we can push other entities and not blocked by the world
if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) {
clipModel->SetPosition( current.origin, clipModel->GetAxis() );
// clip movement, only push idMoveables, don't push entities the player is standing on
// apply impact to pushed objects
pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE;
// clip & push
totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin );
if ( totalMass > 0.0f ) {
// decrease velocity based on the total mass of the objects being pushed ?
current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f );
pushed = true;
}
current.origin = trace.endpos;
time_left -= time_left * trace.fraction;
// if moved the entire distance
if ( trace.fraction >= 1.0f ) {
break;
}
}
if ( !stepped ) {
// let the entity know about the collision
self->Collide( trace, current.velocity );
}
if ( numplanes >= MAX_CLIP_PLANES ) {
// MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris
// where it may hit the max clip planes
current.velocity = vec3_origin;
return true;
}
//
// if this is the same plane we hit before, nudge velocity
// out along it, which fixes some epsilon issues with
// non-axial planes
//
for ( i = 0; i < numplanes; i++ ) {
if ( ( trace.c.normal * planes[i] ) > 0.999f ) {
current.velocity += trace.c.normal;
break;
}
}
if ( i < numplanes ) {
continue;
}
planes[numplanes] = trace.c.normal;
numplanes++;
//
// modify velocity so it parallels all of the clip planes
//
// find a plane that it enters
for ( i = 0; i < numplanes; i++ ) {
into = current.velocity * planes[i];
if ( into >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// slide along the plane
clipVelocity = current.velocity;
clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
// slide along the plane
endClipVelocity = endVelocity;
endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
// see if there is a second plane that the new move enters
for ( j = 0; j < numplanes; j++ ) {
if ( j == i ) {
continue;
}
if ( ( clipVelocity * planes[j] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// try clipping the move to the plane
clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
// see if it goes back into the first clip plane
if ( ( clipVelocity * planes[i] ) >= 0 ) {
continue;
}
// slide the original velocity along the crease
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * current.velocity;
clipVelocity = d * dir;
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * endVelocity;
endClipVelocity = d * dir;
// see if there is a third plane the the new move enters
for ( k = 0; k < numplanes; k++ ) {
if ( k == i || k == j ) {
continue;
}
if ( ( clipVelocity * planes[k] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
current.velocity = vec3_origin;
return true;
}
}
// if we have fixed all interactions, try another move
current.velocity = clipVelocity;
endVelocity = endClipVelocity;
break;
}
}
// step down
if ( stepDown && groundPlane ) {
stepEnd = current.origin + gravityNormal * maxStepHeight;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) {
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_DOWN;
current.velocity *= PM_STEPSCALE;
}
}
if ( gravity ) {
current.velocity = endVelocity;
}
// come to a dead stop when the velocity orthogonal to the gravity flipped
clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal;
endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal;
if ( clipVelocity * endClipVelocity < 0.0f ) {
current.velocity = gravityNormal * current.velocity * gravityNormal;
}
return (bool)( bumpcount == 0 );
}
/*
==================
idPhysics_Player::Friction
Handles both ground friction and water friction
==================
*/
void idPhysics_Player::Friction( void ) {
idVec3 vel;
float speed, newspeed, control;
float drop;
vel = current.velocity;
if ( walking ) {
// ignore slope movement, remove all velocity in gravity direction
vel += (vel * gravityNormal) * gravityNormal;
}
speed = vel.Length();
if ( speed < 1.0f ) {
// remove all movement orthogonal to gravity, allows for sinking underwater
if ( fabs( current.velocity * gravityNormal ) < 1e-5f ) {
current.velocity.Zero();
} else {
current.velocity = (current.velocity * gravityNormal) * gravityNormal;
}
// FIXME: still have z friction underwater?
return;
}
drop = 0;
// spectator friction
if ( current.movementType == PM_SPECTATOR ) {
drop += speed * PM_FLYFRICTION * frametime;
}
// apply ground friction
else if ( walking && waterLevel <= WATERLEVEL_FEET ) {
// no friction on slick surfaces
if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) {
// if getting knocked back, no friction
if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) {
control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed;
drop += control * PM_FRICTION * frametime;
}
}
}
// apply water friction even if just wading
else if ( waterLevel ) {
drop += speed * PM_WATERFRICTION * waterLevel * frametime;
}
// apply air friction
else {
drop += speed * PM_AIRFRICTION * frametime;
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
current.velocity *= ( newspeed / speed );
}
/*
===================
idPhysics_Player::WaterJumpMove
Flying out of the water
===================
*/
void idPhysics_Player::WaterJumpMove( void ) {
// waterjump has no control, but falls
idPhysics_Player::SlideMove( true, true, false, false );
// add gravity
current.velocity += gravityNormal * frametime;
// if falling down
if ( current.velocity * gravityNormal > 0.0f ) {
// cancel as soon as we are falling down again
current.movementFlags &= ~PMF_ALL_TIMES;
current.movementTime = 0;
}
}
/*
===================
idPhysics_Player::WaterMove
===================
*/
void idPhysics_Player::WaterMove( void ) {
idVec3 wishvel;
float wishspeed;
idVec3 wishdir;
float scale;
float vel;
if ( idPhysics_Player::CheckWaterJump() ) {
idPhysics_Player::WaterJumpMove();
return;
}
idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
// user intentions
if ( !scale ) {
wishvel = gravityNormal * 60; // sink towards bottom
} else {
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishvel -= scale * gravityNormal * command.upmove;
}
wishdir = wishvel;
wishspeed = wishdir.Normalize();
if ( wishspeed > playerSpeed * PM_SWIMSCALE ) {
wishspeed = playerSpeed * PM_SWIMSCALE;
}
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE );
// make sure we can go up slopes easily under water
if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) {
vel = current.velocity.Length();
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
current.velocity.Normalize();
current.velocity *= vel;
}
idPhysics_Player::SlideMove( false, true, false, false );
}
/*
===================
idPhysics_Player::FlyMove
===================
*/
void idPhysics_Player::FlyMove( void ) {
idVec3 wishvel;
float wishspeed;
idVec3 wishdir;
float scale;
// normal slowdown
idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
if ( !scale ) {
wishvel = vec3_origin;
} else {
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishvel -= scale * gravityNormal * command.upmove;
}
wishdir = wishvel;
wishspeed = wishdir.Normalize();
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
idPhysics_Player::SlideMove( false, false, false, false );
}
/*
===================
idPhysics_Player::AirMove
===================
*/
void idPhysics_Player::AirMove( void ) {
idVec3 wishvel;
idVec3 wishdir;
float wishspeed;
float scale;
idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
// project moves down to flat plane
viewForward -= (viewForward * gravityNormal) * gravityNormal;
viewRight -= (viewRight * gravityNormal) * gravityNormal;
viewForward.Normalize();
viewRight.Normalize();
wishvel = viewForward * command.forwardmove + viewRight * command.rightmove;
wishvel -= (wishvel * gravityNormal) * gravityNormal;
wishdir = wishvel;
wishspeed = wishdir.Normalize();
wishspeed *= scale;
// not on ground, so little effect on velocity
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_AIRACCELERATE );
// we may have a ground plane that is very steep, even
// though we don't have a groundentity
// slide along the steep plane
if ( groundPlane ) {
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
}
idPhysics_Player::SlideMove( true, false, false, false );
}
/*
===================
idPhysics_Player::WalkMove
===================
*/
void idPhysics_Player::WalkMove( void ) {
idVec3 wishvel;
idVec3 wishdir;
float wishspeed;
float scale;
float accelerate;
idVec3 oldVelocity, vel;
float oldVel, newVel;
if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) {
// begin swimming
idPhysics_Player::WaterMove();
return;
}
if ( idPhysics_Player::CheckJump() ) {
// jumped away
if ( waterLevel > WATERLEVEL_FEET ) {
idPhysics_Player::WaterMove();
}
else {
idPhysics_Player::AirMove();
}
return;
}
idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
// project moves down to flat plane
viewForward -= (viewForward * gravityNormal) * gravityNormal;
viewRight -= (viewRight * gravityNormal) * gravityNormal;
// project the forward and right directions onto the ground plane
viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
//
viewForward.Normalize();
viewRight.Normalize();
wishvel = viewForward * command.forwardmove + viewRight * command.rightmove;
wishdir = wishvel;
wishspeed = wishdir.Normalize();
wishspeed *= scale;
// clamp the speed lower if wading or walking on the bottom
if ( waterLevel ) {
float waterScale;
waterScale = waterLevel / 3.0f;
waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale;
if ( wishspeed > playerSpeed * waterScale ) {
wishspeed = playerSpeed * waterScale;
}
}
// when a player gets hit, they temporarily lose full control, which allows them to be moved a bit
if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) {
accelerate = PM_AIRACCELERATE;
}
else {
accelerate = PM_ACCELERATE;
}
idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate );
if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) {
current.velocity += gravityVector * frametime;
}
oldVelocity = current.velocity;
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
// if not clipped into the opposite direction
if ( oldVelocity * current.velocity > 0.0f ) {
newVel = current.velocity.LengthSqr();
if ( newVel > 1.0f ) {
oldVel = oldVelocity.LengthSqr();
if ( oldVel > 1.0f ) {
// don't decrease velocity when going up or down a slope
current.velocity *= idMath::Sqrt( oldVel / newVel );
}
}
}
// don't do anything if standing still
vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal;
if ( !vel.LengthSqr() ) {
return;
}
gameLocal.push.InitSavingPushedEntityPositions();
idPhysics_Player::SlideMove( false, true, true, true );
}
/*
==============
idPhysics_Player::DeadMove
==============
*/
void idPhysics_Player::DeadMove( void ) {
float forward;
if ( !walking ) {
return;
}
// extra friction
forward = current.velocity.Length();
forward -= 20;
if ( forward <= 0 ) {
current.velocity = vec3_origin;
}
else {
current.velocity.Normalize();
current.velocity *= forward;
}
}
/*
===============
idPhysics_Player::NoclipMove
===============
*/
void idPhysics_Player::NoclipMove( void ) {
float speed, drop, friction, newspeed, stopspeed;
float scale, wishspeed;
idVec3 wishdir;
// friction
speed = current.velocity.Length();
if ( speed < 20.0f ) {
current.velocity = vec3_origin;
}
else {
stopspeed = playerSpeed * 0.3f;
if ( speed < stopspeed ) {
speed = stopspeed;
}
friction = PM_NOCLIPFRICTION;
drop = speed * friction * frametime;
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
current.velocity *= newspeed / speed;
}
// accelerate
scale = idPhysics_Player::CmdScale( command );
wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishdir -= scale * gravityNormal * command.upmove;
wishspeed = wishdir.Normalize();
wishspeed *= scale;
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_ACCELERATE );
// move
current.origin += frametime * current.velocity;
}
/*
===============
idPhysics_Player::SpectatorMove
===============
*/
void idPhysics_Player::SpectatorMove( void ) {
idVec3 wishvel;
float wishspeed;
idVec3 wishdir;
float scale;
idVec3 end;
// fly movement
idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
if ( !scale ) {
wishvel = vec3_origin;
} else {
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
}
wishdir = wishvel;
wishspeed = wishdir.Normalize();
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
idPhysics_Player::SlideMove( false, false, false, false );
}
/*
============
idPhysics_Player::LadderMove
============
*/
void idPhysics_Player::LadderMove( void ) {
idVec3 wishdir, wishvel, right;
float wishspeed, scale;
float upscale;
// stick to the ladder
wishvel = -100.0f * ladderNormal;
current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel;
upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f;
if ( upscale > 1.0f ) {
upscale = 1.0f;
}
else if ( upscale < -1.0f ) {
upscale = -1.0f;
}
scale = idPhysics_Player::CmdScale( command );
wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove;
// strafe
if ( command.rightmove ) {
// right vector orthogonal to gravity
right = viewRight - (gravityNormal * viewRight) * gravityNormal;
// project right vector into ladder plane
right = right - (ladderNormal * right) * ladderNormal;
right.Normalize();
// if we are looking away from the ladder, reverse the right vector
if ( ladderNormal * viewForward > 0.0f ) {
right = -right;
}
wishvel += 2.0f * right * scale * (float) command.rightmove;
}
// up down movement
if ( command.upmove ) {
wishvel += -0.5f * gravityNormal * scale * (float) command.upmove;
}
// do strafe friction
idPhysics_Player::Friction();
// accelerate
wishspeed = wishvel.Normalize();
idPhysics_Player::Accelerate( wishvel, wishspeed, PM_ACCELERATE );
// cap the vertical velocity
upscale = current.velocity * -gravityNormal;
if ( upscale < -PM_LADDERSPEED ) {
current.velocity += gravityNormal * (upscale + PM_LADDERSPEED);
}
else if ( upscale > PM_LADDERSPEED ) {
current.velocity += gravityNormal * (upscale - PM_LADDERSPEED);
}
if ( (wishvel * gravityNormal) == 0.0f ) {
if ( current.velocity * gravityNormal < 0.0f ) {
current.velocity += gravityVector * frametime;
if ( current.velocity * gravityNormal > 0.0f ) {
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
else {
current.velocity -= gravityVector * frametime;
if ( current.velocity * gravityNormal < 0.0f ) {
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
}
idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false );
}
/*
=============
idPhysics_Player::CorrectAllSolid
=============
*/
void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) {
if ( debugLevel ) {
gameLocal.Printf( "%i:allsolid\n", c_pmove );
}
// FIXME: jitter around to find a free spot ?
if ( trace.fraction >= 1.0f ) {
memset( &trace, 0, sizeof( trace ) );
trace.endpos = current.origin;
trace.endAxis = clipModelAxis;
trace.fraction = 0.0f;
trace.c.dist = current.origin.z;
trace.c.normal.Set( 0, 0, 1 );
trace.c.point = current.origin;
trace.c.entityNum = ENTITYNUM_WORLD;
trace.c.id = 0;
trace.c.type = CONTACT_TRMVERTEX;
trace.c.material = NULL;
trace.c.contents = contents;
}
}
/*
=============
idPhysics_Player::CheckGround
=============
*/
void idPhysics_Player::CheckGround( void ) {
int i, contents;
idVec3 point;
bool hadGroundContacts;
hadGroundContacts = HasGroundContacts();
// set the clip model origin before getting the contacts
clipModel->SetPosition( current.origin, clipModel->GetAxis() );
EvaluateContacts();
// setup a ground trace from the contacts
groundTrace.endpos = current.origin;
groundTrace.endAxis = clipModel->GetAxis();
if ( contacts.Num() ) {
groundTrace.fraction = 0.0f;
groundTrace.c = contacts[0];
for ( i = 1; i < contacts.Num(); i++ ) {
groundTrace.c.normal += contacts[i].normal;
}
groundTrace.c.normal.Normalize();
} else {
groundTrace.fraction = 1.0f;
}
contents = gameLocal.clip.Contents( current.origin, clipModel, clipModel->GetAxis(), -1, self );
if ( contents & MASK_SOLID ) {
// do something corrective if stuck in solid
idPhysics_Player::CorrectAllSolid( groundTrace, contents );
}
// if the trace didn't hit anything, we are in free fall
if ( groundTrace.fraction == 1.0f ) {
groundPlane = false;
walking = false;
groundEntityPtr = NULL;
return;
}
groundMaterial = groundTrace.c.material;
groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ];
// check if getting thrown off the ground
if ( (current.velocity * -gravityNormal) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 10.0f ) {
if ( debugLevel ) {
gameLocal.Printf( "%i:kickoff\n", c_pmove );
}
groundPlane = false;
walking = false;
return;
}
// slopes that are too steep will not be considered onground
if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) {
if ( debugLevel ) {
gameLocal.Printf( "%i:steep\n", c_pmove );
}
// FIXME: if they can't slide down the slope, let them walk (sharp crevices)
// make sure we don't die from sliding down a steep slope
if ( current.velocity * gravityNormal > 150.0f ) {
current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal;
}
groundPlane = true;
walking = false;
return;
}
groundPlane = true;
walking = true;
// hitting solid ground will end a waterjump
if ( current.movementFlags & PMF_TIME_WATERJUMP ) {
current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND );
current.movementTime = 0;
}
// if the player didn't have ground contacts the previous frame
if ( !hadGroundContacts ) {
// don't do landing time if we were just going down a slope
if ( (current.velocity * -gravityNormal) < -200.0f ) {
// don't allow another jump for a little while
current.movementFlags |= PMF_TIME_LAND;
current.movementTime = 250;
}
}
// let the entity know about the collision
self->Collide( groundTrace, current.velocity );
if ( groundEntityPtr.GetEntity() ) {
impactInfo_t info;
groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info );
if ( info.invMass != 0.0f ) {
groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) );
}
}
}
/*
==============
idPhysics_Player::CheckDuck
Sets clip model size
==============
*/
void idPhysics_Player::CheckDuck( void ) {
trace_t trace;
idVec3 end;
idBounds bounds;
float maxZ;
if ( current.movementType == PM_DEAD ) {
maxZ = pm_deadheight.GetFloat();
} else {
// stand up when up against a ladder
if ( command.upmove < 0 && !ladder ) {
// duck
current.movementFlags |= PMF_DUCKED;
} else {
// stand up if possible
if ( current.movementFlags & PMF_DUCKED ) {
// try to stand up
end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
if ( trace.fraction >= 1.0f ) {
current.movementFlags &= ~PMF_DUCKED;
}
}
}
if ( current.movementFlags & PMF_DUCKED ) {
playerSpeed = crouchSpeed;
maxZ = pm_crouchheight.GetFloat();
} else {
maxZ = pm_normalheight.GetFloat();
}
}
// if the clipModel height should change
if ( clipModel->GetBounds()[1][2] != maxZ ) {
bounds = clipModel->GetBounds();
bounds[1][2] = maxZ;
if ( pm_usecylinder.GetBool() ) {
clipModel->LoadModel( idTraceModel( bounds, 8 ) );
} else {
clipModel->LoadModel( idTraceModel( bounds ) );
}
}
}
/*
================
idPhysics_Player::CheckLadder
================
*/
void idPhysics_Player::CheckLadder( void ) {
idVec3 forward, start, end;
trace_t trace;
float tracedist;
if ( current.movementTime ) {
return;
}
// if on the ground moving backwards
if ( walking && command.forwardmove <= 0 ) {
return;
}
// forward vector orthogonal to gravity
forward = viewForward - (gravityNormal * viewForward) * gravityNormal;
forward.Normalize();
if ( walking ) {
// don't want to get sucked towards the ladder when still walking
tracedist = 1.0f;
} else {
tracedist = 48.0f;
}
end = current.origin + tracedist * forward;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
// if near a surface
if ( trace.fraction < 1.0f ) {
// if a ladder surface
if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) {
// check a step height higher
end = current.origin - gravityNormal * ( maxStepHeight * 0.75f );
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
start = trace.endpos;
end = start + tracedist * forward;
gameLocal.clip.Translation( trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self );
// if also near a surface a step height higher
if ( trace.fraction < 1.0f ) {
// if it also is a ladder surface
if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) {
ladder = true;
ladderNormal = trace.c.normal;
}
}
}
}
}
/*
=============
idPhysics_Player::CheckJump
=============
*/
bool idPhysics_Player::CheckJump( void ) {
idVec3 addVelocity;
if ( command.upmove < 10 ) {
// not holding jump
return false;
}
// must wait for jump to be released
if ( current.movementFlags & PMF_JUMP_HELD ) {
return false;
}
// don't jump if we can't stand up
if ( current.movementFlags & PMF_DUCKED ) {
return false;
}
groundPlane = false; // jumping away
walking = false;
current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED;
addVelocity = 2.0f * maxJumpHeight * -gravityVector;
addVelocity *= idMath::Sqrt( addVelocity.Normalize() );
current.velocity += addVelocity;
return true;
}
/*
=============
idPhysics_Player::CheckWaterJump
=============
*/
bool idPhysics_Player::CheckWaterJump( void ) {
idVec3 spot;
int cont;
idVec3 flatforward;
if ( current.movementTime ) {
return false;
}
// check for water jump
if ( waterLevel != WATERLEVEL_WAIST ) {
return false;
}
flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal;
flatforward.Normalize();
spot = current.origin + 30.0f * flatforward;
spot -= 4.0f * gravityNormal;
cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self );
if ( !(cont & CONTENTS_SOLID) ) {
return false;
}
spot -= 16.0f * gravityNormal;
cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self );
if ( cont ) {
return false;
}
// jump out of water
current.velocity = 200.0f * viewForward - 350.0f * gravityNormal;
current.movementFlags |= PMF_TIME_WATERJUMP;
current.movementTime = 2000;
return true;
}
/*
=============
idPhysics_Player::SetWaterLevel
=============
*/
void idPhysics_Player::SetWaterLevel( void ) {
idVec3 point;
idBounds bounds;
int contents;
//
// get waterlevel, accounting for ducking
//
waterLevel = WATERLEVEL_NONE;
waterType = 0;
bounds = clipModel->GetBounds();
// check at feet level
point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterType = contents;
waterLevel = WATERLEVEL_FEET;
// check at waist level
point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterLevel = WATERLEVEL_WAIST;
// check at head level
point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterLevel = WATERLEVEL_HEAD;
}
}
}
}
/*
================
idPhysics_Player::DropTimers
================
*/
void idPhysics_Player::DropTimers( void ) {
// drop misc timing counter
if ( current.movementTime ) {
if ( framemsec >= current.movementTime ) {
current.movementFlags &= ~PMF_ALL_TIMES;
current.movementTime = 0;
}
else {
current.movementTime -= framemsec;
}
}
}
/*
================
idPhysics_Player::MovePlayer
================
*/
void idPhysics_Player::MovePlayer( int msec ) {
// this counter lets us debug movement problems with a journal
// by setting a conditional breakpoint for the previous frame
c_pmove++;
walking = false;
groundPlane = false;
ladder = false;
// determine the time
framemsec = msec;
frametime = framemsec * 0.001f;
// default speed
playerSpeed = walkSpeed;
// remove jumped and stepped up flag
current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN);
current.stepUp = 0.0f;
if ( command.upmove < 10 ) {
// not holding jump
current.movementFlags &= ~PMF_JUMP_HELD;
}
// if no movement at all
if ( current.movementType == PM_FREEZE ) {
return;
}
// move the player velocity into the frame of a pusher
current.velocity -= current.pushVelocity;
// view vectors
viewAngles.ToVectors( &viewForward, NULL, NULL );
viewForward *= clipModelAxis;
viewRight = gravityNormal.Cross( viewForward );
viewRight.Normalize();
// fly in spectator mode
if ( current.movementType == PM_SPECTATOR ) {
SpectatorMove();
idPhysics_Player::DropTimers();
return;
}
// special no clip mode
if ( current.movementType == PM_NOCLIP ) {
idPhysics_Player::NoclipMove();
idPhysics_Player::DropTimers();
return;
}
// no control when dead
if ( current.movementType == PM_DEAD ) {
command.forwardmove = 0;
command.rightmove = 0;
command.upmove = 0;
}
// set watertype and waterlevel
idPhysics_Player::SetWaterLevel();
// check for ground
idPhysics_Player::CheckGround();
// check if up against a ladder
idPhysics_Player::CheckLadder();
// set clip model size
idPhysics_Player::CheckDuck();
// handle timers
idPhysics_Player::DropTimers();
// move
if ( current.movementType == PM_DEAD ) {
// dead
idPhysics_Player::DeadMove();
}
else if ( ladder ) {
// going up or down a ladder
idPhysics_Player::LadderMove();
}
else if ( current.movementFlags & PMF_TIME_WATERJUMP ) {
// jumping out of water
idPhysics_Player::WaterJumpMove();
}
else if ( waterLevel > 1 ) {
// swimming
idPhysics_Player::WaterMove();
}
else if ( walking ) {
// walking on ground
idPhysics_Player::WalkMove();
}
else {
// airborne
idPhysics_Player::AirMove();
}
// set watertype, waterlevel and groundentity
idPhysics_Player::SetWaterLevel();
idPhysics_Player::CheckGround();
// move the player velocity back into the world frame
current.velocity += current.pushVelocity;
current.pushVelocity.Zero();
}
/*
================
idPhysics_Player::GetWaterLevel
================
*/
waterLevel_t idPhysics_Player::GetWaterLevel( void ) const {
return waterLevel;
}
/*
================
idPhysics_Player::GetWaterType
================
*/
int idPhysics_Player::GetWaterType( void ) const {
return waterType;
}
/*
================
idPhysics_Player::HasJumped
================
*/
bool idPhysics_Player::HasJumped( void ) const {
return ( ( current.movementFlags & PMF_JUMPED ) != 0 );
}
/*
================
idPhysics_Player::HasSteppedUp
================
*/
bool idPhysics_Player::HasSteppedUp( void ) const {
return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 );
}
/*
================
idPhysics_Player::GetStepUp
================
*/
float idPhysics_Player::GetStepUp( void ) const {
return current.stepUp;
}
/*
================
idPhysics_Player::IsCrouching
================
*/
bool idPhysics_Player::IsCrouching( void ) const {
return ( ( current.movementFlags & PMF_DUCKED ) != 0 );
}
/*
================
idPhysics_Player::OnLadder
================
*/
bool idPhysics_Player::OnLadder( void ) const {
return ladder;
}
/*
================
idPhysics_Player::idPhysics_Player
================
*/
idPhysics_Player::idPhysics_Player( void ) {
debugLevel = false;
clipModel = NULL;
clipMask = 0;
memset( &current, 0, sizeof( current ) );
saved = current;
walkSpeed = 0;
crouchSpeed = 0;
maxStepHeight = 0;
maxJumpHeight = 0;
memset( &command, 0, sizeof( command ) );
viewAngles.Zero();
framemsec = 0;
frametime = 0;
playerSpeed = 0;
viewForward.Zero();
viewRight.Zero();
walking = false;
groundPlane = false;
memset( &groundTrace, 0, sizeof( groundTrace ) );
groundMaterial = NULL;
ladder = false;
ladderNormal.Zero();
waterLevel = WATERLEVEL_NONE;
waterType = 0;
}
/*
================
idPhysics_Player_SavePState
================
*/
void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) {
savefile->WriteVec3( state.origin );
savefile->WriteVec3( state.velocity );
savefile->WriteVec3( state.localOrigin );
savefile->WriteVec3( state.pushVelocity );
savefile->WriteFloat( state.stepUp );
savefile->WriteInt( state.movementType );
savefile->WriteInt( state.movementFlags );
savefile->WriteInt( state.movementTime );
}
/*
================
idPhysics_Player_RestorePState
================
*/
void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) {
savefile->ReadVec3( state.origin );
savefile->ReadVec3( state.velocity );
savefile->ReadVec3( state.localOrigin );
savefile->ReadVec3( state.pushVelocity );
savefile->ReadFloat( state.stepUp );
savefile->ReadInt( state.movementType );
savefile->ReadInt( state.movementFlags );
savefile->ReadInt( state.movementTime );
}
/*
================
idPhysics_Player::Save
================
*/
void idPhysics_Player::Save( idSaveGame *savefile ) const {
idPhysics_Player_SavePState( savefile, current );
idPhysics_Player_SavePState( savefile, saved );
savefile->WriteFloat( walkSpeed );
savefile->WriteFloat( crouchSpeed );
savefile->WriteFloat( maxStepHeight );
savefile->WriteFloat( maxJumpHeight );
savefile->WriteInt( debugLevel );
savefile->WriteUsercmd( command );
savefile->WriteAngles( viewAngles );
savefile->WriteInt( framemsec );
savefile->WriteFloat( frametime );
savefile->WriteFloat( playerSpeed );
savefile->WriteVec3( viewForward );
savefile->WriteVec3( viewRight );
savefile->WriteBool( walking );
savefile->WriteBool( groundPlane );
savefile->WriteTrace( groundTrace );
savefile->WriteMaterial( groundMaterial );
savefile->WriteBool( ladder );
savefile->WriteVec3( ladderNormal );
savefile->WriteInt( (int)waterLevel );
savefile->WriteInt( waterType );
}
/*
================
idPhysics_Player::Restore
================
*/
void idPhysics_Player::Restore( idRestoreGame *savefile ) {
idPhysics_Player_RestorePState( savefile, current );
idPhysics_Player_RestorePState( savefile, saved );
savefile->ReadFloat( walkSpeed );
savefile->ReadFloat( crouchSpeed );
savefile->ReadFloat( maxStepHeight );
savefile->ReadFloat( maxJumpHeight );
savefile->ReadInt( debugLevel );
savefile->ReadUsercmd( command );
savefile->ReadAngles( viewAngles );
savefile->ReadInt( framemsec );
savefile->ReadFloat( frametime );
savefile->ReadFloat( playerSpeed );
savefile->ReadVec3( viewForward );
savefile->ReadVec3( viewRight );
savefile->ReadBool( walking );
savefile->ReadBool( groundPlane );
savefile->ReadTrace( groundTrace );
savefile->ReadMaterial( groundMaterial );
savefile->ReadBool( ladder );
savefile->ReadVec3( ladderNormal );
savefile->ReadInt( (int &)waterLevel );
savefile->ReadInt( waterType );
/* DG: It can apparently happen that the player saves while the clipModel's axis are
* modified by idPush::TryRotatePushEntity() -> idPhysics_Player::Rotate() -> idClipModel::Link()
* Normally idPush seems to reset them to the identity matrix in the next frame,
* but apparently not when coming from a savegame.
* Usually clipModel->axis is the identity matrix, and if it isn't there's clipping bugs
* like CheckGround() reporting that it's steep even though the player is only trying to
* walk up normal stairs.
* Resetting the axis to mat3_identity when restoring a savegame works around that issue
* and makes sure players can go on playing if their savegame was "corrupted" by saving
* while idPush was active. See https://github.com/dhewm/dhewm3/issues/328 for more details */
if ( clipModel != nullptr ) {
clipModel->SetPosition( clipModel->GetOrigin(), mat3_identity );
}
}
/*
================
idPhysics_Player::SetPlayerInput
================
*/
void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) {
command = cmd;
viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles
}
/*
================
idPhysics_Player::SetSpeed
================
*/
void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) {
walkSpeed = newWalkSpeed;
crouchSpeed = newCrouchSpeed;
}
/*
================
idPhysics_Player::SetMaxStepHeight
================
*/
void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) {
maxStepHeight = newMaxStepHeight;
}
/*
================
idPhysics_Player::GetMaxStepHeight
================
*/
float idPhysics_Player::GetMaxStepHeight( void ) const {
return maxStepHeight;
}
/*
================
idPhysics_Player::SetMaxJumpHeight
================
*/
void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) {
maxJumpHeight = newMaxJumpHeight;
}
/*
================
idPhysics_Player::SetMovementType
================
*/
void idPhysics_Player::SetMovementType( const pmtype_t type ) {
current.movementType = type;
}
/*
================
idPhysics_Player::SetKnockBack
================
*/
void idPhysics_Player::SetKnockBack( const int knockBackTime ) {
if ( current.movementTime ) {
return;
}
current.movementFlags |= PMF_TIME_KNOCKBACK;
current.movementTime = knockBackTime;
}
/*
================
idPhysics_Player::SetDebugLevel
================
*/
void idPhysics_Player::SetDebugLevel( bool set ) {
debugLevel = set;
}
/*
================
idPhysics_Player::Evaluate
================
*/
bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) {
idVec3 masterOrigin, oldOrigin;
idMat3 masterAxis;
waterLevel = WATERLEVEL_NONE;
waterType = 0;
oldOrigin = current.origin;
clipModel->Unlink();
// if bound to a master
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.origin = masterOrigin + current.localOrigin * masterAxis;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f );
masterDeltaYaw = masterYaw;
masterYaw = masterAxis[0].ToYaw();
masterDeltaYaw = masterYaw - masterDeltaYaw;
return true;
}
ActivateContactEntities();
idPhysics_Player::MovePlayer( timeStepMSec );
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
if ( IsOutsideWorld() ) {
gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) );
}
return true; //( current.origin != oldOrigin );
}
/*
================
idPhysics_Player::UpdateTime
================
*/
void idPhysics_Player::UpdateTime( int endTimeMSec ) {
}
/*
================
idPhysics_Player::GetTime
================
*/
int idPhysics_Player::GetTime( void ) const {
return gameLocal.time;
}
/*
================
idPhysics_Player::GetImpactInfo
================
*/
void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
info->invMass = invMass;
info->invInertiaTensor.Zero();
info->position.Zero();
info->velocity = current.velocity;
}
/*
================
idPhysics_Player::ApplyImpulse
================
*/
void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
if ( current.movementType != PM_NOCLIP ) {
current.velocity += impulse * invMass;
}
}
/*
================
idPhysics_Player::IsAtRest
================
*/
bool idPhysics_Player::IsAtRest( void ) const {
return false;
}
/*
================
idPhysics_Player::GetRestStartTime
================
*/
int idPhysics_Player::GetRestStartTime( void ) const {
return -1;
}
/*
================
idPhysics_Player::SaveState
================
*/
void idPhysics_Player::SaveState( void ) {
saved = current;
}
/*
================
idPhysics_Player::RestoreState
================
*/
void idPhysics_Player::RestoreState( void ) {
current = saved;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
EvaluateContacts();
}
/*
================
idPhysics_Player::SetOrigin
================
*/
void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
current.localOrigin = newOrigin;
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.origin = masterOrigin + newOrigin * masterAxis;
}
else {
current.origin = newOrigin;
}
clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() );
}
/*
================
idPhysics_Player::GetOrigin
================
*/
const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const {
return current.origin;
}
/*
================
idPhysics_Player::SetAxis
================
*/
void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) {
clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis );
}
/*
================
idPhysics_Player::Translate
================
*/
void idPhysics_Player::Translate( const idVec3 &translation, int id ) {
current.localOrigin += translation;
current.origin += translation;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
}
/*
================
idPhysics_Player::Rotate
================
*/
void idPhysics_Player::Rotate( const idRotation &rotation, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
current.origin *= rotation;
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
}
else {
current.localOrigin = current.origin;
}
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() );
}
/*
================
idPhysics_Player::SetLinearVelocity
================
*/
void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
current.velocity = newLinearVelocity;
}
/*
================
idPhysics_Player::GetLinearVelocity
================
*/
const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const {
return current.velocity;
}
/*
================
idPhysics_Player::SetPushed
================
*/
void idPhysics_Player::SetPushed( int deltaTime ) {
idVec3 velocity;
float d;
// velocity with which the player is pushed
velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC );
// remove any downward push velocity
d = velocity * gravityNormal;
if ( d > 0.0f ) {
velocity -= d * gravityNormal;
}
current.pushVelocity += velocity;
}
/*
================
idPhysics_Player::GetPushedLinearVelocity
================
*/
const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const {
return current.pushVelocity;
}
/*
================
idPhysics_Player::ClearPushedVelocity
================
*/
void idPhysics_Player::ClearPushedVelocity( void ) {
current.pushVelocity.Zero();
}
/*
================
idPhysics_Player::SetMaster
the binding is never orientated
================
*/
void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( master ) {
if ( !masterEntity ) {
// transform from world space to master space
self->GetMasterPosition( masterOrigin, masterAxis );
current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
masterEntity = master;
masterYaw = masterAxis[0].ToYaw();
}
ClearContacts();
}
else {
if ( masterEntity ) {
masterEntity = NULL;
}
}
}
const float PLAYER_VELOCITY_MAX = 4000;
const int PLAYER_VELOCITY_TOTAL_BITS = 16;
const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1;
const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS;
const int PLAYER_MOVEMENT_TYPE_BITS = 3;
const int PLAYER_MOVEMENT_FLAGS_BITS = 8;
/*
================
idPhysics_Player::WriteToSnapshot
================
*/
void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteFloat( current.origin[0] );
msg.WriteFloat( current.origin[1] );
msg.WriteFloat( current.origin[2] );
msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] );
msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] );
msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.stepUp );
msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS );
msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS );
msg.WriteDeltaInt( 0, current.movementTime );
}
/*
================
idPhysics_Player::ReadFromSnapshot
================
*/
void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) {
current.origin[0] = msg.ReadFloat();
current.origin[1] = msg.ReadFloat();
current.origin[2] = msg.ReadFloat();
current.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] );
current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] );
current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] );
current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.stepUp = msg.ReadDeltaFloat( 0.0f );
current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS );
current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS );
current.movementTime = msg.ReadDeltaInt( 0 );
if ( clipModel ) {
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
}
}