Module: rcar.cpp

class RCar
.h

constructorRCar(cstring carName)
destructor~RCar()

Flat functions
 

SkipWordstatic cstring SkipWord(cstring s)

Skip from "word1 word2" to "word2" (example)

GetTokenstatic cstring GetToken(cstring s)

class RCar
.h

Loadbool Load()

Read all parts

GetMassrfloat GetMass()

Returns total mass of all components

GetVelocityTachrfloat GetVelocityTach()

Get velocity as to be displayed on the tachiometer (sp?)


Flat functions
 

deg2radstatic float deg2rad(float d)

class RCar
.h

PreAnimatevoid PreAnimate()

Determine state in which all car parts are situated for later
Newtonian calculations.

Animatevoid Animate()

Animate all parts
Connects the parts of the car


Flat functions
 

timingPosPreWC.DbgPrinttimingPosPreWC.DbgPrint("timingpos pre");

timingPosPostWC.DbgPrint("timingpos post");
#endif
int ctl=RMGR->scene->curTimeLine[index];
if(RMGR->trackVRML->timeLine[ctl]->CrossesPlane(
&timingPosPreWC,&timingPosPostWC))
{
// Notify scene of our achievement
RMGR->scene->TimeLineCrossed(this);
}
}
void RCar::SetInput(int ctlSteer,int _ctlThrottle,int _ctlBrakes,int ctlClutch)
Process the inputs that come from the controller
and pass them on to the objects in the car that need it


class RCar
.h

OnGfxFramevoid OnGfxFrame()

Update the car in areas where physics integration is not a concern
This is a place where you put things that need to be done only once
every graphics frame, like polling controllers, settings sample
frequencies etc.

PaintShadowvoid PaintShadow()

Paint the car's shadow

Paintvoid Paint()

Paint the entire car

ApplyRotationsvoid ApplyRotations()
ApplyAccelerationvoid ApplyAcceleration()

Look at the wheel forces and accelerate the car
with the sum

Warpvoid Warp(RCarPos *pos)

Move the car to the specified location/orientation
Used with Shift-R for example


Flat functions
 

pos->from.DbgPrintpos->from.DbgPrint("Warp from");

pos->to.DbgPrint("Warp from");
axis.DbgPrint("Warp axis");
#endif

m.DbgPrintm.DbgPrint("Source matrix for quat");

body->GetRotPosM()->FromQuaternion(q);
body->GetRotPosM()->DbgPrint("Result matrix from quat");


class RCar
.h

ConvertCarToWorldOrientationvoid ConvertCarToWorldOrientation(DVector3 *from0,DVector3 *toFinal)

Convert orientation 'from' from car coordinates (local)
to world coordinates (into 'to')
Order is reverse to YPR; RPY here (ZXY)

ConvertCarToWorldCoordsvoid ConvertCarToWorldCoords(DVector3 *from,DVector3 *to)

Convert vector 'from' from car coordinates (local)
to world coordinates (into 'to')
Should be used for coordinates, NOT orientations (a translation
is used here as well as a rotation)

ConvertWorldToCarOrientationvoid ConvertWorldToCarOrientation(DVector3 *from0,DVector3 *toFinal)

Convert vector 'from' from world coordinates (global)
to car coordinates (into 'to')

ConvertWorldToCarCoordsvoid ConvertWorldToCarCoords(DVector3 *from,DVector3 *to)

Convert vector 'from' from car coordinates (local)
to world coordinates (into 'to')
Should be used for coordinates, NOT orientations (a translation
is used here as well as a rotation)

SetDefaultMaterialvoid SetDefaultMaterial()

When using models, materials are applied from the model files
To use the stub graphics, these material changes must be reset
to their default values



/*
 * RCar - a car
 * 05-08-00: Created! (13:28:24)
 * 12-12-00: Wheel loading not by name anymore
 * 30-01-01: Wheel lock is now halved (20deg lock=-10..+10)
 * NOTES:
 * BUGS:
 * - Warp should look at track info to really get the highest position
 * for the worst-case wheel (not based the height info on the grid line)
 * (C) MarketGraph/RvG
 */

