Game Development Community

dev|Pro Game Development Curriculum

All Wheel Steering for Torque Vehicles

by Martin "Founder" Hoover · 06/14/2004 (2:09 pm) · 5 comments

I have tried to make this as general as I could, but I am sure it could stand plenty of improvement. Its' features are as follows:

Set any or all wheels to steer normally, or in the opposite direction.
Allows for separate player controls for normal and opposite steered wheels.
Allows any or all wheels to be locked to any desired steering angle.
Includes animation support for oppositely steered wheels.
Includes tweaks to the rendering code to allow for non-linear hub/spring animations. (Allows wheels to move in an arc)
Includes a fix for the steering animations so that they will match the engines' turning/steering rate.
Provides an option for normalizing the oppositely steered wheels movement to match that of the normal steering wheels. (It is often helpful to limit the oppositely steered wheels to a smaller maxSteeringAngle to keep the vehicles' steering from becoming too sensitive. As a result, the oppositely steered wheels will reach their maximum steering angle before the normal wheels, and stop turning farther while the normal wheels still have room to turn. Normalizing them will set the opposite wheels to the same percentage of steering angle as the normal wheels.)

Start in wheeledVehicle.h

Start by adding some dataBlock variables in struct WheeledVehicleData: public VehicleData :

Find:

U32 wheelCount;
   ClippedPolyList rigidBody;    // Extracted from shape
   S32 brakeLightSequence;       // Brakes
   S32 steeringSequence;         // Steering animation

Beneath it add:

[b]//Founder -opposite steering
   F32 maxOppositeSteeringAngle;
   S32 oppositeSteeringSequence;
   S32 steeringSequenceWheels;  //starting from the first, how many wheels are in the animation?
   S32 oppositeSteeringSequenceWheels; //starting from rear, how many wheels are in the anim? [/b]


Next in: class WheeledVehicle: public Vehicle

Inside the wheel struct definition, near the bottom, find:

F32 steering;           // Wheel steering scale
      bool powered;           // Powered by engine
      bool slipping;          // Traction on last tick
      F32 torqueScale;        // Max torque % applied to wheel (0-1)
      F32 slip;               // Amount of wheel slip (0-1)
      SimObjectPtr<ParticleEmitter> emitter; 
   };

Add to it as the following:

F32 steering;           // Wheel steering scale
      bool powered;           // Powered by engine
      bool slipping;          // Traction on last tick
      F32 torqueScale;        // Max torque % applied to wheel (0-1)
      F32 slip;               // Amount of wheel slip (0-1)
      SimObjectPtr<ParticleEmitter> emitter; 

      [b]//Founder -opposite wheel steering
      bool oppositeSteer;
      bool seperateSteerControl;
      bool oppositeLocked;
      bool normalizeAngle;
      F32 oppositeLockedAngle;[/b]
   };

After the thread variables:

Wheel mWheel[WheeledVehicleData::MaxWheels];
   TSThread* mSteeringThread;

Add one for the new steering thread:

//Founder -opposite steering
   TSThread* mOppositeSteeringThread;

Now we need to change some of the function prototypes and add some, in the public area of the class definition find:

S32 getWheelCount();
   void setWheelSteering(S32 wheel,F32 steering);
   void setWheelPowered(S32 wheel,bool powered);
   void setWheelTire(S32 wheel,WheeledVehicleTire*);
   void setWheelSpring(S32 wheel,WheeledVehicleSpring*);

And change it to the following:

S32 getWheelCount();
   [b]//Founder
   void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering, bool oppositeSteer, bool seperateSteerControl, bool oppositeLocked, bool normalizeAngle);[/b]
   void setWheelPowered(S32 wheel,bool powered);
   void setWheelTire(S32 wheel,WheeledVehicleTire*);
   void setWheelSpring(S32 wheel,WheeledVehicleSpring*);
   [b]//Founder
   void WheeledVehicle::setWheelLockAngle(S32 wheel,F32 angle);
   void WheeledVehicle::setAllWheelLockAngle(F32 angle); [/b]

