mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
991 lines
25 KiB
C++
991 lines
25 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
|
|
|
|
IceTech 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.
|
|
|
|
IceTech 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
|
|
const float OVERCLIP = 1.001f;
|
|
|
|
|
|
// Doom 3 keeps monster movement as delta-based StepMove/SlideMove calls, while
|
|
// the newer idTech code moved to a frame-time based SlideMove. These constants
|
|
// let us port the low-risk parts of the newer clipping/step behaviour without
|
|
// changing the public Physics_Monster interface.
|
|
const int MONSTER_MAX_CLIP_PLANES = 5;
|
|
const int MONSTER_MAX_BUMPS = 4;
|
|
const float MONSTER_STEP_DOWN_EPSILON = 1.0e-4f;
|
|
|
|
/*
|
|
=====================
|
|
idPhysics_Monster::CheckGround
|
|
=====================
|
|
*/
|
|
void idPhysics_Monster::CheckGround(monsterPState_t& state) {
|
|
int i;
|
|
trace_t groundTrace;
|
|
idVec3 down;
|
|
|
|
if (gravityNormal == vec3_zero) {
|
|
state.onGround = false;
|
|
groundEntityPtr = NULL;
|
|
return;
|
|
}
|
|
|
|
// The newer physics code builds the ground plane from actual contacts instead
|
|
// of a tiny downward trace. This is less sensitive to CONTACT_EPSILON and
|
|
// better represents monsters standing on multiple triangles or clipped movers.
|
|
clipModel->SetPosition(state.origin, clipModel->GetAxis());
|
|
EvaluateContacts();
|
|
|
|
groundTrace.endpos = state.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 {
|
|
// Preserve Doom 3's tiny downward trace as a fallback when no contact was
|
|
// generated for the current position.
|
|
down = state.origin + gravityNormal * CONTACT_EPSILON;
|
|
gameLocal.clip.Translation(groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
}
|
|
|
|
// If neither contacts nor the fallback trace found anything, we are in free fall.
|
|
if (groundTrace.fraction == 1.0f || groundTrace.c.entityNum == ENTITYNUM_NONE) {
|
|
state.onGround = false;
|
|
groundEntityPtr = NULL;
|
|
return;
|
|
}
|
|
|
|
groundEntityPtr = gameLocal.entities[groundTrace.c.entityNum];
|
|
|
|
// Match the newer kick-off check so upward velocity can detach the monster
|
|
// from the ground instead of keeping it artificially grounded.
|
|
if ((state.velocity * -gravityNormal) > 0.0f && (state.velocity * groundTrace.c.normal) > 10.0f) {
|
|
state.onGround = false;
|
|
return;
|
|
}
|
|
|
|
if ((groundTrace.c.normal * -gravityNormal) < minFloorCosine) {
|
|
state.onGround = false;
|
|
return;
|
|
}
|
|
|
|
state.onGround = true;
|
|
|
|
// let the entity know about the collision
|
|
self->Collide(groundTrace, state.velocity);
|
|
|
|
// apply impact to a non world floor entity
|
|
if (groundTrace.c.entityNum != ENTITYNUM_WORLD && 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, 0, groundTrace.c.point, state.velocity / (info.invMass * 10.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idPhysics_Monster::SlideMove
|
|
=====================
|
|
*/
|
|
monsterMoveResult_t idPhysics_Monster::SlideMove(idVec3& start, idVec3& velocity, const idVec3& delta) {
|
|
int i, j, k;
|
|
int bumpcount, numplanes;
|
|
float d, into;
|
|
trace_t tr;
|
|
idVec3 move, remainingMove, clipVelocity, dir;
|
|
idVec3 planes[MONSTER_MAX_CLIP_PLANES];
|
|
bool clipped;
|
|
|
|
blockingEntity = NULL;
|
|
|
|
move = delta;
|
|
if (move.LengthSqr() <= idMath::FLT_EPSILON) {
|
|
return MM_OK;
|
|
}
|
|
|
|
clipped = false;
|
|
numplanes = 0;
|
|
|
|
// Never turn back into the original velocity direction. This mirrors the
|
|
// newer multi-plane SlideMove logic but keeps Doom 3's delta-based interface.
|
|
if (velocity.LengthSqr() > idMath::FLT_EPSILON) {
|
|
planes[numplanes] = velocity;
|
|
planes[numplanes].Normalize();
|
|
numplanes++;
|
|
}
|
|
|
|
for (bumpcount = 0; bumpcount < MONSTER_MAX_BUMPS; bumpcount++) {
|
|
gameLocal.clip.Translation(tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
|
|
start = tr.endpos;
|
|
|
|
if (tr.fraction >= 1.0f) {
|
|
return clipped ? MM_SLIDING : MM_OK;
|
|
}
|
|
|
|
clipped = true;
|
|
|
|
if (tr.c.entityNum != ENTITYNUM_NONE) {
|
|
blockingEntity = gameLocal.entities[tr.c.entityNum];
|
|
}
|
|
|
|
remainingMove = move * (1.0f - tr.fraction);
|
|
|
|
if (numplanes >= MONSTER_MAX_CLIP_PLANES) {
|
|
// Same fail-safe as the newer code: high-poly/slanted collision soup can
|
|
// exhaust the available clip planes, so stop instead of oscillating.
|
|
velocity = vec3_origin;
|
|
return MM_BLOCKED;
|
|
}
|
|
|
|
// If this is effectively the same plane we hit before, clip into the real
|
|
// trace normal and nudge velocity out along it. This fixes epsilon issues
|
|
// with non-axial planes that can otherwise leave the monster stuck.
|
|
for (i = 0; i < numplanes; i++) {
|
|
if ((tr.c.normal * planes[i]) > 0.999f) {
|
|
velocity.ProjectOntoPlane(tr.c.normal, OVERCLIP);
|
|
velocity += tr.c.normal;
|
|
remainingMove.ProjectOntoPlane(tr.c.normal, OVERCLIP);
|
|
break;
|
|
}
|
|
}
|
|
if (i < numplanes) {
|
|
move = remainingMove;
|
|
continue;
|
|
}
|
|
|
|
planes[numplanes] = tr.c.normal;
|
|
numplanes++;
|
|
|
|
// Modify velocity so it parallels all of the clip planes. This is the
|
|
// newer multi-plane clipping behaviour adapted to the old monster API.
|
|
for (i = 0; i < numplanes; i++) {
|
|
into = velocity * planes[i];
|
|
if (into >= 0.1f) {
|
|
continue;
|
|
}
|
|
|
|
clipVelocity = velocity;
|
|
clipVelocity.ProjectOntoPlane(planes[i], OVERCLIP);
|
|
|
|
for (j = 0; j < numplanes; j++) {
|
|
if (j == i) {
|
|
continue;
|
|
}
|
|
if ((clipVelocity * planes[j]) >= 0.1f) {
|
|
continue;
|
|
}
|
|
|
|
clipVelocity.ProjectOntoPlane(planes[j], OVERCLIP);
|
|
|
|
if ((clipVelocity * planes[i]) >= 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
dir = planes[i].Cross(planes[j]);
|
|
dir.Normalize();
|
|
d = dir * velocity;
|
|
clipVelocity = d * dir;
|
|
|
|
for (k = 0; k < numplanes; k++) {
|
|
if (k == i || k == j) {
|
|
continue;
|
|
}
|
|
if ((clipVelocity * planes[k]) >= 0.1f) {
|
|
continue;
|
|
}
|
|
|
|
velocity = vec3_origin;
|
|
return MM_BLOCKED;
|
|
}
|
|
}
|
|
|
|
velocity = clipVelocity;
|
|
break;
|
|
}
|
|
|
|
// Keep the remaining movement delta consistent with the newly collected
|
|
// planes. Doom 3's original code reused the full projected move, which can
|
|
// overshoot after a partial trace; use only the leftover distance.
|
|
for (i = 0; i < numplanes; i++) {
|
|
if ((remainingMove * planes[i]) < 0.1f) {
|
|
remainingMove.ProjectOntoPlane(planes[i], OVERCLIP);
|
|
}
|
|
}
|
|
move = remainingMove;
|
|
}
|
|
|
|
return MM_BLOCKED;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idPhysics_Monster::StepMove
|
|
|
|
move start into the delta direction
|
|
the velocity is clipped conform any collisions
|
|
=====================
|
|
*/
|
|
monsterMoveResult_t idPhysics_Monster::StepMove(idVec3& start, idVec3& velocity, const idVec3& delta) {
|
|
trace_t tr;
|
|
idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel;
|
|
monsterMoveResult_t result1, result2;
|
|
float stepdist;
|
|
float nostepdist;
|
|
|
|
if (delta == vec3_origin) {
|
|
return MM_OK;
|
|
}
|
|
|
|
// try to move without stepping up
|
|
noStepPos = start;
|
|
noStepVel = velocity;
|
|
result1 = SlideMove(noStepPos, noStepVel, delta);
|
|
if (result1 == MM_OK) {
|
|
velocity = noStepVel;
|
|
if (gravityNormal == vec3_zero) {
|
|
start = noStepPos;
|
|
return MM_OK;
|
|
}
|
|
|
|
// try to step down so that we walk down slopes and stairs at a normal rate
|
|
down = noStepPos + gravityNormal * maxStepHeight;
|
|
gameLocal.clip.Translation(tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
if (tr.fraction > MONSTER_STEP_DOWN_EPSILON && tr.fraction < 1.0f && (tr.c.normal * -gravityNormal) >= minFloorCosine) {
|
|
start = tr.endpos;
|
|
return MM_STEPPED;
|
|
}
|
|
else {
|
|
start = noStepPos;
|
|
return MM_OK;
|
|
}
|
|
}
|
|
|
|
if (blockingEntity && blockingEntity->IsType(idActor::Type)) {
|
|
// try to step down in case walking into an actor while going down steps
|
|
down = noStepPos + gravityNormal * maxStepHeight;
|
|
gameLocal.clip.Translation(tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
if (tr.fraction > MONSTER_STEP_DOWN_EPSILON && tr.fraction < 1.0f && (tr.c.normal * -gravityNormal) >= minFloorCosine) {
|
|
start = tr.endpos;
|
|
}
|
|
else {
|
|
start = noStepPos;
|
|
}
|
|
velocity = noStepVel;
|
|
return MM_BLOCKED;
|
|
}
|
|
|
|
if (gravityNormal == vec3_zero) {
|
|
return result1;
|
|
}
|
|
|
|
// try to step up
|
|
up = start - gravityNormal * maxStepHeight;
|
|
gameLocal.clip.Translation(tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
if (tr.fraction == 0.0f) {
|
|
start = noStepPos;
|
|
velocity = noStepVel;
|
|
return result1;
|
|
}
|
|
|
|
// try to move at the stepped up position
|
|
stepPos = tr.endpos;
|
|
stepVel = velocity;
|
|
result2 = SlideMove(stepPos, stepVel, delta);
|
|
if (result2 == MM_BLOCKED) {
|
|
start = noStepPos;
|
|
velocity = noStepVel;
|
|
return result1;
|
|
}
|
|
|
|
// step down again
|
|
down = stepPos + gravityNormal * maxStepHeight;
|
|
gameLocal.clip.Translation(tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self);
|
|
stepPos = tr.endpos;
|
|
|
|
// if the move is further without stepping up, or the slope is too steep, don't step up.
|
|
// The newer code accepts a full-fraction step-down trace here, which avoids
|
|
// rejecting a valid step just because there was no immediate floor contact at
|
|
// the end of the downward trace.
|
|
nostepdist = (noStepPos - start).LengthSqr();
|
|
stepdist = (stepPos - start).LengthSqr();
|
|
if ((nostepdist >= stepdist) || (tr.fraction < 1.0f && (tr.c.normal * -gravityNormal) < minFloorCosine)) {
|
|
start = noStepPos;
|
|
velocity = noStepVel;
|
|
return MM_SLIDING;
|
|
}
|
|
|
|
start = stepPos;
|
|
velocity = stepVel;
|
|
|
|
return MM_STEPPED;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Activate
|
|
================
|
|
*/
|
|
void idPhysics_Monster::Activate(void) {
|
|
current.atRest = -1;
|
|
self->BecomeActive(TH_PHYSICS);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Rest
|
|
================
|
|
*/
|
|
void idPhysics_Monster::Rest(void) {
|
|
current.atRest = gameLocal.time;
|
|
current.velocity.Zero();
|
|
self->BecomeInactive(TH_PHYSICS);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::PutToRest
|
|
================
|
|
*/
|
|
void idPhysics_Monster::PutToRest(void) {
|
|
Rest();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::idPhysics_Monster
|
|
================
|
|
*/
|
|
idPhysics_Monster::idPhysics_Monster(void) {
|
|
|
|
memset(¤t, 0, sizeof(current));
|
|
current.atRest = -1;
|
|
saved = current;
|
|
|
|
delta.Zero();
|
|
maxStepHeight = 18.0f;
|
|
minFloorCosine = 0.7f;
|
|
moveResult = MM_OK;
|
|
forceDeltaMove = false;
|
|
fly = false;
|
|
useVelocityMove = false;
|
|
noImpact = false;
|
|
blockingEntity = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster_SavePState
|
|
================
|
|
*/
|
|
void idPhysics_Monster_SavePState(idSaveGame* savefile, const monsterPState_t& state) {
|
|
savefile->WriteVec3(state.origin);
|
|
savefile->WriteVec3(state.velocity);
|
|
savefile->WriteVec3(state.localOrigin);
|
|
savefile->WriteVec3(state.pushVelocity);
|
|
savefile->WriteBool(state.onGround);
|
|
savefile->WriteInt(state.atRest);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster_RestorePState
|
|
================
|
|
*/
|
|
void idPhysics_Monster_RestorePState(idRestoreGame* savefile, monsterPState_t& state) {
|
|
savefile->ReadVec3(state.origin);
|
|
savefile->ReadVec3(state.velocity);
|
|
savefile->ReadVec3(state.localOrigin);
|
|
savefile->ReadVec3(state.pushVelocity);
|
|
savefile->ReadBool(state.onGround);
|
|
savefile->ReadInt(state.atRest);
|
|
|
|
state.origin.FixDenormals();
|
|
state.velocity.FixDenormals();
|
|
state.localOrigin.FixDenormals();
|
|
state.pushVelocity.FixDenormals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Save
|
|
================
|
|
*/
|
|
void idPhysics_Monster::Save(idSaveGame* savefile) const {
|
|
|
|
idPhysics_Monster_SavePState(savefile, current);
|
|
idPhysics_Monster_SavePState(savefile, saved);
|
|
|
|
savefile->WriteFloat(maxStepHeight);
|
|
savefile->WriteFloat(minFloorCosine);
|
|
savefile->WriteVec3(delta);
|
|
|
|
savefile->WriteBool(forceDeltaMove);
|
|
savefile->WriteBool(fly);
|
|
savefile->WriteBool(useVelocityMove);
|
|
savefile->WriteBool(noImpact);
|
|
|
|
savefile->WriteInt((int)moveResult);
|
|
savefile->WriteObject(blockingEntity);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Restore
|
|
================
|
|
*/
|
|
void idPhysics_Monster::Restore(idRestoreGame* savefile) {
|
|
|
|
idPhysics_Monster_RestorePState(savefile, current);
|
|
idPhysics_Monster_RestorePState(savefile, saved);
|
|
|
|
savefile->ReadFloat(maxStepHeight);
|
|
savefile->ReadFloat(minFloorCosine);
|
|
savefile->ReadVec3(delta);
|
|
|
|
savefile->ReadBool(forceDeltaMove);
|
|
savefile->ReadBool(fly);
|
|
savefile->ReadBool(useVelocityMove);
|
|
savefile->ReadBool(noImpact);
|
|
|
|
savefile->ReadInt((int&)moveResult);
|
|
savefile->ReadObject(reinterpret_cast<idClass*&>(blockingEntity));
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SetDelta
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SetDelta(const idVec3& d) {
|
|
delta = d;
|
|
if (delta != vec3_origin) {
|
|
Activate();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SetMaxStepHeight
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SetMaxStepHeight(const float newMaxStepHeight) {
|
|
maxStepHeight = newMaxStepHeight;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetMaxStepHeight
|
|
================
|
|
*/
|
|
float idPhysics_Monster::GetMaxStepHeight(void) const {
|
|
return maxStepHeight;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::OnGround
|
|
================
|
|
*/
|
|
bool idPhysics_Monster::OnGround(void) const {
|
|
return current.onGround;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetSlideMoveEntity
|
|
================
|
|
*/
|
|
idEntity* idPhysics_Monster::GetSlideMoveEntity(void) const {
|
|
return blockingEntity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetMoveResult
|
|
================
|
|
*/
|
|
monsterMoveResult_t idPhysics_Monster::GetMoveResult(void) const {
|
|
return moveResult;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::ForceDeltaMove
|
|
================
|
|
*/
|
|
void idPhysics_Monster::ForceDeltaMove(bool force) {
|
|
forceDeltaMove = force;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::UseFlyMove
|
|
================
|
|
*/
|
|
void idPhysics_Monster::UseFlyMove(bool force) {
|
|
fly = force;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::UseVelocityMove
|
|
================
|
|
*/
|
|
void idPhysics_Monster::UseVelocityMove(bool force) {
|
|
useVelocityMove = force;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::EnableImpact
|
|
================
|
|
*/
|
|
void idPhysics_Monster::EnableImpact(void) {
|
|
noImpact = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::DisableImpact
|
|
================
|
|
*/
|
|
void idPhysics_Monster::DisableImpact(void) {
|
|
noImpact = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Evaluate
|
|
================
|
|
*/
|
|
bool idPhysics_Monster::Evaluate(int timeStepMSec, int endTimeMSec) {
|
|
idVec3 masterOrigin, oldOrigin;
|
|
idMat3 masterAxis;
|
|
float timeStep;
|
|
|
|
// Snap away tiny values before prediction/network deltas. This mirrors the
|
|
// newer code's denormal cleanup and avoids long-running physics slowdowns.
|
|
current.origin.FixDenormals();
|
|
current.velocity.FixDenormals();
|
|
current.localOrigin.FixDenormals();
|
|
current.pushVelocity.FixDenormals();
|
|
delta.FixDenormals();
|
|
|
|
timeStep = MS2SEC(timeStepMSec);
|
|
|
|
moveResult = MM_OK;
|
|
blockingEntity = NULL;
|
|
oldOrigin = current.origin;
|
|
|
|
// 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());
|
|
if (timeStepMSec > 0) {
|
|
current.velocity = (current.origin - oldOrigin) / timeStep;
|
|
}
|
|
else {
|
|
current.velocity.Zero();
|
|
}
|
|
masterDeltaYaw = masterYaw;
|
|
masterYaw = masterAxis[0].ToYaw();
|
|
masterDeltaYaw = masterYaw - masterDeltaYaw;
|
|
return (current.origin != oldOrigin);
|
|
}
|
|
|
|
// if the monster is at rest
|
|
if (current.atRest >= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (timeStepMSec <= 0) {
|
|
return false;
|
|
}
|
|
|
|
ActivateContactEntities();
|
|
|
|
// move the monster velocity into the frame of a pusher
|
|
current.velocity -= current.pushVelocity;
|
|
|
|
clipModel->Unlink();
|
|
|
|
// check if on the ground
|
|
idPhysics_Monster::CheckGround(current);
|
|
|
|
// if not on the ground or moving upwards
|
|
float upspeed;
|
|
if (gravityNormal != vec3_zero) {
|
|
upspeed = -(current.velocity * gravityNormal);
|
|
}
|
|
else {
|
|
upspeed = current.velocity.z;
|
|
}
|
|
if (fly || (!forceDeltaMove && (!current.onGround || upspeed > 1.0f))) {
|
|
if (upspeed < 0.0f) {
|
|
moveResult = MM_FALLING;
|
|
}
|
|
else {
|
|
current.onGround = false;
|
|
moveResult = MM_OK;
|
|
}
|
|
delta = current.velocity * timeStep;
|
|
if (delta != vec3_origin) {
|
|
moveResult = idPhysics_Monster::SlideMove(current.origin, current.velocity, delta);
|
|
delta.Zero();
|
|
}
|
|
|
|
if (!fly) {
|
|
current.velocity += gravityVector * timeStep;
|
|
}
|
|
}
|
|
else {
|
|
if (useVelocityMove) {
|
|
delta = current.velocity * timeStep;
|
|
}
|
|
else {
|
|
current.velocity = delta / timeStep;
|
|
}
|
|
|
|
current.velocity -= (current.velocity * gravityNormal) * gravityNormal;
|
|
|
|
if (delta == vec3_origin) {
|
|
Rest();
|
|
}
|
|
else {
|
|
// try moving into the desired direction
|
|
moveResult = idPhysics_Monster::StepMove(current.origin, current.velocity, delta);
|
|
delta.Zero();
|
|
}
|
|
}
|
|
|
|
clipModel->Link(gameLocal.clip, self, 0, current.origin, clipModel->GetAxis());
|
|
|
|
// get all the ground contacts
|
|
EvaluateContacts();
|
|
|
|
// move the monster velocity back into the world frame
|
|
current.velocity += current.pushVelocity;
|
|
current.pushVelocity.Zero();
|
|
|
|
if (IsOutsideWorld()) {
|
|
gameLocal.Warning("clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0));
|
|
Rest();
|
|
}
|
|
|
|
return (current.origin != oldOrigin);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::UpdateTime
|
|
================
|
|
*/
|
|
void idPhysics_Monster::UpdateTime(int endTimeMSec) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetTime
|
|
================
|
|
*/
|
|
int idPhysics_Monster::GetTime(void) const {
|
|
return gameLocal.time;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetImpactInfo
|
|
================
|
|
*/
|
|
void idPhysics_Monster::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_Monster::ApplyImpulse
|
|
================
|
|
*/
|
|
void idPhysics_Monster::ApplyImpulse(const int id, const idVec3& point, const idVec3& impulse) {
|
|
if (noImpact) {
|
|
return;
|
|
}
|
|
current.velocity += impulse * invMass;
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::IsAtRest
|
|
================
|
|
*/
|
|
bool idPhysics_Monster::IsAtRest(void) const {
|
|
return current.atRest >= 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetRestStartTime
|
|
================
|
|
*/
|
|
int idPhysics_Monster::GetRestStartTime(void) const {
|
|
return current.atRest;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SaveState
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SaveState(void) {
|
|
saved = current;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::RestoreState
|
|
================
|
|
*/
|
|
void idPhysics_Monster::RestoreState(void) {
|
|
current = saved;
|
|
|
|
clipModel->Link(gameLocal.clip, self, 0, current.origin, clipModel->GetAxis());
|
|
|
|
EvaluateContacts();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Player::SetOrigin
|
|
================
|
|
*/
|
|
void idPhysics_Monster::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, current.origin, clipModel->GetAxis());
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Player::SetAxis
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SetAxis(const idMat3& newAxis, int id) {
|
|
clipModel->Link(gameLocal.clip, self, 0, current.origin, newAxis);
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Translate
|
|
================
|
|
*/
|
|
void idPhysics_Monster::Translate(const idVec3& translation, int id) {
|
|
|
|
current.localOrigin += translation;
|
|
current.origin += translation;
|
|
clipModel->Link(gameLocal.clip, self, 0, current.origin, clipModel->GetAxis());
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::Rotate
|
|
================
|
|
*/
|
|
void idPhysics_Monster::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());
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SetLinearVelocity
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SetLinearVelocity(const idVec3& newLinearVelocity, int id) {
|
|
current.velocity = newLinearVelocity;
|
|
Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetLinearVelocity
|
|
================
|
|
*/
|
|
const idVec3& idPhysics_Monster::GetLinearVelocity(int id) const {
|
|
return current.velocity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SetPushed
|
|
================
|
|
*/
|
|
void idPhysics_Monster::SetPushed(int deltaTime) {
|
|
if (deltaTime <= 0) {
|
|
return;
|
|
}
|
|
|
|
// velocity with which the monster is pushed
|
|
current.pushVelocity += (current.origin - saved.origin) / (deltaTime * idMath::M_MS2SEC);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::GetPushedLinearVelocity
|
|
================
|
|
*/
|
|
const idVec3& idPhysics_Monster::GetPushedLinearVelocity(const int id) const {
|
|
return current.pushVelocity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::SetMaster
|
|
|
|
the binding is never orientated
|
|
================
|
|
*/
|
|
void idPhysics_Monster::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;
|
|
Activate();
|
|
}
|
|
}
|
|
}
|
|
|
|
const float MONSTER_VELOCITY_MAX = 4000;
|
|
const int MONSTER_VELOCITY_TOTAL_BITS = 16;
|
|
const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger(idMath::BitsForFloat(MONSTER_VELOCITY_MAX)) + 1;
|
|
const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS;
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idPhysics_Monster::WriteToSnapshot(idBitMsgDelta& msg) const {
|
|
msg.WriteFloat(current.origin[0]);
|
|
msg.WriteFloat(current.origin[1]);
|
|
msg.WriteFloat(current.origin[2]);
|
|
msg.WriteFloat(current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
msg.WriteFloat(current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
msg.WriteFloat(current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_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], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
msg.WriteDeltaFloat(0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
msg.WriteDeltaFloat(0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
msg.WriteLong(current.atRest);
|
|
msg.WriteBits(current.onGround, 1);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPhysics_Monster::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idPhysics_Monster::ReadFromSnapshot(const idBitMsgDelta& msg) {
|
|
current.origin[0] = msg.ReadFloat();
|
|
current.origin[1] = msg.ReadFloat();
|
|
current.origin[2] = msg.ReadFloat();
|
|
current.velocity[0] = msg.ReadFloat(MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
current.velocity[1] = msg.ReadFloat(MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
current.velocity[2] = msg.ReadFloat(MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_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, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
current.pushVelocity[1] = msg.ReadDeltaFloat(0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
current.pushVelocity[2] = msg.ReadDeltaFloat(0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS);
|
|
current.atRest = msg.ReadLong();
|
|
current.onGround = msg.ReadBits(1) != 0;
|
|
|
|
current.origin.FixDenormals();
|
|
current.velocity.FixDenormals();
|
|
current.localOrigin.FixDenormals();
|
|
current.pushVelocity.FixDenormals();
|
|
|
|
if (clipModel) {
|
|
clipModel->Link(gameLocal.clip, self, 0, current.origin, clipModel->GetAxis());
|
|
}
|
|
}
|