#include <racer/racer.h>
#include <qlib/debug.h>
#pragma hdrstop
#include <d3/global.h>
#include <math.h>
DEBUG_ENABLE

#define CAR_INI_NAME		"car.ini"
#define VIEW_INI_NAME           "views.ini"

#undef  DBG_CLASS
#define DBG_CLASS "RCar"

/*********
* C/Dtor *
*********/
RCar::RCar(cstring carName)
{
  DBG_C("ctor")
  QASSERT_V(carName)

  int i;
  
  index=0;
  
  // Init member variables
  cg.SetToZero();
  texShadow=0;
  
  // Decide car directory
  char buf[256],*dir;
  dir=getenv("RACER");
  if(dir)sprintf(buf,"%s/data/cars/%s",dir,carName);
  else   sprintf(buf,"data/cars/%s",carName);
  carDir=buf;

  // Defaults
  wheels=0;
  for(i=0;i<MAX_WHEEL;i++)
    wheel[i]=0;
  for(i=0;i<MAX_CAMERA;i++)
  { camera[i]=new RCamera(this);
    camera[i]->SetIndex(i);
  }
  curCamera=0;
  
  // Audio
  rap=0;
  if(RMGR->audio)
  { rap=new RAudioProducer();
    RMGR->audio->AddProducer(rap);
  }
  
  // Parts
  body=new RBody(this);
  steer=new RSteer(this);
  engine=new REngine(this);
  for(i=0;i<MAX_WHEEL;i++)
  {
    susp[i]=new RSuspension(this);
  }

  // Open info files
  info=new QInfo(RFindFile(CAR_INI_NAME,carDir));
  // Open default info file
qdbg("carDir='%s'\n",carDir.cstr());
  sprintf(buf,"%s/../default/%s",carDir.cstr(),CAR_INI_NAME);
qdbg("buf='%s'\n",buf);
  infoDefault=new QInfo(buf);
  info->SetFallback(infoDefault);
  
  // Auto-load
  Load();

  // Read views for this car
  {
    QInfo info(RFindFile(VIEW_INI_NAME,carDir));
    views=new RViews(this);
    views->Load(&info);
  }
}
RCar::~RCar()
{
  int i;

  if(body)delete body;
  if(steer)delete steer;
  if(engine)delete engine;
  for(i=0;i<MAX_WHEEL;i++)
    if(susp[i])
      delete susp[i];
  for(i=0;i<MAX_CAMERA;i++)
    if(camera[i])
      delete camera[i];
  if(views)delete views;
  
  if(rap)
  {
    RMGR->audio->RemoveProducer(rap);
    delete rap;
  }
  if(infoDefault)delete infoDefault;
  if(info)delete info;
}

/**********
* Loading *
**********/
static cstring SkipWord(cstring s)
// Skip from "word1 word2" to "word2" (example)
{
  for(;*s;s++)
  {
    if(*s==' ')break;
  }
  // Spaces
  for(;*s;s++)
  {
    if(*s!=' ')break;
  }
  return s;
}
static cstring GetToken(cstring s)
{
  static char buf[128];
  char *d=buf;
  
  for(;*s!=' '&&*s!=0;s++)
  { *d++=*s;
  }
  *d=0;
  return buf;
}

