# Average quaternions?

Discussion in 'Scripting' started by jarden, Apr 25, 2011.

1. ### jarden

Joined:
Mar 29, 2011
Posts:
74
I dug this code up from 2006 on this forum:
Code (csharp):
1. var Quaternion[] quats;
2.
3. Quaternion avg = Quaternion(0,0,0,0);
4. for (var q : Quaternion in quats)
5. {
6.     if (Quaternion.Dot (q, avg) > 0)
7.     {
8.        avg.x += q.x;
9.        avg.y += q.y ;
10.        avg.z += q.z;
11.        avg.w += q.w;
12.     }
13.     else
14.     {
15.        avg.x += -q.x;
16.        avg.y += -q.y;
17.        avg.z += -q.z;
18.        avg.w += -q.w;
19.     }
20. }
21.
22. var mag = Mathf.Sqrt(avg.x* avg.x + avg.y* avg.y + avg.z * avg.z + avg.w * avg.w);
23.
24. if (mag > 0.0001)
25. {
26.    avg.x /= mag;
27.    avg.y /= mag;
28.    avg.z /= mag;
29.    avg.w /= mag;
30. }
31. else
32.    avg = quats[0];
But I think the author said theres an easier way with slerp and multiplication. Anyone know it or some easier code?

Last edited: Apr 25, 2011

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
32,401
var average = Quaternion.Lerp(quaternion1, quaternion2, .5); maybe?

--Eric

3. ### jarden

Joined:
Mar 29, 2011
Posts:
74
Whats weird about that is it gives the same value no matter what .5 is. 0, 1, all the same return.

4. ### Dreamora

Joined:
Apr 5, 2008
Posts:
26,601
then it was the same already at start if the lerp / slerp one is the same value as one of the inputs

5. ### jarden

Joined:
Mar 29, 2011
Posts:
74
Thanks all. For reference I did get a good average with this code:
Code (csharp):
1.     var test : Quaternion = Quaternion.identity;
2.     test = Quaternion.Lerp(first.transform.rotation, second.transform.rotation, 0.5);

Divkaran_NIIT likes this.

### Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
32,401
That's basically what I posted. Replace "quaternion1" with "first.transform.rotation" etc.; you don't need to define variables on separate lines like that.

--Eric

7. ### Elecman

Joined:
May 5, 2011
Posts:
1,374
Lerp or Slerp is only accurate if you have only two quaternions. If you have more than two, that method will give you a different result if you execute it in a different order.

Here is the mathematically correct way of averaging more than two quaternions:

Wiki:
http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors

Code (csharp):
1.
2. //Global variable which holds the amount of rotations which
3. //need to be averaged.
5.
6. //Global variable which represents the additive quaternion
8.
9. //The averaged rotational value
10. Quaternion averageRotation;
11.
12. //multipleRotations is an array which holds all the quaternions
13. //which need to be averaged.
14. Quaternion[] multipleRotations new Quaternion[totalAmount];
15.
16. //Loop through all the rotational values.
17. foreach(Quaternion singleRotation in multipleRotations){
18.
19.     //Temporary values
20.     float w;
21.     float x;
22.     float y;
23.     float z;
24.
25.     //Amount of separate rotational values so far
27.
37.
38.     //Normalize. Note: experiment to see whether you
39.     //can skip this step.
40.     float D = 1.0f / (w*w + x*x + y*y + z*z);
41.     w *= D;
42.     x *= D;
43.     y *= D;
44.     z *= D;
45.
46.     //The result is valid right away, without
47.     //first going through the entire array.
48.     averageRotation = new Quaternion(x, y, z, w);
49. }
50.
Edit:
Before you average a quaternion, you have to check whether it has x y z and w reversed compared to the rest. The rotation of q and -q is the same, even though the sign of x y z and w is reversed. However, quaternions q and -q cannot be averaged right away and have to have its sign reversed first.

The way to solve it is to take the first quaternion in the array and then taking a dot product with each other quaternion. If the result is negative then the quaternion must have x y z and w reversed before it is averaged with the rest of it.

