d158e10c40
This is not complete; TODO: * Remove "demoAvailable" variable from car struct * Remove "UNREGISTERED" string from main menu * etc.
1333 lines
48 KiB
C++
Executable File
1333 lines
48 KiB
C++
Executable File
//carphysics.cpp
|
|
//simulation of all forces and tourques moving the cars around
|
|
|
|
#include <math.h>
|
|
|
|
#include <AmbrosiaTools/stdtypes.h>
|
|
|
|
#include "vectors.h"
|
|
#include "entities.h"
|
|
#include "carphysics.h"
|
|
#include "gamemem.h"
|
|
#include "gameframe.h"
|
|
#include "roads.h"
|
|
#include "text.h"
|
|
#include "particles.h"
|
|
#include "tracks.h"
|
|
#include "collision.h"
|
|
#include "config.h"
|
|
#include "environment.h"
|
|
#include "gamesound.h"
|
|
#include "renderframe.h"
|
|
#include "networkphysics.h"
|
|
|
|
#define kMaxSmoke 120.0
|
|
|
|
#define kIndicatorTime 0.5
|
|
#define kMaxTwistAngle (PI/12)
|
|
#define kSpinRotations 10.0
|
|
#define kTrackPrecision 5 //each nTH frame a skid mark is generated
|
|
|
|
void InstallCarAddOns(tCarPhysics *phys)
|
|
{
|
|
tCarDefinition *car=&(phys->car);
|
|
for(int i=0;i<car->numAddOns;i++)
|
|
//is this add-on installed?
|
|
if((phys->addOns>>i)&1)
|
|
if(!(phys->addOns&car->addOns[i].conflicts))
|
|
{
|
|
car->mass+=car->addOns[i].mass;
|
|
car->massCenter=car->massCenter+car->addOns[i].massCenter;
|
|
car->rearLift+=car->addOns[i].rearLift;
|
|
car->frontLift+=car->addOns[i].frontLift;
|
|
car->frontAirResistance+=car->addOns[i].frontAirResistance;
|
|
|
|
if(car->addOns[i].powerPercent>0)
|
|
{
|
|
car->power*=car->addOns[i].powerPercent;
|
|
car->powerRPM*=car->addOns[i].powerPercent;
|
|
}
|
|
if(car->addOns[i].frontSwayBar>0)
|
|
car->frontSwayBar=car->addOns[i].frontSwayBar;
|
|
if(car->addOns[i].exhaustFire>0)
|
|
car->exhaustFire=car->addOns[i].exhaustFire;
|
|
if(car->addOns[i].rearSwayBar>0)
|
|
car->rearSwayBar=car->addOns[i].rearSwayBar;
|
|
if(car->addOns[i].damperStrength>0)
|
|
car->damperStrength=car->addOns[i].damperStrength;
|
|
if(car->addOns[i].engineInertia>0)
|
|
car->engineInertia=car->addOns[i].engineInertia;
|
|
if(car->addOns[i].topGearRatio>0)
|
|
car->gearRatios[car->numGears-1]=car->addOns[i].topGearRatio;
|
|
if(car->addOns[i].maxRPM>0)
|
|
car->maxRPM=car->addOns[i].maxRPM;
|
|
if(car->addOns[i].track>0)
|
|
{
|
|
car->wheels[0].pos.x-=car->addOns[i].track;
|
|
car->wheels[1].pos.x+=car->addOns[i].track;
|
|
car->wheels[2].pos.x-=car->addOns[i].track;
|
|
car->wheels[3].pos.x+=car->addOns[i].track;
|
|
}
|
|
if(car->addOns[i].frontWheelWidth>0)
|
|
{
|
|
car->wheels[0].width=car->addOns[i].frontWheelWidth;
|
|
car->wheels[1].width=car->addOns[i].frontWheelWidth;
|
|
}
|
|
if(car->addOns[i].rearWheelWidth>0)
|
|
{
|
|
car->wheels[2].width=car->addOns[i].rearWheelWidth;
|
|
car->wheels[3].width=car->addOns[i].rearWheelWidth;
|
|
}
|
|
if(car->addOns[i].frontMaxSuspension>0)
|
|
{
|
|
car->wheels[0].maxSuspension=car->addOns[i].frontMaxSuspension;
|
|
car->wheels[1].maxSuspension=car->addOns[i].frontMaxSuspension;
|
|
}
|
|
if(car->addOns[i].rearMaxSuspension>0)
|
|
{
|
|
car->wheels[2].maxSuspension=car->addOns[i].rearMaxSuspension;
|
|
car->wheels[3].maxSuspension=car->addOns[i].rearMaxSuspension;
|
|
}
|
|
if(car->addOns[i].torquePercent>0)
|
|
car->torque*=car->addOns[i].torquePercent;
|
|
if(car->addOns[i].differentialLockCoefficient>0)
|
|
car->differentialLockCoefficient=car->addOns[i].differentialLockCoefficient;
|
|
if(car->addOns[i].finalDriveRatio>0)
|
|
car->finalDriveRatio=car->addOns[i].finalDriveRatio;
|
|
if(car->addOns[i].gearSwitchTime>0)
|
|
car->gearSwitchTime=car->addOns[i].gearSwitchTime;
|
|
}
|
|
}
|
|
|
|
|
|
float WheelGroundDistance(tGameEntity *carEntity,tVector3 wheelPos,tVector3 yDir,tWheelDefinition *wheel,tVector3 *groundNormal,float oldHeight,int *surfaceType,float *bump)
|
|
//returns the distance between the wheel at wheelPos and the ground.
|
|
//gives the normal of the ground under the wheel in groundNormal
|
|
//and the surface type of the ground in surfaceType
|
|
{
|
|
tVector3 wheelGroundPoint=wheelPos-yDir*(wheel->radius+oldHeight);
|
|
return GetGroundOffsetAndBump(wheelGroundPoint,&carEntity->lastRoadIndex,groundNormal,surfaceType,bump)+oldHeight;
|
|
}
|
|
|
|
void CalcWheelPositions(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
//Calculates the positions and directions/angles of all wheels of the car passed in carEntity.
|
|
{
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
wheels[i].pos=car->wheels[i].pos*carEntity->dir;
|
|
wheels[i].velo=((wheels[i].pos*carEntity->rVelo)-wheels[i].pos)*kFPS+carEntity->velo;
|
|
wheels[i].oldAngle=phys->wheels[i].angle;
|
|
if(phys->wheels[i].rotation>=2*PI)
|
|
phys->wheels[i].rotation-=2*PI;
|
|
else if(phys->wheels[i].rotation<0)
|
|
phys->wheels[i].rotation+=2*PI;
|
|
phys->wheels[i].angle=car->wheels[i].maxAngle*phys->steering;//*(0.5+0.5*sqr(phys->steering));
|
|
}
|
|
}
|
|
|
|
void CalcWheelGroundOffsets(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
//Calculates the spring supression for all wheels of the car passed in carEntity.
|
|
{
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
float dist=WheelGroundDistance(carEntity,wheels[i].pos+carEntity->pos,*MatrixGetYVector(carEntity->dir),&car->wheels[i],&wheels[i].groundNormal,phys->wheels[i].suspension,&phys->wheels[i].surfaceType,&(wheels[i].bump));
|
|
if(wheels[i].onGround=dist<car->wheels[i].maxSuspension)
|
|
{
|
|
phys->wheels[i].suspension=dist;
|
|
if(phys->wheels[i].suspension<0)
|
|
phys->wheels[i].suspension=0;
|
|
}
|
|
else
|
|
phys->wheels[i].suspension=car->wheels[i].maxSuspension;
|
|
}
|
|
}
|
|
|
|
|
|
void CalcSwayBars(tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
//Calculates the Forces generated by the cars Anti-Sway-Bars.
|
|
{
|
|
float force;
|
|
int arcade=(gGameInfo->arcade==kGameModeArcade||gGameInfo->arcade==kGameModeTurbo);
|
|
|
|
force=(phys->wheels[1].suspension-phys->wheels[0].suspension)*(car->frontSwayBar+(arcade?car->mass*10:0));
|
|
wheels[0].suspensionForce+=force;
|
|
wheels[1].suspensionForce-=force;
|
|
force=(phys->wheels[3].suspension-phys->wheels[2].suspension)*(car->rearSwayBar+(arcade?car->mass*10:0));
|
|
wheels[2].suspensionForce+=force;
|
|
wheels[3].suspensionForce-=force;
|
|
}
|
|
|
|
|
|
void CalcWheelSupension(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
//Calculates spring and damper forces
|
|
{
|
|
//Calculate the distance of each wheel from car's center of gravity
|
|
float totalInvMassDistance=0;
|
|
float invMassDistance[kMaxWheels];
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
invMassDistance[i]=1/~(car->massCenter-car->wheels[i].pos);
|
|
totalInvMassDistance+=invMassDistance[i];
|
|
}
|
|
|
|
//factor used to calculate the spring forces
|
|
float totalSpringForceFactor=car->mass*2*gEnvironment->gravity;
|
|
|
|
tVector3 zDir=*MatrixGetZVector(carEntity->dir);
|
|
float airRes=~carEntity->velo*0.5*fabs(zDir*carEntity->velo)*gEnvironment->airDensity;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
//from the distance to CG, determine the fraction of the cars mass this wheel has to carry.
|
|
//not entierly correct, but should suffice for most cases
|
|
float massFraction=invMassDistance[i]/totalInvMassDistance;
|
|
float calcSuspension=(phys->wheels[i].suspension/car->wheels[i].maxSuspension)*2-1;
|
|
calcSuspension=calcSuspension*calcSuspension*calcSuspension;
|
|
calcSuspension=(calcSuspension*0.5+0.5)*car->wheels[i].maxSuspension;
|
|
calcSuspension=phys->wheels[i].suspension;
|
|
//calculate spring force, based on totalSpringForceFactor, massFraction, and the ratio of spring compression
|
|
//wheels[i].suspensionForce=(car->wheels[i].maxSuspension-phys->wheels[i].suspension)*totalSpringForceFactor*massFraction/car->wheels[i].maxSuspension;
|
|
wheels[i].suspensionForce=(car->wheels[i].maxSuspension-calcSuspension)*totalSpringForceFactor*massFraction/car->wheels[i].maxSuspension;
|
|
|
|
//if(gGameInfo->arcade&&gFrameCount*kFrameTime<=1)wheels[i].suspensionForce*=gFrameCount*kFrameTime;
|
|
|
|
//if the springs are fully compressed, we need to add another force, applied by the ground,
|
|
//which keeps the wheel from falling into the ground
|
|
if(!phys->wheels[i].suspension)
|
|
{
|
|
float aerodynamicForce=airRes*(i<2?car->frontLift:car->rearLift);
|
|
|
|
float fGroundForce=-wheels[i].velo*wheels[i].groundNormal*kFPS*car->mass/car->numWheels-aerodynamicForce/2;
|
|
if(fGroundForce>car->mass/car->numWheels*gEnvironment->gravity-aerodynamicForce/2)
|
|
fGroundForce=car->mass/car->numWheels*gEnvironment->gravity-aerodynamicForce/2;
|
|
if(fGroundForce>0)
|
|
wheels[i].suspensionForce+=fGroundForce;
|
|
}
|
|
|
|
//the spring and ground force make up the normal force to the wheel, used to calculate tire traction
|
|
wheels[i].normalForce=wheels[i].suspensionForce;
|
|
|
|
//and now, calculate damper forces
|
|
if(wheels[i].onGround){
|
|
//wheels speed WRT the ground
|
|
float wheelYVelo=wheels[i].velo*wheels[i].groundNormal;
|
|
|
|
//friction caused by the suspension (constant, not dependant on the speed of wheel Y movement)
|
|
float suspensionResistance=sign(wheelYVelo)*car->supsensionFriction*massFraction;
|
|
|
|
//force caused by dampers (a linear function of wheel Y velocity)
|
|
//dampers stronger in first second to avoid arcade mode shake bug.
|
|
suspensionResistance+=wheelYVelo*car->damperStrength*massFraction*((gGameInfo->arcade&&gFrameCount*kFrameTime<=1)?5:1);
|
|
|
|
//do not apply forces which are higher than necessary to damp all motion
|
|
if(fabs(suspensionResistance)>fabs(wheelYVelo*kFPS*car->mass/car->numWheels))
|
|
suspensionResistance=wheelYVelo*kFPS*car->mass/car->numWheels;
|
|
|
|
//to prevent unexpected behavior when loosing ground contact (ie. jumping with the car)
|
|
if(suspensionResistance>2*car->mass)
|
|
suspensionResistance=2*car->mass;
|
|
|
|
//apply damper resistance
|
|
wheels[i].suspensionForce-=suspensionResistance;
|
|
}
|
|
}
|
|
|
|
CalcSwayBars(car,phys,wheels);
|
|
|
|
int wall=true;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
if(wheels[i].groundNormal.y!=0)
|
|
wall=false;
|
|
if(wall)
|
|
if(wheels[1].pos.y>wheels[0].pos.y)
|
|
{
|
|
wheels[1].suspensionForce+=100000;
|
|
wheels[3].suspensionForce+=100000;
|
|
}
|
|
else
|
|
{
|
|
wheels[0].suspensionForce+=100000;
|
|
wheels[2].suspensionForce+=100000;
|
|
}
|
|
}
|
|
|
|
|
|
tVector3 PacejkaCalcWheelRoadForces(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels,int i,float engineTorque,float frictionTorque);
|
|
|
|
|
|
float CalcTorque(float rpm,tCarDefinition *car)
|
|
//Calculate the maximal torque the car can currently produce, based on engine revs.
|
|
//this is a function based on the car's power and torque ratings, and does not
|
|
//necessaryly correspond to the actual car's torque curve, but it is usually close enough.
|
|
//the advantage of this is that we do not need to know a car's actual torque chart to simulate a car.
|
|
{
|
|
float torqueResult;
|
|
if(rpm<car->torqueRPM)
|
|
torqueResult=car->torque*(-sqr(rpm/car->torqueRPM-1)+1);
|
|
else {
|
|
float maxPowerTorque=car->power/(car->powerRPM*2*PI/60);
|
|
float aproxFactor=(car->torque-maxPowerTorque)/(2*car->torqueRPM*car->powerRPM-sqr(car->powerRPM)-sqr(car->torqueRPM));
|
|
float torque=aproxFactor*sqr(rpm-car->torqueRPM)+car->torque;
|
|
torqueResult=torque>0?torque:0;
|
|
}
|
|
if(rpm>car->maxRPM)
|
|
{
|
|
torqueResult*=1-((rpm-car->maxRPM)*0.006);
|
|
if(torqueResult<0)
|
|
torqueResult=0;
|
|
}
|
|
if(rpm<0)
|
|
torqueResult=0;
|
|
return torqueResult;
|
|
}
|
|
|
|
#include "random.h"
|
|
void CalcWheelRoadForceNoClutch(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
//Calculater the force the wheels apply onto road when the clutch is fully engaged
|
|
{
|
|
//Calculate the drivetrain ratio
|
|
float ratio=car->gearRatios[phys->gear+1]*car->finalDriveRatio;
|
|
if(gGameInfo->arcade==kGameModeTurbo)
|
|
ratio*=0.8;
|
|
|
|
//Calculate the engine inertia at the final drive
|
|
float inertia=car->engineInertia*sqr(ratio);
|
|
|
|
//Calculate the frictional braking torque of the engine (in N*m)
|
|
float engineFrictionTorque=25+phys->rpm*0.02;
|
|
if(car->engineBaseFriction||car->engineRPMFriction)
|
|
engineFrictionTorque=car->engineBaseFriction+car->idleRPM*car->engineRPMFriction;
|
|
|
|
//Calculate the current engine torque (in N*m)
|
|
float engineTorque=(CalcTorque(phys->rpm,car)+engineFrictionTorque)*phys->throttle;
|
|
|
|
if(phys->damage>kFatalDamage)
|
|
engineTorque=0;
|
|
if(phys->damage>kEngineDamage)
|
|
engineTorque=RandomProb(0.6)?engineTorque:0;
|
|
|
|
//Calculate average radius of powered wheels
|
|
float wheelRadius=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
wheelRadius+=car->wheels[i].radius*car->wheels[i].powered;
|
|
|
|
//Calculate average angular velocity of powered wheels
|
|
float averageAngularVelo=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
averageAngularVelo+=car->wheels[i].powered*phys->wheels[i].angularVelo*(car->wheels[i].radius/wheelRadius);
|
|
|
|
float rpm=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
//Calculate the inertia at the wheel
|
|
wheels[i].inertia=car->wheels[i].inertia+car->wheels[i].powered*inertia;
|
|
|
|
//Calculate the amount of braking applied to the wheel
|
|
float braked=phys->brake+phys->handbrake*car->wheels[i].handbraked;
|
|
if(braked>1)braked=1;
|
|
|
|
float glow=braked*fabs(phys->wheels[i].angularVelo)*0.02;
|
|
if(glow>1)glow=1;
|
|
phys->wheels[i].glow+=(glow-0.2)*0.3*kFrameTime;
|
|
if(phys->wheels[i].glow>1)phys->wheels[i].glow=1;
|
|
else if(phys->wheels[i].glow<0)phys->wheels[i].glow=0;
|
|
|
|
//Calculate the torque applied by the brakes
|
|
float brakeFriction=braked*car->wheels[i].braked+fabs(phys->wheels[i].angularVelo)*wheels[i].normalForce*gSurfaceTypes->types[phys->wheels[i].surfaceType].brakeFactor;
|
|
|
|
//Calculate the torque applied by differential lock
|
|
float lockingTorque=(averageAngularVelo-phys->wheels[i].angularVelo*(car->wheels[i].radius/wheelRadius))*car->wheels[i].powered*car->differentialLockCoefficient;
|
|
|
|
//Calculate the total braking torque (brakes, friction) applied on the wheel
|
|
float wheelFrictionTorque=engineFrictionTorque*car->wheels[i].powered*fabs(ratio)+car->wheels[i].friction+brakeFriction;
|
|
|
|
//Calculate the engine torque applied on the wheels
|
|
float wheelEngineTorque=engineTorque*car->wheels[i].powered*ratio+lockingTorque;
|
|
|
|
if(wheels[i].onGround)
|
|
//Calculate the force the wheel applies onto the road
|
|
wheels[i].roadForce=PacejkaCalcWheelRoadForces(carEntity,car,phys,wheels,i,wheelEngineTorque,wheelFrictionTorque);
|
|
else{
|
|
//wheel applies no force onto the road
|
|
wheels[i].roadForce=Vector(0,0,0);
|
|
phys->wheels[i].slipVelo=0;
|
|
|
|
//Calculate wheel's new angular velocity
|
|
float angularAcceleration=((wheelEngineTorque-wheelFrictionTorque*sign(phys->wheels[i].angularVelo))/wheels[i].inertia);
|
|
if(-angularAcceleration*kFrameTime>phys->wheels[i].angularVelo)
|
|
phys->wheels[i].angularVelo=0;
|
|
else
|
|
phys->wheels[i].angularVelo+=angularAcceleration*kFrameTime;
|
|
}
|
|
//rotate the wheel according to it's angular velocity
|
|
phys->wheels[i].rotation+=phys->wheels[i].angularVelo*kFrameTime;
|
|
|
|
//Calulate average wheels angular velocities.
|
|
rpm+=phys->wheels[i].angularVelo*car->wheels[i].powered;
|
|
}
|
|
|
|
//Calculate new engine RPM
|
|
phys->engineAngularVelo=rpm*ratio;
|
|
phys->rpm=phys->engineAngularVelo*60.0/(2*PI);
|
|
}
|
|
|
|
|
|
//Calculates how much throttle has to be given to maintain the car's idle RPM
|
|
float CalcIdleThrottle(tCarDefinition *car)
|
|
{
|
|
float engineFrictionTorque=25+car->idleRPM*0.02;
|
|
if(car->engineBaseFriction||car->engineRPMFriction)
|
|
engineFrictionTorque=car->engineBaseFriction+car->idleRPM*car->engineRPMFriction;
|
|
float engineTorque=CalcTorque(car->idleRPM,car)+engineFrictionTorque;
|
|
return engineFrictionTorque/engineTorque;
|
|
}
|
|
|
|
float CalcEngine(tCarDefinition *car,tCarPhysics *phys)
|
|
{
|
|
if(phys->idleThrottle==0)
|
|
phys->idleThrottle=CalcIdleThrottle(car);
|
|
float rawEngineTorque=CalcTorque(phys->rpm,car);
|
|
float engineFrictionTorque=25+phys->rpm*0.02;
|
|
if(car->engineBaseFriction||car->engineRPMFriction)
|
|
engineFrictionTorque=car->engineBaseFriction+phys->rpm*car->engineRPMFriction;
|
|
float engineTorque=(rawEngineTorque+fabs(engineFrictionTorque))*phys->throttle;
|
|
if(phys->rpm>5*car->maxRPM)//safety against engine physics getting out of hand.
|
|
{
|
|
phys->rpm=0;
|
|
phys->gear=0;
|
|
}
|
|
if(phys->damage>kFatalDamage)
|
|
engineTorque=0;
|
|
if(phys->damage>kEngineDamage)
|
|
engineTorque=RandomProb(0.6)?engineTorque:0;
|
|
|
|
float engineAngularAcceleration=(engineTorque-engineFrictionTorque)/car->engineInertia;
|
|
phys->rpm=phys->engineAngularVelo*(60.0/(2*PI));
|
|
if(phys->rpm<car->idleRPM)
|
|
{
|
|
phys->rpm=car->idleRPM;
|
|
phys->clutch=0;
|
|
}
|
|
phys->engineAngularVelo+=engineAngularAcceleration*kFrameTime;
|
|
return engineAngularAcceleration;
|
|
}
|
|
|
|
float CalcWheelTorques(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels,float ratio)
|
|
{
|
|
float oldDrivetrainAngularVelo=phys->drivetrainAngularVelo;
|
|
phys->drivetrainAngularVelo=0;
|
|
|
|
//Calculate average radius of powered wheels
|
|
float wheelRadius=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
wheelRadius+=car->wheels[i].radius*car->wheels[i].powered;
|
|
|
|
//Calculate average angular velocity of powered wheels
|
|
float averageAngularVelo=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
averageAngularVelo+=car->wheels[i].powered*phys->wheels[i].angularVelo*(car->wheels[i].radius/wheelRadius);
|
|
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
wheels[i].inertia=car->wheels[i].inertia;
|
|
|
|
//Calculate the amount of braking applied to the wheel
|
|
float braked=phys->brake+phys->handbrake*car->wheels[i].handbraked;
|
|
if(braked>1)braked=1;
|
|
if(gFrameCount*kFrameTime<kStartGameDelaySeconds)
|
|
braked=1;
|
|
|
|
float glow=braked*fabs(phys->wheels[i].angularVelo)*0.02;
|
|
if(glow>1)glow=1;
|
|
phys->wheels[i].glow+=(glow-0.2)*0.3*kFrameTime;
|
|
if(phys->wheels[i].glow>1)phys->wheels[i].glow=1;
|
|
else if(phys->wheels[i].glow<0)phys->wheels[i].glow=0;
|
|
|
|
//Calculate the torque applied by the brakes
|
|
float brakeFriction=braked*car->wheels[i].braked+fabs(phys->wheels[i].angularVelo)*wheels[i].normalForce*gSurfaceTypes->types[phys->wheels[i].surfaceType].brakeFactor;
|
|
|
|
//Calculate the torque applied by differential lock
|
|
float lockingTorque=(averageAngularVelo-phys->wheels[i].angularVelo*(car->wheels[i].radius/wheelRadius))*car->wheels[i].powered*car->differentialLockCoefficient;
|
|
|
|
//Calculate the total braking torque (brakes, friction) applied on the wheel
|
|
float wheelFrictionTorque=car->wheels[i].friction+brakeFriction;
|
|
|
|
float wheelEngineTorque=phys->clutchTorque*car->wheels[i].powered*ratio+lockingTorque;
|
|
if(wheels[i].onGround)
|
|
wheels[i].roadForce=PacejkaCalcWheelRoadForces(carEntity,car,phys,wheels,i,wheelEngineTorque,wheelFrictionTorque);
|
|
else{
|
|
float angularAcceleration=((wheelEngineTorque-wheelFrictionTorque*sign(phys->wheels[i].angularVelo))/wheels[i].inertia);
|
|
if(-angularAcceleration*kFrameTime>phys->wheels[i].angularVelo)
|
|
phys->wheels[i].angularVelo=0;
|
|
else
|
|
phys->wheels[i].angularVelo+=angularAcceleration*kFrameTime;
|
|
wheels[i].roadForce=Vector(0,0,0);
|
|
phys->wheels[i].slipVelo=0;
|
|
}
|
|
phys->wheels[i].rotation+=phys->wheels[i].angularVelo*kFrameTime;
|
|
phys->drivetrainAngularVelo+=phys->wheels[i].angularVelo*car->wheels[i].powered;
|
|
}
|
|
phys->drivetrainAngularVelo*=ratio;
|
|
return (phys->drivetrainAngularVelo-oldDrivetrainAngularVelo)/kFrameTime;
|
|
}
|
|
|
|
void CalcClutchTorqueTransfer(tCarDefinition *car,tCarPhysics *phys,float ratio,float engineAngularAcceleration,float drivetrainAngularAcceleration)
|
|
{
|
|
if(ratio==0||phys->clutch==0)
|
|
{
|
|
phys->clutchTorque=0;
|
|
return;
|
|
}
|
|
float angularAcceleration=(phys->engineAngularVelo+engineAngularAcceleration*kFrameTime-phys->drivetrainAngularVelo+drivetrainAngularAcceleration*kFrameTime)/kFrameTime;
|
|
float i1=car->engineInertia;
|
|
float i2=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
if(car->wheels[i].powered)
|
|
i2+=car->wheels[i].inertia/sqr(ratio);
|
|
float inertia=(i1*i2)/(i1+i2);
|
|
float optimalTorque=inertia*angularAcceleration;
|
|
|
|
float maxTorque=car->maxClutchTorqueTransfer*phys->clutch*2;
|
|
phys->clutchTorque=optimalTorque<maxTorque?optimalTorque:maxTorque;
|
|
phys->engineAngularVelo-=phys->clutchTorque/i1*kFrameTime;
|
|
}
|
|
|
|
void CalcWheelRoadForceClutch(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
{
|
|
float ratio=car->gearRatios[phys->gear+1]*car->finalDriveRatio;
|
|
if(gGameInfo->arcade==kGameModeTurbo)
|
|
ratio*=0.8;
|
|
float engineAngularAcceleration=CalcEngine(car,phys);
|
|
float drivetrainAngularAcceleration=CalcWheelTorques(carEntity,car,phys,wheels,ratio);
|
|
CalcClutchTorqueTransfer(car,phys,ratio,engineAngularAcceleration,drivetrainAngularAcceleration);
|
|
}
|
|
|
|
void CalcWheelRoadForce(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
{
|
|
if(phys->clutch==1.0&&phys->gear!=0)
|
|
CalcWheelRoadForceNoClutch(carEntity,car,phys,wheels);
|
|
else
|
|
CalcWheelRoadForceClutch(carEntity,car,phys,wheels);
|
|
}
|
|
|
|
void CalcTotalWheelForce(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels,tVector3 *totalWheelForce,tVector3 *totalWheelTorque)
|
|
{
|
|
*totalWheelForce=Vector(0,0,0);
|
|
*totalWheelTorque=Vector(0,0,0);
|
|
for(int i=0;i<car->numWheels;i++){
|
|
tVector3 wheelForce=wheels[i].roadForce+wheels[i].suspensionForce*wheels[i].groundNormal;
|
|
tVector3 wheelGroundPoint=wheels[i].pos-*MatrixGetYVector(carEntity->dir)*(car->wheels[i].radius+phys->wheels[i].suspension);
|
|
tVector3 wheelCarTorque=(wheelGroundPoint-car->massCenter*carEntity->dir)%wheelForce;
|
|
*totalWheelTorque=*totalWheelTorque+wheelCarTorque;
|
|
*totalWheelForce=*totalWheelForce+wheelForce;
|
|
}
|
|
if(phys->rpm<car->jerkRPM)
|
|
phys->rpm=car->jerkRPM;
|
|
}
|
|
|
|
#define kMaxDraftDistance 60.0
|
|
#define kMaxDraftAngle (PI/15.0)
|
|
#define kMaxDraftVeloAngle (PI/10.0)
|
|
|
|
float CalcDraftFactor(tGameEntity *car1)
|
|
{
|
|
float draftFactor=0;
|
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
|
if(gCarEntities[i]!=car1)
|
|
{
|
|
tGameEntity *car2=gCarEntities[i];
|
|
tVector3 v=car2->pos-car1->pos;
|
|
float dir,veloDir,dist;
|
|
|
|
dir=1-acos((!car1->velo)*!v)/kMaxDraftAngle;
|
|
if(isnan(dir))dir=1;
|
|
if(dir<0)dir=0;
|
|
|
|
veloDir=1-acos((!car1->velo)*(!car2->velo))/kMaxDraftVeloAngle;
|
|
if(isnan(veloDir))veloDir=1;
|
|
if(veloDir<0)veloDir=0;
|
|
|
|
dist=1-(~v)/kMaxDraftDistance;
|
|
if(dist<0)dist=0;
|
|
|
|
draftFactor+=dir*veloDir*dist;
|
|
}
|
|
if(draftFactor>1)
|
|
draftFactor=1;
|
|
return draftFactor;
|
|
}
|
|
|
|
//#define __NO_AERODYNAMICS
|
|
void CalcAerodynamics(tGameEntity *carEntity,tCarDefinition *car,tVector3 *aerodynamicForce,tVector3 *aerodynamicTorque)
|
|
{
|
|
#ifndef __NO_AERODYNAMICS
|
|
tVector3 xDir=*MatrixGetXVector(carEntity->dir);
|
|
tVector3 yDir=*MatrixGetYVector(carEntity->dir);
|
|
tVector3 zDir=*MatrixGetZVector(carEntity->dir);
|
|
float fVelo=~carEntity->velo;
|
|
float draftFactor=CalcDraftFactor(carEntity);
|
|
|
|
float airRes=fVelo*0.5*fabs(zDir*carEntity->velo)*gEnvironment->airDensity;
|
|
tVector3 frontSpoilPoint=Vector(0,car->massCenter.y,car->wheels[0].pos.z)*carEntity->dir;
|
|
tVector3 rearSpoilPoint=Vector(0,car->massCenter.y,car->wheels[2].pos.z)*carEntity->dir;
|
|
*aerodynamicForce=yDir*airRes*(car->frontLift+car->rearLift);
|
|
*aerodynamicTorque=(frontSpoilPoint-car->massCenter*carEntity->dir)%(yDir*airRes*(car->frontLift))+(rearSpoilPoint-car->massCenter*carEntity->dir)%(yDir*airRes*(car->rearLift));
|
|
|
|
tVector3 dragCenter=Vector(0,car->massCenter.y,0);
|
|
float dragFactor=fabs(zDir*carEntity->velo*car->frontAirResistance)+fabs(yDir*carEntity->velo*car->topAirResistance)+fabs(xDir*carEntity->velo*car->sideAirResistance);
|
|
*aerodynamicForce=*aerodynamicForce-carEntity->velo*0.5*dragFactor*gEnvironment->airDensity*(1-0.9*draftFactor);
|
|
*aerodynamicTorque=*aerodynamicTorque+((dragCenter-car->massCenter)*carEntity->dir)%*aerodynamicForce;
|
|
#else
|
|
*aerodynamicForce=Vector(0,0,0);
|
|
*aerodynamicTorque=Vector(0,0,0);
|
|
#endif
|
|
}
|
|
|
|
void CarLights(tCarPhysics *phys)
|
|
{
|
|
phys->lightFlags&=~(kLightFlagRevLight+kLightFlagBrakeLight);
|
|
if((int)(gFrameCount*kFrameTime/kIndicatorTime)&1)
|
|
phys->lightFlags&=~(kLightFlagLIndLight+kLightFlagRIndLight);
|
|
if(phys->brake>0)
|
|
phys->lightFlags|=kLightFlagBrakeLight;
|
|
if(phys->gear==-1)
|
|
phys->lightFlags|=kLightFlagRevLight;
|
|
if(gEnvironment->spotLightEnable&&gFrameCount==0)
|
|
phys->lightFlags|=kLightFlagDriveLight;
|
|
}
|
|
|
|
//Create Tracks and Particle Effects for the car
|
|
void CarCreateEffects(tGameEntity *carEntity,tCarDefinition *car,tCarPhysics *phys,tWheelCalcData *wheels)
|
|
{
|
|
float totalSlip=0;
|
|
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
tSurfaceType *surf=gSurfaceTypes->types+phys->wheels[i].surfaceType;
|
|
|
|
totalSlip+=fabs(phys->wheels[i].slipVelo)/car->numWheels;
|
|
|
|
//does this surface smoke?
|
|
if(wheels[i].onGround)
|
|
if(surf->smokeEnable&&!((gCameraMode==kCameraCockpit||gCameraMode==kCameraCockpitCarHidden)&&carEntity==gViewedEntity))
|
|
{
|
|
//is the wheel slipping enough to smoke?
|
|
float smoke=(fabs(phys->wheels[i].slipVelo)-surf->minSmokeSlideVelo)/(surf->maxSmokeSlideVelo-surf->minSmokeSlideVelo);
|
|
if(smoke>0)
|
|
{
|
|
//create particles
|
|
tParticlesDef def;
|
|
ParticlesInitDef(&def);
|
|
|
|
if(smoke>1)smoke=1;
|
|
smoke*=kMaxSmoke;
|
|
def.sprite=surf->smokeTexture;
|
|
def.maxSpread=surf->smokeSpread;
|
|
def.maxVelo=surf->smokeMaxVelo;
|
|
def.gravity=surf->smokeGravity;
|
|
def.maxLife=surf->smokeMaxLife;
|
|
def.brightness=0.5+(gEnvironment->ambient.x+gEnvironment->ambient.y+gEnvironment->ambient.z)*0.166;
|
|
def.screenTexture=surf->smokeStickEnable?surf->smokeTexture:-1;
|
|
def.xSize=surf->smokeSize;def.ySize=surf->smokeSize;
|
|
tVector3 baseVelo=Vector(0,0.03,-0.1*phys->wheels[i].slip)*carEntity->dir*surf->smokeSpeed+carEntity->velo*0.3;
|
|
ParticlesCreate(smoke*kFrameTime,wheels[i].pos+carEntity->pos,baseVelo,&def);
|
|
}
|
|
}
|
|
|
|
//does this surface leave tracks?
|
|
if(surf->trackEnable&&!((gCameraMode==kCameraCockpit||gCameraMode==kCameraCockpitCarHidden)&&carEntity==gViewedEntity))
|
|
//we only generate tracks every kTrackPrecision-th frame for performance reasons
|
|
if(!(gFrameCount%kTrackPrecision))
|
|
{
|
|
//Calculate track intensity
|
|
float tracks=(fabs(phys->wheels[i].slipVelo)-surf->minTrackSlideVelo)/(surf->maxTrackSlideVelo-surf->minTrackSlideVelo);
|
|
if(tracks>0&&wheels[i].onGround)
|
|
{
|
|
//create tracks.
|
|
if(tracks>1)tracks=1;
|
|
tVector3 pos1=wheels[i].pos+carEntity->pos-wheels[i].groundNormal*(phys->wheels[i].suspension+car->wheels[i].radius);
|
|
pos1.y-=wheels[i].bump;
|
|
tVector3 pos2=pos1+wheels[i].velo*kFrameTime;
|
|
phys->wheels[i].lastTrack=TracksAdd(pos1,pos2,wheels[i].groundNormal,car->wheels[i].width,gSurfaceTypes->types[phys->wheels[i].surfaceType].trackTexture,tracks,phys->wheels[i].lastTrack);
|
|
}
|
|
else phys->wheels[i].lastTrack=0;
|
|
}
|
|
}
|
|
|
|
//make the car dirtier
|
|
phys->dirt+=totalSlip*kFrameTime*0.1;
|
|
if(phys->dirt>100)
|
|
phys->dirt=100;
|
|
//exhaust fire
|
|
if(car->exhaustFire+(phys->damage>kEngineDamage?100000:0))
|
|
{
|
|
float exhaustFire=(((phys->oldThrottle-phys->throttle)*kFPS*phys->rpm)-(car->exhaustFire+(phys->damage>kEngineDamage?100000:0)))/((car->exhaustFire+(phys->damage>kEngineDamage?100000:0)));
|
|
if(exhaustFire>0)
|
|
{
|
|
if(exhaustFire>1)exhaustFire=1;
|
|
CarPlayCrashNoise(carEntity,FileGetReference("exhaust.wav"),exhaustFire);
|
|
if(exhaustFire>0.5)
|
|
{
|
|
tParticlesDef def;
|
|
ParticlesInitDef(&def);
|
|
|
|
def.sprite=FileGetReference("exhaustfire.pct");
|
|
def.maxSpread=0.1;
|
|
def.maxVelo=0.2;
|
|
def.gravity=-0.2;
|
|
def.maxLife=0.3;
|
|
def.screenTexture=-1;
|
|
def.xSize=0.6;def.ySize=0.6;
|
|
def.grow=true;
|
|
tVector3 baseVelo=Vector(0,0,0);
|
|
//two exhaust pipes. Both may be at the same space to make it look like one.
|
|
ParticlesCreate(exhaustFire*200*kFrameTime,car->exhaust1Pos*carEntity->dir+carEntity->pos,baseVelo,&def);
|
|
ParticlesCreate(exhaustFire*200*kFrameTime,car->exhaust2Pos*carEntity->dir+carEntity->pos,baseVelo,&def);
|
|
}
|
|
}
|
|
}
|
|
phys->oldThrottle=phys->throttle;
|
|
|
|
//exhaust smoke.
|
|
float v=~carEntity->velo;
|
|
if(v<10&&phys->damage<kFatalDamage)
|
|
{
|
|
tParticlesDef def;
|
|
ParticlesInitDef(&def);
|
|
|
|
float smoke=0.2+0.8*phys->throttle;
|
|
def.sprite=FileGetReference("exhaustsmoke.pct");
|
|
def.maxSpread=0.1;
|
|
def.maxVelo=0.2;
|
|
def.gravity=-0.8;
|
|
def.maxLife=2;
|
|
def.a=1-(v*0.1);
|
|
def.screenTexture=-1;
|
|
def.xSize=0.4;def.ySize=0.4;
|
|
def.grow=true;
|
|
tVector3 baseVelo=-*MatrixGetZVector(carEntity->dir)*smoke*2;
|
|
//two exhaust pipes. Both may be at the same space to make it look like one.
|
|
ParticlesCreate(smoke*40*kFrameTime,car->exhaust1Pos*carEntity->dir+carEntity->pos,baseVelo,&def);
|
|
ParticlesCreate(smoke*40*kFrameTime,car->exhaust2Pos*carEntity->dir+carEntity->pos,baseVelo,&def);
|
|
}
|
|
|
|
if(phys->damage>kEngineDamage)
|
|
{
|
|
tParticlesDef def;
|
|
ParticlesInitDef(&def);
|
|
|
|
float smoke=0.2+0.8*phys->throttle;
|
|
def.sprite=FileGetReference("exhaustsmoke.pct");
|
|
def.maxSpread=0.2;
|
|
def.maxVelo=0.3;
|
|
def.gravity=-0.7;
|
|
def.maxLife=2;
|
|
def.a=1;
|
|
def.screenTexture=-1;
|
|
def.xSize=1.5;def.ySize=1.5;
|
|
def.grow=true;
|
|
tVector3 baseVelo=Vector(0,1,0);
|
|
//two exhaust pipes. Both may be at the same space to make it look like one.
|
|
ParticlesCreate(smoke*60*kFrameTime,Vector(0,1,2.3)*carEntity->dir+carEntity->pos,baseVelo,&def);
|
|
}
|
|
}
|
|
|
|
void CarPhysicsEntitySimulation(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
tCarDefinition *car=&(phys->car);
|
|
tWheelCalcData wheels[kMaxWheels];
|
|
|
|
//Wheels position and velocity
|
|
CalcWheelPositions(carEntity,car,phys,wheels);
|
|
|
|
//wheels position relative to the ground
|
|
CalcWheelGroundOffsets(carEntity,car,phys,wheels);
|
|
|
|
//wheels springs & suspension
|
|
CalcWheelSupension(carEntity,car,phys,wheels);
|
|
|
|
if(carEntity->physicsMachine==kPhysicsRemote||gReplay)
|
|
{
|
|
for(int i=0;i<kMaxWheels;i++)
|
|
wheels[i].roadForce=Vector(0,0,0);
|
|
//Sum of wheel forces
|
|
tVector3 totalWheelForce,totalWheelTorque;
|
|
CalcTotalWheelForce(carEntity,car,phys,wheels,&totalWheelForce,&totalWheelTorque);
|
|
|
|
//Gravity
|
|
tVector3 gravityForce=Vector(0,-car->mass*gEnvironment->gravity,0);
|
|
|
|
//Apply Forces
|
|
tVector3 totalForce=totalWheelForce+gravityForce;
|
|
tVector3 acceleration=totalForce*(1/car->mass);
|
|
carEntity->velo=carEntity->velo+acceleration*kFrameTime;
|
|
}
|
|
else
|
|
{
|
|
//Force the wheel projects onto the road
|
|
CalcWheelRoadForce(carEntity,car,phys,wheels);
|
|
|
|
//Sum of wheel forces
|
|
tVector3 totalWheelForce,totalWheelTorque;
|
|
CalcTotalWheelForce(carEntity,car,phys,wheels,&totalWheelForce,&totalWheelTorque);
|
|
|
|
//Aerodynamics
|
|
tVector3 aerodynamicForce,aerodynamicTorque;
|
|
CalcAerodynamics(carEntity,car,&aerodynamicForce,&aerodynamicTorque);
|
|
|
|
//Gravity
|
|
tVector3 gravityForce=Vector(0,-car->mass*gEnvironment->gravity,0);
|
|
|
|
//Apply Forces
|
|
tVector3 totalForce=totalWheelForce+aerodynamicForce+gravityForce;
|
|
tVector3 acceleration=totalForce*(1/car->mass);
|
|
carEntity->accel=acceleration;
|
|
carEntity->velo=carEntity->velo+acceleration*kFrameTime;
|
|
|
|
//Apply Torques
|
|
tVector3 totalTorque=totalWheelTorque+aerodynamicTorque;
|
|
|
|
tMatrix3 invMatrix;
|
|
MatrixTranspose(carEntity->dir,invMatrix);
|
|
totalTorque=totalTorque*invMatrix;
|
|
float zTorq=1;
|
|
if(gFrameCount<kFPS*5)
|
|
zTorq=gFrameCount/(kFPS*5);
|
|
tVector3 angularAcceleration=Vector(totalTorque.x/car->inertia.x,totalTorque.y/car->inertia.y,zTorq*totalTorque.z/car->inertia.z)*carEntity->dir;
|
|
tMatrix3 accelerationMatrix;
|
|
RotationVectorToMatrix(angularAcceleration*kFrameTime*kFrameTime,accelerationMatrix);
|
|
MatrixMult(carEntity->rVelo,accelerationMatrix,carEntity->rVelo);
|
|
|
|
//if(carEntity->physicsMachine==kPhysicsLocal&&!gReplay)
|
|
//{
|
|
//Lights
|
|
CarLights(phys);
|
|
|
|
int wheelOnGround=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
if(wheels[i].onGround)
|
|
wheelOnGround++;
|
|
phys->wheels[i].onGround=wheels[i].onGround;
|
|
}
|
|
// if(wheelOnGround>2)//no y network Accel when on ground to dampen spring motion on bumpy surfaces
|
|
// carEntity->accel.y=0;
|
|
|
|
//Crash Recovery
|
|
if(((wheelOnGround<car->numWheels&&wheelOnGround<4)||phys->collision>gFrameCount-10)&&phys->damage<kFatalDamage)
|
|
{
|
|
if(gFrameCount-phys->crashTime>kFPS*5)
|
|
if(sqr(carEntity->velo)<sqr(2)||(phys->collision>gFrameCount-10&&sqr(carEntity->velo)<sqr(15)))
|
|
{
|
|
RoadCenterCar(carEntity);
|
|
phys->crashTime=gFrameCount;
|
|
phys->stuckTime=gFrameCount;
|
|
}
|
|
}
|
|
else
|
|
phys->crashTime=gFrameCount;
|
|
|
|
carEntity->lastActivity=gFrameCount;
|
|
|
|
int wheelsOnGround=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
if(wheels[i].onGround)
|
|
wheelsOnGround++;
|
|
phys->onGround=wheelsOnGround>=2;
|
|
|
|
}
|
|
|
|
//Smoke and Tracks!
|
|
CarCreateEffects(carEntity,car,phys,wheels);
|
|
}
|
|
|
|
#define kMaxCornerAccel 15
|
|
#define kMaxEngineAccel 6
|
|
#define kMaxBrakeAccel 8
|
|
#define kEngineFactor 0.8
|
|
|
|
void CarPhysicsEntitySimple(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
|
|
tVector3 velo=carEntity->velo;
|
|
|
|
tVector3 rVelo=MatrixToEulerAngles(carEntity->rVelo);
|
|
rVelo.y=0;
|
|
EulerAnglesToMatrix(rVelo,carEntity->rVelo);
|
|
|
|
CarPhysicsEntitySimulation(carEntity);
|
|
|
|
carEntity->velo.x=(5*velo.x+carEntity->velo.x)/6.0;
|
|
carEntity->velo.z=(5*velo.z+carEntity->velo.z)/6.0;
|
|
|
|
tCarDefinition *car=&(phys->car);
|
|
|
|
float groundFriction=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
groundFriction+=gSurfaceTypes->types[phys->wheels[i].surfaceType].grip/(float)car->numWheels;
|
|
|
|
//Calculate the drivetrain ratio
|
|
float ratio=car->gearRatios[phys->gear+1]*car->finalDriveRatio;
|
|
|
|
//Calculate the engine inertia at the final drive
|
|
float inertia=car->engineInertia*sqr(ratio);
|
|
|
|
//Calculate the frictional braking torque of the engine (in N*m)
|
|
float engineFrictionTorque=25+phys->rpm*0.02;
|
|
if(car->engineBaseFriction||car->engineRPMFriction)
|
|
engineFrictionTorque=car->engineBaseFriction+phys->rpm*car->engineRPMFriction;
|
|
|
|
//Calculate the current engine torque (in N*m)
|
|
float engineTorque=(CalcTorque(phys->rpm,car)+engineFrictionTorque)*phys->throttle-engineFrictionTorque;
|
|
|
|
if(engineTorque<0&&carEntity->velo**MatrixGetZVector(carEntity->dir)*ratio<0)
|
|
engineTorque*=-1;
|
|
|
|
tVector3 flatDir=!Vector(MatrixGetZVector(carEntity->dir)->x,0,MatrixGetZVector(carEntity->dir)->z);
|
|
tVector3 flatVelo=Vector(velo.x,0,velo.z);
|
|
|
|
float fEngineForce=(car->aiSpeedIndex!=0?car->aiSpeedIndex:1)*kEngineFactor*engineTorque*ratio/car->wheels[2].radius;
|
|
if(fEngineForce>car->mass*kMaxEngineAccel*groundFriction)
|
|
fEngineForce=car->mass*kMaxEngineAccel*groundFriction;
|
|
tVector3 engineForce=flatDir*fEngineForce;
|
|
|
|
float arcadeDraftBoost=CalcDraftFactor(carEntity)*(~carEntity->velo)/70.0;
|
|
|
|
if(phys->arcadeDraftBoost<arcadeDraftBoost)
|
|
phys->arcadeDraftBoost+=2*kFrameTime;
|
|
if(phys->arcadeDraftBoost>arcadeDraftBoost)
|
|
phys->arcadeDraftBoost-=2*kFrameTime;
|
|
|
|
if(phys->arcadeDraftBoost>1)phys->arcadeDraftBoost=1;
|
|
if(phys->arcadeDraftBoost<0)phys->arcadeDraftBoost=0;
|
|
|
|
if(carEntity->controlType==kControlTypeAIInput)
|
|
phys->arcadeDraftBoost=0;
|
|
|
|
//engineForce=engineForce+!engineForce*14*car->mass*phys->arcadeDraftBoost;
|
|
|
|
tVector3 airRes=-carEntity->velo*~carEntity->velo*0.5*car->frontAirResistance*gEnvironment->airDensity;
|
|
|
|
float surfaceBrake=0;
|
|
for(int i=0;i<car->numWheels;i++)
|
|
surfaceBrake+=gSurfaceTypes->types[phys->wheels[i].surfaceType].brakeFactor*fabs(phys->wheels[i].angularVelo)/(float)car->numWheels;
|
|
float brake=phys->arcadeBrake+phys->handbrake*0.5;
|
|
if(brake>1)brake=1;
|
|
brake+=surfaceBrake;
|
|
tVector3 brakeForce=-!flatVelo*brake*car->mass*kMaxBrakeAccel;
|
|
if(sqr(brakeForce/car->mass)*kFrameTime>sqr(flatVelo))
|
|
brakeForce=-!flatVelo*car->mass*kFrameTime;
|
|
|
|
|
|
tVector3 totalForce;
|
|
if(phys->onGround)
|
|
totalForce=engineForce+airRes+brakeForce;
|
|
else
|
|
totalForce=airRes;
|
|
tVector3 totalAccel=totalForce/car->mass;
|
|
if(gGameInfo->arcade==kGameModeTurbo)
|
|
totalAccel=totalAccel*2.3;
|
|
|
|
carEntity->velo=carEntity->velo+(totalAccel*kFrameTime);
|
|
|
|
tMatrix3 m;
|
|
MatrixIdentity(m);
|
|
MatrixRotY(m,phys->steering*0.1);
|
|
|
|
flatDir=flatDir*~flatVelo;
|
|
flatDir=flatDir*m;
|
|
float rev=sign(flatDir*flatVelo);
|
|
flatDir=flatDir*rev;
|
|
float diff=~(flatVelo-flatDir);
|
|
float cornerAccel=kFrameTime*kMaxCornerAccel*groundFriction;
|
|
if(gGameInfo->arcade==kGameModeTurbo)
|
|
cornerAccel*=1.9;
|
|
if(cornerAccel>diff)cornerAccel=diff;
|
|
|
|
tVector3 vCornerAccel=-!(flatVelo-flatDir)*cornerAccel;
|
|
carEntity->velo=carEntity->velo+vCornerAccel;
|
|
carEntity->accel=totalAccel+vCornerAccel*kFPS;
|
|
|
|
float handBrakeFactor=1+phys->handbrake*2;
|
|
if(rev<0)
|
|
handBrakeFactor=1;
|
|
float veloSteer=((15/(~velo+1))+1)*handBrakeFactor;
|
|
float maxRotSteer=10*kFrameTime*handBrakeFactor;
|
|
float acardeSteerVeloInput=rev*phys->steering*~velo*kFrameTime*0.1*handBrakeFactor;
|
|
if(!phys->onGround)
|
|
acardeSteerVeloInput=0;
|
|
if(acardeSteerVeloInput>phys->arcadeSteerVelo){
|
|
phys->arcadeSteerVelo+=0.015*veloSteer*kFrameTime;
|
|
if(acardeSteerVeloInput<phys->arcadeSteerVelo)
|
|
phys->arcadeSteerVelo=acardeSteerVeloInput;
|
|
}
|
|
else{
|
|
phys->arcadeSteerVelo-=0.015*veloSteer*kFrameTime;
|
|
if(acardeSteerVeloInput>phys->arcadeSteerVelo)
|
|
phys->arcadeSteerVelo=acardeSteerVeloInput;
|
|
}
|
|
if(fabs(phys->arcadeSteerVelo)>maxRotSteer)
|
|
phys->arcadeSteerVelo=sign(phys->arcadeSteerVelo)*maxRotSteer;
|
|
MatrixRotY(carEntity->rVelo,phys->arcadeSteerVelo);
|
|
}
|
|
|
|
|
|
void CarPhysicsEntityMagic(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
|
|
MatrixIdentity(carEntity->rVelo);
|
|
|
|
tVector3 x[4],a[4];
|
|
x[0]=Vector(1.2,0,2);
|
|
x[1]=Vector(-1.2,0,2);
|
|
x[2]=Vector(1.2,0,-2);
|
|
x[3]=Vector(-1.2,0,-2);
|
|
tVector3 howerAccel=Vector(0,0,0);
|
|
tVector3 howerRotAccel=Vector(0,0,0);
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
x[i]=x[i]*carEntity->dir;
|
|
tVector3 normal;
|
|
float offs=GetGroundOffset(x[i]+carEntity->pos,&carEntity->lastRoadIndex,&normal,NULL);
|
|
if(offs<0.2)offs=0.2;
|
|
a[i]=normal*(1.25/offs)*(1+0.5*phys->steering*(i&1?1:-1))*(1+0.1*phys->brake*(i&2?1:-1))*(1-0.1*phys->throttle*(i&2?1:-1));
|
|
howerAccel=howerAccel+a[i];
|
|
howerRotAccel=howerRotAccel+x[i]%a[i];
|
|
}
|
|
howerAccel=howerAccel*(1+sin(gFrameCount*kFrameTime*2)*0.12);
|
|
|
|
tMatrix3 invMatrix;
|
|
MatrixTranspose(carEntity->dir,invMatrix);
|
|
howerRotAccel=Vector(howerRotAccel.x*5,0,howerRotAccel.z*30);
|
|
tMatrix3 accelerationMatrix;
|
|
RotationVectorToMatrix(howerRotAccel*kFrameTime*kFrameTime,accelerationMatrix);
|
|
MatrixMult(carEntity->rVelo,accelerationMatrix,carEntity->rVelo);
|
|
|
|
tVector3 velo=carEntity->velo;
|
|
tVector3 gravityAccel=Vector(0,-gEnvironment->gravity,0);
|
|
tVector3 engineAccel=phys->throttle**MatrixGetZVector(carEntity->dir)*(20);
|
|
tVector3 brakeAccel=phys->brake*!(*MatrixGetZVector(carEntity->dir)+((sqr(carEntity->velo)>0.001)?carEntity->velo*0.1:Vector(0,0,0)))*-(22);
|
|
if(gFrameCount*kFrameTime<=kStartGameDelaySeconds)
|
|
{
|
|
engineAccel=Vector(0,0,0);
|
|
brakeAccel=Vector(0,0,0);
|
|
}
|
|
if(phys->lapCount>gGameInfo->numLaps&&gGameInfo->numLaps!=-1)
|
|
brakeAccel=phys->brake*(((sqr(carEntity->velo)>0.001)?!carEntity->velo:Vector(0,0,0)))*-(22);
|
|
brakeAccel.y=0;
|
|
tVector3 dampAccel=Vector(0,0,0);
|
|
tVector3 airResAccel=Vector(0,0,0);
|
|
if(sqr(carEntity->velo)>0.001)
|
|
{
|
|
dampAccel=-!carEntity->velo*5;
|
|
airResAccel=-!carEntity->velo*sqr(carEntity->velo)*0.003;
|
|
if(sqr(dampAccel*kFrameTime)>sqr(carEntity->velo))
|
|
dampAccel=-carEntity->velo*kFPS;
|
|
dampAccel.x*=0.1;
|
|
dampAccel.z*=0.1;
|
|
}
|
|
tVector3 totalAccel=gravityAccel+howerAccel+engineAccel+brakeAccel+dampAccel+airResAccel;
|
|
|
|
carEntity->velo=carEntity->velo+totalAccel*kFrameTime;
|
|
carEntity->lastActivity=gFrameCount;
|
|
|
|
tVector3 flatDir=!Vector(MatrixGetZVector(carEntity->dir)->x,0,MatrixGetZVector(carEntity->dir)->z);
|
|
tVector3 flatVelo=Vector(velo.x,0,velo.z);
|
|
|
|
tMatrix3 m;
|
|
MatrixIdentity(m);
|
|
MatrixRotY(m,phys->steering*0.1);
|
|
|
|
flatDir=flatDir*~flatVelo;
|
|
flatDir=flatDir*m;
|
|
float rev=sign(flatDir*flatVelo);
|
|
flatDir=flatDir*rev;
|
|
float diff=~(flatVelo-flatDir);
|
|
|
|
float cornerAccel=kFrameTime*kMaxCornerAccel;
|
|
if(gGameInfo->arcade==kGameModeTurbo)
|
|
cornerAccel*=1.9;
|
|
if(cornerAccel>diff)cornerAccel=diff;
|
|
|
|
tVector3 vCornerAccel=-!(flatVelo-flatDir)*cornerAccel;
|
|
carEntity->velo=carEntity->velo+vCornerAccel;
|
|
carEntity->accel=totalAccel+vCornerAccel*kFPS;
|
|
|
|
float acardeSteerVeloInput=phys->steering*kFrameTime*(3+~velo*0.04);
|
|
if(acardeSteerVeloInput>phys->arcadeSteerVelo){
|
|
phys->arcadeSteerVelo+=0.15*kFrameTime;
|
|
if(acardeSteerVeloInput<phys->arcadeSteerVelo)
|
|
phys->arcadeSteerVelo=acardeSteerVeloInput;
|
|
}
|
|
else{
|
|
phys->arcadeSteerVelo-=0.15*kFrameTime;
|
|
if(acardeSteerVeloInput>phys->arcadeSteerVelo)
|
|
phys->arcadeSteerVelo=acardeSteerVeloInput;
|
|
}
|
|
if(gFrameCount*kFrameTime>=kStartGameDelaySeconds)
|
|
MatrixRotY(carEntity->rVelo,phys->arcadeSteerVelo);
|
|
|
|
if(gEnvironment->spotLightEnable&&gFrameCount==0)
|
|
phys->lightFlags|=kLightFlagDriveLight;
|
|
|
|
if(phys->collision>gFrameCount-10)
|
|
{
|
|
if(gFrameCount-phys->crashTime>kFPS*5)
|
|
if(sqr(carEntity->velo)<sqr(2))
|
|
{
|
|
RoadCenterCar(carEntity);
|
|
phys->crashTime=gFrameCount;
|
|
phys->stuckTime=gFrameCount;
|
|
}
|
|
}else
|
|
phys->crashTime=gFrameCount;
|
|
|
|
*MatrixGetYVector(carEntity->dir)=!(*MatrixGetYVector(carEntity->dir)+(Vector(0,1,0)-*MatrixGetYVector(carEntity->dir))*kFrameTime*0.5);
|
|
*MatrixGetXVector(carEntity->dir)=!(*MatrixGetYVector(carEntity->dir)%*MatrixGetZVector(carEntity->dir));
|
|
*MatrixGetZVector(carEntity->dir)=!(*MatrixGetXVector(carEntity->dir)%*MatrixGetYVector(carEntity->dir));
|
|
}
|
|
|
|
|
|
|
|
void CarPhysicsEntity(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
tCarDefinition *car=&(phys->car);
|
|
if(car->magic)
|
|
CarPhysicsEntityMagic(carEntity);
|
|
else if((gGameInfo->arcade==kGameModeTurbo||gGameInfo->arcade==kGameModeArcade||carEntity->controlType==kControlTypeAIInput)&&carEntity->physicsMachine==kPhysicsLocal)
|
|
CarPhysicsEntitySimple(carEntity);
|
|
else
|
|
CarPhysicsEntitySimulation(carEntity);
|
|
}
|
|
#define kPredictTime 0.15
|
|
//Move the car around
|
|
|
|
void MatrixReAdjust2(tMatrix3 m)
|
|
{
|
|
*(tVector3*)m[1]=!*(tVector3*)m[1];
|
|
*(tVector3*)m[0]=!(*(tVector3*)m[1]%*(tVector3*)m[2]);
|
|
*(tVector3*)m[2]=!(*(tVector3*)m[0]%*(tVector3*)m[1]);
|
|
}
|
|
|
|
void FullyResetCar(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
carEntity->pos=gMapInfo->startPos+Vector(0,10,0);
|
|
carEntity->oldPos=carEntity->pos;
|
|
carEntity->netPos=carEntity->pos;
|
|
carEntity->velo=Vector(0,0,0);
|
|
carEntity->netVelo=Vector(0,0,0);
|
|
carEntity->collVelo=Vector(0,0,0);
|
|
carEntity->accel=Vector(0,0,0);
|
|
MatrixIdentity(carEntity->dir);
|
|
MatrixIdentity(carEntity->rVelo);
|
|
MatrixIdentity(carEntity->oldDir);
|
|
MatrixIdentity(carEntity->netDir);
|
|
MatrixIdentity(carEntity->netRVelo);
|
|
for(int i=0;i<kNumAvgRVelos;i++)
|
|
{
|
|
MatrixIdentity(carEntity->lastRVelos[i]);
|
|
carEntity->lastVelos[i]=Vector(0,0,0);
|
|
carEntity->lastAccel[i]=Vector(0,0,0);
|
|
}
|
|
carEntity->remoteCameraPos=Vector(0,0,0);
|
|
carEntity->zDist=0;
|
|
phys->maxSpeed=0;
|
|
phys->averageSpeed=0;
|
|
phys->accel100=0;
|
|
phys->accel200=0;
|
|
phys->accelQuarter=0;
|
|
phys->accelKM=0;
|
|
phys->odo;
|
|
phys->position=0;
|
|
phys->lead=0;
|
|
phys->rpm=0;
|
|
phys->engineAngularVelo=0;
|
|
phys->drivetrainAngularVelo=0;
|
|
phys->clutchTorque=0;
|
|
for(int i=0;i<kMaxWheels;i++)
|
|
{
|
|
phys->wheels[i].angle=0;
|
|
phys->wheels[i].suspension=0;
|
|
phys->wheels[i].slipVelo=0;
|
|
phys->wheels[i].slip=0;
|
|
phys->wheels[i].slipAngle=0;
|
|
phys->wheels[i].rotation=0;
|
|
phys->wheels[i].angularVelo=0;
|
|
phys->wheels[i].glow=0;
|
|
}
|
|
}
|
|
|
|
|
|
void CarMotionEntity(tGameEntity *carEntity)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)carEntity->physics;
|
|
tCarDefinition *car=&(phys->car);
|
|
|
|
for(int i=kNumAvgRVelos-2;i>=0;i--)
|
|
{
|
|
MatrixCopy(carEntity->lastRVelos[i],carEntity->lastRVelos[i+1]);
|
|
carEntity->lastVelos[i+1]=carEntity->lastVelos[i];
|
|
carEntity->lastAccel[i+1]=carEntity->lastAccel[i];
|
|
}
|
|
MatrixCopy(carEntity->rVelo,carEntity->lastRVelos[0]);
|
|
carEntity->lastVelos[0]=carEntity->velo;
|
|
carEntity->lastAccel[0]=carEntity->accel;
|
|
|
|
|
|
carEntity->lastActivity=gFrameCount;
|
|
//move the car
|
|
|
|
if(carEntity->physicsMachine==kPhysicsRemote)
|
|
{
|
|
if(!gReplay)
|
|
{
|
|
float smoothness=0;
|
|
for(int i=0;i<kNumLastCollisions;i++)
|
|
if((gFrameCount-phys->lastCollisions[i].frameCount)*kFrameTime<0.5)
|
|
smoothness+=(8.0f/kNumLastCollisions);
|
|
if(smoothness>1)
|
|
smoothness=1;
|
|
float predictTime=kPredictTime*(1+smoothness*1.5);
|
|
carEntity->netVelo=carEntity->netVelo+carEntity->accel*kFrameTime;
|
|
carEntity->netPos=carEntity->netPos+carEntity->netVelo*kFrameTime;
|
|
carEntity->velo=carEntity->velo+((carEntity->netPos+carEntity->netVelo*predictTime+carEntity->accel*predictTime*predictTime*0.5)-(carEntity->pos+carEntity->velo*predictTime))/predictTime;
|
|
MatrixMult(carEntity->netDir,carEntity->rVelo,carEntity->netDir);
|
|
tVector3 netx=*MatrixGetXVector(carEntity->netDir);
|
|
tVector3 nety=*MatrixGetYVector(carEntity->netDir);
|
|
tVector3 netz=*MatrixGetZVector(carEntity->netDir);
|
|
tVector3 curx=*MatrixGetXVector(carEntity->dir);
|
|
tVector3 curz=*MatrixGetZVector(carEntity->dir);
|
|
|
|
*MatrixGetXVector(carEntity->dir)=curx+(netx-curx)*0.3;
|
|
*MatrixGetYVector(carEntity->dir)=nety;
|
|
*MatrixGetZVector(carEntity->dir)=curz+(netz-curz)*0.3;
|
|
MatrixReAdjust2(carEntity->dir);
|
|
|
|
if(sqr(carEntity->pos-carEntity->netPos)>sqr(5+smoothness*10))
|
|
{
|
|
carEntity->pos=carEntity->netPos;
|
|
carEntity->velo=carEntity->netVelo;
|
|
MatrixCopy(carEntity->netDir,carEntity->dir);
|
|
}
|
|
}
|
|
for(int i=0;i<car->numWheels;i++)
|
|
phys->wheels[i].rotation+=phys->wheels[i].angularVelo*kFrameTime;
|
|
}
|
|
if(gReplay)
|
|
{
|
|
carEntity->velo=carEntity->velo+carEntity->accel*kFrameTime;
|
|
MatrixMult(carEntity->netDir,carEntity->rVelo,carEntity->netDir);
|
|
tVector3 netx=*MatrixGetXVector(carEntity->netDir);
|
|
tVector3 nety=*MatrixGetYVector(carEntity->netDir);
|
|
tVector3 netz=*MatrixGetZVector(carEntity->netDir);
|
|
tVector3 curx=*MatrixGetXVector(carEntity->dir);
|
|
tVector3 curz=*MatrixGetZVector(carEntity->dir);
|
|
|
|
*MatrixGetXVector(carEntity->dir)=curx+(netx-curx)*0.3;
|
|
*MatrixGetYVector(carEntity->dir)=nety;
|
|
*MatrixGetZVector(carEntity->dir)=curz+(netz-curz)*0.3;
|
|
MatrixReAdjust2(carEntity->dir);
|
|
}
|
|
carEntity->pos=carEntity->pos+carEntity->velo*kFrameTime;
|
|
|
|
|
|
//rotate the car around it's center of gravity
|
|
tVector3 massCenter=car->massCenter*carEntity->dir;
|
|
tVector3 massMotion=((massCenter*carEntity->rVelo)-massCenter);
|
|
MatrixMult(carEntity->dir,carEntity->rVelo,carEntity->dir);
|
|
carEntity->pos=carEntity->pos-massMotion;
|
|
|
|
//update road position index
|
|
float oldPosition=phys->position;
|
|
phys->position=RoadGetPosition(carEntity->pos,carEntity->lastRoadIndex,NULL);
|
|
|
|
if(carEntity==gViewedEntity)
|
|
for(int i=0;i<gNumCornerSigns;i++)
|
|
if(gGameInfo->reverse){
|
|
if(phys->position<gCornerSigns[i].pos&&oldPosition>gCornerSigns[i].pos)
|
|
{
|
|
gAccelSignDisplayIntensity=1.5;
|
|
gAccelSignDisplayCorner=-gCornerSigns[i].accel;
|
|
}
|
|
}else{
|
|
if(phys->position>gCornerSigns[i].pos&&oldPosition<gCornerSigns[i].pos)
|
|
{
|
|
gAccelSignDisplayIntensity=1.5;
|
|
gAccelSignDisplayCorner=gCornerSigns[i].accel;
|
|
}
|
|
}
|
|
|
|
|
|
//have we crossed the road position lap line?
|
|
if(gGameInfo->reverse)
|
|
{
|
|
if(oldPosition>phys->position+50)
|
|
phys->lap--;
|
|
else if(oldPosition<phys->position-50)
|
|
phys->lap++;
|
|
}
|
|
else
|
|
{
|
|
if(oldPosition>phys->position+50)
|
|
phys->lap++;
|
|
else if(oldPosition<phys->position-50)
|
|
phys->lap--;
|
|
}
|
|
|
|
//test if we are stuck (used for ai recovery)
|
|
int stuck=gFrameCount*kFrameTime>5&&sqr(carEntity->velo)<sqr(8)&&phys->lapCount<=gGameInfo->numLaps;
|
|
if(stuck)
|
|
phys->stuckTime++;
|
|
else
|
|
phys->stuckTime=0;
|
|
|
|
//are we moving backwards? (used to display "wrong direction" message).
|
|
if((gGameInfo->reverse?oldPosition<=phys->position:oldPosition>=phys->position)&&sqr(carEntity->velo)>sqr(5))
|
|
phys->wrongDriectionFrames++;
|
|
else
|
|
phys->wrongDriectionFrames-=10;
|
|
if(phys->wrongDriectionFrames<0)
|
|
phys->wrongDriectionFrames=0;
|
|
else if(phys->wrongDriectionFrames>kFPS*5)
|
|
{
|
|
phys->wrongDriectionFrames=kFPS*5;
|
|
if(gGameInfo->network&kGameInfoNetworkCantGoBackwards)
|
|
{
|
|
if(carEntity==gViewedEntity)
|
|
TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,0.9,1.8,"Wrong Way!");
|
|
RoadCenterCar(carEntity);
|
|
}
|
|
}
|
|
|
|
float velo=~carEntity->velo;
|
|
//Check if we have beaten our speed record for this game
|
|
if(velo>phys->maxSpeed)
|
|
phys->maxSpeed=velo;
|
|
|
|
//Update our average speed
|
|
if(phys->lapCount&&(phys->lapCount<=gGameInfo->numLaps||gGameInfo->numLaps==-1)&&!gReplay)
|
|
{
|
|
phys->averageSpeed=((phys->averageSpeed*(phys->averageSpeedFrames))+velo)/(phys->averageSpeedFrames+1);
|
|
phys->averageSpeedFrames++;
|
|
}
|
|
if(velo>=100/3.6&&phys->accel100==0)
|
|
phys->accel100=gFrameCount*kFrameTime-kStartGameDelaySeconds;
|
|
if(velo>=200/3.6&&phys->accel200==0)
|
|
phys->accel200=gFrameCount*kFrameTime-kStartGameDelaySeconds;
|
|
phys->odo+=velo*kFrameTime;
|
|
if(phys->odo>=402.32454&&phys->accelQuarter==0)
|
|
phys->accelQuarter=gFrameCount*kFrameTime-kStartGameDelaySeconds;
|
|
if(phys->odo>=1000&&phys->accelKM==0)
|
|
phys->accelKM=gFrameCount*kFrameTime-kStartGameDelaySeconds;
|
|
/*if(Button()&&phys->accel200!=0&&!gReplay)
|
|
{
|
|
printf("0-100km/h: %.1f seconds\n0-200km/h: %.1f seconds\n100-200km/h: %.1f seconds\nQuarter Mile: %.1f seconds\n1km: %.1f seconds\nTop Speed:%.1f km/h\n",phys->accel100,phys->accel200,phys->accel200-phys->accel100,phys->accelQuarter,phys->accelKM,phys->maxSpeed*3.6);
|
|
phys->accel200=0;
|
|
}*/
|
|
if(isnan(carEntity->pos.x)||isnan(carEntity->pos.y)||isnan(carEntity->pos.z))
|
|
FullyResetCar(carEntity);
|
|
} |