Incremental rotation towards target 3D
#1 Members - Reputation: 120
Posted 07 November 2012 - 11:26 PM
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]
#2 Members - Reputation: 5845
Posted 08 November 2012 - 09:45 AM
[EDIT: Please, ignore that.]
You can
Edited by Álvaro, 08 November 2012 - 03:36 PM.
#3 Members - Reputation: 120
Posted 09 November 2012 - 01:51 PM
#4 Members - Reputation: 5845
Posted 09 November 2012 - 02:11 PM
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).
#5 Members - Reputation: 120
Posted 10 November 2012 - 11:03 PM
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 matrixMatrix.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?
#6 Members - Reputation: 5845
Posted 11 November 2012 - 12:31 AM
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: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?
#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);
}
Edited by Álvaro, 11 November 2012 - 12:33 AM.
#7 Members - Reputation: 120
Posted 13 November 2012 - 12:27 PM
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?
#8 Members - Reputation: 5845
Posted 13 November 2012 - 01:23 PM
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?
#9 Members - Reputation: 120
Posted 14 November 2012 - 02:19 AM
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
#11 Members - Reputation: 120
Posted 14 November 2012 - 10:31 PM
In my last post I had assumed the unreal part was 0 + q.x+q.y+q.z by making i,j and k equal to 1. As I said that produced bad results. I don't think setting them to zero would work, but I also tried multiplying each by the real part "w". Again bad results.
Sorry for not understanding, I think I'm struggling to understand the imaginary part of quaternions. It seems sometimes i,j,k just get dropped (meaning value of 1), and sometimes they are zero. I really appreciate your help.
#12 Members - Reputation: 5845
Posted 15 November 2012 - 01:41 AM
Sorry Alvaro, the documentation didn't help me as it does not show how they calculate the unreal part. If you say the unreal part is "0 + xi +yj +zk", then how do I get the value of i,j,k?
The unreal part is what I said, and the documentation says the same thing: The unreal part of a quaternion is another quaternion, which happens to have the real part zeroed out. To get the values of x, y and z from q = w + xi + yj + zk, you use something like R_component_2(), R_component_3() and R_component_4(). But this should be irrelevant, since you aren't using Boost.Quaternions.
i, j and k are never 1 and they are never 0. I am not sure what you have seen that gave you that impression.Sorry for not understanding, I think I'm struggling to understand the imaginary part of quaternions. It seems sometimes i,j,k just get dropped (meaning value of 1), and sometimes they are zero. I really appreciate your help.
#13 Members - Reputation: 120
Posted 15 November 2012 - 02:43 AM
[source lang="java"]public double unreal() { double i = FloatMath.sqrt(-1); double unreal = this.x*i + this.y*i + this.z*i; return unreal; }[/source]
However, I just plugged that in and it didn't work. So I will check over some of my code.
#14 Members - Reputation: 5845
Posted 15 November 2012 - 07:53 AM
public Quaternion unreal() {
return Quaternion(0, this.x, this.y, this.z); // or with `new', or however this is done in your language.
}
#15 Members - Reputation: 120
Posted 18 November 2012 - 09:30 PM
The code you gave me works as often as the original code I had with angle/axis, however with the opposite effect. Basically if my plane is upside down I hit the target, otherwise not. If I remove the lines to negate the quaternion when q.real() is less than 0, I get the original results (plane upside down = miss & plane right side up = hit).
If you have any suggestion or insight please let me know. I feel like this is really close to the right solution as the rotation is being correctly limited to the maximum rotation, but still needs to be inverted properly. I am wondering if there is anything that can be referenced to determine when to negate the quaternion, such as the up vectors of the rocket and target?
Here is my code as-is:
[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();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).normalize();float turn_threshold = 0.9998477f; // cos(maxrot/2) = cos(1)Quaternion newq = rot.limit_rotation(rot, turn_threshold); // see Quaternion code belowfloat[] qmat = newq.toMatrix(); Matrix.multiplyMM(R1Matrix, 0, R1Matrix, 0, qmat, 0);[/source]
And my relative Quaternion functions:
[source lang="java"]public Quaternion limit_rotation(Quaternion q, float min_real_part) {if (q.real() <= -min_real_part || q.real() >= min_real_part) return q;if(q.real() < 0){ q = q.multF(-1); // negate quaternion}float desired_unreal_length = FloatMath.sqrt(1 - min_real_part * min_real_part);float old_unreal_length = q.unreal().abs(); Quaternion limit = new Quaternion(min_real_part, q.x * (desired_unreal_length / old_unreal_length), q.y * (desired_unreal_length / old_unreal_length), q.z * (desired_unreal_length / old_unreal_length));Log.i("QuatLimit", "Result = w: " + Float.toString(min_real_part) + ", x:" + Float.toString(limit.x) + ", y:" + Float.toString(limit.y) + ", z:"+ Float.toString(limit.z)); return limit;}public Quaternion(float real) { this.w = real; this.x = 0; this.y = 0; this.z = 0;}public float real() { return this.w;}public Quaternion unreal() { return new Quaternion(0, this.x, this.y, this.z);}public float abs() { return FloatMath.sqrt(x * x + y * y + z * z + w * w);}public Quaternion (float w, float x, float y, float z) { this.set(w, x, y, z);}public Quaternion multF(float scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; this.w *= scalar; return this;}[/source]
Edited by JM33, 18 November 2012 - 09:34 PM.
#16 Members - Reputation: 5845
Posted 18 November 2012 - 09:33 PM
#17 Members - Reputation: 120
Posted 11 December 2012 - 02:30 AM
So I ended up making a quaternion of the current rocket rotation from the current matrix, and multiplying that by the limit_rotation quaternion from before. Then I set that back to a matrix plugged that into the rotation portion of the rockets matrix(Instead of multiplying the two matrices).
[source lang="java"]Quaternion transform_rotation = Quaternion.setFromMatrix(R1Matrix).normalize(); 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).normalize(); float turn_threshold = 0.999874f; // maxrot is the rockets maximum rotation angle (2) rot = Quaternion.limit_rotation(rot, turn_threshold).multQ(transform_rotation); float[] qmat = rot.toMatrix(); for (int i= 0; i< 10; i++){ R1Matrix[i] = qmat[i]; }[/source]
Thank you so much Álvaro, your code worked perfectly to limit the rotation of the quaternion.