That's all for the header file, now move into wheeledVehicle.cc.

First we need to intialize some of the dataBlock variables. Add the following to the very bottom of WheeledVehicleData::WheeledVehicleData()

[b]//Founder
   maxOppositeSteeringAngle = 0.785;
   steeringSequenceWheels = 0;
   oppositeSteeringSequenceWheels = 0; [/b]

Next, in bool WheeledVehicleData::preload(bool server, char errorBuffer[256]) find the following:

// Check for steering. Should think about normalizing the
   // steering animation the way the suspension is, but I don't
   // think it's as critical.
   steeringSequence = shape->findSequence("steering");

Beneath it add:

//Founder, look for opposite steering thread
   oppositeSteeringSequence = shape->findSequence("oppositeSteering");

Now here is an optional little tweak that your artists just might love you for using. It will allow the artist full control over the side to side placement of the wheels on the final model. In bool WheeledVehicleData::mirrorWheel(Wheel* we) find the following line and comment it out:

we->pos.x = -wp->pos.x;

This removes a lot of the guess work for modelers trying to figure out how to adjust the placement of the hub node positions.

Next we need to let the scripting system know about the DataBlock parameters. Add the following to the bottom of void WheeledVehicleData::initPersistFields()

//Founder -opposite steering
   addField("maxOppositeSteeringAngle", TypeF32, Offset(maxOppositeSteeringAngle, WheeledVehicleData));
   addField("steeringSequenceWheels", TypeS32, Offset(steeringSequenceWheels, WheeledVehicleData));
   addField("oppositeSteeringSequenceWheels", TypeS32, Offset(oppositeSteeringSequenceWheels, WheeledVehicleData));


Next, add to the following to the very bottom of void WheeledVehicleData::packData(BitStream* stream)

//Founder -opposite Steering
   stream->write(maxOppositeSteeringAngle);
   stream->write(steeringSequenceWheels);
   stream->write(oppositeSteeringSequenceWheels);

And using the same ordering, add this to the very bottom of void WheeledVehicleData::unpackData(BitStream* stream)

//Founder -opposite steering
   stream->read(&maxOppositeSteeringAngle);
   stream->read(&steeringSequenceWheels);
   stream->read(&oppositeSteeringSequenceWheels);

Next for WheeledVehicle::WheeledVehicle() add the following: (denoted in bold)

WheeledVehicle::WheeledVehicle()
{
   mDataBlock = 0;
   mGenerateShadow = true;
   mBraking = false;
   mJetSound = 0;
   mEngineSound = 0;
   mSquealSound = 0;
   mTailLightThread = 0;
   mSteeringThread = 0;

   [b]//Founder -opposite steering
   mOppositeSteeringThread = 0;[/b]

   mScriptSteer = 0.0f;

   for (S32 j = 0; j < WheeledVehicleData::MaxWheels; j++) {
      mWheel[j].springThread = 0;
      mWheel[j].Dy = mWheel[j].Dx = 0;
      mWheel[j].tire = 0;
      mWheel[j].spring = 0;
      mWheel[j].shapeInstance = 0;
      mWheel[j].steering = 0;
      mWheel[j].powered = true;
      mWheel[j].slipping = false;

      [b]//Founder -opposite steering
      mWheel[j].oppositeSteer = false;
      mWheel[j].seperateSteerControl = false;
      mWheel[j].oppositeLocked = false;
      mWheel[j].normalizeAngle = false;
      mWheel[j].oppositeLockedAngle = 0;[/b]
   }
}

Next, in bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr) find the following:

// Steering sequence
   if (mDataBlock->steeringSequence != -1) {
      mSteeringThread = mShapeInstance->addThread();
      mShapeInstance->setSequence(mSteeringThread,mDataBlock->steeringSequence,0);
   }
   else
      mSteeringThread = 0;

