Game Development Community

Space Vehicle Physics

by Charlie Sibbach · in Torque Game Engine · 07/09/2008 (11:56 pm) · 20 replies

I put up a job posting for this, but I've only had 1 hit and I can't meet the (fully justified) pricetag, so I'm trying to roll my own here. I'm knee deep in the vehicle code, and [re]learning an awful lot about vectors and matrixes, and just plain learning alot about the physics of rotation (angular momentum, torque, etc).

I'm trying to create a space vehicle, based on the helicopter vehicle code for the auto-leveling, and I'm doing OK. I feel like I'm really close here. I've stripped out everything to do with gravity and hover jets and drag; my current problem is to get it to turn the way I want. Here's some pertinent code for reference:

void SpaceVehicle::updateForces(F32 /*dt*/)
{
   MatrixF currPosMat;
   mRigid.getTransform(&currPosMat);
   mRigid.atRest = false;

   Point3F massCenter;
   currPosMat.mulP(mDataBlock->massCenter,&massCenter);

   // xv, yv, and zv are the components of the velocity of the ship.
   // They are pulled out of the current position matrix.
   Point3F xv,yv,zv;
   currPosMat.getColumn(0,&xv);
   currPosMat.getColumn(1,&yv);
   currPosMat.getColumn(2,&zv);
   
   // Calculate the speed
   F32 speed = mRigid.linVelocity.len(); 

   Point3F force  = Point3F(0, 0, 0);
   Point3F torque = Point3F(0, 0, 0);

...

   // Steering
   Point2F steering;
   steering.x = mSteering.x / mDataBlock->maxSteeringAngle;
   steering.x *= mFabs(steering.x);
   steering.y = mSteering.y / mDataBlock->maxSteeringAngle;
   steering.y *= mFabs(steering.y);
   
   // Pitch
   torque -= xv * steering.y * mDataBlock->steeringForce;
    //torque -= Point3F(steering.y * mDataBlock->steeringForce, 0, 0); //Mine
   
   // Yaw
   torque -= Point3F(0, 0, steering.x * mDataBlock->steeringForce); //Mine
   //torque -= zv * steering.x * mDataBlock->steeringForce;

   mRigid.force  = force;
   mRigid.torque = torque;
}

The part I'm focussing on right now is in the Steering, Pitch, and Yaw section. The first five lines in Steering are pretty much unchanged from FlyingVehicle. It's where they are converted to Torque that I'm getting messed up. What I need is for the ship to rotate about the Z axis independently of it's pitch; if it's nosed down I want it to stay that way. For pitch, I want it to only move up or down, independent of the rotation. Basically, I want it to do it's pitch movement and then do it's yaw. Or, put another way, I want the vehicle to always face more or less Up on the +Z world axis.

So I changed the torque for yaw about to generate a twist always around the Z axis, and that seems to work OK. The original, if I understand it correctly, uses the zv (column 2 from the position matrix), and it rotates around its local Z axis, not the world Z axis. For pitch, I want the opposite; I want it to rotate around the objects' X axis, not the world X axis, so I left it alone (although I put in some sample code for the world X that doesn't work either).

Can anybody help me out here, or at least explain what I'm doing wrong? Right now the ship twists like nuts until it's heeled over 90 degrees, at which point pitching and yawing are one and the same.

If I can get some help here, I promise to release my code as a [badly needed] SpaceVehicle resource when it's done. I also have some questions about making the vehicle stop turning correctly, and to make it turn 180 to it's current velocity, which I may be able to figure out if I can get this to work, maybe not.

Thanks for any help!

#1
07/10/2008 (1:44 am)
But in space, which way is up?
#2
07/10/2008 (10:54 am)
In our game, up is always +Z. That may not be true for everybody but it is for us!

I had as an idea that I could apply the torque using the cross-product of the vector <0 0 1> and the normalized vector of the ships' current facing. The cross product would be a unit vector (? unit cross unit = unit, right?) that is perpendicular to the Z axis and to the ship, which should allow me to scale it and twist the ship up or down in its current facing. However, I don't know if the direction of the cross vector will be consistent as it twists around, or do I?

