WheeledVehicle steering animation code
by Martin "Founder" Hoover · in Torque Game Engine · 04/06/2004 (4:15 pm) · 17 replies
After spending a couple of hours trying to tweak my vehicles' steering animation so it would match the wheel movement (before which I spent a good while trying to tweak the maxSteeringAngle datablock setting) I finally decided that the thread position of the animation must not be, well, working right. I tweaked it so that an animation that turns the wheels 45 degrees will syncronize with a maxSteeringAngle of 45 degrees (0.7854 radians).
The tweak is in WheeledVehicle::advanceTime(F32 dt)...
near the bottom where it checks for the steering thread...
The tweak is in WheeledVehicle::advanceTime(F32 dt)...
near the bottom where it checks for the steering thread...
// 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);
}
Change to:
// Update the steering animation: sequence time 0 is full right,
// and time 0.5 is straight ahead.
if (mSteeringThread)
{
//find what percentage it is between straight forward and fully turned
F32 percent = -(mSteering.x / (mDataBlock->maxSteeringAngle + mDataBlock->maxSteeringAngle));
//add 50% to get the value between 0 and 1
percent += 0.5;
mShapeInstance->setPos(mSteeringThread, percent);
}It's been wroking great for me, and matches up the animations very well.
#2
I don't see any problem with making the animation as a DSQ, provided you make a TSShapeConstructor block for it, but I assume you know about those if you have been using DSQ's.
The name is important for the sequence, it needs to be named "steering" so Torque will recognize it.
I am not very familiar with the AIWheeledVehicle class, however, the parent class should handle all of the animations so you should get the same things as with the regular WheeledVehicle class. Unless the AIWheeledVehicle class has been specifically modified to prevent advanceTime from being executed it should work fine. The easy way to tell is, if the wheels spin on the AIWheeledVehicle when it moves, then the parent class is running the animations.
Combine your right and left animations into a single one named steering, full right for the first frame, turning to full left at the last frame.
By the way, that is an excellent model, good work!
04/29/2005 (7:05 pm)
The steering animations I use are not DSQ's. But it does need to be a blend so that it does not interfere with the spring animations that are always playing. (Though I suppose if you do not use any spring animations you could make it non-blend).I don't see any problem with making the animation as a DSQ, provided you make a TSShapeConstructor block for it, but I assume you know about those if you have been using DSQ's.
The name is important for the sequence, it needs to be named "steering" so Torque will recognize it.
I am not very familiar with the AIWheeledVehicle class, however, the parent class should handle all of the animations so you should get the same things as with the regular WheeledVehicle class. Unless the AIWheeledVehicle class has been specifically modified to prevent advanceTime from being executed it should work fine. The easy way to tell is, if the wheels spin on the AIWheeledVehicle when it moves, then the parent class is running the animations.
Combine your right and left animations into a single one named steering, full right for the first frame, turning to full left at the last frame.
By the way, that is an excellent model, good work!
#3
I came up with some code that will render the wheel in the proper location, but for the physics calculations it will not be in the proper place.
In other words... the fix makes it LOOK proper, but the physics code will think it works the way it does now.
The fix is a bit involved, so I will have to put it together and post it later this evening.
05/03/2005 (3:27 pm)
Yes I know why it is doing that. Basically the engine does the steering, assuming that the hub node is the pivot point for all rotation axis.I came up with some code that will render the wheel in the proper location, but for the physics calculations it will not be in the proper place.
In other words... the fix makes it LOOK proper, but the physics code will think it works the way it does now.
The fix is a bit involved, so I will have to put it together and post it later this evening.
#4
in file WheeledVehicle.h
in struct WheeledVehicleData: public VehicleData {
somewhere near the bottom add:
in file wheeledVehicle.cc
In WheeledVehicleData::WheeledVehicleData()
add somewhere:
add:
in void WheeledVehicleData::packData(BitStream* stream)
immediately after:
immediately after:
continued.....
05/03/2005 (5:16 pm)
Okay here's the fix I came up with:in file WheeledVehicle.h
in struct WheeledVehicleData: public VehicleData {
somewhere near the bottom add:
S32 steeringSequenceWheels; //starting from the first, how many wheels are in the animation?
//assumes hub0 is the first in the animated set of wheelsin file wheeledVehicle.cc
In WheeledVehicleData::WheeledVehicleData()
add somewhere:
steeringSequenceWheels = 0;in void WheeledVehicleData::initPersistFields()
add:
addField("steeringSequenceWheels", TypeS32, Offset(steeringSequenceWheels, WheeledVehicleData));in void WheeledVehicleData::packData(BitStream* stream)
immediately after:
stream->write(brakeTorque);add:
stream->write(steeringSequenceWheels);In void WheeledVehicleData::unpackData(BitStream* stream)
immediately after:
stream->read(&brakeTorque);add:
stream->read(&steeringSequenceWheels);
continued.....
#5
comment the entore function out and replace it with this one:
05/03/2005 (5:17 pm)
Near the bottom of the file, find void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image)comment the entore function out and replace it with this one:
void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image)
{
Parent::renderImage(state, image);
// Shape transform
glPushMatrix();
dglMultMatrix(&getRenderTransform());
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
// The position of the mount point needs to be scaled.
//MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni];
realHub = mShapeInstance->mNodeTransforms[wheel->data->springNode];
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = realHub.getPosition();
position.convolve( scale );
realHub.setPosition( position );
// Also we would like the object to be scaled to the model.
realHub.scale( scale );
//mat->mul(getRenderTransform(), mountTransform);
if(mDataBlock->steeringSequenceWheels > 0 && z <= mDataBlock->steeringSequenceWheels )
{
//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;
MatrixF hub(EulerF(0,0,mSteering.x * 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 * mObjScale.z) * wheel->extension;
}
hub.setColumn(3,pos);
const Point3F& hscale = getScale();
// The position of the mount point needs to be scaled.
Point3F hposition = hub.getPosition();
hposition.convolve( hscale );
hub.setPosition( hposition );
// Also we would like the object to be scaled to the model.
hub.scale( scale );
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);
//this is to make the textures nice mirrors of each other,
if (wheel->data->pos.x < 0)
{
MatrixF rrot(EulerF(0,M_PI,0));
dglMultMatrix(&rrot);
}
// Render it
wheel->shapeInstance->animate();
wheel->shapeInstance->render();
glPopMatrix();
}
}
glPopMatrix();
}continued....
#6
steeringSequenceWheels = 1;
I have not tested this exact code since my version includes a bunch of junk that base torque doesn't need. Let me know if it won't compile or gives odd errors
05/03/2005 (5:17 pm)
For this you need to add a field to the vehicle datablock called "steeringSequenceWheels", and for it's value give it the number of wheels that are affected by the steering animation. So for your bike it should look like so:steeringSequenceWheels = 1;
I have not tested this exact code since my version includes a bunch of junk that base torque doesn't need. Let me know if it won't compile or gives odd errors
#7
I do have an idea for setting the animation for your pedal movement. It's basically the way the wheels worked in the very first Torque release.
What you can do is setup a new animation sequence for wheeled vehicles and use some of the actual wheel movement code to set the animation to the proper frame. The setup is basically like adding a second steering animation thread, so you can use that as a guide.
in WheeledVehicle.h, in the WheeledVehicleData definition you need to add a variable to check for the threads existance.. like:
S32 pedalSequence;
then in the wheeledVehicle class definition you need a pointer for the animation thread...
TSThread* mPedalThread;
then in wheeledVehicle.cc, in the function WheeledVehicleData::preload search the dts for the pedal sequence...
pedalSequence= shape->findSequence("pedal");
then in WheeledVehicle::WheeledVehicle() initialize the thread pointer...
mPedalThread = 0;
then in WheeledVehicle::onNewDataBlock you add the animation thread to the object...
if (mDataBlock->pedalSequence!= -1) {
mPedalThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mPedalThread ,mDataBlock->pedalSequence,0);
}
else
mPedalThread = 0;
now the magic happens in render image when you use the wheels rotational position to set the animation frame...
The call would need to go down near the bottom of the function near the comments "//Wheel rotation".
Now the thing to remember is that this is inside a loop that cycles through all of the wheels, so you have to be careful to only syncronize the animation to one wheel, otherwise it'll be wasting time and might possibly animate wrong. I added the variable "z" so I could count which wheel on the vehicle the loop is dealing with and treat it properly.
I imagine it should look something like this...
if(z >= mDataBlock->steeringSequenceWheels)
{
if (mPedalThread )
mShapeInstance->setPos(mPedalThread ,wheel->apos);
}
This would NOT use any of the steering wheels to set the pedal animation, it would use all wheels after the steering wheels to set the animation frame. This assumes you are only doing a 2 wheeled vehicle. If you are using more then 2 wheels to balance your bikes, I would suggest adding a new datablock variable very similar to the one I added for "steeringSequenceWheels", so you could specify what hub number to syncronize the pedal animation to. SO if it was named something like "pedalAnimationHub" you could do it like so:
if(z == mDataBlock->pedalAnimationHub)
{
if (mPedalThread )
mShapeInstance->setPos(mPedalThread ,wheel->apos);
}
Then it would only be syncronized to the hub/wheel you specify in the dataBlock.
05/04/2005 (2:41 pm)
Oh yes, there was no need for that oppositeSteer variable in there, it was from some bulky code I thouight I had removed.I do have an idea for setting the animation for your pedal movement. It's basically the way the wheels worked in the very first Torque release.
What you can do is setup a new animation sequence for wheeled vehicles and use some of the actual wheel movement code to set the animation to the proper frame. The setup is basically like adding a second steering animation thread, so you can use that as a guide.
in WheeledVehicle.h, in the WheeledVehicleData definition you need to add a variable to check for the threads existance.. like:
S32 pedalSequence;
then in the wheeledVehicle class definition you need a pointer for the animation thread...
TSThread* mPedalThread;
then in wheeledVehicle.cc, in the function WheeledVehicleData::preload search the dts for the pedal sequence...
pedalSequence= shape->findSequence("pedal");
then in WheeledVehicle::WheeledVehicle() initialize the thread pointer...
mPedalThread = 0;
then in WheeledVehicle::onNewDataBlock you add the animation thread to the object...
if (mDataBlock->pedalSequence!= -1) {
mPedalThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mPedalThread ,mDataBlock->pedalSequence,0);
}
else
mPedalThread = 0;
now the magic happens in render image when you use the wheels rotational position to set the animation frame...
The call would need to go down near the bottom of the function near the comments "//Wheel rotation".
Now the thing to remember is that this is inside a loop that cycles through all of the wheels, so you have to be careful to only syncronize the animation to one wheel, otherwise it'll be wasting time and might possibly animate wrong. I added the variable "z" so I could count which wheel on the vehicle the loop is dealing with and treat it properly.
I imagine it should look something like this...
if(z >= mDataBlock->steeringSequenceWheels)
{
if (mPedalThread )
mShapeInstance->setPos(mPedalThread ,wheel->apos);
}
This would NOT use any of the steering wheels to set the pedal animation, it would use all wheels after the steering wheels to set the animation frame. This assumes you are only doing a 2 wheeled vehicle. If you are using more then 2 wheels to balance your bikes, I would suggest adding a new datablock variable very similar to the one I added for "steeringSequenceWheels", so you could specify what hub number to syncronize the pedal animation to. SO if it was named something like "pedalAnimationHub" you could do it like so:
if(z == mDataBlock->pedalAnimationHub)
{
if (mPedalThread )
mShapeInstance->setPos(mPedalThread ,wheel->apos);
}
Then it would only be syncronized to the hub/wheel you specify in the dataBlock.
#8
Now if you have a player or AiPlayer object mounted to the bicycle, there in the renderImage function you could get the object that is mounted to the vehicle and have it play an animation in a similar way as the pedal animation, though you will probably need to make a player method function to actually do it.
A different way to do it would be to play the animations from script. Look in shapeBase.cc near the bottom of the file for:
ConsoleMethod( ShapeBase, playThread, bool, 3, 4, "(int slot, string sequenceName)")
That and the following several functions are for playing animations from script. %objectID.playthread(slot#, "sequenceName"); Every shapebase derived object can have up to 4 script "threads" (animations) playing on it (slots 0-3). So you could use scripts to trigger the animations on both the bike and the rider, BUT the animations would play at a constant rate. I believe that the speed of the animations can be changed, but there are not any console functions that allow you to set or change it (at least none that I'm aware of).
05/05/2005 (7:02 pm)
I think I understand what you are needing to do, basically you need to have a guy sitting on the bike pedaling. As for speeds, I would have thought that the pedal animation would speed up and slow down, even reverse along with the wheel motoin.Now if you have a player or AiPlayer object mounted to the bicycle, there in the renderImage function you could get the object that is mounted to the vehicle and have it play an animation in a similar way as the pedal animation, though you will probably need to make a player method function to actually do it.
A different way to do it would be to play the animations from script. Look in shapeBase.cc near the bottom of the file for:
ConsoleMethod( ShapeBase, playThread, bool, 3, 4, "(int slot, string sequenceName)")
That and the following several functions are for playing animations from script. %objectID.playthread(slot#, "sequenceName"); Every shapebase derived object can have up to 4 script "threads" (animations) playing on it (slots 0-3). So you could use scripts to trigger the animations on both the bike and the rider, BUT the animations would play at a constant rate. I believe that the speed of the animations can be changed, but there are not any console functions that allow you to set or change it (at least none that I'm aware of).
#9
I think the best results would come from finding the mounted player in the WheeledVehicle::renderImage function and setting it's animation to the same frame as the pedal animation. Again, I don't have the code to do that, I have never tried it, nor do I really know how to go about it.
I will say that I don't think using setPos for the animation from script would work very well, as that doesn't actually play the animation, but rather sets it to a particular frame. Your script functons are running on a timer of 32ms, while the renderImage runs at the frame rate of the machine.
I don't know of anyone who has done this for torque so the code probably does not exist.
For you, the best method might be to combine your player model with the bicycle model, then you would only have 1 animation to play. Instead of mounting the player to the bicycle, you would setControl of the client to the vehicle 9Or use an AIVehicle).
05/12/2005 (2:08 pm)
Well, I have already stated the ideas I had about it, as for the code required for it, I don't have it, nor have I ever tried to do it in C++.I think the best results would come from finding the mounted player in the WheeledVehicle::renderImage function and setting it's animation to the same frame as the pedal animation. Again, I don't have the code to do that, I have never tried it, nor do I really know how to go about it.
I will say that I don't think using setPos for the animation from script would work very well, as that doesn't actually play the animation, but rather sets it to a particular frame. Your script functons are running on a timer of 32ms, while the renderImage runs at the frame rate of the machine.
I don't know of anyone who has done this for torque so the code probably does not exist.
For you, the best method might be to combine your player model with the bicycle model, then you would only have 1 animation to play. Instead of mounting the player to the bicycle, you would setControl of the client to the vehicle 9Or use an AIVehicle).
#10
If you are using 3DSMax I can help, however I don't have or use other modelers.
Are you using 3ds Max?
05/17/2005 (7:48 pm)
It should be possible, but the scene probably needs to be setup properly.If you are using 3DSMax I can help, however I don't have or use other modelers.
Are you using 3ds Max?
#11
These two can actually be combined fairly easy. I would recommend merging the bicycle into the rider scene. Now you can seperate the animations out into dsq's but it would probably be easier to include them in the main scene.
The linking will be the main thing that needs attention. all of the bicycle parts can be linked under the main "start" or "base" or whatever your root dummy (node) is named as they normally would for a dts. And the rider mesh will need to be unlinked from any nodes as a "skin" mesh should be. Now I have not been able to mix shapes that use multires and shapes that don't. (Keep in mind that I only have max 5.2 so I can't test it on max7) So I would suggest either having all of your shapes use multires, or none of them (if you don't use multires, be sure and assign a detail number to the riders "skin" mesh.)
On occasion the exporter (for max5) has trouble detecting animated meshes for animations that are only 2 or 3 frames. If some or all of your animations don't actually make anything move, I suggest increasing the frame length of the sequence, then go through and add more key frames for everything that's being moved.
After looking at your screen capture, I see that the bicycle is just one mesh, is it also a "skin" mesh? If it is, you could consider combining it with the riders mesh (which might be hard to do), or breaking it up it into normal non deformed meshes.
05/22/2005 (8:59 pm)
Well, looking over your models I am assuming the bicycle rider is using the "skin" modifier and is a deformable mesh for the animations, but seperate non-deforming meshes are used for the bicycle.These two can actually be combined fairly easy. I would recommend merging the bicycle into the rider scene. Now you can seperate the animations out into dsq's but it would probably be easier to include them in the main scene.
The linking will be the main thing that needs attention. all of the bicycle parts can be linked under the main "start" or "base" or whatever your root dummy (node) is named as they normally would for a dts. And the rider mesh will need to be unlinked from any nodes as a "skin" mesh should be. Now I have not been able to mix shapes that use multires and shapes that don't. (Keep in mind that I only have max 5.2 so I can't test it on max7) So I would suggest either having all of your shapes use multires, or none of them (if you don't use multires, be sure and assign a detail number to the riders "skin" mesh.)
On occasion the exporter (for max5) has trouble detecting animated meshes for animations that are only 2 or 3 frames. If some or all of your animations don't actually make anything move, I suggest increasing the frame length of the sequence, then go through and add more key frames for everything that's being moved.
After looking at your screen capture, I see that the bicycle is just one mesh, is it also a "skin" mesh? If it is, you could consider combining it with the riders mesh (which might be hard to do), or breaking it up it into normal non deformed meshes.
#12
www.mechina.com/dsimpleskin.zip
05/25/2005 (8:05 pm)
Heres an example file of 2 animated "skin" meshes.www.mechina.com/dsimpleskin.zip
#13
You can make some simple geometry for wheels and add them to the model , be sure to include them in the steering animation, then assign a completely transparent texture to them, like a small black .png, with a black alpha channel.Since the texture is transparent, you won't see the wheels, but they will cast a shadow. Of course this is not magical, they are actually rendered, so they will use graphics processing power. OF course it would probably be better to program a proper shadowing solution, but I never could. But this method worked for me.
06/07/2005 (5:10 pm)
Well, there is a way to cheat to get the shadows rendered for the wheels...You can make some simple geometry for wheels and add them to the model , be sure to include them in the steering animation, then assign a completely transparent texture to them, like a small black .png, with a black alpha channel.Since the texture is transparent, you won't see the wheels, but they will cast a shadow. Of course this is not magical, they are actually rendered, so they will use graphics processing power. OF course it would probably be better to program a proper shadowing solution, but I never could. But this method worked for me.
#14
06/17/2005 (3:11 pm)
Are you reffering to the texture on the tire? If so I can say it's not a transparent material. In the max material editor, make sure the check box for the opacity map is checked, though there is no need to specify a map for opacity since it will only use information contained in the diffuse map. And the alpha channel of your bitmap for the wheels needs to be completely black.
#15
06/19/2005 (2:51 am)
Wow, this thread is really great! Following along with you guys has really helped me to learn a good deal.
#16
06/20/2005 (1:54 pm)
@Jaun, yes that should work. There is no need to specify anything for the opacity map itself, all you need to do is check the checkBox for it. When TGE loads the model, and sees that opacity is flagged, it will automatically use the alpha channel from the .png from the diffuse map for the transparency information.
#17
If it's the disappearing chain and cog that's bothering you, that is an issue for doube sided materials. I've not had to use them, so I am not certain how to flag them, though I would think that all you would need to do is check the double-sided checkbox in the material properties in Max.
06/28/2005 (2:32 pm)
I loaded it up in Show Tool Pro and Torque, and the wheels were invisible in both, so what's the problem?If it's the disappearing chain and cog that's bothering you, that is an issue for doube sided materials. I've not had to use them, so I am not certain how to flag them, though I would think that all you would need to do is check the double-sided checkbox in the material properties in Max.
Torque 3D Owner Martin "Founder" Hoover
Then for the datablock value, simply use the amount of rotation from the animation.