Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I offset a global directional force to be applied over a local axis?

I want to apply a forward force in relation to the object's local axis, but the engine I'm using only allows to me apply a force over the global axis.

I have access to the object's global rotation as a quaternion. I'm not familiar with using quats however (generally untrained in advanced maths). Is that sufficient information to offset the applied force along the desired axis? How?

For example, to move forward globally I would do:

this.entity.rigidbody.applyForce(0, 0, 5);

but to keep that force applied along the object's local axis, I need to distribute the applied force in a different way along the axes, based on the object's rotational quat, for example:

w:0.5785385966300964
x:0
y:-0.815654993057251
z:0

I've researched quaternions trying to figure this out, but watching a video on what they are and why they're used hasn't helped me figure out how to actually work with them to even begin to figure out how to apply the offset needed here.

What I've tried so far was sort of a guess on how to do it, but it's wrong:

Math.degrees = function(radians) {
    return radians * 180 / Math.PI;
};

//converted this from a python func on wikipedia, 
//not sure if it's working properly or not
function convertQuatToEuler(w, x, y, z){ 
    ysqr = y * y;
    t0 = 2 * (w * x + y * z);
    t1 = 1 - 2 * (x * x + ysqr);
    X = Math.degrees(Math.atan2(t0, t1));
    t2 = 2 * (w * y - z * x);
    t2 = (t2 >= 1) ? 1 : t2;
    t2 = (t2 < -1) ? -1 : t2;
    Y = Math.degrees(Math.asin(t2));
    t3 = 2 * (w * z + x * y);
    t4 = 1 - 2 * (ysqr + z * z);
    Z = Math.degrees(Math.atan2(t3, t4));
    console.log('converted', {w, x, y, z}, 'to', {X, Y, Z});
    return {X, Y, Z};
}

function applyGlobalShift(x, y, z, quat) {
    var euler = convertQuatToEuler(quat.w, quat.x, quat.y, quat.z);
    x = x - euler.X; // total guess
    y = y - euler.Y; // total guess
    z = z - euler.Z; // total guess
    console.log('converted', quat, 'to', [x, y, z]);
    return [x, y, z];
}

// represents the entity's current local rotation in space
var quat = {
    w:0.6310858726501465,
    x:0,
    y:-0.7757129669189453,
    z:0
}

console.log(applyGlobalShift(-5, 0, 0, quat));

Don't laugh at my terrible guess at how to calculate the offset :P I knew it was not even close but I'm really bad at math

like image 422
john doe Avatar asked Jan 16 '18 23:01

john doe


1 Answers

Quaternions are used as a replacement for euler angles. Your approach, thus, defeats their purpose. Instead of trying to use euler angles, levy the properties of a quaternion.

  1. A quaternion has 4 components, 3 vector components and a scalar component.

    q = x*i + y*j + z*k + w
    

    A quaternion therefore has a vector part x*i + y*j + z*k and a scalar part w. A vector is thus a quaternion with a zero scalar or real component.

  2. It is important to note that a vector multiplied by a quaternion is another vector. This can be easily proved by using the rules of multiplication of quaternion basis elements (left as an exercise for the reader).

  3. The inverse of a quaternion is simply its conjugate divided by its magnitude. The conjugate of a quaternion w + (x*i + y*j + z*k) is simply w - (x*i + y*j + z*k), and its magnitude is sqrt(x*x + y*y + z*z + w*w).

A rotation of a vector is simply the vector obtained by rotating that vector through an angle about an axis. Rotation quaternions represent such an angle-axis rotation as shown here.

A vector v can be rotated about the axis and through the angle represented by a rotation quaternion q by conjugating v by q. In other words,

v' = q * v * inverse(q)

Where v' is the rotated vector and q * v * inverse(q) is the conjugation operation.

Since the quaternion represents a rotation, it can be reasonably assumed that its magnitude is one, making inverse(q) = q* where q* is the conjugate of q.

On separating q into real part s and vector part u and simplifying the quaternion operation (as beautifully shown here),

v' = 2 * dot(u, v) * u + (s*s - dot(u, u)) * v + 2 * s * cross(u, v)

Where dot returns the dot product of two vectors, and cross returns the cross product of two vectors.

Putting the above into (pseudo)code,

function rotate(v: vector3, q: quaternion4) -> vector3 {
    u = vector3(q.x, q.y, q.z)
    s = q.w
    return 2 * dot(u, v) * u + (s*s - dot(u, u)) * v + 2 * s * cross(u, v)
}

Now that we know how to rotate a vector with a quaternion, we can use the world (global) rotation quaternion to find the corresponding world direction (or axis) for a local direction by conjugating the local direction by the rotation quaternion.

The local forward axis is always given by 0*i + 0*j + 1*k. Therefore, to find the world forward axis for an object, you must conjugate the vector (0, 0, 1) with the world rotation quaternion.

Using the function defined above, the forward axis becomes

forward = rotate(vector3(0, 0, 1), rotationQuaternion)

Now that you have the world forward axis, a force applied along it will simply be a scalar multiple of the world forward axis.

like image 117
EvilTak Avatar answered Nov 10 '22 08:11

EvilTak