Like this:
Code (csharp):
1.
2. //Get an average (mean) from more than two quaternions (with two, slerp would be used).
3. //Note: this only works if all the quaternions are relatively close together.
4. //Usage:
5. //-Cumulative is an external Vector4 which holds all the added x y z and w components.
6. //-newRotation is the next rotation to be added to the average pool
7. //-firstRotation is the first quaternion of the array to be averaged
8. //-addAmount holds the total amount of quaternions which are currently added
9. //This function returns the current average quaternion
10. public static Quaternion AverageQuaternion(ref Vector4 cumulative, Quaternion newRotation, Quaternion firstRotation, int addAmount){
11.
12.     float w = 0.0f;
13.     float x = 0.0f;
14.     float y = 0.0f;
15.     float z = 0.0f;
16.
17.     //Before we add the new rotation to the average (mean), we have to check whether the quaternion has to be inverted. Because
18.     //q and -q are the same rotation, but cannot be averaged, we have to make sure they are all the same.
19.     if(!Math3d.AreQuaternionsClose(newRotation, firstRotation)){
20.
21.         newRotation = Math3d.InverseSignQuaternion(newRotation);
22.     }
23.
24.     //Average the values
26.     cumulative.w += newRotation.w;
27.     w = cumulative.w * addDet;
28.     cumulative.x += newRotation.x;
29.     x = cumulative.x * addDet;
30.     cumulative.y += newRotation.y;
31.     y = cumulative.y * addDet;
32.     cumulative.z += newRotation.z;
33.     z = cumulative.z * addDet;
34.
35.     //note: if speed is an issue, you can skip the normalization step
36.     return NormalizeQuaternion(x, y, z, w);
37. }
38.
39. public static Quaternion NormalizeQuaternion(float x, float y, float z, float w){
40.
41.     float lengthD = 1.0f / (w*w + x*x + y*y + z*z);
42.     w *= lengthD;
43.     x *= lengthD;
44.     y *= lengthD;
45.     z *= lengthD;
46.
47.     return new Quaternion(x, y, z, w);
48. }
49.
50. //Changes the sign of the quaternion components. This is not the same as the inverse.
51. public static Quaternion InverseSignQuaternion(Quaternion q){
52.
53.     return new Quaternion(-q.x, -q.y, -q.z, -q.w);
54. }
55.
56. //Returns true if the two input quaternions are close to each other. This can
57. //be used to check whether or not one of two quaternions which are supposed to
58. //be very similar but has its component signs reversed (q has the same rotation as
59. //-q)
60. public static bool AreQuaternionsClose(Quaternion q1, Quaternion q2){
61.
62.     float dot = Quaternion.Dot(q1, q2);
63.
64.     if(dot < 0.0f){
65.
66.         return false;
67.     }
68.
69.     else{
70.
71.         return true;
72.     }
73. }
74.

Last edited: Apr 19, 2015
8. ### konsnos

Joined:
Feb 13, 2012
Posts:
121
Sorry to bump this old thread but it's the only one which had the answer I seek.

Thanks for the solution, but what do you mean by that?

9. ### Elecman

Joined:
May 5, 2011
Posts:
1,374
The rotations from all quaternions should not be too much different.

Let's say you have a bunch of quaternions, each pointing at a random star in the sky. Averaging those will fail. They are too different.

Now imagine you have a bunch of quaternions, each pointing at the same star, but made with different measurement equipment in your yard. Those are probably close enough.

Of course a quaternion doesn't just point at something, but have a rotation as well. Same logic for the rotation part. It shouldn't differ too much.

I used that function to average a bunch of world space pose estimates from an AR marker. The input estimates surely have some jitter, but the average output is accurate.

Some more information and links scientific papers here, if you are interested in averaging totally random quaternions:
http://stackoverflow.com/questions/12374087/average-of-multiple-quaternions

10. ### hgdebarba

Joined:
Mar 12, 2015
Posts:
5

