#include "common.h"
#include "patcher.h"
#include "World.h"
#include "Timer.h"
#include "ModelIndices.h"
#include "Vehicle.h"
#include "Ped.h"
#include "Object.h"
#include "Glass.h"
#include "ParticleObject.h"
#include "Particle.h"
#include "SurfaceTable.h"
#include "Physical.h"
void
CPhysical::Add(void)
{
int x, xstart, xmid, xend;
int y, ystart, ymid, yend;
CSector *s;
CPtrList *list;
CRect bounds = GetBoundRect();
xstart = CWorld::GetSectorIndexX(bounds.left);
xend = CWorld::GetSectorIndexX(bounds.right);
xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
ystart = CWorld::GetSectorIndexY(bounds.bottom);
yend = CWorld::GetSectorIndexY(bounds.top);
ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
assert(xstart >= 0);
assert(xend < NUMSECTORS_X);
assert(ystart >= 0);
assert(yend < NUMSECTORS_Y);
for(y = ystart; y <= yend; y++)
for(x = xstart; x <= xend; x++){
s = CWorld::GetSector(x, y);
if(x == xmid && y == ymid) switch(m_type){
case ENTITY_TYPE_VEHICLE:
list = &s->m_lists[ENTITYLIST_VEHICLES];
break;
case ENTITY_TYPE_PED:
list = &s->m_lists[ENTITYLIST_PEDS];
break;
case ENTITY_TYPE_OBJECT:
list = &s->m_lists[ENTITYLIST_OBJECTS];
break;
default:
assert(0);
}else switch(m_type){
case ENTITY_TYPE_VEHICLE:
list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
break;
case ENTITY_TYPE_PED:
list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
break;
case ENTITY_TYPE_OBJECT:
list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
break;
default:
assert(0);
}
CPtrNode *node = list->InsertItem(this);
assert(node);
m_entryInfoList.InsertItem(list, node, s);
}
}
void
CPhysical::Remove(void)
{
CEntryInfoNode *node, *next;
for(node = m_entryInfoList.first; node; node = next){
next = node->next;
node->list->DeleteNode(node->listnode);
m_entryInfoList.DeleteNode(node);
}
}
void
CPhysical::RemoveAndAdd(void)
{
int x, xstart, xmid, xend;
int y, ystart, ymid, yend;
CSector *s;
CPtrList *list;
CRect bounds = GetBoundRect();
xstart = CWorld::GetSectorIndexX(bounds.left);
xend = CWorld::GetSectorIndexX(bounds.right);
xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
ystart = CWorld::GetSectorIndexY(bounds.bottom);
yend = CWorld::GetSectorIndexY(bounds.top);
ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
assert(xstart >= 0);
assert(xend < NUMSECTORS_X);
assert(ystart >= 0);
assert(yend < NUMSECTORS_Y);
// we'll try to recycle nodes from here
CEntryInfoNode *next = m_entryInfoList.first;
for(y = ystart; y <= yend; y++)
for(x = xstart; x <= xend; x++){
s = CWorld::GetSector(x, y);
if(x == xmid && y == ymid) switch(m_type){
case ENTITY_TYPE_VEHICLE:
list = &s->m_lists[ENTITYLIST_VEHICLES];
break;
case ENTITY_TYPE_PED:
list = &s->m_lists[ENTITYLIST_PEDS];
break;
case ENTITY_TYPE_OBJECT:
list = &s->m_lists[ENTITYLIST_OBJECTS];
break;
}else switch(m_type){
case ENTITY_TYPE_VEHICLE:
list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
break;
case ENTITY_TYPE_PED:
list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
break;
case ENTITY_TYPE_OBJECT:
list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
break;
}
if(next){
// If we still have old nodes, use them
next->list->RemoveNode(next->listnode);
list->InsertNode(next->listnode);
next->list = list;
next->sector = s;
next = next->next;
}else{
CPtrNode *node = list->InsertItem(this);
m_entryInfoList.InsertItem(list, node, s);
}
}
// Remove old nodes we no longer need
CEntryInfoNode *node;
for(node = next; node; node = next){
next = node->next;
node->list->DeleteNode(node->listnode);
m_entryInfoList.DeleteNode(node);
}
}
CRect
CPhysical::GetBoundRect(void)
{
CVector center;
float radius;
GetBoundCentre(center);
radius = GetBoundRadius();
return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius);
}
void
CPhysical::AddToMovingList(void)
{
m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this);
}
void
CPhysical::RemoveFromMovingList(void)
{
if(m_movingListNode){
CWorld::GetMovingEntityList().DeleteNode(m_movingListNode);
m_movingListNode = nil;
}
}
/*
* Some quantities (german in parens):
*
* acceleration: distance/time^2: a
* velocity: distance/time: v (GTA: speed)
* momentum (impuls): velocity*mass: p
* impulse (kraftstoss): delta momentum, force*time: J
*
* angular equivalents:
* velocity -> angular velocity (GTA: turn speed)
* momentum -> angular momentum (drehimpuls): L = r cross p
* force -> torque (drehmoment): tau = r cross F
* mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass)
*/
CVector
CPhysical::GetSpeed(const CVector &r)
{
return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r);
}
void
CPhysical::ApplyMoveSpeed(void)
{
GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep();
}
void
CPhysical::ApplyTurnSpeed(void)
{
// Move the coordinate axes by their speed
// Note that this denormalizes the matrix
CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep();
GetRight() += CrossProduct(turnvec, GetRight());
GetForward() += CrossProduct(turnvec, GetForward());
GetUp() += CrossProduct(turnvec, GetUp());
}
void
CPhysical::ApplyMoveForce(float jx, float jy, float jz)
{
m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass);
}
void
CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz)
{
CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass);
}
void
CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz)
{
m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass);
}
void
CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz)
{
CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass);
}
void
CPhysical::ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3)
{
if(1.0f - f2 <= 0.0f)
return;
float step = min(CTimer::GetTimeStep(), 3.0f);
float strength = -0.008f*m_fMass*2.0f*step * f1 * (1.0f-f2) * f3;
ApplyMoveForce(v.x*strength, v.y*strength, v.z*strength);
ApplyTurnForce(v.x*strength, v.y*strength, v.z*strength, p.x, p.y, p.z);
}
void
CPhysical::ApplyGravity(void)
{
if(bAffectedByGravity)
m_vecMoveSpeed.z -= 0.008f * CTimer::GetTimeStep();
}
void
CPhysical::ApplyFriction(void)
{
m_vecMoveSpeed += m_vecMoveFriction;
m_vecTurnSpeed += m_vecTurnFriction;
m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
}
void
CPhysical::ApplyAirResistance(void)
{
if(m_fAirResistance > 0.1f){
float f = powf(m_fAirResistance, CTimer::GetTimeStep());
m_vecMoveSpeed *= f;
m_vecTurnSpeed *= f;
}else{
float f = powf(1.0f/(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep());
m_vecMoveSpeed *= f;
m_vecTurnSpeed *= 0.99f;
}
}
bool
CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB)
{
float eA, eB;
CPhysical *A = this;
CObject *Bobj = (CObject*)B;
bool ispedcontactA = false;
bool ispedcontactB = false;
float timestepA;
if(B->bPedPhysics){
timestepA = 10.0f;
if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A)
ispedcontactA = true;
}else
timestepA = A->m_phy_flagA1 ? 2.0f : 1.0f;
float timestepB;
if(A->bPedPhysics){
if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() &&
(B->m_status == STATUS_ABANDONED || B->m_status == STATUS_WRECKED || A->bHasHitWall))
timestepB = 2200.0f / B->m_fMass;
else
timestepB = 10.0f;
if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B)
ispedcontactB = true;
}else
timestepB = B->m_phy_flagA1 ? 2.0f : 1.0f;
float speedA, speedB;
if(B->bIsStatic){
if(A->bPedPhysics){
speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
if(speedA < 0.0f){
if(B->IsObject()){
impulseA = -speedA * A->m_fMass;
impulseB = impulseA;
if(impulseA > Bobj->m_fUprootLimit){
if(IsGlass(B->GetModelIndex()))
CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
else if(!B->bInfiniteMass)
B->bIsStatic = false;
}else{
if(IsGlass(B->GetModelIndex()))
CGlass::WindowRespondsToSoftCollision(B, impulseA);
if(!A->bInfiniteMass)
A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
return true;
}
}else if(!B->bInfiniteMass)
B->bIsStatic = false;
if(B->bInfiniteMass){
impulseA = -speedA * A->m_fMass;
impulseB = 0.0f;
if(!A->bInfiniteMass)
A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
return true;
}
}
}else{
CVector pointposA = colpoint.point - A->GetPosition();
speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
if(speedA < 0.0f){
if(B->IsObject()){
if(A->bHasHitWall)
eA = -1.0f;
else
eA = -(1.0f + A->m_fElasticity);
impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal);
impulseB = impulseA;
if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){
Bobj->ObjectDamage(impulseA);
if(!B->bUsesCollision){
if(!A->bInfiniteMass){
A->ApplyMoveForce(colpoint.normal*0.2f*impulseA);
A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA);
}
return false;
}
}
if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) &&
!B->bInfiniteMass){
if(IsGlass(B->GetModelIndex()))
CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
else
B->bIsStatic = false;
int16 model = B->GetModelIndex();
if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){
CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true);
Bobj->bHasBeenDamaged = true;
}else if(B->IsObject() && model != MI_EXPLODINGBARREL && model != MI_PETROLPUMP)
Bobj->bHasBeenDamaged = true;
}else{
if(IsGlass(B->GetModelIndex()))
CGlass::WindowRespondsToSoftCollision(B, impulseA);
CVector f = colpoint.normal * impulseA;
if(A->IsVehicle() && colpoint.normal.z < 0.7f)
f.z *= 0.3f;
if(!A->bInfiniteMass){
A->ApplyMoveForce(f);
if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque)
A->ApplyTurnForce(f, pointposA);
}
return true;
}
}else if(!B->bInfiniteMass)
B->bIsStatic = false;
}
}
if(B->bIsStatic)
return false;
if(!B->bInfiniteMass)
B->AddToMovingList();
}
// B is not static
if(A->bPedPhysics && B->bPedPhysics){
// negative if A is moving towards B
speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
// positive if B is moving towards A
// not interested in how much B moves into A apparently?
// only interested in cases where A collided into B
speedB = max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal));
// A has moved into B
if(speedA < speedB){
if(!A->bHasHitWall)
speedB -= (speedA - speedB) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
impulseA = (speedB-speedA) * A->m_fMass * timestepA;
if(!A->bInfiniteMass)
A->ApplyMoveForce(colpoint.normal*(impulseA/timestepA));
return true;
}
}else if(A->bPedPhysics){
CVector pointposB = colpoint.point - B->GetPosition();
speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
float a = A->m_fMass*timestepA;
float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
float speedSum = (b*speedB + a*speedA)/(a + b);
if(speedA < speedSum){
if(A->bHasHitWall)
eA = speedSum;
else
eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
if(B->bHasHitWall)
eB = speedSum;
else
eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
impulseA = (eA - speedA) * a;
impulseB = -(eB - speedB) * b;
CVector fA = colpoint.normal*(impulseA/timestepA);
CVector fB = colpoint.normal*(-impulseB/timestepB);
if(!A->bInfiniteMass){
if(fA.z < 0.0f) fA.z = 0.0f;
if(ispedcontactB){
fA.x *= 2.0f;
fA.y *= 2.0f;
}
A->ApplyMoveForce(fA);
}
if(!B->bInfiniteMass && !ispedcontactB){
B->ApplyMoveForce(fB);
B->ApplyTurnForce(fB, pointposB);
}
return true;
}
}else if(B->bPedPhysics){
CVector pointposA = colpoint.point - A->GetPosition();
speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
float b = B->m_fMass*timestepB;
float speedSum = (b*speedB + a*speedA)/(a + b);
if(speedA < speedSum){
if(A->bHasHitWall)
eA = speedSum;
else
eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
if(B->bHasHitWall)
eB = speedSum;
else
eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
impulseA = (eA - speedA) * a;
impulseB = -(eB - speedB) * b;
CVector fA = colpoint.normal*(impulseA/timestepA);
CVector fB = colpoint.normal*(-impulseB/timestepB);
if(!A->bInfiniteMass && !ispedcontactA){
if(fA.z < 0.0f) fA.z = 0.0f;
A->ApplyMoveForce(fA);
A->ApplyTurnForce(fA, pointposA);
}
if(!B->bInfiniteMass){
if(fB.z < 0.0f){
fB.z = 0.0f;
if(fabs(speedA) < 0.01f)
fB *= 0.5f;
}
if(ispedcontactA){
fB.x *= 2.0f;
fB.y *= 2.0f;
}
B->ApplyMoveForce(fB);
}
return true;
}
}else{
CVector pointposA = colpoint.point - A->GetPosition();
CVector pointposB = colpoint.point - B->GetPosition();
speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
float speedSum = (b*speedB + a*speedA)/(a + b);
if(speedA < speedSum){
if(A->bHasHitWall)
eA = speedSum;
else
eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
if(B->bHasHitWall)
eB = speedSum;
else
eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
impulseA = (eA - speedA) * a;
impulseB = -(eB - speedB) * b;
CVector fA = colpoint.normal*(impulseA/timestepA);
CVector fB = colpoint.normal*(-impulseB/timestepB);
if(A->IsVehicle() && !A->bHasHitWall){
fA.x *= 1.4f;
fA.y *= 1.4f;
if(colpoint.normal.z < 0.7f)
fA.z *= 0.3f;
if(A->m_status == STATUS_PLAYER)
pointposA *= 0.8f;
if(CWorld::bNoMoreCollisionTorque){
A->ApplyFrictionMoveForce(fA*-0.3f);
A->ApplyFrictionTurnForce(fA*-0.3f, pointposA);
}
}
if(B->IsVehicle() && !B->bHasHitWall){
fB.x *= 1.4f;
fB.y *= 1.4f;
if(colpoint.normal.z < 0.7f)
fB.z *= 0.3f;
if(B->m_status == STATUS_PLAYER)
pointposB *= 0.8f;
if(CWorld::bNoMoreCollisionTorque){
// BUG: the game actually uses A here, but this can't be right
B->ApplyFrictionMoveForce(fB*-0.3f);
B->ApplyFrictionTurnForce(fB*-0.3f, pointposB);
}
}
if(!A->bInfiniteMass){
A->ApplyMoveForce(fA);
A->ApplyTurnForce(fA, pointposA);
}
if(!B->bInfiniteMass){
if(B->bIsInSafePosition)
B->UnsetIsInSafePosition();
B->ApplyMoveForce(fB);
B->ApplyTurnForce(fB, pointposB);
}
return true;
}
}
return false;
}
bool
CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed)
{
float normalSpeed;
float e;
CVector speed;
CVector vImpulse;
if(bPedPhysics){
normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
if(normalSpeed < 0.0f){
impulse = -normalSpeed * m_fMass;
ApplyMoveForce(colpoint.normal * impulse);
return true;
}
}else{
CVector pointpos = colpoint.point - GetPosition();
speed = GetSpeed(pointpos);
normalSpeed = DotProduct(speed, colpoint.normal);
if(normalSpeed < 0.0f){
float minspeed = 0.0104f * CTimer::GetTimeStep();
if((IsObject() || IsVehicle() && GetUp().z < -0.3f) &&
!bHasContacted &&
fabs(m_vecMoveSpeed.x) < minspeed &&
fabs(m_vecMoveSpeed.y) < minspeed &&
fabs(m_vecMoveSpeed.z) < minspeed*2.0f)
e = -1.0f;
else
e = -(m_fElasticity + 1.0f);
impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal);
// ApplyMoveForce
vImpulse = colpoint.normal*impulse;
if(IsVehicle() &&
(!bHasHitWall ||
!(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass))))
moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass);
else
moveSpeed += vImpulse * (1.0f/m_fMass);
// ApplyTurnForce
CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
CVector turnimpulse = CrossProduct(pointpos-com, vImpulse);
turnSpeed += turnimpulse*(1.0f/m_fTurnMass);
return true;
}
}
return false;
}
bool
CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
{
CVector speedA, speedB;
float normalSpeedA, normalSpeedB;
CVector vOtherSpeedA, vOtherSpeedB;
float fOtherSpeedA, fOtherSpeedB;
float speedSum;
CVector frictionDir;
float impulseA, impulseB;
float impulseLimit;
CPhysical *A = this;
if(A->bPedPhysics && B->bPedPhysics){
normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
fOtherSpeedA = vOtherSpeedA.Magnitude();
fOtherSpeedB = vOtherSpeedB.Magnitude();
frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass);
if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit; // BUG: game has A's clamp again here, but this can't be right
A->ApplyFrictionMoveForce(frictionDir*impulseA);
B->ApplyFrictionMoveForce(frictionDir*impulseB);
return true;
}
}else if(A->bPedPhysics){
if(B->IsVehicle())
return false;
CVector pointposB = colpoint.point - B->GetPosition();
speedB = B->GetSpeed(pointposB);
normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
normalSpeedB = DotProduct(speedB, colpoint.normal);
vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
fOtherSpeedA = vOtherSpeedA.Magnitude();
fOtherSpeedB = vOtherSpeedB.Magnitude();
frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
float massB = B->GetMass(pointposB, frictionDir);
speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass);
if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
impulseB = (speedSum - fOtherSpeedB) * massB;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit;
A->ApplyFrictionMoveForce(frictionDir*impulseA);
B->ApplyFrictionMoveForce(frictionDir*impulseB);
B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
return true;
}
}else if(B->bPedPhysics){
if(A->IsVehicle())
return false;
CVector pointposA = colpoint.point - A->GetPosition();
speedA = A->GetSpeed(pointposA);
normalSpeedA = DotProduct(speedA, colpoint.normal);
normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
fOtherSpeedA = vOtherSpeedA.Magnitude();
fOtherSpeedB = vOtherSpeedB.Magnitude();
frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
float massA = A->GetMass(pointposA, frictionDir);
speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA);
if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * massA;
impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit;
A->ApplyFrictionMoveForce(frictionDir*impulseA);
A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
B->ApplyFrictionMoveForce(frictionDir*impulseB);
return true;
}
}else{
CVector pointposA = colpoint.point - A->GetPosition();
CVector pointposB = colpoint.point - B->GetPosition();
speedA = A->GetSpeed(pointposA);
speedB = B->GetSpeed(pointposB);
normalSpeedA = DotProduct(speedA, colpoint.normal);
normalSpeedB = DotProduct(speedB, colpoint.normal);
vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
fOtherSpeedA = vOtherSpeedA.Magnitude();
fOtherSpeedB = vOtherSpeedB.Magnitude();
frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
float massA = A->GetMass(pointposA, frictionDir);
float massB = B->GetMass(pointposB, frictionDir);
speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA);
if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * massA;
impulseB = (speedSum - fOtherSpeedB) * massB;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit;
A->ApplyFrictionMoveForce(frictionDir*impulseA);
A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
B->ApplyFrictionMoveForce(frictionDir*impulseB);
B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
return true;
}
}
return false;
}
bool
CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint)
{
CVector speed;
float normalSpeed;
CVector vOtherSpeed;
float fOtherSpeed;
CVector frictionDir;
float fImpulse;
float impulseLimit;
if(bPedPhysics){
normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed;
fOtherSpeed = vOtherSpeed.Magnitude();
if(fOtherSpeed > 0.0f){
frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
// not really impulse but speed
// maybe use ApplyFrictionMoveForce instead?
fImpulse = -fOtherSpeed;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass;
if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
CVector vImpulse = frictionDir*fImpulse;
m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f);
return true;
}
}else{
CVector pointpos = colpoint.point - GetPosition();
speed = GetSpeed(pointpos);
normalSpeed = DotProduct(speed, colpoint.normal);
vOtherSpeed = speed - colpoint.normal*normalSpeed;
fOtherSpeed = vOtherSpeed.Magnitude();
if(fOtherSpeed > 0.0f){
frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
fImpulse = -fOtherSpeed * m_fMass;
impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5f;
if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
ApplyFrictionMoveForce(frictionDir*fImpulse);
ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos);
if(fOtherSpeed > 0.1f &&
colpoint.surfaceB != SURFACE_2 && colpoint.surfaceB != SURFACE_4 &&
CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){
CVector v = frictionDir * fOtherSpeed * 0.25f;
for(int i = 0; i < 4; i++)
CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v);
}
return true;
}
}
return false;
}
// ProcessCollision calls
// CheckCollision
// CheckCollision_SimpleCar
// CheckCollision calls
// ProcessCollisionSectorList
// CheckCollision_SimpleCar
// ProcessCollisionSectorList_SimpleCar
// ProcessShift calls
// ProcessCollisionSectorList
// ProcessShiftSectorList
void
CPhysical::AddCollisionRecord(CEntity *ent)
{
AddCollisionRecord_Treadable(ent);
this->bHasCollided = true;
ent->bHasCollided = true;
if(IsVehicle() && ent->IsVehicle()){
if(((CVehicle*)this)->m_nAlarmState == -1)
((CVehicle*)this)->m_nAlarmState = 15000;
if(((CVehicle*)ent)->m_nAlarmState == -1)
((CVehicle*)ent)->m_nAlarmState = 15000;
}
if(bUseCollisionRecords){
int i;
for(i = 0; i < m_nCollisionRecords; i++)
if(m_aCollisionRecords[i] == ent)
return;
if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS)
m_aCollisionRecords[m_nCollisionRecords++] = ent;
m_nLastTimeCollided = CTimer::GetTimeInMilliseconds();
}
}
void
CPhysical::AddCollisionRecord_Treadable(CEntity *ent)
{
if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){
CTreadable *t = (CTreadable*)ent;
if(t->m_nodeIndicesPeds[0] >= 0 ||
t->m_nodeIndicesPeds[1] >= 0 ||
t->m_nodeIndicesPeds[2] >= 0 ||
t->m_nodeIndicesPeds[3] >= 0)
m_pedTreadable = t;
if(t->m_nodeIndicesCars[0] >= 0 ||
t->m_nodeIndicesCars[1] >= 0 ||
t->m_nodeIndicesCars[2] >= 0 ||
t->m_nodeIndicesCars[3] >= 0)
m_carTreadable = t;
}
}
bool
CPhysical::GetHasCollidedWith(CEntity *ent)
{
int i;
if(bUseCollisionRecords)
for(i = 0; i < m_nCollisionRecords; i++)
if(m_aCollisionRecords[i] == ent)
return true;
return false;
}
bool
CPhysical::ProcessShiftSectorList(CPtrList *lists)
{
int i, j;
CPtrList *list;
CPtrNode *node;
CPhysical *A, *B;
CObject *Bobj;
bool canshift;
CVector center;
float radius;
int numCollisions;
int mostColliding;
CColPoint colpoints[32];
CVector shift = { 0.0f, 0.0f, 0.0f };
bool doShift = false;
CEntity *boat = nil;
bool skipShift;
A = this;
A->GetBoundCentre(center);
radius = A->GetBoundRadius();
for(i = 0; i <= ENTITYLIST_PEDS_OVERLAP; i++){
list = &lists[i];
for(node = list->first; node; node = node->next){
B = (CPhysical*)node->item;
Bobj = (CObject*)B;
skipShift = false;
if(B->IsBuilding() ||
B->IsObject() && B->bInfiniteMass)
canshift = true;
else
canshift = A->IsPed() &&
B->IsObject() && B->bInfiniteMass && !Bobj->bHasBeenDamaged;
if(B == A ||
B->m_scanCode == CWorld::GetCurrentScanCode() ||
!B->bUsesCollision ||
(A->bHasHitWall && !canshift) ||
!B->GetIsTouching(center, radius))
continue;
// This could perhaps be done a bit nicer
if(B->IsBuilding())
skipShift = false;
else if(IsTrafficLight(A->GetModelIndex()) &&
(B->IsVehicle() || B->IsPed()) &&
A->GetUp().z < 0.66f)
skipShift = true;
else if((A->IsVehicle() || A->IsPed()) &&
B->GetUp().z < 0.66f &&
IsTrafficLight(B->GetModelIndex()))
skipShift = true;
else if(A->IsObject() && B->IsVehicle()){
CObject *Aobj = (CObject*)A;
if(Aobj->ObjectCreatedBy != TEMP_OBJECT &&
!Aobj->bHasBeenDamaged &&
Aobj->bIsStatic){
if(Aobj->m_pCollidingVehicle == B)
Aobj->m_pCollidingVehicle = nil;
}else if(Aobj->m_pCollidingVehicle != B){
CMatrix inv;
CVector size = CModelInfo::GetModelInfo(A->GetModelIndex())->GetColModel()->boundingBox.GetSize();
size = A->GetMatrix() * size;
if(size.z < B->GetPosition().z ||
(Invert(B->GetMatrix(), inv) * size).z < 0.0f){
skipShift = true;
Aobj->m_pCollidingVehicle = (CVehicle*)B;
}
}
}else if(B->IsObject() && A->IsVehicle()){
CObject *Bobj = (CObject*)B;
if(Bobj->ObjectCreatedBy != TEMP_OBJECT &&
!Bobj->bHasBeenDamaged &&
Bobj->bIsStatic){
if(Bobj->m_pCollidingVehicle == A)
Bobj->m_pCollidingVehicle = nil;
}else if(Bobj->m_pCollidingVehicle != A){
CMatrix inv;
CVector size = CModelInfo::GetModelInfo(B->GetModelIndex())->GetColModel()->boundingBox.GetSize();
size = B->GetMatrix() * size;
if(size.z < A->GetPosition().z ||
(Invert(A->GetMatrix(), inv) * size).z < 0.0f){
skipShift = true;
Bobj->m_pCollidingVehicle = (CVehicle*)A;
}
}
}else if(IsBodyPart(A->GetModelIndex()) && B->IsPed())
skipShift = true;
else if(A->IsPed() && IsBodyPart(B->GetModelIndex()))
skipShift = true;
else if(A->IsPed() && ((CPed*)A)->m_pCollidingVehicle == B ||
B->IsPed() && ((CPed*)B)->m_pCollidingVehicle == A ||
A->GetModelIndex() == MI_RCBANDIT && B->IsVehicle() ||
B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle()))
skipShift = true;
if(skipShift)
continue;
B->m_scanCode = CWorld::GetCurrentScanCode();
numCollisions = A->ProcessEntityCollision(B, colpoints);
if(numCollisions <= 0)
continue;
mostColliding = 0;
for(j = 1; j < numCollisions; j++)
if(colpoints[j].depth > colpoints[mostColliding].depth)
mostColliding = j;
if(CWorld::bSecondShift)
for(j = 0; j < numCollisions; j++)
shift += colpoints[j].normal * colpoints[j].depth * 1.5f/numCollisions;
else
for(j = 0; j < numCollisions; j++)
shift += colpoints[j].normal * colpoints[j].depth * 1.2f/numCollisions;
if(A->IsVehicle() && B->IsVehicle()){
CVector dir = A->GetPosition() - B->GetPosition();
dir.Normalise();
if(dir.z < 0.0f && dir.z < A->GetForward().z && dir.z < A->GetRight().z)
dir.z = min(0.0f, min(A->GetForward().z, A->GetRight().z));
shift += dir * colpoints[mostColliding].depth * 0.5f;
}else if(A->IsPed() && B->IsVehicle() && ((CVehicle*)B)->IsBoat()){
CVector dir = colpoints[mostColliding].normal;
float f = min(fabs(dir.z), 0.9f);
dir.z = 0.0f;
dir.Normalise();
shift += dir * colpoints[mostColliding].depth / (1.0f - f);
boat = B;
}else if(B->IsPed() && A->IsVehicle() && ((CVehicle*)A)->IsBoat()){
CVector dir = colpoints[mostColliding].normal * -1.0f;
float f = min(fabs(dir.z), 0.9f);
dir.z = 0.0f;
dir.Normalise();
B->GetPosition() += dir * colpoints[mostColliding].depth / (1.0f - f);
// BUG? how can that ever happen? A is a Ped
if(B->IsVehicle())
B->ProcessEntityCollision(A, colpoints);
}else{
if(CWorld::bSecondShift)
shift += colpoints[mostColliding].normal * colpoints[mostColliding].depth * 0.4f;
else
shift += colpoints[mostColliding].normal * colpoints[mostColliding].depth * 0.2f;
}
doShift = true;
}
}
if(!doShift)
return false;
GetPosition() += shift;
if(boat)
ProcessEntityCollision(boat, colpoints);
return true;
}
void
CPhysical::ProcessControl(void)
{
if(!IsPed())
m_phy_flagA8 = false;
bHasContacted = false;
bIsInSafePosition = false;
bWasPostponed = false;
bHasHitWall = false;
if(m_status == STATUS_SIMPLE)
return;
m_nCollisionRecords = 0;
bHasCollided = false;
m_nCollisionPieceType = 0;
m_fCollisionImpulse = 0.0f;
m_pCollidingEntity = nil;
if(!bIsStuck){
if(IsObject() ||
IsPed() && !bPedPhysics){
m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f;
m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f;
float step = CTimer::GetTimeStep() * 0.003;
if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step &&
m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){
m_nStaticFrames++;
if(m_nStaticFrames > 10){
m_nStaticFrames = 10;
bIsStatic = true;
m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
m_vecMoveFriction = m_vecMoveSpeed;
m_vecTurnFriction = m_vecTurnSpeed;
return;
}
}else
m_nStaticFrames = 0;
}
}
ApplyGravity();
ApplyFriction();
ApplyAirResistance();
}
STARTPATCHES
InjectHook(0x4951F0, &CPhysical::Add_, PATCH_JUMP);
InjectHook(0x4954B0, &CPhysical::Remove_, PATCH_JUMP);
InjectHook(0x495540, &CPhysical::RemoveAndAdd, PATCH_JUMP);
InjectHook(0x495F10, &CPhysical::ProcessControl_, PATCH_JUMP);
InjectHook(0x4958F0, &CPhysical::AddToMovingList, PATCH_JUMP);
InjectHook(0x495940, &CPhysical::RemoveFromMovingList, PATCH_JUMP);
InjectHook(0x497180, &CPhysical::AddCollisionRecord, PATCH_JUMP);
InjectHook(0x4970C0, &CPhysical::AddCollisionRecord_Treadable, PATCH_JUMP);
InjectHook(0x497240, &CPhysical::GetHasCollidedWith, PATCH_JUMP);
InjectHook(0x49DA10, &CPhysical::ProcessShiftSectorList, PATCH_JUMP);
#define F3 float, float, float
InjectHook(0x495B10, &CPhysical::ApplyMoveSpeed, PATCH_JUMP);
InjectHook(0x497280, &CPhysical::ApplyTurnSpeed, PATCH_JUMP);
InjectHook(0x4959A0, (void (CPhysical::*)(F3))&CPhysical::ApplyMoveForce, PATCH_JUMP);
InjectHook(0x495A10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyTurnForce, PATCH_JUMP);
InjectHook(0x495D90, (void (CPhysical::*)(F3))&CPhysical::ApplyFrictionMoveForce, PATCH_JUMP);
InjectHook(0x495E10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyFrictionTurnForce, PATCH_JUMP);
InjectHook(0x499890, &CPhysical::ApplySpringCollision, PATCH_JUMP);
InjectHook(0x495B50, &CPhysical::ApplyGravity, PATCH_JUMP);
InjectHook(0x495B80, (void (CPhysical::*)(void))&CPhysical::ApplyFriction, PATCH_JUMP);
InjectHook(0x495C20, &CPhysical::ApplyAirResistance, PATCH_JUMP);
InjectHook(0x4973A0, &CPhysical::ApplyCollision, PATCH_JUMP);
InjectHook(0x4992A0, &CPhysical::ApplyCollisionAlt, PATCH_JUMP);
InjectHook(0x499BE0, (bool (CPhysical::*)(float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
InjectHook(0x49A180, (bool (CPhysical::*)(CPhysical*, float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
ENDPATCHES