I'll play with it and report.
#3
07/10/2008 (12:42 pm)
OK, hit a stumbling block with that idea right off the bat. There is no direction vector for the ship's facing, there's only a transform matrix. How can I go from a transform matrix to a direction vector? In other words, can I pull out a normalized vector that will describe the end direction that my object is facing? I know there's issues about world vs. screen coords, but I think the vehicle code all deals in world coords. Can I go from currPosMat to a Point3F that describes only the direction, in the same coordinate space? I think I'm repeating myself too much now...
#4
07/10/2008 (2:06 pm)
Yes, you can extract a suitable direction vector for your vehicle from it's transform. The essence of what you want can be found in the columns 0, 1, and 2 of the transform.

NB: I haven't tried this *exact* example in this post, but your final solution will likely wind up being similar.

Suppose, for example, that you want to apply thrust so that the vehicle will move relative to it's +Y (ie, +X is to the right, +Z is up, and +Y is forward), independent of it's actual orientation in 3-space.

Point3F xv, yv, zv, axis, impulse;

mRigid.getTransform(&currentPosMat);
currentPosMat.getColumn(0, &xv);
currentPosMat.getColumn(1, &yv);
currentPosMat.getColumn(2, &zv);

// crossing xv & zv yields a relative y-axis.
mCross(xv, zv, &axis);
axis.normalize();
impulse = -AMOUNT_OF_IMPULSE * axis;
mRigid.applyImpulse(position, impulse);

Something like that. Probably. :)
#5
07/10/2008 (2:18 pm)
Thanks Andy. That looks quite similar to the code already there. I didn't realize that the xv and yv columns actually did relate to the direction so well. I'll give it a shot and report back.
#6
07/10/2008 (2:21 pm)
Andy, doesn't xv CROSS zv == yv ? (or possibly -yv, depending on the handedness of things)
also, xv, yv, and zv can be relied upon to be unit length,
so you wouldn't need to normalize after the cross.
#7
07/10/2008 (2:31 pm)
@Orion: yes, I believe you are correct on all counts. As I said:
Quote:Something like that. Probably. :)

#8
07/10/2008 (2:47 pm)
Interesting and fruitful results, guys. Here's my steering code so far:

// Steering
   Point2F steering;
   steering.x = mSteering.x / mDataBlock->maxSteeringAngle;
   steering.x *= mFabs(steering.x);
   steering.y = mSteering.y / mDataBlock->maxSteeringAngle;
   steering.y *= mFabs(steering.y);
   
   // Pitch
   //torque -= xv * steering.y * mDataBlock->steeringForce;
   //torque -= Point3F(steering.y * mDataBlock->steeringForce, 0, 0);
   Point3F pitchAxis;
   mCross(yv, zv, &pitchAxis);
   //pitchAxis.normalize();
   torque -= pitchAxis * steering.y * mDataBlock->steeringForce;
   
   // Yaw
   torque -= Point3F(0, 0, 1) * steering.x * mDataBlock->steeringForce;