Adding to that, mathematically correct is a quite strong term
but according to this it could yield a close approximation as long as the difference in the angles of rotation is small.
float D = 1.0f / (w*w + x*x + y*y + z*z);
you should do
float D = 1.0f / Mathf.sqrt(w*w + x*x + y*y + z*z);
(but mind that Unity will normalize it for you, so this can be omitted anyway)
AND
you should initialize addedRotation to the first value of the array or set it to 0, 0, 0, 0 (otherwise the identity rotation will be part of your average!)

The code bellow might work better, and q -q should not be a problem (I am not sure about efficiency though)

Code (csharp):
1.
2. // assuming qArray.Length > 1
3. Quaternion AverageQuaternion(Quaternion [] qArray){
4.     Quaternion qAvg = qArray[0];
5.     float weight;
6.     for (int i = 1 ; i < qArray.Length; i++)
7.     {
8.         weight = 1.0f / (float)(i+1);
9.         qAvg = Quaternion.Slerp(qAvg, qArray[i], weight);
10.     }
11.     return qAvg;
12. }
13.

BilalAsadi and Deleted User like this.
11. ### Elecman

Joined:
May 5, 2011
Posts:
1,374
Thanks for the feedback

12. ### tinyant

Joined:
Aug 28, 2015
Posts:
127
how to get the right result quaternion when giving different quaternion with weights?

Q1 0.1
Q2 0.3
Q3 0.4
Q4 0.2

Get the result Q?

13. ### lordofduct

Joined:
Oct 3, 2011
Posts:
8,532
Slerp(identity, q1, w1) * Slerp(idenity, q2, w2) * Slerp(idenity, q3, w3) * Slerp(identity, q4, w4)

...

That'd be my first attempt

koirat likes this.
14. ### Antypodish

Joined:
Apr 29, 2014
Posts:
10,776
Calculating average, not weight based.

This worked for me, based on lordofduct response.

Code (CSharp):
1. Quaternion q_average = Quaternion.identity ;
2.
3. List <Quaternion>  qList = new List <Quaternion> ()
4.
6.
8.
10.
11. // ad some more quaternions
12.
13.
14. float averageWeight = 1f / siblings.Count ;
15.
16. for ( int i = 0; i < qList.Count; i ++ )
17. {
18.     Quaternion q = qList [ i ] ;
19.
20.     // based on [URL='https://forum.unity.com/members/lordofduct.66428/']lordofduct[/URL] response
21.     q_average *= Quaternion.Slerp ( Quaternion.identity, q, f_averageWeight ) ;
22. }
23.
24. // output rotation - attach to some object, or whatever
25. this.transform.rotation = q_average ;
26.

Last edited: Apr 15, 2018

Joined:
May 5, 2011
Posts:
1,374
Thanks!

16. ### Owen-Reynolds

Joined:
Feb 15, 2012
Posts:
1,998
In some cases (when you have one look-from-point and lots of lookAt points) it might be simpler to do whatever math on the points, then find one quaternion. You lose local-z spin, but you probably didn't want it anyway.

17. ### JoshuaMcKenzie

Joined:
Jun 20, 2015
Posts:
916
Chaining Slerps to together isn't quite the proper way to go about it.

I would simply convert the quaternions into look directions (forward and up pairs), add up the directions, normalize them, then convert the result pairs back to a quaternion via LookRotation(). and you got your average (which might be a zero length, but hey that actually can be a valid answer).

Why? Order matters when you add Quaternions together. Unlike normal numbers, Quaternions lack the Communtative property. If you put the same group of Quaternions into that sort of quaternion-only averaging function, with varying orders, you can actually get different averaged quaternion results.

18. ### DavidSWu

Joined:
Jun 20, 2016
Posts:
183
It depends on what you are trying to solve for, but I would expect that you will get a reasonable result by first converting the quaternions into a linear space before applying a weight.
The easiest way to convert to a linear, mostly orthogonal space is to take the derivative.
Try averaging the weighted logarithms, and the use the exponent of the result.
This effectively converts every quaternion into an angular velocity and takes resulting average velocity.
The angular velocities are the velocities that would result in the full rotation after 1s.

Hope that helps.

I can provide code if desired.

David

19. ### Elecman

Joined:
May 5, 2011
Posts:
1,374
Code is always welcome.

DavidSWu likes this.
20. ### DavidSWu

