Incremental rotation towards target 3D

Started by
15 comments, last by JM33 11 years, 4 months ago
Hi everyone, I know this is a common question but my searches are not turning up good solutions (ones that work).

I have a flight simulator / jet fighter game. I have other airplanes (enemy targets) flying around in the world. I want to shoot a guided missile at the target. My original approach for this works most of the time, but sometimes fails.

What I have done is found my missile's heading vector, and the vector between the missile and the target. I take the cross product of the 2 vectors to get my rotation axis vector. The angle of rotation is found by arccosine of the dot product of the cross product of my missile & target vectors. Since I do not want the missile to turn directly at the target immediately, I limit the rotation to 2 degrees. After rotating, I move the missile along its forward vector, by its speed.

To explain the failure in this, let me give in an example. The game starts & out in front of my airplane is the target. For this example both objects are flying forward. If I fire the missile, it goes straight for the target and hits.

However, if I roll my airplane over (upside down) the missile seems to turn the wrong direction. It will often make a big loop and eventually find the target and destroy it. It seems that the rotation angle should be negative in this case, but I don't know how to determine if I need to do so.

Here is some basic code to demonstrate:
[source lang="java"]Vector3D targvec = new Vector3D(targetmatrix[12], targetmatrix[13], targetmatrix[14]);
Vector3D R1pos = new Vector3D(R1Matrix[12], R1Matrix[13], R1Matrix[14]);
Vector3D R1head = new Vector3D(R1Matrix[8], R1Matrix[9], R1Matrix[10]);

Vector3D newtargvec = targvec.subtract(R1pos);
newtargvec = newtargvec.normalize();
R1head = R1head.normalize();

float angle = R1head.angleBetween(newtargvec); // acos(R1head.dotProduct(newtargvec)

Vector3D rotaxis = R1head.crossProduct(newvec);
rotaxis.normalize();

if (angle > 2){
angle = 2;

Matrix.rotateM(R1Matrix, 0, (float) angle, rotaxis.getX(), rotaxis.getY(), rotaxis.getZ());
setMoveForwardMatrix(tempMatrix, -(movedist),R1Matrix[8], R1Matrix[9], R1Matrix[10]);

Matrix.multiplyMM(R1Matrix, 0, tempMatrix, 0, R1Matrix, 0);

[/source]
Advertisement
[s]You can extract a signed angle from the size of the cross product: If you started with unit-length vectors, it's just asin(length(rotaxis)).[/s]
[EDIT: Please, ignore that.]

You can [s]also[/s] avoid using angles at all by computing the quaternion that corresponds to the desired rotation, but applying the 2-degree-per-frame limit is trickier and I don't have time to think about it right now. Maybe I'll post some code tonight.
Thanks Alvaro, I have succesfully used quaternions to achieve the full rotation to towards the target. However, I have no idea how to get a limited (x degrees) rotation from the quaternion without converting it to angle-axis. After converting and limiting the angle, I get the same results. A code example would be great, but even just an explanation of the process would help.
Well, the real part of a quaternion is w = cos(alpha/2), so you can translate an inequality like `alpha <= 2 degrees' to `w >= cos(1 degree)'. So if your quaternion has a real part that is below the threshold, set it to the threshold and rescale the other three numbers to make sure the quaternion preserves unit length.

I haven't thought about it carefully, but if your quaternion has negative real part you may want to flip its sign before you do what I describe above (remember that q and -q represent the same rotation).
This seems to be on the right track, but I'm not sure. The part about limiting the rotation seemed to work but I am still getting inconsistent results. Same problem, when the plane turns upside down, the rocket will miss.

Does it matter that I am still converting the quaternion back to a matrix (instead of converting to axis angle)? I am multiplying the converted matrix by the rockets matrix. As far as I know with quaternions, you have to convert to matrix, as I am using opengles on android.

The other thing I noticed was that the limiting threshold for the quaternion worked when it shouldn't have. At first I only limited the w value to anything below the threshold. code example:
[source lang="java"]Quaternion head = new Quaternion(0, R1head.getX(), R1head.getY(), R1head.getZ()).normalize();
Quaternion newvec = new Quaternion(0, newtargvec.getX(), newtargvec.getY(), newtargvec.getZ()).normalize();
Quaternion rot = head.rotBetween(newvec); //Get Quaternion for rotation between the two vectors

float turn_threshold = FloatMath.cos(maxrot/2); // maxrot is the rockets maximum rotation angle (2)
if (rot.w < turn_threshold){
rot.w = turn_threshold;
}

float[] qmat = rot.toMatrix(); //convert quaternion to matrix
Matrix.multiplyMM(R1Matrix, 0, R1Matrix, 0, qmat, 0);
[/source]

This did not account for a "w" value lower than zero. However when the "w" value was near -0.9, sometimes the rocket would still hit the target. Even though this code changed the value from -0.9 to +0.99 (or cos(1)). When I tried to limit the negative W value the rocket would never hit the target.I have a much harder time visualizing quaternions so I am quite confused.

Also I am not sure what you meant by scaling the other three parts after changing the W value. Do I need to just normalize it?

Also I am not sure what you meant by scaling the other three parts after changing the W value. Do I need to just normalize it?

Not quite. You need to scale the unreal part of the quaternion (the coefficients of i, j and k) so that the whole quaternion is unit length. Something like this:

#include <boost/math/quaternion.hpp>
#include <cmath>

typedef boost::math::quaternion<double> Q;

Q limit_rotation(Q q, double min_real_part) {
if (q.real() <= -min_real_part || q.real() >= min_real_part)
return q;

if (q.real() < 0)
q = -q;

double desired_unreal_length = std::sqrt(1 - min_real_part * min_real_part);
double old_unreal_length = abs(q.unreal());
return Q(min_real_part) + q.unreal() * (desired_unreal_length / old_unreal_length);
}

return Q(min_real_part) + q.unreal() * (desired_unreal_length / old_unreal_length);
}


Sorry I only know Java, but is this returning a Quaternion that is created by a double value? I don't understand how to do that. Don't you need either "w,v" or "w,x,y,z" values?
5.2 is a perfectly good quaternion, also known as 5.2 + 0*i + 0*j + 0*k.

More explicitly, what I am returning there is the quaternion
w = min_real_part
x = q.x * (desired_unreal_length / old_unreal_length)
y = q.y * (desired_unreal_length / old_unreal_length)
z = q.z * (desired_unreal_length / old_unreal_length)

Does that answer your question?
Yes that helps!
Now the part I don't understand is the q.unreal(), what is going on there? My assumption based on q= w +xi + yj + zk is that the unreal part is x+y+z. However when I tried that I got strange results. Thank you for your help
The unreal part of w + xi + yi +zk is 0 + xi + yi + zk. The documentation is here.

This topic is closed to new replies.

Advertisement