Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Quaternion from Orthogonal Basis

I have a projectile object that is moving along a velocity vector. I need to ensure that the object is always facing in the direction of the velocity vector. Furthermore, I am representing object rotation using quaternions, not matrices.

I know that the first step is to find an orthogonal basis:

forward = direction of velocity vector
up = vector.new(0, 1, 0)
right = cross(up, forward) 
up = cross(forward, right)

How might I convert the basis into a rotation quaternion?

Solution

Note, I'd like to give credit to Noel Hughes for providing the answer, but I want to clarify with my own experiences. Pseudocode follows:

   vec3 vel = direction of velocity vector
   vec3 forward = (1, 0, 0)  // Depends on direction your model faces. See below.
   vec3 axis = cross(forward, vel)
   if (axis == 0) then quit // Already facing the right direction!
   axis = normalize(axis)
   float theta = acos(vel.x/sqrt(vel.x^2, vel.y^2, vel.z^2))
   quat result = (0, axis.y * sin(theta/2), axis.z * sin(theta/2), cos(theta/2)

The last element of the quaternion is the scalar part, the first three elements are the imaginary parts. Also, the above pseudocode assumes that your object in "model space" points down the positive x-axis. In my case, the object actually pointed down the positive y-axis, in which case I made the following changes:

   vec3 vel = direction of velocity vector
   vec3 forward = (0, 1, 0)  // Note that y-component is now 1
   vec3 axis = cross(forward, vel)
   if (axis == 0) then quit 
   axis = normalize(axis)
   float theta = acos(vel.x/sqrt(vel.x^2, vel.y^2, vel.z^2))
   quat result = (axis.x * sin(theta/2), 0, axis.z * sin(theta/2), cos(theta/2)
   // Note that SECOND component above is now 0
like image 327
Matt Fichman Avatar asked Oct 25 '09 05:10

Matt Fichman


2 Answers

I assume you don't care about the orientation of your projectile, other than having the longitudinal axis aligned with the velocity vector, and that the longitudinal axis is the x axis of (1, 0, 0).

You are on the right track. Normalize the velocity vector, (vx, vy, vz)/sqrt(vx^2 + vy^2 + vz^2) cross the x axis with it and normalize the result - (0, yn, zn) - this is the rotation axis for the quaternion. The rotation angle is simply theta = inverse cosine of vx/sqrt(vx^2 + vy^2 + vz^2). The resultant quaternion is then

(0, yn, zn)sn(theta/2) cos(theta/2)

Let me know if you have any questions.

Noel Hughes [email protected]

like image 166
noel hughes Avatar answered Nov 07 '22 20:11

noel hughes


Here is a solution that better matches the title: "Quaternion from Orthogonal Basis"

The question and answers as stated above address the problem of aligning a quaternion along a vector ('pointing') but ignore twist (orientation). A complete orthonormal basis includes direction, up and side (one of those being redundant but not two). The original question only considers direction.

One solution for quaternions-from-orthogonal-basis can be found by placing the 3x vectors into the columns of a matrix, then converting the matrix to a quaternion. However, this method requires additional matrix storage (16x floats) in addition to the quaternion, and is quite slow.

A better solution is found by writing out the basis vectors a,b,c and plugging these into the matrix-to-quaternion conversion, where the elements are explicit. (This matrix is in column-major, OpenGL form)

m[0][0] = a.x, m[1][0] = a.y, m[2][0] = a.z
m[0][1] = b.x, m[1][1] = b.y, m[2][1] = b.z
m[0][2] = c.x, m[1][2] = c.z, m[2][2] = c.z

Now, we rewrite the matrix-to-quaternion function using the substituted orthonormal vectors a,b,c. The result is the following function:

// Quaternion from orthogonal basis
Quaternion& Quaternion::toBasis (Vector3DF a, Vector3DF b, Vector3DF c)
{
    float T = a.x + b.y + c.z;
    float s;
    if (T > 0) {
        float s = sqrt(T + 1) * 2.f;
        X = (b.z - c.y) / s;
        Y = (c.x - a.z) / s;
        Z = (a.y - b.x) / s;
        W = 0.25f * s;
    } else if ( a.x > b.y && a.x > c.z) {
        s = sqrt(1 + a.x - b.y - c.z) * 2;
        X = 0.25f * s;
        Y = (a.y + b.x) / s;
        Z = (c.x + a.z) / s;
        W = (b.z - c.y) / s;
    } else if (b.y > c.z) {
        s = sqrt(1 + b.y - a.x - c.z) * 2;
        X = (a.y + b.x) / s;
        Y = 0.25f * s;
        Z = (b.z + c.y) / s;
        W = (c.x - a.z) / s;
    } else {
        s = sqrt(1 + c.z - a.x - b.y) * 2;
        X = (c.x + a.z) / s;
        Y = (b.z + c.y) / s;
        Z = 0.25f * s;
        W = (a.y - b.x) / s;
    }
    normalize();
    return *this;
}

This function gives a quaternion (X,Y,Z,W) directly from the orthonormal basis vectors a,b,c without intermediate storage of a matrix. (Matrices above in column-major, OpenGL form). You may still need to normalize the quaternion. If you have a direction and up-vector, say for a camera, you can construct the basis as: a=dir, b=up, c=cross(dir,up)

like image 42
ramakarl Avatar answered Nov 07 '22 21:11

ramakarl