Joined:
Jun 20, 2016
Posts:
183
This has its shortcomings as mentioned, as the problem of averaging rotations is ill posed.
But it will generally work well for rotations that are not too far apart and for rotations that do not have inherent constraints.
I.e. where you want to minimize the average rotation between the result and the inputs.
If you have constraints (like minimize roll) you can do better.

Code (CSharp):
1.
2. [MethodImpl(MethodImplOptions.AggressiveInlining)]
3.         internal static Vector3 ToAngularVelocity( this Quaternion q )
4.         {
5.             if ( abs(q.w) > 1023.5f / 1024.0f)
6.                 return new Vector3();
7.                 var angle = acos( abs(q.w) );
8.                 var gain = Sign(q.w)*2.0f * angle / Sin(angle);
9.
10.             return new Vector3(q.x * gain, q.y * gain, q.z * gain);
11.         }
12.
13.
14.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
15.         internal static Quaternion FromAngularVelocity( this Vector3 w )
16.         {
17.             var mag = w.magnitude;
18.             if (mag <= 0)
19.                 return Quaternion.identity;
20.             var cs = cos(mag * 0.5f);
21.             var siGain = sin(mag * 0.5f) / mag;
22.             return new Quaternion(w.x * siGain, w.y * siGain, w.z * siGain, cs);
23.
24.         }
25.
26.         internal static Quaternion Average(Quaternion[] source)
27.         {
28.             Assert.IsFalse(source.IsNullOrEmpty());
29.             Vector3 result = new Vector3();
30.             foreach (var q in source)
31.             {
32.                 result += q.ToAngularVelocity();
33.             }
34.
35.             return (result / source.Length).FromAngularVelocity();
36.         }
37.
Hope that helps!

David

Joined:
May 5, 2011
Posts:
1,374
Thanks!

22. ### DavidSWu

Joined:
Jun 20, 2016
Posts:
183
Actually you can get a better result if performance is not a concern byt iterating to find the minimum max error:

Code (CSharp):
1.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
2.         internal static Vector3 ToAngularVelocity( this Quaternion q )
3.         {
4.             if ( abs(q.w) > 1023.5f / 1024.0f)
5.                 return new Vector3();
6.                 var angle = acos( abs(q.w) );
7.                 var gain = Sign(q.w)*2.0f * angle / Sin(angle);
8.             return new Vector3(q.x * gain, q.y * gain, q.z * gain);
9.         }
10.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
11.         internal static Quaternion FromAngularVelocity( this Vector3 w )
12.         {
13.             var mag = w.magnitude;
14.             if (mag <= 0)
15.                 return Quaternion.identity;
16.             var cs = cos(mag * 0.5f);
17.             var siGain = sin(mag * 0.5f) / mag;
18.             return new Quaternion(w.x * siGain, w.y * siGain, w.z * siGain, cs);
19.         }
20.         internal static Quaternion Average(this Quaternion refence, Quaternion[] source)
21.         {
22.
23.             var refernceInverse = refence.Inverse();
24.             Assert.IsFalse(source.IsNullOrEmpty());
25.             Vector3 result = new Vector3();
26.             foreach (var q in source)
27.             {
28.                 result += (refernceInverse*q).ToAngularVelocity();
29.             }
30.             return refence*((result / source.Length).FromAngularVelocity());
31.         }
32.          internal static Quaternion Average(Quaternion[] source)
33.         {
34.             Assert.IsFalse(source.IsNullOrEmpty());
35.             Vector3 result = new Vector3();
36.             foreach (var q in source)
37.             {
38.                 result += q.ToAngularVelocity();
39.             }
40.             return (result / source.Length).FromAngularVelocity();
41.         }
42.          internal static Quaternion Average(Quaternion[] source, int iterations)
43.         {
44.             Assert.IsFalse(source.IsNullOrEmpty());
45.             var reference = Quaternion.identity;
46.             for(int i = 0;i < iterations;i++)
47.             {
48.                 reference = Average(reference,source);
49.
50.             }
51.             return reference;
52.
53.         }

Joined:
Aug 11, 2015
Posts:
229
Awesome stuff. Is there any way to add weights to each rotation?