Below it add:

//Founder- opposite steering sequence
   if (mDataBlock->oppositeSteeringSequence != -1) {
      mOppositeSteeringThread = mShapeInstance->addThread();
      mShapeInstance->setSequence(mOppositeSteeringThread,mDataBlock->oppositeSteeringSequence,0);
   }
   else
      mOppositeSteeringThread = 0;

Next, find void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering) and change it to match the following:

void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering, bool oppositeSteer, bool seperateSteerControl, bool oppositeLocked, bool normalizeAngle)
{
   AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");
   //Con::warnf("setWheelSteering: wheel: %d steering: %d oppositeSteer: %d seperateSteerControl: %d oppositeLocked %d normalize %d",(S32)wheel ,(S32)steering, (S32)oppositeSteer, (S32)seperateSteerControl, (S32)oppositeLocked, (S32)normalizeAngle);
   mWheel[wheel].steering = mClampF(steering,-1,1);

   //Founder
   mWheel[wheel].oppositeSteer = oppositeSteer;
   mWheel[wheel].oppositeLocked = oppositeLocked;

   if(oppositeLocked)
      mWheel[wheel].oppositeLockedAngle = 0.0f;

   mWheel[wheel].seperateSteerControl = seperateSteerControl;
   mWheel[wheel].normalizeAngle = normalizeAngle;

   setMaskBits(WheelMask);
   }

Now paste in the following functions just after void WheeledVehicle::setWheelSpring(S32 wheel,WheeledVehicleSpring* spring)

//Founder -opposite Steering
void WheeledVehicle::setWheelLockAngle(S32 wheel,F32 angle)
{
   AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds");

   if(mWheel[wheel].oppositeLocked)
         mWheel[wheel].oppositeLockedAngle = mClampF(angle,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
   else
   {
      Con::warnf("setWheelLockAngle: wheel not locked");
      return;
   }

   setMaskBits(WheelMask);
}

void WheeledVehicle::setAllWheelLockAngle(F32 angle)
{
   Wheel* wend = &mWheel[mDataBlock->wheelCount];
   for (Wheel* wheel = mWheel; wheel < wend; wheel++)
      if(wheel->oppositeLocked)
         wheel->oppositeLockedAngle = mClampF(angle,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
      
   setMaskBits(WheelMask);
}

The next bit is a fix for how the steering animation is played. Basically it makes it work the way it's supposed to. In void WheeledVehicle::advanceTime(F32 dt) find the following code:

// Update the steering animation: sequence time 0 is full right,
   // and time 0.5 is straight ahead.
   if (mSteeringThread) {
      F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle;
      mShapeInstance->setPos(mSteeringThread,0.5 - t * 0.5);
   }

Replace it with this:

// Update the steering animation: sequence time 0 is full right,
   // and time 0.5 is straight ahead.
   if (mSteeringThread) 
   {
      //Founder -changed this so the animation will actually match how the engine turns the wheels
      //find what percentage it is between straight forward and fully turned
      F32 percent = -(mSteering.x / (mDataBlock->maxSteeringAngle * 2));
      //add 50% to get the value between 0 and 1
      percent += 0.5;
      mShapeInstance->setPos(mSteeringThread, percent);
   }

Now just below that add a similar setup for opposite steering animations:

if (mOppositeSteeringThread) 
   {
      //Founder -added this for opposite steering
      F32 percent = -(mSteering.x / (mDataBlock->maxOppositeSteeringAngle * 2));
      percent += 0.5;
      mShapeInstance->setPos(mOppositeSteeringThread, percent);
   }

Now we get to delve into updateForces. Since this is now dealing with two different setups for steering, the proper angles will need to be calculated per wheel. First find the following.

// Steering angles from current steering wheel position
   F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x));
   F32 cosSteering,sinSteering;
   mSinCos(quadraticSteering, sinSteering, cosSteering);

