5

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

john doe
  • 547
  • 1
  • 6
  • 18
  • Look into converting [quaternions to Euler angles](https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles) to get the object's local axes. – Patrick Roberts Jan 17 '18 at 05:15
  • @PatrickRoberts is it possible to do it without the conversion? This is a scenario where maximum efficiency is necessary (game code, happening every tick). – john doe Jan 17 '18 at 07:28
  • Simplest, convert the force vector from local frame to world frame and apply force in global. – minorlogic Jan 17 '18 at 16:22
  • 1
    @minorlogic if I knew how to do that I wouldn't be asking this question – john doe Jan 18 '18 at 08:45
  • Just apply rotation part of transformation from local to global. To help with it , i need more information, how you store your object transfromation – minorlogic Jan 18 '18 at 08:53
  • @minorlogic I thought I explained that. It's stored as a quaternion. – john doe Jan 18 '18 at 09:52
  • Convert quaternion to rotation matrix using your math lib. Than multiply your force vector with this matrix (or transposed dependent on the transfromation direction) . – minorlogic Jan 18 '18 at 10:06
  • @minorlogic I tired it like this, but it's not right: `var euler = convertQuatToEuler(quat.w, quat.x, quat.y, quat.z); x = x - euler.X; y = y - euler.Y; z = z - euler.Z;` I think my euler conversion func is right, but the way I'm trying to perform the offset is wrong. – john doe Jan 19 '18 at 00:41
  • You wrote completely different from what i said. – minorlogic Jan 19 '18 at 09:14
  • @minorlogic well I just don't understand what you're explaing then sorry. – john doe Jan 19 '18 at 09:20

1 Answers1

7

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.

EvilTak
  • 7,091
  • 27
  • 36
  • Ok, after much confusion (my fault for having poor math skills) and researching and misunderstanding, and thinking and re-thinking, I have a question about this: Your answer ends with explaining how to get the world forward axis, but could you demonstrate in pseudo code how to get all 3 axes? – john doe Jan 19 '18 at 10:40
  • I started with say, vector `[0, 0, 5]` that I want to apply along the local axis (make an object move forward), but to apply the correct force along the global axis (I can only apply along the global axis in my physics engine), my function needs to output, for (a wrong) example: `[0.3, 2, 1.5]` based on whatever the object's local vector is facing (the quaternion). – john doe Jan 19 '18 at 10:41
  • I guess I'm really just asking for elaboration on "Now that you have the world forward axis, a force applied along it will simply be a scalar multiple of the world forward axis." – john doe Jan 19 '18 at 10:46
  • If you have a force along a local axis (say `(0, 0, 5)`), then you simply rotate it with the world rotation quaternion to get the same force along the corresponding world axis. In the example, the world space force will be `rotate(vector3(0, 0, 5), rotationQuaternion)` – EvilTak Jan 20 '18 at 05:47