Basic Dogfight Mode (3): Yaw/Pitch/Throttle Spring Theory

My final stab at this all was taking the spring forces I used earlier, but using them differently. Before, I used them to tweak the position of the ship, then fudged its heading to make things look right... but this made my plane behave so very not like a plane.

Previous Post:
Basic Dogfight Mode (2): Pure Positional Spring Theory - Memories of Melon Pan

Planes can't just slide around in the air - they're controlled by yaw, pitch, roll, and throttle. So what would happen if I applied these spring forces to those instead of position?

Let's think about this. Normally, we were trying to move the plane to a point in 3d space, and figuring out acceleration and velocity based on that. Now all we want to do is adjust the speed of the plane, based on how far the plane is from its target. Having said that, we're no longer trying to place the plane at a point in space - we're trying to adjust its distance to the target.

So we can move our spring force back to one dimensional space, the length of this space being the total distance to the chase target.

f = m * A = (k * (D0 - De)) - (c * S0)


For the variables:

  • f, force
  • m, mass
  • A, acceleration
  • k, the spring constant
  • D0, most recent distance to the chase target
  • De, distance equilibrium
  • c, the damping coefficient
  • S0, most recent speed



Where all variables are scalar values.

And from here, you can figure out the new speed at the current frame just like the other spring force equations.

A = ((k * (D0 - De)) - (c * S0)) / m
S = A * t = ((k * (D0 - De)) - (c * S0)) * t / m


For the variables:

  • A, acceleration
  • k, the spring constant
  • D0, most recent distance to the chase target
  • De, distance equilibrium
  • c, the damping coefficient
  • S0, most recent speed
  • m, mass
  • S, final calculated speed
  • t, time elapsed between updates, in seconds

Some of you may have noticed that I'm no longer negating the spring constant for this formula. This is just how the numbers work out - if we're farther than our equilibrium distance, then we need to speed up to catch up to our target. This will mean that we want positive acceleration. We know (D0 - De) will be positive in that case, and we have to multiply by a positive number afterwards, so we can just get rid of that negation in front of the spring constant.

This gives us the speed we need to be traveling at, but now we gotta figure out what direction we're going in. Like the first iteration, we first have to tell if the angle to our target exceeds some threshold, Tn. Finding that angle doesn't change at all.

dw = normalize(Xt - Xs)
d = dw * transpose(Rs)


α = acos(dot(f, d))


For the variables:

  • dw, direction vector in world space
  • Xt, position of the target
  • Xs, position of your ship
  • d, direction vector relative to your ship in object space
  • Rs, rotation matrix of your ship
  • α, the angle between your heading and the direction to target
  • f, the forward vector (0, 0, -1)



Where normalize(v) is the vector v in normalized form, and
where transpose(m) is the transpose of the matrix m.

This also gives us the direction to the target from the point of view of the chasing ship. Just like the first iteration of this, if we're outside an angular threshold, Tn, we have to yaw and pitch the ship to face the target.

This is where we do things a little differently, but if you've read my canvas renderer posts, the math shouldn't be too hard. See, when we calculated α, we only got one angle between the vectors across some arbitrary axis. We're also going to need an absolute horizontal and vertical angle to the target. If we had those, we would know how far to the left the target was, or how steep we'd need to pitch to face directly towards it.

We can calculate these from our new direction vector d, though. If we project this vector onto the xz-plane, we'll end up with a direction vector that's purely horizontal. The angle between between this new vector and the forward vector is our horizontal angle. We can do the same thing for the vertical angle if we use the yz-plane.

The math is similar to the planar projections I did in my imitation canvas renderer, but I'll go over it here too. We'll need to define a plane first, and planes can be defined with two vectors. For what we're doing, we're going to use a planar forward vector and a planar normal vector, which is orthogonal to the plane.

The first part of the calculation only uses the normal vector. If we're trying to find the horizontal angle, we can project the direction vector onto the xz-plane to get the pure horizontal component of the direction. The planar projection of a vector onto a plane is the rejection of the vector from the plane's normal.

We start by projecting the direction vector onto the plane's normal. Once we have that, we subtract that vector from the original direction vector.

Pn = dot(d, n) * n
Pp = d - Pn


For the variables:

  • d, the direction vector
  • n, the plane's normal vector
  • Pn, the projection of the direction vector onto the normal vector
  • Pp, the planar projection vector



Where dot(v1, v2) is the dot product between two vectors v1 and v2.

Now that we have the purely horizontal component of the direction vector, all we have to do is find the angle between that and our forward vector.

α' = acos(dot(f, Pp))


For the variables:

  • α', the axis angle
  • f, the planar forward vector
  • Pp, the planar projection vector



Where dot(v1, v2) is the dot product between two vectors v1 and v2, and
where acos(x) is the inverse cosine of x.

The only thing is that this angle will always be positive, whether or not the vector runs to the left or to the right. Since we want to know which side the angle runs to, we're going to have to negate that angle if it's off in one of those directions. Go ahead and roll your eyes, but this means we're going to have to prepare ourselves to find a third vector we can compare this to. For this, we'll be taking a vector that runs to the right, and negating the axis angle if it runs to the left.

We can get the right vector by taking the cross product of the forward and the normal vectors. That will get us a vector that's orthogonal to both, and that runs to the right of the forward vector since we use right-hand rule in XNA. If we remember the unit circle, we'll know that we don't have to actually find the angle - all we need to do is take the dot product and see if it's positive or negative, since that will tell us if the angle it makes is greater or less than ninety degrees.


r = cross(f, n)


if (dot(r, Pp) < 0.0) {
  α' = -α'
}


For the variables:

  • r, the right vector
  • f, the planar forward vector
  • n, the planar normal vector
  • Pp, the planar projection vector
  • α', the axis angle



Where cross(v1, v2) is the cross product between two vectors v1 and v2, and
where dot(v1, v2) is the dot product between two vectors v1 and v2.

We calculate these planar angles for both the xz-plane to find the horizontal angle, and the yz-plane for the vertical angle.

  • xz-plane: Forward = (0, 0, -1), Normal = (0, 1, 0)
  • yz-plane: Forward = (0, 0, -1), Normal = (1, 0, 0)

The thing we use these horizontal and vertical angles for is to adjust how much we're going to yaw or pitch to keep up with our moving target. I originally intended to use a spring system for these numbers like I did for throttle... but then, it dawned on me I didn't really need a damping coefficient. The DFM railroad pilot shuts off when you're pointed in the general direction of the target, so the spring would never go back to straight center, which would have been its equilibrium point.

With the damping part out of the picture, what's left?

m * A' = k * α'
A' = (k / m) * α'


V' = A' * t
V' = ((k / m) * α') * t


For the variables:

  • m, mass
  • A', axis acceleration
  • k, spring constant
  • α', axis angle
  • V', axis velocity
  • t, time

When you realize that k and m are just arbitrary values that you decided, it's the same as multiplying α' by some constant, then by time. Compare this to what you do when you're taking input from the controller.

V' = I' * t


For the variables:

  • V', axis velocity
  • I', input axis intensity
  • t, time

The only difference is what's multiplied by t. In one case, we multiply t by the yaw or pitch angle to the chase target times some arbitrary weight. In the other, we multiply t by however much you're tilting the analog stick. Not too complex when you think about it.

But the rest of this is pretty much like it was when we first tried out DFM. If you're within the near threshold Tn, then your ship does whatever the controller says. If it's outside that threshold, then push it back on track so that your ship heads back towards your target. If it's outside the far threshold or if it gets too far from the target, then disengage DFM.

Next Post:
Basic Dogfight Mode (4): Conclusion - Memories of Melon Pan