Comment those lines out, as they will be moved into the wheel loop to facilitate setting the individual values. Now find the start of the wheel loop:

// Sum up spring and wheel torque forces
   for (Wheel* wheel = mWheel; wheel < wend; wheel++)

Right before that paste in the following variable initializations.


//Founder initilize a few variables for Opposite steering
   F32 oppositeSteer = 1.0f;
   F32 steeringAngle = 0.0f;
   F32 normilize = 0.0f;

   // Sum up spring and wheel torque forces
   for (Wheel* wheel = mWheel; wheel < wend; wheel++)

Now, inside the check for surface contact( if(wheel->surface.contact) ), paste the following code:

//Founder opposite steering guts
         oppositeSteer = 1.0f;
         steeringAngle = mSteering.x;
         normilize = mSteering.x;
         
         //if this wheel is set to oppositeSteer, then it needs to point opposite of normal steering angles
         if(wheel->oppositeSteer == 1)
         {
            steeringAngle = mClampF(mSteering.x,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
            oppositeSteer = -1.0f;
         }

         //if set to seperate, then pitch control steers this wheel
         if(wheel->seperateSteerControl == 1)
         {
            steeringAngle = mClampF(mSteering.y,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
            normilize = mSteering.y;
         }
         
         //since we don't always want the rear wheels to steer as sharp as the front, it's rather odd
         //to have the wheel simply stop steering when it reaches its' limit while the front continues 
         //to steer, so compensate so they both turn to 50% of their max angles at the same time
         if(wheel->normalizeAngle == 1)
         {
            F32 percent = normilize / mDataBlock->maxSteeringAngle;
            steeringAngle = mDataBlock->maxOppositeSteeringAngle * percent;

         }
         
         //on occasion we may want to have the oppositeSteer wheels locked at a certain angle
         //yey! damage effects
         if(wheel->oppositeLocked == 1)
            steeringAngle = wheel->oppositeLockedAngle;

         //now pull it all together and steer!
         F32 quadraticSteering = -(steeringAngle * mFabs(steeringAngle)) * oppositeSteer;

         F32 cosSteering,sinSteering;
         mSinCos(quadraticSteering, sinSteering, cosSteering);
         //end opposite steering


Now we do the wheel rendering. Here is the basic idea behind the restructuring of this function; If the wheel has animations from the model, then simply grab the hub position from the instance as the position to render the wheel at. The benefits to doing this are that the wheel will be rendered at exactly the right spot to match up with any and all steering or spring animation sequences. This makes it possible to model a vehicle with a solid axle, which tends to move in a curved path when only one side is extended and the other is compressed. It might not sound like a big deal, but is invaluable in making the movements LOOK right. Since I made a lot of changes and additions to the functions, I suggest commenting out the entire function and replacing it with this:

void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image)
{
   Parent::renderImage(state, image);

   // Shape transform
   glPushMatrix();
   dglMultMatrix(&getRenderTransform());
   
   //Founder
   F32 oppositeSteer = 1.0f;
   F32 steeringAngle = 0.0f;
   F32 normilize = 0.0f;
   S32 z = 0;
   MatrixF realHub;
   Wheel* wend = &mWheel[mDataBlock->wheelCount];
   for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
      if (wheel->shapeInstance)
      {
         glPushMatrix();
         z++;
         //Founder- this holds the matrix for the actual hub node
         realHub = mShapeInstance->mNodeTransforms[wheel->data->springNode];
                  
         if( (
            (mDataBlock->steeringSequenceWheels > 0 && z <= mDataBlock->steeringSequenceWheels ) ||
            (mDataBlock->oppositeSteeringSequenceWheels > 0 && 
            z >= (S32(mDataBlock->wheelCount) - mDataBlock->oppositeSteeringSequenceWheels))) && 
            !wheel->oppositeLocked)
         {  
                           
            //if we have a steering animation for this wheel, then grab the hub matrix
            //from the animated shape and use it for position
            dglMultMatrix(&realHub);
            //now we are totally done with both the steering angle and the vertical 
            //position for this wheel
         }
         else
         {
            //okay at this point our wheel does not have a steering animation, BUT
            //that does not mean that it doesn't steer
            Point3F pos;
            oppositeSteer = 1.0f;
            steeringAngle = mSteering.x;

            if(wheel->steering || wheel->oppositeSteer)
            {
               //so these wheels are steering, will need to use another method for getting vertical
               //position a lil farther down
               
               normilize = mSteering.x;
               
               if(wheel->oppositeSteer)
               {
                  steeringAngle = mClampF(mSteering.x,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
                  oppositeSteer = -1.0f;
               }
               if(wheel->seperateSteerControl)
               {
                  steeringAngle = mClampF(mSteering.y,-mDataBlock->maxOppositeSteeringAngle, mDataBlock->maxOppositeSteeringAngle);
                  normilize = mSteering.y;
               }
               if(wheel->normalizeAngle)
               {
                  F32 percent = normilize / mDataBlock->maxSteeringAngle;
                  steeringAngle = mDataBlock->maxOppositeSteeringAngle * percent;

               }
               if(wheel->oppositeLocked)
                  steeringAngle = wheel->oppositeLockedAngle;

               //end opposite steering
            }
            MatrixF hub(EulerF(0,0,steeringAngle * oppositeSteer * wheel->steering));
            
            if(wheel->data->springSequence != -1)
            {
               //if we have a spring sequence for this wheel, get the position from it
               realHub = mShapeInstance->mNodeTransforms[wheel->data->springNode];
               realHub.getColumn(3, &pos);
            }
            else
            {
               //we have a real looser of a wheel here, no steering and no spring anim
               //so we have to calculate the position for this guy
               pos = wheel->data->pos;
               pos.z -= wheel->spring->length * wheel->extension;
            }
            hub.setColumn(3,pos);
            dglMultMatrix(&hub);
         }
         
 
         // Wheel rotation
         MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
         dglMultMatrix(&rot);

         // Rotation the tire to face the right direction
         // (could pre-calculate this)
         MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
         dglMultMatrix(&wrot);

         // Render it
         wheel->shapeInstance->animate();
         wheel->shapeInstance->render();
         glPopMatrix();
         
      }
      
   }

   glPopMatrix();
}

We are almost done. Now we just need to add some info to the vehicle objects' pack and unPack methods.
In U32 WheeledVehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) find the following (which is inside the wheel loop):