The Pitch code works beautifully; regardless of what direction you are facing it pitches up and down straight in that direction. The Yaw code is flawed, though, but I might just be having a problem with combined torques here. If you pitch, stop, then yaw, it does what it should. But if you do both movements at the same time (I'm using keyboard instead of mouse control, BTW), it starts to twist in horrible ways, and until I get some auto-level code back in there (right now I just set mRigid.angMomentum = (0, 0, 0)), there's no way to recover from the twist, which shouldn't be happenning in the first place.

I need to keep that yv X zv vector (pitchaxis) orthogonal to <0 0 1>, I think would be the technical term. Any ideas? Thanks for the advice so far!

Edit: Also just noticed that any linear velocity screws everything up. The pitch no longer works if I'm moving, so something else is happening.
#9
07/10/2008 (3:49 pm)
Orion, based on your post, I realized that the whole extra cross vector PitchAxis I have in there is identical to the function above from a previous attempt:

torque -= xv * steering.y * mDataBlock->steeringForce;

Equals

Point3F pitchAxis;
   mCross(yv, zv, &pitchAxis);
   torque -= pitchAxis * steering.y * mDataBlock->steeringForce;

So I'm actually not buying anything here, unfortunately.
#10
07/10/2008 (3:59 pm)
Sounds like you've got a complex situation.
if you can narrow it to a more specific math question i might be able to help.
#11
07/10/2008 (5:34 pm)
It's definitely turning out that way, although I can't tell you why. It seems so simple in my head. I don't understand why linear velocity is affecting the movement of a rigid body undergoing a torque. I also don't understand why even the original flying vehicle physics aren't working and the ship is rolling around like a pig. Oh boy. I'll see if I can't distill the issue into a specific question later tonight.

Right now, can you give me a very quick primer on the meaning of the values in the transformation matrix' columns, xv, yv, and zv? Apparently they are unit vectors that point in the direction of the object with respect to world coordinates, is that true? Also, they are apparently all orthogonal to eachother, such that xv CROSS yv = zv and so on and so forth. Is this also accurate? Thanks!
#12
07/10/2008 (5:50 pm)
Sure -

you've pretty much nailed it:

the upper 3x3 cells of a "normal" 4x4 transformation matrix:
* store the rotation of the transformation
* the three column vectors represent the unit X, Y, and Z vectors when transformed by that matrix,
and are indeed unit length and mutually perpendicular.

note that you can use this in reverse to great advantage.
suppose you know what heading you want an object to be pointing in,
and you also know what you want it's local "up" and "right" vectors to be.
there's a complex way to derive the rotation matrix to orient your object that way,
where you calculate lots of angles and build lots of intermediate matrices,
but you can just skip all that and simply set the columns of the matrix to be
the vectors you know you want them to be.

also note that you can make a matrix where the column vectors are *not* perpendicular.
this would give objects going through that matrix a skewed appearance.

re crossing one vector into another,
there is one detail to remember which is that A CROSS B = negative (B CROSS A).
the rule of thumb (ho ho) i use to figure out what direction the cross-product will be in is
the right-hand rule: hold your right hand out flat, with the fingers together and the thumb at 90 degrees.
pretend the fingers are vector A, and then close your fingers so they become vector B. - your thumb will be pointing in the direction of A CROSS B.
#13
07/11/2008 (11:41 am)
Thanks for that, Orion. Alot of stuff is making more sense now. I have two more questions for you.

1. The code I have actually works, it makes the ship move in sort of a polar fashion, as long as you don't combine a pitch and a yaw movement. If you do that, it makes the ship twist up and to the left (for example) to end up at an angle relative to Z, and then everything starts breaking. In math terms, the xv vector is no longer in the XY plane, it has Z != 0. To get the motion I want, I need to keep this property.

I think I can do it using the properties of the Dot product. If to vectors are orthogonal/perpendicular to eachother, the dot product of those vectors is 0, yes (cosine 90 = 0)? So if I apply a torque along the yv vector of the ship that is relative to the dot product of <0 0 1> and xv, I'll generate a counter-rotate torque that should keep the ship correctly faced "up". I'm going to try it and see.

2. On a rigid body, should applying a force (to <0 0 0> create a torque? Basically, when I am at a standstill, other than the twisting above, it works right. But as soon as I have any velocity at all (I know, not a force) and apply any kind of torque, the ship twists around in horrible ways, which gets worse with higher velocity. I think this has something to do with rotational dynamics that I don't understand. Do you have any idea what is causing this action, and/or how I can make it stop?

Thanks!
#14
07/11/2008 (12:03 pm)
> So if I apply a torque along the yv vector of the ship that is relative to the dot product of <0 0 1> and xv,
> I'll generate a counter-rotate torque that should keep the ship correctly faced "up". I'm going to try it and see.

exciting stuff!

unfortunately i'm totally unfamiliar with the TGE rigid body code.
i'm not sure if <0,0,0> is assumed to be the center of mass or not.
if not, then yes, i would expect a force at that point to generate some spin as well as linear velocity.

in general i would recommend trying to reduce situations to the simplest possible setup which produces the problem. eg, turn off thrust, etc, etc.
#15
07/11/2008 (1:37 pm)
Rigid is really overkill for this sort of functionallity. We ended up taking FlyingVehicle, merging with Vehicle and removing Rigid from it. What's left is a pretty close copy of the ships in Descent/Wing Commander/Freelancer etc.

If that's what you want, I would really look into seperating Rigid out from the whole deal since it's heavy for the purpose of a flight sim, and difficult to understand.
#16
07/11/2008 (2:09 pm)
Stefan,

I have considered the exact same things many times lately! What I keep coming back to is dealing with collision handling and reactions; I really like the idea of the ship twisting and bucking under heavy fire, and we are going to have a few ships that move differently, so having that common base is tempting. Combining Vehicle and Space Vehicle is probably a done deal now that you mention it, though. I think I'm zeroing in on the answers I need, though; it's been a great review of linear algebra and intermediate physics for me.

How about this, which I think is a bug. You sound like you've also been through the wringer with the vehicle code. In Vehicle::updatePos(), we put in code to check the mission area, lifted from the Flying Game Example. However, this code doesn't work, because getTransform() (inherited back from Scene Object) always returns <0 0 0> as the current position! I can't find any code anywhere that links the transform matrix calculated in mRigid back to mObjToWorld in SceneObject. Check this code (called successfully and often from Vehicle::updatePos()):

void Vehicle::checkMissionArea() //Need this function for flying AI example!!
{
   // Checks to see if the player is in the Mission Area...
   Point3F pos;
   MissionArea * obj = dynamic_cast<MissionArea*>(Sim::findObject("MissionArea"));

   if(!obj)
      return;

   const RectI &area = obj->getArea();
   getTransform().getColumn(3, &pos);

   if ((pos.x < area.point.x || pos.x > area.point.x + area.extent.x ||
       pos.y < area.point.y || pos.y > area.point.y + area.extent.y)) {
      if(mInMissionArea) {
         mInMissionArea = false;
         Con::executef(mDataBlock,3,"onLeaveMissionArea",scriptThis());
      }
   }
   else if(!mInMissionArea)
   {
      mInMissionArea = true;
      Con::executef(mDataBlock,3,"onEnterMissionArea",scriptThis());
   }
}

pos is always <0 0 0>! I'm still working through debugging this, so I can't say for sure what the hell is going on, but I do know that onLeaveMissionArea is never being called. Further strangeness: in the Torque console, I can do echo($MyFlyingVehicle.getPosition()); and have it tell me where I am correctly. Something is funny in Denmark, says I.

Edit: the position and rotation are set in processTick(), but not via the transform directly, which is why I didn't catch it before. It uses setPosition and setRotation instead.
#17
07/11/2008 (3:17 pm)
OK, now we're cooking with fire! I think I have it. The key was the relationship between updatePos() and Rigid::integrate() and updateMove(). I have code that stops the ship from moving after you let up on the move key, but because updatePos() gets called more times than updateMove(), it was still messing with the steering.

Key part is this: it bloody works! Last bit to fix is the pitch auto-level, and the auto-level roll.

Question before the court is this: the whole thing looks slightly shaky and jerky at higher speeds. Any way I can fix that? SpaceVehicle resource here I come.
#18
07/11/2008 (5:33 pm)
Can't be of much help. We didn't use mission borders nor did we use physics to simulate "ship twisting", simply because it was too heavy for the amount of players we wanted at once in a given area. You can get the same effect very cheaply but not via physics, of course.

Good luck :) Looks like you'll get to it.
#19
07/11/2008 (6:28 pm)
Thanks for the encouragement, Stefan. Orion, your help as well has been invaluable.

Things to clear up before I can pack this up and put it to bed:

1. The shakiness. I'm using the Advanced Camera, but I've been told that the original cam looks better with everything else being equal. Has anybody else had problems with Advanced Cam? Any fixes? The version I'm using was modified from the code nearly two years ago; has it advanced significantly in that time?

2. I need to implement a "reverse direction" key, that will turn you 180 to your current velocity. Thankfully, I know how to do that now, I think. Vectors and dot products are fun.

3. The auto-roll correction tends to overshoot, making the ship rock back and forth around the +Z axis instead of coming to a nice stop. This may just be a matter of reducing the torque involved.

4. I need a Z thruster to go with the X and Y thrust.

5. Auto-level the pitch after a delay.

A few days ago this would have seemed like the hardest part of the code, now it feels routine!

I'll post again when it's either done or I hit some major snag. Thanks again!
#20
10/19/2009 (9:55 pm)
Any chance you could share a copy of your SpaceVehicle code? (Compatible with TGE 1.5.2) I'm trying to do basic space sim for a class I'm in. There's only 9 weeks total in the class, we're starting week 2 now, and I've got quite the load of functionality I've got to manage.