class RCar | .h |
constructor | RCar(cstring carName) |
destructor | ~RCar() |
Flat functions |
SkipWord | static cstring SkipWord(cstring s) Skip from "word1 word2" to "word2" (example) |
GetToken | static cstring GetToken(cstring s) |
class RCar | .h |
Load | bool Load() Read all parts |
GetMass | rfloat GetMass() Returns total mass of all components |
GetVelocityTach | rfloat GetVelocityTach() Get velocity as to be displayed on the tachiometer (sp?) |
Flat functions |
deg2rad | static float deg2rad(float d) |
class RCar | .h |
PreAnimate | void PreAnimate() Determine state in which all car parts are situated for later Newtonian calculations. |
Animate | void Animate() Animate all parts Connects the parts of the car |
Flat functions |
timingPosPreWC.DbgPrint | 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 |
class RCar | .h |
OnGfxFrame | void 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. |
PaintShadow | void PaintShadow() Paint the car's shadow |
Paint | void Paint() Paint the entire car |
ApplyRotations | void ApplyRotations() |
ApplyAcceleration | void ApplyAcceleration() Look at the wheel forces and accelerate the car with the sum |
Warp | void Warp(RCarPos *pos) Move the car to the specified location/orientation Used with Shift-R for example |
Flat functions |
pos->from.DbgPrint | pos->from.DbgPrint("Warp from"); pos->to.DbgPrint("Warp from"); axis.DbgPrint("Warp axis"); #endif |
m.DbgPrint | m.DbgPrint("Source matrix for quat"); body->GetRotPosM()->FromQuaternion(q); body->GetRotPosM()->DbgPrint("Result matrix from quat"); |
class RCar | .h |
ConvertCarToWorldOrientation | void 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) |
ConvertCarToWorldCoords | void 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) |
ConvertWorldToCarOrientation | void ConvertWorldToCarOrientation(DVector3 *from0,DVector3 *toFinal) Convert vector 'from' from world coordinates (global) to car coordinates (into 'to') |
ConvertWorldToCarCoords | void 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) |
SetDefaultMaterial | void 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);
}