The ship also has a location, a velocity and maximum angle by which it can turn by per tick ('turnAngle')
Each tick, I take the cross product of the direction and target vectors to get an axis of rotation.
I then use quaternions to rotate the direction around that axis by turnAngle radians.
Finally I multiply each part of the direction vector by the velocity and add the results to the ship's location to move it
The problem is that I often (but not always) end up with the direction vector coming to lie along the rotation axis (I assume this is the famous gimbal lock that I keep hearing about; see screenshot). Any help with this would be most appreciated as I've spent ages trying all sorts of things to solve this all to no avail. Below is my code (all in java):
public class Spaceship{
Vector3F targetVector;
Vector3F directionVector;
Vector3F rotationAxis;
Vector3F location;
float velocity = 20;
float turnAngle = 0.05f;
Waypoint currentWaypoint;
private Location getPredictedLocation(Vector3F vector1) {
Vector3F vector = new Vector3F(vector1);
if(vector.x==0 && vector.y==0 && vector.z ==0){
return location;
}
Vector3F normalisedVector = getNormalisedVector(vector);
// calculate predicted location
Vector3F predicted = new Vector3F(location);
predicted.x += (normalisedVector.x * velocity);
predicted.y += (normalisedVector.y * velocity);
predicted.z += (normalisedVector.z * velocity);
return predicted;
}
private Vector3F getWaypointVector(Waypoint wp) {
Vector3F returnvector = new Vector3F();
returnvector.x = wp.getWaypointLocation().x - location.x;
returnvector.y = wp.getWaypointLocation().y - location.y;
returnvector.z = wp.getWaypointLocation().z - location.z;
returnvector = getNormalisedVector(returnvector);
return returnvector;
}
private Vector3F getNormalisedVector(Vector3F vector){
float total = Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z);
if(total==0){
return new Vector3F();
}
vector.x /= totalmovement;
vector.y /= totalmovement;
vector.z /= totalmovement;
return vector;
}
private Vector3F crossProduct(Vector3F v1, Vector3F v2){
Vector3F retVec = new Vector3F();
v1 = Normalise(v1);
v2 = Normalise(v2);
retVec.x = v1.y*v2.z - v1.z*v2.y;
retVec.y = v1.z*v2.z - v1.x*v2.z;
retVec.z = v1.x*v2.y - v1.y*v2.x;
return retVec;
}
private void turn(){
targetVector = getWaypointVector(currentWaypoint);
directionVector = getNormalisedVector(directionVector);
targetVector = getNormalisedVector(targetVector);
rotationAxis = crossProduct(directionVector, targetVector);
rotationAxis = getNormalisedVector(rotationAxis);
Quaternion quat = new Quaternion(rotationAxis,(float) turnAngle);
directionVector = quat.RotateVector(directionVector);
directionVector = getNormalisedVector(directionVector);
}
public void move() {
turn();
Vector3F predicted = getPredictedLocation();
Vector3F newloc = new Vector3F(predicted);
location.x = newloc.x;
location.y = newloc.y;
location.z = newloc.z;
}
}
Quaternion object taken from http://content.gpwik...resent_rotation :
public class Quaternion {
private static final float TOLERANCE = 0.00001f;
float w;
float x;
float y;
float z;
public Quaternion(float w, float x, float y, float z){
this.w = w;
this.x = x;
this.y = y;
this.z = z;
Normalise();
}
public Quaternion(Vector3F axis, float angleInRadians){
w = (float) Math.cos( angleInRadians/2);
x = (float) (axis.x * Math.sin( angleInRadians/2 ));
y = (float) (axis.y * Math.sin( angleInRadians/2 ));
z = (float) (axis.z * Math.sin( angleInRadians/2 ));
Normalise();
}
public void Normalise(){
// Don't normalize if we don't have to
float mag2 = w * w + x * x + y * y + z * z;
if (Math.abs(mag2) > TOLERANCE && Math.abs(mag2 - 1.0f) > TOLERANCE) {
float mag = (float) Math.sqrt(mag2);
w /= mag;
x /= mag;
y /= mag;
z /= mag;
}
}
public Quaternion getConjugate(){
return new Quaternion(w, -x, -y, -z);
}
public Quaternion Multiply(Quaternion rq){
return new Quaternion( w * rq.w - x * rq.x - y * rq.y - z * rq.z,
w * rq.x + x * rq.w + y * rq.z - z * rq.y,
w * rq.y + y * rq.w + z * rq.x - x * rq.z,
w * rq.z + z * rq.w + x * rq.y - y * rq.x);
}
public Vector3F RotateVector(Vector3F vec){
Vector3F vn = new Vector3F();
vn.x = vec.x;
vn.y = vec.y;
vn.z = vec.z;
vn = GraphicsUtil.Normalise(vn);
Quaternion vecQuat = new Quaternion(0.0f, vn.x, vn.y, vn.z);
Quaternion resQuat;
resQuat = vecQuat.Multiply(getConjugate());
resQuat = this.Multiply(resQuat);
Vector3F returnvec = new Vector3F();
returnvec.x = resQuat.x;
returnvec.y = resQuat.y;
returnvec.z = resQuat.z;
return returnvec;
}
}