stream->writeFlag(wheel->powered);
            // Steering must be sent with full precision as it's
            // used directly in state force calculations.
            stream->write(wheel->steering);

Now change it so that it looks like so:

stream->writeFlag(wheel->powered);
            // Steering must be sent with full precision as it's
            // used directly in state force calculations.
            stream->write(wheel->steering); 
            //Founder -opposite steering
            stream->writeFlag(wheel->oppositeSteer);
            stream->writeFlag(wheel->oppositeLocked);
            stream->writeFlag(wheel->seperateSteerControl);
            stream->writeFlag(wheel->normalizeAngle);
            stream->write(wheel->oppositeLockedAngle);

Now we'll use the same ordering in void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream). Find the following:

wheel->powered = stream->readFlag();
            stream->read(&wheel->steering);

And change it to this:

wheel->powered = stream->readFlag();
            stream->read(&wheel->steering);

//Founder -opposite steering
wheel->oppositeSteer = stream->readFlag();
wheel->oppositeLocked = stream->readFlag();
wheel->seperateSteerControl = stream->readFlag();
wheel->normalizeAngle = stream->readFlag();
stream->read(&wheel->oppositeLockedAngle);


Down in the console methods section, find the setWheelSteering method and replace it with this one:

ConsoleMethod(WheeledVehicle, setWheelSteering, bool, 8, 8, "obj.setWheelSteering(wheel#,float,oppositeSteer, seperateSteerControl, oppositeLocked, normalize)")
{
   S32 wheel = dAtoi(argv[2]);
   if (wheel >= 0 && wheel < object->getWheelCount())
   {
      if(dAtob(argv[5]) == true && dAtob(argv[6]) == true)
      {
         Con::warnf("setWheelSteering: Cannot set steering to BOTH controlled and locked!!!");
         return false;
      }
      object->setWheelSteering(wheel,dAtof(argv[3]),dAtob(argv[4]),dAtob(argv[5]), dAtob(argv[6]), dAtob(argv[7]));
      
      return true;
   }
   else
      Con::warnf("setWheelSteering: wheel index out of bounds, vehicle has %d hubs",
         argv[3],object->getWheelCount());
   return false;
}


