Quaternion : how to compute delta angle for each axis

Hi,

I’m trying to compute the delta angle for each axis for a rotating sphere. At first I naively retrieve the transform.rotation.x/y/z and compare with the previous frame values. But after some tests I noticed that x,y and z angle values do not change from 0 to 359 - mostly when the sphere rotate along more than one axis - there is jump values that I can’t explain.

I was wondering if it is possible to compute between to frames (Update function) the delta angle for each axis of a GameObject and how ? I think Quaternion is the right way to search but I didn’t succeed - There is a way to compute the angle between two Quaternion but not the angle for each axis.

If someone can help me on this it will be very appreciated :slight_smile:

Thanks

Well you can use Quaternion.Inverse to get the inverse of a rotation.

And if you combine the inverse of 1 rotation, with another rotation, you’ll get the difference between the two.

//rotation from a to b
var c = b * Quaternion.Inverse(a);

That rotation though is still a quaternion, you can then convert it to whatever you want.

Just remember, quaternion exist for a reason… things like euler angles jump around or get locked (gimbal lock) because of the way they’re designed. Quaternions don’t suffer from these problems, if you want to save things like “change in angles”… save them as a quaternion, and do your math that way.

A quaternion is more like a “how much rotation to apply, or was applied”. A eulerAngle is more a “at what rotation am I relative to no rotation”. The quat is more robust because “how much rotation to apply” can be relative to any other rotation (including no rotation). It’s stackable. Euler angles are ALWAYS relative to the same thing (no rotation)… making them not stack well (this is a simplification of course).

2 Likes

Hi Lordofduct,

Thank you for the answer and especially for the explanation about Quaternion and Eurler angle.

I will try your code example, but I have to admit that I don’t quite understand it. You reverse a rotation and then multiply it by the previous rotation and that’s suppose to return the difference, is that right ?

Thank you

Well, Quaternions don’t have a divide or subtract method.

Here’s how it works.

To combine to Quaternions you multiply them, they’re like matrices in this respect (actually quaternions are a special kind of matrix):

var c = a * b;

Now, when you want to do the opposite of add, what do you do? You subtract. But is subtract? It’s the opposite, or inverse of add.
If you wanted to subtract b from a, you could just add the additive inverse of b to a:

a - b == a + -b

When you want to do the opposite of multiply, you divide. Division is the opposite, or inverse of multiplication.
If you wanted to divide a by b, you could just multiply a by the multiplicative inverse of b. The multiplicative inverse is the reciprocal of a value.

a / b = a * 1/b

2 / (2/3) == 2 * (3/2) == 6 / 2 == 3

Side Note - this is actually how I always did my arithmetic growing up. Division and subtraction aren’t associative, which made it very difficult to do my math, because I couldn’t just simplify parts of it… I had to do the math left to right, because / and - required it. BUT if I could just remove all the division and subtraction signs… then the entire thing became associative. I can just rearrange as I saw fit.

3 + 1 / 4 - 6 * 4 == 3 + 1 * (1/4) + -6 * 4 == 3 + (0.25) + -24 == -20.75 or -20 3/4

I actually still can’t solve that with out changing to the inverses. I don’t know how anyone does… it just seems so ridiculous.

This same thing works with matrix/quaternion multiplication (remember, a quaternion IS a kind of matrix). Here’s the thing though, there is no inverse operation to matrix multiplication. Why? Well matrix multiplcation has a weird definition, we call it multiplication because the definition utilizes scalar multiplication for most of its steps. But it’s not actually multiplicaiton in the end… it’s a very complex algorithm. And that algorithm doesn’t translate to a process that includes division as it’s primary operator on the scalars in the matrix. Because it’s not multiplication! It’s just an algorithm that we called multiplication because some of its parts are. But the over all operation acts nothing like multiplicaiton… multiplication is both associative and commutative, matrix multiplication is not.

BUT, really, subtraction and division are defined as the inverse operations of addition and multiplication. If we wanted. If we can find a general case algorithm to get the operative inverse of a value, then we can just multiply by that inverse. Additive inverse algorithm, 0 - value. Multiplicative inverse, 1 / value. Matric multiplicative inverse, well… it’s a pretty complex algorithm, BUT we know it (see: Invertible matrix - Wikipedia).

So, to undo a matrix multiplication, we can multipy by the inverse. It LOOKS like the same operation in scalar multiplication.

MA / MB == MA * Inverse(MB)

Oh, and I’d like to point out one last problem with this though, and another reason why we didn’t create an operator for it. Inverting a matrix doesn’t always work. Some matrices just can’t be inverted (see that wiki article I posted). Which makes special cases where this operation fails.

Don’t worry though, all the quaternions you use to represent rotation are invertible.

That’s is a very detailed explanation, thank you !
I have to read it again in order to make all that clear in my head but it is already helpful !

I’m sorry to bother you again but one last question : is it possible according compute the angle between two Quaternion but for each axis ? The difference between two Quaternion give me the rotation to apply to go to one from the other rotation - but is it possible to compute the delta angle for each axis between two Quaternion ?

Thank you

If you get the eulerAngles of that computed difference quaternion, it technically is that change.

But the values can sometimes be a little weird, they make sense sure… but instead of say -3 degrees around x axis, you’ll get 357 degrees around the x axis. Same thing, but it might make your math a little tricky.