bool RCar::Load()
// Read all parts
{
  DBG_C("Load")

  char buf[100];
  int  i;
  cstring name,wName;
  
  // Preferences for car 3D models
  dglobal.prefs.Reset();
  dglobal.prefs.envMode=DTexture::MODULATE;
  
  // Read car properties
  cg.x=info->GetFloat("car.cg.x");
  cg.y=info->GetFloat("car.cg.y",.5f);
  cg.z=info->GetFloat("car.cg.z");

  // Shadow
  info->GetString("car.shadow.texture",buf);
  if(buf[0])
  {
    DBitMapTexture *tbm;
    QImage img(RFindFile(buf,carDir));
    if(img.IsRead())
    {
      tbm=new DBitMapTexture(&img);
      texShadow=(DTexture*)tbm;
    }
  }
  
  // Read component props
  body->Load(info,"body");
  
  // Read cameras
  for(i=0;i<MAX_CAMERA;i++)
    camera[i]->Load(info);

  // Read suspensions from wheels
  wheels=info->GetInt("car.wheels",4);
  if(wheels>MAX_WHEEL)
  { qwarn("RCar: wheels=%d, but max=%d",wheels,MAX_WHEEL);
    wheels=MAX_WHEEL;
  }
  for(i=0;i<wheels;i++)
  {
    sprintf(buf,"susp%d",i);
//qdbg("RCar: Load suspension '%s'\n",buf);
    //susp[i]=new RSusp(this);
    susp[i]->Load(info,buf);
  }
  
  // Read wheels
  for(i=0;i<wheels;i++)
  {
    sprintf(buf,"wheel%d",i);
//qdbg("RCar: Load wheel '%s'\n",buf);
    wheel[i]=new RWheel(this);
    // Attach its suspension (for convenience)
    wheel[i]->SetSuspension(susp[i]);
    wheel[i]->Load(info,buf);
  }
  
  // Read steering wheel
  steer->Load(info,"steer");
  
  // Read engine
  engine->Load(info,"engine");

  // Motor sound
  if(RMGR->audio)
  { info->GetString("engine.sample",buf);
    rap->LoadSample(RFindFile(buf,GetCarDir()));
    rap->SetSampleSpeed(info->GetInt("engine.sample_rpm"));
    rap->SetCurrentSpeed(0);
  }

  // Debugging
  ctlBrakes=RMGR->infoDebug->GetInt("engine.brakes");

  // Restore gfx settings
  //dglobal.prefs.Reset();
  
  return TRUE;
}

/**********
* Attribs *
**********/
rfloat RCar::GetMass()
// Returns total mass of all components
{
  int i;
  rfloat w=0;
  w=body->GetMass();
  w+=engine->GetMass();
  for(i=0;i<wheels;i++)
  { w+=wheel[i]->GetMass();
  }
  return w;
}
rfloat RCar::GetVelocityTach()
// Get velocity as to be displayed on the tachiometer (sp?)
{
  return body->GetLinVel()->Length();
}