Edit:
Multiplying the ToAngularVelocity() by the weight (0.0 - 1.0) and returning result.FromAngularVelocity() seems to work fine.

Last edited: Apr 19, 2021
24. ### Wiedergaenger

Joined:
Mar 14, 2019
Posts:
2
This is my solution for averaging weighted rotations (quaternions).
Base is a class for weighted rotations with a weight between 0 and 1.
Code (CSharp):
1. using System.Collections;
2. using System.Collections.Generic;
3. using UnityEngine;
4.
5.
6. public class WeightedRotation
7. {
8.     public Quaternion Rotation;
9.     public float RotWeight; // between 0 und 1
10.
11.     public WeightedRotation(Quaternion rotation, float weigth)
12.     {
13.         if (weigth > 1 || weigth < 0)
14.         {
15.             Debug.LogError("weight out of bound");
16.         }
17.         else
18.         {
19.             Rotation = rotation;
20.             RotWeight = weigth;
21.         }
22.     }
23. }
24.     public class AverageRotations : MonoBehaviour
25.     {
26.     public List<WeightedRotation> Rotations;
27.
28.         private Quaternion AverageRotation()
29.         {
30.
31.
32.             if (Rotations.Count > 0)
33.             {
34.                 Quaternion q_average = Rotations[0].Rotation;
35.
36.                 for (int i = 1; i < Rotations.Count; i++)
37.                 {
38.                     float weight = 1f / (i + 1) * Rotations[i].RotWeight;
39.                     q_average = Quaternion.Slerp(q_average, Rotations[i].Rotation, weight);
40.
41.                 }
42.
43.                 Rotations.Clear();
44.                 return q_average;
45.             }
46.             Rotations.Clear();
47.             return transform.rotation;
48.         }
49.     }

25. ### JoshuaMcKenzie

Joined:
Jun 20, 2015
Posts:
916
Like I mentioned before chaining Slerps is not how you average several Quaternions. Slerp is only a valid average between 2 quaternions. Think of it like this.

Whats the average of [1,2,3,4,5]?
(1+2+3+4+5)/5 = 15/5 = 3. The average is 3.
But chaining slerps is basically ((((1+2)/2 + 3)/2 + 4)/2 + 5)/2 = 4.0625. these are not the same! It favors the last elements. The earlier Quaternions become exponentially insignificant the more members you try to calculate this way. It ONLY works if there are two elements! including more will give an incorrectly skewed result.

Plus, due to the dimension of math Quaternions reside in, they lack the commutative property, meaning order matters... quite a lot. Even just adding the 2 same rotations will give completely different answers based on which one is added to which.

Take your code for example. Say you take 3 different rotations, give them whatever weights you like and you attempt to calculate that result. Remember that result. now try the same thing again. take the exact same 3 rotations, give them the exact same 3 weights. but you shuffle the order in the list which you used to calculate them. unless you did something like choose the exact same rotation/weights 3 times the 2nd result will be different from the 1st and will play favorites to the later quaternions more.

To average Quaternions correctly
By far the simplest way that's actually correct is to convert each quaternion into a forward and up look direction. Doing so pulls the Quaternion out of the higher dimension of math it resides in.
- Add all forwards together, add all ups together. You can even include your weights if you wanted its trivial for vectors.
- Then normalize the forwardSum, normalize the upSum.
- create a new quaternion using that normalizedForwardSum and normalizedUpSum.

That quaternion is the answer, its the proper averaged quaternion out of all quaternions, weighted or not, and regardless of order, that you've included.

halley and orionsyndrome like this.
26. ### Bunny83

Joined:
Oct 18, 2010
Posts:
4,000
Well you missed that they used a weight of
``1f / (i + 1)``
. So your example is wrong. The chaining is NOT
``((((1+2)/2 + 3)/2 + 4)/2 + 5)/2``
but the weight gets progressively smaller and sticks closer to the average value. So the first step would do
``(1*(1/2) + 2*(1/2))``
. The second step would take the result of the first and does this:
``((1*(1/2) + 2*(1/2))*(2/3) + 3*(1/3)) ``
the next step is
``(((1*(1/2) + 2*(1/2))*(2/3) + 3*(1/3))*(3/4) + 4*(1/4)) ``
and the last one
``(((1*(1/2) + 2*(1/2))*(2/3) + 3*(1/3))*(3/4) + 4*(1/4))*(4/5) + 5*(1/5) ``
which gives you the value 3 as well.

