class REngine | .h |
constructor | REngine(RCar *_car) |
destructor | ~REngine() |
Init | void Init() |
Reset | void Reset() Motor init before usage |
Load | bool Load(QInfo *info,cstring path) 'path' may be 0, in which case the default "engine" is used |
SetGears | void SetGears(int n) |
Paint | void Paint() |
SetGear | void SetGear(int gear) |
SetInput | void SetInput(int ctlThrottle,int ctlClutch) Controller input from 0..1000 Calculates resulting throttle and clutch from 0..1 |
CalcState | void CalcState() |
GetMaxTorque | rfloat GetMaxTorque(rfloat rpm) Returns the maximum torque generated at 'rpm' |
GetMinTorque | rfloat GetMinTorque(rfloat rpm) Returns the maximum torque generated at 'rpm' |
GetInertiaForWheel | rfloat GetInertiaForWheel(RWheel *w) Return effective inertia as seen in the perspective of wheel 'w' Takes into account any clutch effects |
GetTorqueForWheel | rfloat GetTorqueForWheel(RWheel *w) Return effective torque for wheel 'w' Takes into account any clutch effects |
CalcForces | void CalcForces() |
ApplyForces | void ApplyForces() |
Integrate | void Integrate() Based on current input values, adjust the engine |
OnGfxFrame | void OnGfxFrame() |
/*
* REngine - the engine
* 05-08-00: Created!
* 10-03-01: Constant torque is obsolete; you now must ALWAYS have a curve.
* 15-03-01: No engine is painted anymore. It's of so little use with models.
* NOTES:
* - Clutch is modeled as linear.
* (C) MarketGraph/RvG
*/
#include <racer/racer.h>
#include <qlib/debug.h>
#pragma hdrstop
DEBUG_ENABLE
#define DEF_SIZE .25
#define DEF_MAXRPM 5000
#define DEF_MAXPOWER 100
#define DEF_FRICTION 0
#define DEF_MAXTORQUE 340 // ~ F1 Jos Verstappen
#define DEF_TORQUE 100 // In case no curve is present
// If USE_HARD_REVLIMIT is used, any rpm above the max. RPM
// returns a 0 engine torque. Although this works, it gives quite
// hard rev limits (esp. when in 1st gear). Better is to supply
// a curve which moves down to 0 torque when rpm gets above maxRPM,
// so a more natural (smooth) balance is obtained (and turn
// this define off)
//#define USE_HARD_REVLIMIT
REngine::REngine(RCar *_car)
{
car=_car;
crvTorque=0;
Init();
#ifdef OBS
// Debugging
throttle=RMGR->infoDebug->GetInt("engine.throttle");
#endif
}
REngine::~REngine()
{
if(crvTorque)delete crvTorque;
}
void REngine::Init()
{
int i;
flags=0;
size=DEF_SIZE;
quad=gluNewQuadric();
mass=0;
maxRPM=DEF_MAXRPM;
idleRPM=500;
friction=0;
brakingCoeff=0;
position.SetToZero();
force=0;
torque=0;
maxTorque=0;
timeToDeclutch=timeToClutch=0;
autoShiftStart=0;
futureGear=0;
inertiaEngine=1.0f;
inertiaDriveShaft=0;
clutch=1;
throttle=0;
brakes=0;
rotationV=0;
for(i=0;i<MAX_GEAR;i++)
{ gearRatio[i]=1;
gearInertia[i]=0;
}
endRatio=1;
curGear=1;
gears=4;
if(crvTorque)delete crvTorque;
crvTorque=0;
}
void REngine::Reset()
// Motor init before usage
{
rotationV=0;
curGear=1;
autoShiftStart=0;
}
/*************
* Definition *
*************/
bool REngine::Load(QInfo *info,cstring path)
// 'path' may be 0, in which case the default "engine" is used
{
char buf[128],fname[128];
int i;
QInfo *infoCurve;
if(!path)path="engine";
// Location
sprintf(buf,"%s.x",path);
position.x=info->GetFloat(buf);
sprintf(buf,"%s.y",path);
position.y=info->GetFloat(buf);
sprintf(buf,"%s.z",path);
position.z=info->GetFloat(buf);
sprintf(buf,"%s.size",path);
size=info->GetFloat(buf,DEF_SIZE);
// Physical attribs
#ifdef OBS
sprintf(buf,"%s.rolling_friction_coeff",path);
rollingFrictionCoeff=info->GetFloat(buf);
#endif
sprintf(buf,"%s.mass",path);
mass=info->GetFloat(buf);
// Power/torque
sprintf(buf,"%s.max_rpm",path);
maxRPM=info->GetFloat(buf,DEF_MAXRPM);
sprintf(buf,"%s.idle_rpm",path);
idleRPM=info->GetFloat(buf,1000);
// Torque curve or constant (curve is preferred)
//sprintf(buf,"%s.constant_torque",path);
sprintf(buf,"%s.max_torque",path);
maxTorque=info->GetFloat(buf,DEF_MAXTORQUE);
crvTorque=new QCurve();
sprintf(buf,"%s.curve_torque",path);
info->GetString(buf,fname);
//qdbg("fname_torque='%s'\n",fname);
if(fname[0])
{ infoCurve=new QInfo(RFindFile(fname,car->GetCarDir()));
crvTorque->Load(infoCurve,"curve");
delete infoCurve;
} else
{ // No torque curve
delete crvTorque;
crvTorque=0;
}
sprintf(buf,"%s.inertia.engine",path);
inertiaEngine=info->GetFloat(buf);
sprintf(buf,"%s.inertia.final_drive",path);
inertiaDriveShaft=info->GetFloat(buf);
sprintf(buf,"%s.friction",path);
friction=info->GetFloat(buf,DEF_FRICTION);
sprintf(buf,"%s.braking_coeff",path);
brakingCoeff=info->GetFloat(buf);
// Shifting
sprintf(buf,"%s.shifting.automatic",path);
if(info->GetInt(buf))
flags|=AUTOMATIC;
//qdbg("Autoshift: flags=%d, buf='%s'\n",flags,buf);
sprintf(buf,"%s.shifting.shift_up_rpm",path);
shiftUpRPM=info->GetFloat(buf,3500);
sprintf(buf,"%s.shifting.shift_down_rpm",path);
shiftDownRPM=info->GetFloat(buf,2000);
sprintf(buf,"%s.shifting.time_to_declutch",path);
timeToDeclutch=info->GetFloat(buf,500);
sprintf(buf,"%s.shifting.time_to_clutch",path);
timeToClutch=info->GetFloat(buf,500);
// Gearbox
path="gearbox"; // !
sprintf(buf,"%s.gears",path);
gears=info->GetInt(buf,4);
for(i=0;i<gears;i++)
{ sprintf(buf,"%s.gear%d.ratio",path,i);
gearRatio[i]=info->GetFloat(buf,1.0);
sprintf(buf,"%s.gear%d.inertia",path,i);
gearInertia[i]=info->GetFloat(buf);
}
sprintf(buf,"%s.end_ratio",path);
endRatio=info->GetFloat(buf,3.0);
Reset();
return TRUE;
}
#ifdef OBS
void REngine::SetGears(int n)
{
gears=n;
}
#endif
void REngine::Paint()
{
#ifdef OBS_NO_MOTOR
car->SetDefaultMaterial();
glPushMatrix();
// Location
glTranslatef(position.GetX(),position.GetY(),position.GetZ());
#ifdef OBS
// Stats
char buf[80];
rfloat p;
// Calc generated power in Watts (J/s)
// Note also that 1hp = 746W
p=2*PI*(rpm/60)*torque;
sprintf(buf,"RPM=%.f, Torque=%.f Nm, Power %.3fhp, Gear-ratio %.2f",
rpm,torque,p/746,gearRatio[curGear]*endRatio);
GfxText3D(position.GetX(),position.GetY(),position.GetZ(),buf);
#endif
// Discs are painted at Z=0
//glRotatef(90,0,1,0);
// Center point for quad
//glTranslatef(0,0,-size/2);
float colMotor[]={ .8,.8,.82 };
int slices=6;
glMaterialfv(GL_FRONT,GL_DIFFUSE,colMotor);
// Draw a box (=cylinder with 4 slices)
gluCylinder(quad,size,size,size,slices,1);
// Caps
gluQuadricOrientation(quad,GLU_INSIDE);
gluDisk(quad,0,size,slices,1);
gluQuadricOrientation(quad,GLU_OUTSIDE);
glTranslatef(0,0,size);
gluDisk(quad,0,size,slices,1);
glPopMatrix();
#endif
}
/**********
* Gearbox *
**********/
void REngine::SetGear(int gear)
{
QASSERT_V(gear>=0&&gear<gears);
curGear=gear;
}
/********
* Input *
********/
void REngine::SetInput(int ctlThrottle,int ctlClutch)
// Controller input from 0..1000
// Calculates resulting throttle and clutch from 0..1
{
// Throttle is pushed
// Convert to 0..1
throttle=((rfloat)ctlThrottle)/1000.0f;
if(throttle>1)throttle=1;
else if(throttle<0)throttle=0;
//qdbg("REngine:SetInput(); throttle=%f\n",throttle);
//qdbg("REngine:SetInput(ctlClutch=%d)\n",ctlClutch);
// Only update clutch value when the car is not auto-shifting
if(!autoShiftStart)
{
clutch=((rfloat)ctlClutch)/1000.0f;
if(clutch>1)clutch=1;
else if(clutch<0)clutch=0;
}
}
/******************
* Calculate state *
******************/
void REngine::CalcState()
{
}
/**********
* Physics *
**********/
rfloat REngine::GetMaxTorque(rfloat rpm)
// Returns the maximum torque generated at 'rpm'
{
#ifdef USE_HARD_REVLIMIT
// Clip hard at max rpm (return 0 torque if rpm gets too high)
if(rpm>maxRPM)
return 0;
#endif
if(!crvTorque)
{
// No curve available, use a less realistic constant torque
return DEF_TORQUE;
} else
{
rfloat t;
// Find normalized torque in RPM-to-torque curve
t=(rfloat)crvTorque->GetValue(rpm);
//qdbg("REngine: rpm=%.2f => (curve)maxTorque=%.2f\n",rpm,t);
return t*maxTorque;
}
}
rfloat REngine::GetMinTorque(rfloat rpm)
// Returns the maximum torque generated at 'rpm'
{
// If 0% throttle, this is the minimal torque which is generated
// by the engine
return -brakingCoeff*rpm/60;
}
rfloat REngine::GetInertiaForWheel(RWheel *w)
// Return effective inertia as seen in the perspective of wheel 'w'
// Takes into account any clutch effects
{
rfloat totalInertia;
rfloat inertiaBehindClutch;
rfloat NtfSquared,NfSquared;
rfloat rotationA;
RWheel *wheel;
int i;
// Calculate total ratio multiplier; note the multipliers are squared,
// and not just a multiplier for the inertia. See Gillespie's book,
// 'Fundamentals of Vehicle Dynamics', page 33.
NtfSquared=gearRatio[curGear]*endRatio;
NtfSquared*=NtfSquared;
// Calculate total inertia that is BEHIND the clutch
NfSquared=endRatio*endRatio;
inertiaBehindClutch=gearInertia[curGear]*NtfSquared+
inertiaDriveShaft*NfSquared;
// Add inertia of attached and powered wheels
// This is a constant, so it should be cached actually (except
// when a wheel breaks off)
for(i=0;i<car->GetWheels();i++)
{
wheel=car->GetWheel(i);
if(wheel->IsPowered()&&wheel->IsAttached())
inertiaBehindClutch+=wheel->GetRotationalInertia()->x;
}
// Add the engine's inertia based on how far the clutch is engaged
totalInertia=inertiaBehindClutch+clutch*inertiaEngine*NtfSquared;
return totalInertia;
}
rfloat REngine::GetTorqueForWheel(RWheel *w)
// Return effective torque for wheel 'w'
// Takes into account any clutch effects
{
//qdbg("clutch=%f, T=%f, ratio=%f\n",clutch,torque,gearRatio[curGear]*endRatio);
return clutch*torque*gearRatio[curGear]*endRatio;
}
/**************
* Calc Forces *
**************/
void REngine::CalcForces()
{
rfloat minTorque,maxTorque;
rfloat rpm=GetRPM();
// Calculate minimal torque (if 0% throttle)
minTorque=GetMinTorque(rpm);
// Calculate maximum torque (100% throttle situation)
maxTorque=GetMaxTorque(rpm);
// The throttle accounts for how much of the torque is actually
// produced.
// NOTE: The output power then is 2*PI*rps*outputTorque
// (rps=rpm/60; rotations per second). Nothing but a statistic now.
torque=(maxTorque-minTorque)*throttle+minTorque;
#ifdef OBS
qdbg("minTorque=%f, maxTorque=%.f, throttle=%f => torque=%f\n",
minTorque,maxTorque,throttle,torque);
#endif
#ifdef OBS
// Torque is multiplied by gear and end ratio before it hits
// the wheels
torque=torque*gearRatio[curGear]*endRatio;
#endif
#ifdef ND_FUTURE
// Apply engine friction
// Is linearly affected by rpm
//force-=friction*maxPower;
torque-=friction*rpm/60;
#endif
}
/***************
* Apply forces *
***************/
void REngine::ApplyForces()
{
}
/************
* Integrate *
************/
void REngine::Integrate()
// Based on current input values, adjust the engine
{
rfloat totalInertia;
rfloat inertiaBehindClutch;
rfloat NtfSquared,NfSquared;
rfloat rotationA;
int i;
RWheel *wheel;
// Calculate total ratio multiplier; note the multipliers are squared,
// and not just a multiplier for the inertia. See Gillespie's book,
// 'Fundamentals of Vehicle Dynamics', page 33.
NtfSquared=gearRatio[curGear]*endRatio;
NtfSquared*=NtfSquared;
//qdbg("REngine:Integrate(); clutch=%f\n",clutch);
if(clutch<D3_EPSILON)
{
// Consider the clutch fully depressed
// This means all inertia beyond the engine doesn't count
// for the rotational acceleration of the engine itself
totalInertia=inertiaEngine*NtfSquared;
} else
{
// The clutch is active, <0..1]
// Calculate total inertia that is BEHIND the clutch
NfSquared=endRatio*endRatio;
inertiaBehindClutch=gearInertia[curGear]*NtfSquared+
inertiaDriveShaft*NfSquared;
// Add inertia of attached and powered wheels
// This is a constant, so it should be cached actually (except
// when a wheel breaks off)
for(i=0;i<car->GetWheels();i++)
{
wheel=car->GetWheel(i);
if(wheel->IsPowered()&&wheel->IsAttached())
inertiaBehindClutch+=wheel->GetRotationalInertia()->x;
}
// All of this inertia affects the engine speed through the clutch
totalInertia=inertiaEngine*NtfSquared+clutch*inertiaBehindClutch;
}
// Calculate resulting engine rotation (the RPM)
if(totalInertia>-D3_EPSILON&&totalInertia<D3_EPSILON)
{
// This is not right, don't do anything
} else
{
// Calculate engine rotation acceleration
rotationA=torque/(totalInertia/NtfSquared);
//rotationA=torque/totalInertia;
#ifdef OBS
qdbg("REngine:Int: torque=%.2f, effective inertia=%f, rotA=%f\n",
torque,totalInertia,rotationA);
#endif
}
if(clutch<0.5f)
{
// Let engine rotate independently of wheels; clutch
// is depressed
rotationV+=rotationA*RMGR->time->span;
} else
{
// RPM is derived from wheel rotation (and diff)
// Open diff; rpm=average rpm of wheels
rfloat rotVTotal=0.0f;
int count=0;
for(i=0;i<car->GetWheels();i++)
{
wheel=car->GetWheel(i);
//qdbg("REngine: rotV%d=%f\n",i,wheel->GetRotationV());
if(wheel->IsPowered())
{
rotVTotal+=wheel->GetRotationV();
count++;
}
}
#ifdef OBS
qdbg("curGear=%d, ratio=%f, endRatio=%f\n",curGear,
gearRatio[curGear],endRatio);
#endif
//rpsTotal=(rpsTotal/(2*PI))*gearRatio[curGear]*endRatio;
rotVTotal=rotVTotal*gearRatio[curGear]*endRatio;
if(count>0)
rotationV=rotVTotal/count;
else
rotationV=0;
//qdbg(" engine_rotV=%f, rpm=%f\n",rotationV,GetRPM());
}
// Automatic shifting
if(autoShiftStart==0&&((flags&AUTOMATIC)!=0))
{
// Check RPM
rfloat rpm=GetRPM();
//qdbg("Automatic check rpm=%f up=%f, dn=%f\n",rpm,shiftUpRPM,shiftDownRPM);
if(curGear<gears-1&&rpm>shiftUpRPM)
{
qdbg("Automatic shift up\n");
autoShiftStart=RMGR->time->GetSimTime();
futureGear=curGear+1;
clutch=0;
} else if(curGear>1&&rpm<shiftDownRPM)
{
autoShiftStart=RMGR->time->GetSimTime();
futureGear=curGear-1;
clutch=0;
}
}
// The shifting process
if(autoShiftStart)
{
// We are in a shifting operation
if(curGear!=futureGear)
{
// We are in the pre-shift phase
if(RMGR->time->GetSimTime()>=autoShiftStart+timeToDeclutch)
{
//qdbg("Shift: declutch ready, change gear\n");
// Declutch is ready, change gear
SetGear(futureGear);
// Trigger gear shift sample
//...
}
} else
{
// We are in the post-shift phase
if(RMGR->time->GetSimTime()>=autoShiftStart+
timeToClutch+timeToDeclutch)
{
//qdbg("Shift: clutch ready, end shift\n");
// Clutch is ready, end shifting process
clutch=1.0f;
autoShiftStart=0;
}
}
}
}
/********************
* On Graphics Frame *
********************/
void REngine::OnGfxFrame()
{
if(autoShiftStart==0)
{
// No shift in progress; check shift commands from the controllers
if(RMGR->controls->control[RControls::T_SHIFTUP]->value)
{
//qdbg("Shift Up!\n");
if(curGear<gears-1)
{
autoShiftStart=RMGR->time->GetSimTime();
futureGear=curGear+1;
clutch=0;
}
} else if(RMGR->controls->control[RControls::T_SHIFTDOWN]->value)
{
if(curGear>1)
{
autoShiftStart=RMGR->time->GetSimTime();
futureGear=curGear-1;
clutch=0;
}
}
}
}