/**********
* Animate *
**********/
static float deg2rad(float d)
{
  return (d/180.0)*3.14159265358979;
}
void RCar::PreAnimate()
// Determine state in which all car parts are situated for later
// Newtonian calculations.
{
  int i;
  
  // Pre-work for next physics pass
  engine->CalcState();
  body->PreAnimate();
  for(i=0;i<wheels;i++)
  {
    wheel[i]->PreAnimate();
    susp[i]->PreAnimate();
  }
}
void RCar::Animate()
// Animate all parts
// Connects the parts of the car
{
  DBG_C("Animate")

  int i,count;
  float force,torque;
  float accMag;

//qdbg("---- frame %d\n",rStats.frame);
//qdbg("----\n");

  // Pass on controller data
  
  // Send steering angle through to steering wheels
  for(i=0;i<wheels;i++)
    if(wheel[i]->IsSteering())
    { float steerLock,wheelLock;
      float factor;
      
      steerLock=steer->GetLock();
      factor=steerLock/wheel[i]->GetLock()*2.0f;
      wheel[i]->SetHeading(steer->GetAngle()/factor /*+GetHeading()*/);
//qdbg("wheel%d: heading %f deg\n",
//i,(steer->GetAngle()/factor)*RR_RAD_DEG_FACTOR);
    }
  
  // Animate car as a whole
  PreAnimate();
  
  //
  // Calculate state of vehicle before calculating forces
  //
  // Calculate all forces on the car
  //
  engine->CalcForces();
  for(i=0;i<wheels;i++)
    susp[i]->CalcForces();

  for(i=0;i<wheels;i++)
    wheel[i]->CalcForces();

  body->CalcForces();

  //
  // Now that all forces have been generated, apply
  // them to get accelerations (translational/rotational)
  //
  engine->ApplyForces();
  for(i=0;i<wheels;i++)
    wheel[i]->ApplyForces();
  body->ApplyForces();
  
  // Rotational dynamics; 3 axes of rotation, 6 DOF
  ApplyRotations();
  ApplyAcceleration();

  // After all forces & accelerations are done, deduce
  // data for statistics
  
#ifdef OBS_RPM_IS_NEW
  // Engine RPM is related to the rotation of the powered wheels,
  // since they are physically connected, somewhat
  // Take the minimal rotation
  float minV=99999.0f,maxV=0;
  for(i=0;i<wheels;i++)
    if(wheel[i]->IsPowered())
    { 
      if(wheel[i]->GetRotationV()>maxV)
        maxV=wheel[i]->GetRotationV();
#ifdef OBS_HMM
      if(wheel[i]->GetRotationV()<minV)
        minV=wheel[i]->GetRotationV();
#endif
//qdbg("  rpm: wheel%d: rotV=%f\n",i,wheel[i]->GetRotationV());
    }
  engine->ApplyWheelRotation(maxV);
#endif
  
  // Remember old location of car to check for hitting things
  DVector3 timingPosBC,timingPosPreWC,timingPosPostWC;
  timingPosBC.SetToZero();
  body->ConvertBodyToWorldPos(&timingPosBC,&timingPosPreWC);
  
  // Integrate step for all parts
  body->Integrate();
  engine->Integrate();
  steer->Integrate();
  for(i=0;i<wheels;i++)
    wheel[i]->Integrate();
  for(i=0;i<wheels;i++)
    susp[i]->Integrate();
    
  // Check if something was hit/crossed
  body->ConvertBodyToWorldPos(&timingPosBC,&timingPosPostWC);
#ifdef OBS
timingPosPreWC.DbgPrint("timingpos pre");
timingPosPostWC.DbgPrint("timingpos post");
#endif
  int ctl=RMGR->scene->curTimeLine[index];
  if(RMGR->trackVRML->timeLine[ctl]->CrossesPlane(
    &timingPosPreWC,&timingPosPostWC))
  {
    // Notify scene of our achievement
    RMGR->scene->TimeLineCrossed(this);
  }
}
void RCar::SetInput(int ctlSteer,int _ctlThrottle,int _ctlBrakes,int ctlClutch)
// Process the inputs that come from the controller
// and pass them on to the objects in the car that need it
{
  DBG_C("SetInput")

  // Controller input
  ctlThrottle=_ctlThrottle;
  ctlBrakes=_ctlBrakes;
  steer->SetInput(ctlSteer);
  engine->SetInput(ctlThrottle,ctlClutch);
}
void RCar::OnGfxFrame()
// Update the car in areas where physics integration is not a concern
// This is a place where you put things that need to be done only once
// every graphics frame, like polling controllers, settings sample
// frequencies etc.
{
  int i;
  // Audio of engine
  rap->SetCurrentSpeed(engine->GetRPM());
  engine->OnGfxFrame();
  for(i=0;i<wheels;i++)
    wheel[i]->OnGfxFrame();
}