So the lerp / slerp is not used to simply pick the midpoint but a weighted factor so it adds up correctly.

Though while this does work mathematically, I'm sure it's not that numerically stable and also not terribly efficient either.

27. ### JoshuaMcKenzie

Joined:
Jun 20, 2015
Posts:
916
It may have been a poor example, but my main point actually still stands. To be clear, I didn't have a problem with the pre-weights, I have a problem with the slerps. Even when taking the
``1f / (i + 1)``
pre-weights into account, it doesn't fix the pitfalls Quaternion's Slerp itself is susceptible to.

Yes, the pre weights correct the typical imbalance for a set of numbers chained this way, and generally it should give an accurate average. Of the formulas posted on this thread that try to solve using a Slerp-chaining technique its among the most accurate implementations.

but it's not 100% accurate. It doesn't solve the other problems inherent specifically to Quaternions. Using this formula I can still easily think up sets of Quaternions which would calculate to a completely incorrect average, where a correct average is still possible to acquire.

Lets say you have 3 quaternions A,B,and C. Each with forward vectors Vector3.Left, Vector3.Right, and Vector3.Down. What is the Quaternion result D of Slerp(A,B)? Where should the result point to? Its basically undefined since Slerp can't give a valid answer between rotations that are polar opposites. and now what happens if you try to find Quaternion E from Slerp(D,C)? you end up with an answer that can't be guaranteed to be correct.

However if you take those same 3 rotations (A,B,C) and change the order in which you slerp them:
D = Slerp(A,C)
E = Slerp(D,B)
you won't get undefined answers. it'll even be the correct average if you include on the pre-weights. But this presents a problem. This means the formula now has to validate each slerp and compensate when it returns undefines. suddenly the formula is no longer so simple if it wants to be consistently correct.

This is why I push for calculating via look directions so much, it bypasses this issue entirely, its conceptually intuitive to follow the logic, and doesn't require any pre-weighting.

28. ### lordofduct

Joined:
Oct 3, 2011
Posts:
8,532
Looks like I really should have not shot in the dark 5 years ago.

Kurt-Dekker likes this.
29. ### Rumbleball

Joined:
Feb 25, 2022
Posts:
6
Put the code of @Elecman into a out of the box usable class

