Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Quaternion Lerp and Gimbal Lock(?)

Discussion in 'Scripting' started by djrazorburn, Sep 25, 2021.

  1. djrazorburn

    djrazorburn

    Joined:
    Sep 25, 2021
    Posts:
    16
    Hi, I'm having some trouble with quaternion lerp.

    I'm simply drawing 2 rotation gizmos, which the user can rotate, and then drawing the resulting rotation in the middle.

    I want the interpolated rotation to always be half way between the two (so t = 0.5). Here's the code:

    _interpolatedRot = Quaternion.Lerp(_fromRot, _toRot, 0.5f);


    i1.PNG

    i2.png

    i3.png

    When an axis from _fromRot lines up with one from _toRot, it gimbal locks and the interpolated rotation flips. I think understand why this happens (although I'm not sure why people say quaternion lerp doesn't have this problem, maybe I'm misunderstanding some terminology).

    I've tried doing the rotations with quaternions, euler angles, rotation towards, LerpAngle and everything in between, but I just can't seem to get it to work. I think the problem lies in knowing which direction the handle was rotated and rotating about an axis, but I just can't seem to make it work. Can someone help explain how to get this right?

    Code (CSharp):
    1.        
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public class GizmoRenderer : EditorWindow
    8. {
    9.     static float _handleSize = 1.0f;
    10.     static float _t = 0.5f;
    11.  
    12.     static Vector3 _fromPos = new Vector3(-5.0f, 0.0f, 0.0f);
    13.     static Quaternion _fromRot = Quaternion.identity;
    14.  
    15.     static Vector3 _toPos = new Vector3(5.0f, 0.0f, 0.0f);
    16.     static Quaternion _toRot = Quaternion.identity;
    17.  
    18.     static Vector3 _interpolatedPos = new Vector3(0.0f, 0.0f, 0.0f);
    19.     static Quaternion _interpolatedRot = Quaternion.identity;
    20.  
    21.     [UnityEditor.Callbacks.DidReloadScripts]
    22.     static GizmoRenderer()
    23.     {
    24. #if UNITY_2019_1_OR_NEWER
    25.         SceneView.duringSceneGui -= OnScene;
    26.         SceneView.duringSceneGui += OnScene;
    27. #else
    28.         SceneView.onSceneGUIDelegate -= OnScene;
    29.         SceneView.onSceneGUIDelegate += OnScene;
    30. #endif
    31.     }
    32.  
    33.     [MenuItem("Tools/Gizmo Renderer")]
    34.     public static void ShowWindow()
    35.     {
    36.         GizmoRenderer window = (GizmoRenderer)EditorWindow.GetWindow(typeof(GizmoRenderer));
    37.         window.titleContent = new GUIContent("Gizmo Renderer");
    38.         window.Show();
    39.     }
    40.  
    41.     //------------------------------------------------------
    42.     private static void OnScene(SceneView sceneView)
    43.     {
    44.         _fromRot = RotationHandle(_fromPos, _fromRot, _handleSize);
    45.         DrawAxis(_fromPos, _fromRot, _handleSize); // Just debug, helps vis orientation
    46.  
    47.         _toRot = RotationHandle(_toPos, _toRot, _handleSize);
    48.         DrawAxis(_toPos, _toRot, _handleSize); // Just debug, helps vis orientation
    49.  
    50.         // Interpolate
    51.  
    52.         _interpolatedRot = Quaternion.Lerp(_fromRot, _toRot, 0.5f);    // ** Requirement ** t must remain constant
    53.         DrawAxis(_interpolatedPos, _interpolatedRot, _handleSize);
    54.     }
    55.  
    56.     //--------------------------------------------------------------
    57.     public static void DrawAxis(Vector3 pos, Quaternion rot, float handleSize)
    58.     {
    59.         Color prevColor = Handles.color;
    60.         Handles.color = Color.green;
    61.         Handles.ArrowHandleCap(0, pos, Quaternion.LookRotation(rot * Vector3.up), handleSize, EventType.Repaint);
    62.         Handles.color = Color.red;
    63.         Handles.ArrowHandleCap(0, pos, Quaternion.LookRotation(rot * Vector3.right), handleSize, EventType.Repaint);
    64.         Handles.color = Color.blue;
    65.         Handles.ArrowHandleCap(0, pos, Quaternion.LookRotation(rot * Vector3.forward), handleSize, EventType.Repaint);
    66.         Handles.color = prevColor;
    67.     }
    68.  
    69.     //--------------------------------------------------------------
    70.     public static Quaternion RotationHandle(Vector3 position, Quaternion rotation, float handleSize)
    71.     {
    72.         Color color = Handles.color;
    73.         Handles.color = Handles.xAxisColor;
    74.         rotation = Handles.Disc(rotation, position, rotation * Vector3.right, handleSize, true, EditorPrefs.GetFloat("RotationSnap"));
    75.         Handles.color = Handles.yAxisColor;
    76.         rotation = Handles.Disc(rotation, position, rotation * Vector3.up, handleSize, true, EditorPrefs.GetFloat("RotationSnap"));
    77.         Handles.color = Handles.zAxisColor;
    78.         rotation = Handles.Disc(rotation, position, rotation * Vector3.forward, handleSize, true, EditorPrefs.GetFloat("RotationSnap"));
    79.         Handles.color = Handles.centerColor;
    80.         rotation = Handles.Disc(rotation, position, Camera.current.transform.forward, handleSize * 1.1f, false, 0.0f);
    81.  
    82.         rotation = Handles.FreeRotateHandle(rotation, position, handleSize);
    83.         Handles.color = color;
    84.  
    85.         return rotation;
    86.     }
    87.  
    88.     //------------------------------------------------------
    89.     void OnGUI()
    90.     {
    91.         EditorGUI.BeginChangeCheck();
    92.         _t = EditorGUILayout.Slider("t", _t, 0.0f, 1.0f);
    93.         _handleSize = EditorGUILayout.Slider("Handle Size", _handleSize, 1.0f, 10.0f);
    94.  
    95.         if (EditorGUI.EndChangeCheck())
    96.             SceneView.RepaintAll();
    97.     }
    98. }
    99.  
    100.  
    101.  
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,889
    Try
    Code (CSharp):
    1. _interpolatedRot = Quaternion.Slerp(_fromRot, _toRot, 0.5f);
     
  3. djrazorburn

    djrazorburn

    Joined:
    Sep 25, 2021
    Posts:
    16
    Unfortunately Slerp has the same effect
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,915
    I'm not quite sure what your question is. A quaternion essentially has a max angle of 180°. So your rotation looks right. There can not be any kind of gimbal lock with pure quaternions since quaterions do not have any gimbals. You would only introduce such issues when you use euler angles at some point. Euler angles fundamentally represent 3 gimbals. Quaternions define a 3d rotation as one single rotation around a single axis.

    It's possible to do a slerp the "long way" when you slerp manually because Unity's Slerp will always rotate the shortest path. See my implementation over here.
     
    djrazorburn likes this.
  5. djrazorburn

    djrazorburn

    Joined:
    Sep 25, 2021
    Posts:
    16
    I agree, the rotations are right, just not what I wanted. The behavior I wanted is what you pointed me to, god bless you sir