/***********
* Painting *
***********/
void RCar::PaintShadow()
// Paint the car's shadow
{
  DVector3 vCC,vWC;
  DVector3 *size;
  RSurfaceInfo *si;
  int i;
  
  // This algo needs 4 points for a single quad
  if(wheels!=4)return;

  size=body->GetSize();
  
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glDisable(GL_LIGHTING);
  if(texShadow)
  {
    // Use shadow texturemap
    glEnable(GL_TEXTURE_2D);
    // The color is modulated, so you can decide how thick the shadow is.
    // Idea is to thin out the shadow when the car is not on the ground.
    glColor4f(0,0,0,.7);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    //glDisable(GL_BLEND);
    texShadow->Select();
  } else
  {
    // Paint a gray color to get something (not even THAT bad)
    glDisable(GL_TEXTURE_2D);
    glColor4f(0,0,0,.3);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  }
  //glBegin(GL_TRIANGLES);
  glBegin(GL_QUADS);
    vCC.x=-size->x/2;
    vCC.y=0;
    vCC.z=size->z/2;
    ConvertCarToWorldCoords(&vCC,&vWC);
    // Project to ground
    vWC.y=wheel[0]->GetSurfaceInfo()->y;
    glTexCoord2f(0,1);
    glVertex3f(vWC.x,vWC.y,vWC.z);
    
    vCC.x=size->x/2;
    vCC.y=0;
    vCC.z=size->z/2;
    ConvertCarToWorldCoords(&vCC,&vWC);
    // Project to ground
    vWC.y=wheel[1]->GetSurfaceInfo()->y;
    glTexCoord2f(1,1);
    glVertex3f(vWC.x,vWC.y,vWC.z);
    
    vCC.x=size->x/2;
    vCC.y=0;
    vCC.z=-size->z/2;
    ConvertCarToWorldCoords(&vCC,&vWC);
    // Project to ground
    vWC.y=wheel[2]->GetSurfaceInfo()->y;
    glTexCoord2f(1,0);
    glVertex3f(vWC.x,vWC.y,vWC.z);
    
    vCC.x=-size->x/2;
    vCC.y=0;
    vCC.z=-size->z/2;
    ConvertCarToWorldCoords(&vCC,&vWC);
    // Project to ground
    vWC.y=wheel[3]->GetSurfaceInfo()->y;
    glTexCoord2f(0,0);
    glVertex3f(vWC.x,vWC.y,vWC.z);
  glEnd();
  // Restore state
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_TEXTURE_2D);
}
void RCar::Paint()
// Paint the entire car
{
  DBG_C("Paint")

  int i;

  // View
#ifdef OBS
//qdbg("car pos=%f,%f,%f\n",position.GetX(),position.GetY(),position.GetZ());
  glTranslatef(position.GetX(),position.GetY(),position.GetZ());
  // Heading is in radians
  glRotatef(rotation.GetY()*RAD_DEG_FACTOR,0,1,0);
  //glRotatef(20,0,1,0);
#endif
  
  // Body hierarchy
#ifdef RR_GFX_OGL
  glPushMatrix();
#endif
  PaintShadow();
  body->Paint();
  engine->Paint();
  steer->Paint();
  for(i=0;i<wheels;i++)
    susp[i]->Paint();
  for(i=0;i<wheels;i++)
    wheel[i]->Paint();
#ifdef RR_GFX_OGL
  glPopMatrix();
#endif
}  

/**********
* Physics *
**********/
void RCar::ApplyRotations()
{
  body->ApplyRotations();
}

void RCar::ApplyAcceleration()
// Look at the wheel forces and accelerate the car
// with the sum
{
}

