Module: rengine.cpp

class REngine
.h

constructorREngine(RCar *_car)
destructor~REngine()
Initvoid Init()
Resetvoid Reset()

Motor init before usage

Loadbool Load(QInfo *info,cstring path)

'path' may be 0, in which case the default "engine" is used

SetGearsvoid SetGears(int n)
Paintvoid Paint()
SetGearvoid SetGear(int gear)
SetInputvoid SetInput(int ctlThrottle,int ctlClutch)

Controller input from 0..1000
Calculates resulting throttle and clutch from 0..1

CalcStatevoid CalcState()
GetMaxTorquerfloat GetMaxTorque(rfloat rpm)

Returns the maximum torque generated at 'rpm'

GetMinTorquerfloat GetMinTorque(rfloat rpm)

Returns the maximum torque generated at 'rpm'

GetInertiaForWheelrfloat GetInertiaForWheel(RWheel *w)

Return effective inertia as seen in the perspective of wheel 'w'
Takes into account any clutch effects

GetTorqueForWheelrfloat GetTorqueForWheel(RWheel *w)

Return effective torque for wheel 'w'
Takes into account any clutch effects

CalcForcesvoid CalcForces()
ApplyForcesvoid ApplyForces()
Integratevoid Integrate()

Based on current input values, adjust the engine

OnGfxFramevoid 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;
      }
    }
  }
}