You can of course use various arithmetic tricks to normalize these values to whatever use case you need. I have a class with various methods to that… here are a few of those methods:

		/// <summary>
		/// Wraps a value around some significant range.
		/// 
		/// Similar to modulo, but works in a unary direction over any range (including negative values).
		/// 
		/// ex:
		/// Wrap(8,6,2) == 4
		/// Wrap(4,2,0) == 0
		/// Wrap(4,2,-2) == -2
		/// </summary>
		/// <param name="value">value to wrap</param>
		/// <param name="max">max in range</param>
		/// <param name="min">min in range</param>
		/// <returns>A value wrapped around min to max</returns>
		/// <remarks></remarks>
        public static float Wrap(float value, float max, float min)
		{
			value -= min;
			max -= min;
			if (max == 0)
				return min;

			value = value % max;
			value += min;
			while (value < min) {
				value += max;
			}

			return value;

		}

		/// <summary>
		/// set an angle with in the bounds of -PI to PI
		/// </summary>
		/// <param name="angle"></param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static float NormalizeAngle(float angle, bool useRadians)
		{
			float rd = (useRadians ? PI : 180);
			return Wrap(angle, rd, -rd);
		}

		/// <summary>
		/// closest angle between two angles from a1 to a2
		/// absolute value the return for exact angle
		/// </summary>
		/// <param name="a1"></param>
		/// <param name="a2"></param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static float NearestAngleBetween(float a1, float a2, bool useRadians)
		{
			float rd_2 = (useRadians ? PI_2 : 90);
			float two_rd = (useRadians ? TWO_PI : 360);

			a1 = NormalizeAngle(a1, useRadians);
			a2 = NormalizeAngle(a2, useRadians);

			if (a1 < -rd_2  a2 > rd_2)
				a1 += two_rd;
			if (a2 < -rd_2  a1 > rd_2)
				a2 += two_rd;

			return a2 - a1;
		}

		/// <summary>
		/// normalizes independent and then sets dep to the nearest value respective to independent
		/// 
		/// 
		/// for instance if dep=-170 degrees and ind=170 degrees then 190 degrees will be returned as an alternative to -170 degrees
		/// note: angle is passed in radians, this written example is in degrees for ease of reading
		/// </summary>
		/// <param name="dep"></param>
		/// <param name="ind"></param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static float NormalizeAngleToAnother(float dep, float ind, bool useRadians)
		{
			return ind + NearestAngleBetween(ind, dep, useRadians);
		}

		/// <summary>
		/// interpolate across the shortest arc between two angles
		/// </summary>
		/// <param name="a1"></param>
		/// <param name="a2"></param>
		/// <param name="weight"></param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static float InterpolateAngle(float a1, float a2, float weight, bool useRadians)
		{
			a1 = NormalizeAngle(a1, useRadians);
			a2 = NormalizeAngleToAnother(a2, a1, useRadians);

			return Interpolate(a1, a2, weight);
		}
		
		public static float ClampAngle(float a, float max, float min, bool bUseRadians)
		{
			a = NormalizeAngle(a, bUseRadians);
			max = NormalizeAngleToAnother(max, a, bUseRadians);
			min = NormalizeAngleToAnother(min, a, bUseRadians);
			return MathUtil.Clamp(a, max, min);
		}

Hi,

I’m trying to solve my problem, compute the delta angle for each axis after a rotation, by using the difference bteween two Quaternion but I didn’t succeeded. Using this difference Quaternion I’m not able to compute those delta angles, the returned values does not match the applied rotation.

I have a small test class : I forced a constant rotation of 60° and -60° to the x and y axis. Then using Quaternion I try to retrieve those 60° and -60° values tant represent the real delta angle for the x and y axis. But the values are never correct - if I apply only a rotation along one axis I have more success.

using UnityEngine;
using System.Collections;

public class TestQuaternion : MonoBehaviour {

	Quaternion prev;
	Quaternion diff;

	// Use this for initialization
	void Start () {
	
		// set the prev Quaternion to the currention rotation
		prev = transform.rotation;
	}
	
	// Update is called once per frame
	void Update () {
	
		//Apply a rotation - the point is to be able to retrieve the 60° and -60° values for the x and y axis
		transform.Rotate(new Vector3(60.0f,-60,0.0f),Space.Self);

		//Compute the difference Quaternion
		diff = prev * Quaternion.Inverse(transform.rotation);

		//Wrap diff Quaternion value - tried different max an min values - best result with 90/-90
		float wrapX = Wrap(diff.eulerAngles.x,90,-90);
		float wrapY = Wrap(diff.eulerAngles.y,90,-90);
		float wrapZ = Wrap(diff.eulerAngles.z,90,-90);

		//Debug the euler angles to see if they match the 60° -60° applied to the x and y axis
		Debug.Log("diff x : " + diff.eulerAngles.x + " y : " + diff.eulerAngles.y);
		Debug.Log(" x: " + wrapX + " y: " + wrapY + " z: " + wrapZ);

		//Set prev Quaternion to the current rotation
		prev = transform.rotation;
	}


	float Wrap(float value, float max, float min){

		value -= min;
		max -= min;
		
		if (max == 0)
			return min;

		value = value % max;
		value += min;
		
		while (value < min) {
			
			value += max;
		}

		return value;
	}
}