/******************
* Warping the car *
******************/
void RCar::Warp(RCarPos *pos)
// Move the car to the specified location/orientation
// Used with Shift-R for example
{
  DQuaternion *q;
  DVector3 axis,vx,vy,vz;
  dfloat   angle,offsetY;
  DVector3 *vBodyPos;
  DMatrix3  m;
  int i;

  q=body->GetRotPosQ();
  // Calculate axis along which car is oriented
  axis=pos->from;
  axis.Subtract(&pos->to);
#ifdef OBS
pos->from.DbgPrint("Warp from");
pos->to.DbgPrint("Warp from");
axis.DbgPrint("Warp axis");
#endif

#ifdef OBS_DOES_NOT_WORK
  // Calculate angle of rotation
  angle=0;
  // Store as a quaternion
  q->Rotation(&axis,angle);
#endif

  // Calculate Z direction of car
  vz=axis;
  // Calc X direction of car as cross product of 'vz' and
  // a line going straight up. This DOES mean the warp orientation
  // of the car should never be straight up, but this is highly
  // unusual.
  DVector3 vUp(0,1.0f,0);
  vx.Cross(&vUp,&vz);
  // Calc Y as the last vector perpendicular to both vz and vx
  vy.Cross(&vz,&vx);
  // Normalize all directions
  vx.Normalize();
  vy.Normalize();
  vz.Normalize();
  // Create the rotation matrix from the vectors
  m.SetRC(0,0,vx.x);
  m.SetRC(1,0,vx.y);
  m.SetRC(2,0,vx.z);
  m.SetRC(0,1,vy.x);
  m.SetRC(1,1,vy.y);
  m.SetRC(2,1,vy.z);
  m.SetRC(0,2,vz.x);
  m.SetRC(1,2,vz.y);
  m.SetRC(2,2,vz.z);
  // Store as a quaternion
  q->FromMatrix(&m);
  // Make sure the rigid body's rotation matrix is equivalent to 'q'
m.DbgPrint("Source matrix for quat");
  body->GetRotPosM()->FromQuaternion(q);
body->GetRotPosM()->DbgPrint("Result matrix from quat");

  // Initially move car center to the 'from' location
  vBodyPos=body->GetLinPos();
  *vBodyPos=pos->from;
  // Get front nose position of car by shifting along the 'vz' axis
  // (which is the car's nose direction in world coordinates)
  vz.Scale(body->GetSize()->z/2);
  vBodyPos->Subtract(&vz);
  // Make sure the car isn't IN the tarmac! (give it some height)
  offsetY=-10000;
  for(i=0;i<wheels;i++)
  {
    dfloat tirePatchOffsetY;
    // Calculate distance so the contact patch will just be on the track
    tirePatchOffsetY=susp[i]->GetLength()-
      susp[i]->GetPosition()->y+wheel[i]->GetRadius();
    if(tirePatchOffsetY>offsetY)
      offsetY=tirePatchOffsetY;
  }
  vBodyPos->y+=offsetY;
  // Add an extra space to avoid directly hitting the surface
  // (which results in funny 1st time lateral jumps)
  vBodyPos->y+=RMGR->GetMainInfo()->GetFloat("car.warp_offset_y");
//qdbg("Warp offsetY=%f\n",offsetY);

  // Stop moving!
  body->GetLinVel()->SetToZero();
  body->GetRotVel()->SetToZero();
  for(i=0;i<wheels;i++)
  {
    susp[i]->Reset();
    // Reset wheel AFTER suspension
    wheel[i]->Reset();
  }
  engine->Reset();
}

/*********************
* Coordinate systems *
*********************/
void RCar::ConvertCarToWorldOrientation(DVector3 *from0,DVector3 *toFinal)
// Convert orientation 'from' from car coordinates (local)
// to world coordinates (into 'to')
// Order is reverse to YPR; RPY here (ZXY)
{
  // Pass on to body
  body->ConvertBodyToWorldOrientation(from0,toFinal);
}
void RCar::ConvertCarToWorldCoords(DVector3 *from,DVector3 *to)
// Convert vector 'from' from car coordinates (local)
// to world coordinates (into 'to')
// Should be used for coordinates, NOT orientations (a translation
// is used here as well as a rotation)
{
  // Pass on to body
  body->ConvertBodyToWorldPos(from,to);
}

void RCar::ConvertWorldToCarOrientation(DVector3 *from0,DVector3 *toFinal)
// Convert vector 'from' from world coordinates (global)
// to car coordinates (into 'to')
{
  // Pass on to body
  body->ConvertWorldToBodyOrientation(from0,toFinal);
}
void RCar::ConvertWorldToCarCoords(DVector3 *from,DVector3 *to)
// Convert vector 'from' from car coordinates (local)
// to world coordinates (into 'to')
// Should be used for coordinates, NOT orientations (a translation
// is used here as well as a rotation)
{
  // Pass on to body
  body->ConvertWorldToBodyPos(from,to);
}

/*******************
* Helper functions *
*******************/
void RCar::SetDefaultMaterial()
// When using models, materials are applied from the model files
// To use the stub graphics, these material changes must be reset
// to their default values
{
  float ambient[]={ 0.2f,0.2f,0.2f,1.0f };
  float diffuse[]={ 0.8f,0.8f,0.8f,1.0f };
  float specular[]={ 0,0,0,1.0 };
  float emission[]={ 0,0,0,1.0 };
  float shininess=0;

  glMaterialfv(GL_FRONT,GL_AMBIENT,ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_EMISSION,emission);
  glMaterialf(GL_FRONT,GL_SHININESS,shininess);
}