And FINALLY go to the bottom of the file and add these two console methods:

ConsoleMethod(WheeledVehicle, setWheelLockAngle, bool, 4, 4, "obj.setWheelLockAngle(wheel#, float radians)")
{
   S32 wheel = dAtoi(argv[2]);
   if (wheel >= 0 && wheel < object->getWheelCount())
   {
      object->setWheelLockAngle(wheel,dAtof(argv[3]));
      return true;
      
   }
   else
      Con::warnf("setWheelLockAngle: wheel index out of bounds, vehicle has %d hubs", argv[3],object->getWheelCount());
   return false;      
   //Con::warnf("setWheelSteering: wheel: %d steering: %d oppositeSteer: %d",wheel ,(S32)dAtof(argv[3]), (S32)dAtob(argv[4]));
}  

ConsoleMethod(WheeledVehicle, setAllWheelLockAngle, bool, 3, 3, "obj.setWheelLockAngle(wheel#, float radians)")
{
   object->setAllWheelLockAngle(dAtof(argv[2]));
   return true;
}


Now that's all for changes to the source (I hope), now it's time to explain a few things regarding this insanity.

DataBlocks

We added 3 variables to the wheeledVehicle dataBlock:

maxRearSteeringAngle = 0.785;
   steeringSequenceWheels = 0;  //starting from the first, how many wheels are in the animation?
   oppositeSteeringSequenceWheels = 0; //starting from rear, how many wheels are in the anim?

maxRearSteeringAngle works the same way maxSteeringAngle does, only it controls the maximum range for the wheels which are set to opposite steer.

The other two parameters specify how many wheels are included in the particular animation. For steeringSequenceWheels starting with the first front wheel on the model, count how many wheels have an actual dts animation. For oppositeSteeringSequenceWheels start counting from the rear, the wheels that have an opposite steering animation (in the dts). The reason for this became apparent when I tried out an eight-wheeled vehicle with animations. I needed some way to tell which wheels were part of a steering animation, and after failing an attempt to detect this in the preLoad function, I decided to go with the dataBlock parameter for simplicity. Now if you want to do something really wacky like only using the interior axles for steering on an 8 wheeled vehicle, then you need to find another code solution, or get a bit creative about the placement and numbering of the hubs in the dts.

**Note** If your model does not have either a steering sequence or an Opposite steering sequence, then enter a 0 (zero) for the appropriate parameter. Only give these non zero values if the wheels have an actual steering animation.

Dts Models

There are no changes required for wheeledVehicle models beyond the default TGE requirements. As you have surely noticed, support is added for a second steering animation for the opposite turning wheels. Name the sequence helper "oppositeSteering". The proper motion sequence for it is as follows:
Time .0 - Full left
Time .5 - Center
Time 1.0 - Full Right


Scripting Methods

SetWheelSteering has been expanded to set all of the newly available options for each wheel. For a cue sheet you can use the following:

//%obj.setWheelSteering(wheel#, steering, opposite, separate control, locked, normalize);
   // when rearSteer/opposite is set the affected wheel turns opposite of mouse direction
   //when set to locked, %obj.setAllWheelLockAngle(angle); and %obj,setwheelLock angle(wheel, angle); controls
   //the steering for those tires.
   //when seperate control is enabled, the up/down movement of the mouse controls the affected wheel's steering
   //
   //normalize will ensure that when the front wheels are turned 50% to the right, that the affected wheel
   //is turned 50% to the (left or right which ever is appropriate) //                  wheel, steering, oppositeSteer, sepCntrl,    Locked, normalizeAngle)
   %obj.setWheelSteering(#7,    1,         1,              0,        0,         1);

If you are going to set a wheel to opposite steering, you will also have to set it's steering value to true. Otherwise set the values to true/false (1/0) for the desired effect.

Separate Control

As it is now, the separate control is setup to use the yaw control. For default TGE this means that the up/down movement of the mouse controls the opposite steering wheels. This is actually pretty fun to play with, and didn't take me very long to get a feel for it.

Wheel Locking

I implemented this for my own project, specifically aimed at doing damage effects. But it can also be used to provide a second control method for the effected wheels. This locks the affected wheels' steering angle to that which is specified, and prevents the other steering code from changing it. To use it you will need to reapply the setWheelSteering function with the proper booleans, which can be done on the fly. Once that is done you use the call objectID.setWheelLockAngle(wheel#, angle); The angle value must be given in radians.

The call objectID.setAllWheelLockAngle(angle); can be used to set the angle (in radians) for all wheels that are set to locked.

With some creative uses of server commands you can set up some keybinds to allow the player to change the locked angles.

I think that pretty well covers it. Undoubtedly there is plenty of room for improvement, so feel free to post if you come up with something better.

#1
06/15/2004 (10:32 am)
Did you know that all wheel stearing is already in the stock Torque? You just use a -1 in the setWheelSteering call for the back two wheels.

//-----------------------------------------------------------------------------
function Predator::onAdd(%this,%obj)
{
	Parent::onAdd(%this, %obj);
	// Setup the car with some defaults tires & springs
	for (%i = 0; %i < %obj.getWheelCount(); %i++) {
      		%obj.setWheelTire(%i,assaultjeepTire);
		%obj.setWheelSpring(%i,assaultjeepSpring);
	}

   	// All wheel drive
   	%obj.setWheelSteering(0,1);
   	%obj.setWheelSteering(1,1);
    %obj.setWheelSteering(2,-1);
    %obj.setWheelSteering(3,-1);

	//   Only power the two rear wheels...
   //%obj.setWheelPowered(0,true);
   //%obj.setWheelPowered(1,true);
   %obj.setWheelPowered(2,true);
   %obj.setWheelPowered(3,true);
}

Of course your code does more than just all wheel drive. I think I'm going to use the wheel locking part of this resource though.
#2
02/09/2006 (3:51 am)
Is it possible to break all these fixes/changes, it's kinda difficult to follow if you only want certain changes.

I would like only these... Sorry for being a dumba55

- Allows any or all wheels to be locked to any desired steering angle.

- Includes tweaks to the rendering code to allow for non-linear hub/spring animations. (Allows wheels to move in an arc)

-Includes a fix for the steering animations so that they will match the engines' turning/steering rate. 

-Provides an option for normalizing the oppositely steered wheels movement to match that of the normal steering wheels.
#3
02/09/2006 (7:19 pm)
Actually, what you are wanting will require pretty much all of my changes/additions. The only thing you aren't listing is the seperate control and added animation support for the rear steering, neither of which consume much in the way of resources or memory unless you use them.
#4
08/04/2006 (2:03 am)
How do i make the wheels return to center after releasing the button? it's kinda annoying that it keeps turning after i release the button.
#5
08/04/2006 (6:20 am)
There is a resource that does that... search for it