Code (CSharp):
1. public static partial class Math
2.     {
3.         public static Quaternion AverageQuaternion(Quaternion[] quats)
4.         {
5.             if(quats.Length == 0)
6.             {
7.                 return Quaternion.identity;
8.             }
9.
10.             Vector4 cumulative = new Vector4(0, 0, 0, 0);
11.
12.       foreach(Quaternion quat in quats)
13.             {
14.                 AverageQuaternion_Internal(ref cumulative, quat, quats[0]);
15.             }
16.
17.             float addDet = 1f / (float)quats.Length;
18.             float x = cumulative.x * addDet;
19.             float y = cumulative.y * addDet;
20.             float z = cumulative.z * addDet;
21.             float w = cumulative.w * addDet;
22.             //note: if speed is an issue, you can skip the normalization step
23.             return NormalizeQuaternion(new Quaternion(x,y,z,w));
24.         }
25.
26.         //Get an average (mean) from more then two quaternions (with two, slerp would be used).
27.         //Note: this only works if all the quaternions are relatively close together.
28.         //Usage:
29.         //-Cumulative is an external Vector4 which holds all the added x y z and w components.
30.         //-newRotation is the next rotation to be added to the average pool
31.         //-firstRotation is the first quaternion of the array to be averaged
32.         //-addAmount holds the total amount of quaternions which are currently added
33.         static void AverageQuaternion_Internal(ref Vector4 cumulative, Quaternion newRotation, Quaternion firstRotation)
34.         {
35.             //Before we add the new rotation to the average (mean), we have to check whether the quaternion has to be inverted. Because
36.             //q and -q are the same rotation, but cannot be averaged, we have to make sure they are all the same.
37.             if (!AreQuaternionsClose(newRotation, firstRotation))
38.             {
39.                 newRotation = InverseSignQuaternion(newRotation);
40.             }
41.
42.             //Average the values
43.             cumulative.w += newRotation.w;
44.             cumulative.x += newRotation.x;
45.             cumulative.y += newRotation.y;
46.             cumulative.z += newRotation.z;
47.         }
48.
49.         public static Quaternion NormalizeQuaternion(Quaternion quat)
50.         {
51.             float lengthD = 1.0f / Mathf.Sqrt(quat.w * quat.w + quat.x * quat.x + quat.y * quat.y + quat.z * quat.z);
52.             quat.x *= lengthD;
53.             quat.y *= lengthD;
54.             quat.z *= lengthD;
55.             quat.w *= lengthD;
56.             return quat;
57.         }
58.
59.         //Changes the sign of the quaternion components. This is not the same as the inverse.
60.         public static Quaternion InverseSignQuaternion(Quaternion q)
61.         {
62.             return new Quaternion(-q.x, -q.y, -q.z, -q.w);
63.         }
64.
65.         //Returns true if the two input quaternions are close to each other. This can
66.         //be used to check whether or not one of two quaternions which are supposed to
67.         //be very similar but has its component signs reversed (q has the same rotation as
68.         //-q)
69.         public static bool AreQuaternionsClose(Quaternion q1, Quaternion q2)
70.         {
71.             float dot = Quaternion.Dot(q1, q2);
72.
73.             if (dot < 0.0f)
74.             {
75.                 return false;
76.             }
77.
78.             else
79.             {
80.                 return true;
81.             }
82.         }
83.     }

Elecman likes this.
30. ### CCrowell

Joined:
Mar 31, 2022
Posts:
9
I actually considered this approach before I came here and the problem is if you average two quaternions that are exact opposites of eacth other you get zero for a look vector and zero for an up vector. But in most other cases I think it would work fine? I'm sure you would have to normalized the averaged forward and up vectors. I don't know if you can use a direction vector who's magnitude is not 1 for defining quaternions. In truth, I have never tried.

31. ### halley

Joined:
Aug 26, 2013
Posts:
2,443
So, I finally had a need for the weighted average of quaternions, and of the three major approaches shown in this thread, I have to say that @JoshuaMcKenzie 's approach was the simplest to implement, and the most predictable.

The white ball gizmos are denoting the weights that I am applying to each pink monkey target. The weights sum up to 1. (Thanks to @Kybernetik for his implementation of this polar blending tree in Animancer, I am not going to publish my adaptation of that routine.)

I then orient the golden monkey to match the weighted average of the pink monkey targets' rotations.

In this snippet, you can see the kind of instability that @CCrowell just mentioned; when the golden monkey moves right behind the center target monkey, there's a little bit of chunkiness, because the farther monkeys are rotated diametrically opposite the center one. In most cases, this won't be a problem, and can be overcome with some careful avoidance of the deadzone.

Code (CSharp):
1.
2.         Quaternion WeightedAverageQuaternions(Quaternion[] quaternions, float[] weights)
3.         {
4.             Assert.IsTrue(quaternions != null && quaternions.Length > 0);
5.             Assert.IsTrue(weights != null && weights.Length >= quaternions.Length);
6.
7.             int count = quaternions.Length;
8.             Vector3 forwardSum = Vector3.zero;
9.             Vector3 upwardSum = Vector3.zero;
10.             for (int i = 0; i < count; i++)
11.             {
12.                 forwardSum += weights[i] * (quaternions[i] * Vector3.forward);
13.                 upwardSum += weights[i] * (quaternions[i] * Vector3.up);
14.             }
15.             forwardSum /= (float)count;
16.             upwardSum /= (float)count;
17.
18.             return Quaternion.LookRotation(forwardSum, upwardSum);
19.         }

_geo__ and Chubzdoomer like this.