Search Unity

Converting Rotation into Another Coordinate System

Discussion in 'Editor & General Support' started by Cygon4, Nov 27, 2017.

  1. Cygon4

    Cygon4

    Joined:
    Sep 17, 2012
    Posts:
    382
    Hi!

    I've created a mouse look controller that rotates only my character's head. When the head is turned 90 degrees and the player tries to rotate further, the remaining rotation is applied by the body instead.

    This is trivial if I assume that the head is always in the same coordinate system as the body:
    straight.png
    However, my character can do funny stuff. For example, he could bow down:

    bent.png

    This rotates the head's coordinate system by 90 degrees. So when the head hits its limits, this is now a Z rotation for the body instead of a Y rotation.

    I can't for the live of me get the rotation translated from the head's local coordinate system into the character's coordinate system. Here's what I've tried:

    Attempt 1: Global Rotation = Chest Rotation * Head Rotation?

    Code (CSharp):
    1. /// <summary>
    2. ///   Called when the player has tried to rotate the head farther than
    3. ///   its movement range allows
    4. /// </summary>
    5. /// <param name="excessRotation">Excess rotation that was clamped off</param>
    6. protected virtual void OnExcessHeadRotation(Quaternion excessRotation) {
    7.   if(this.ApplyExcessHeadMovementToBody) {
    8.     updateLookDirection();
    9.  
    10.     // Translate head rotation into global coordinate system
    11.     Quaternion globalRotation = this.headController.HeadBaseOrientation * excessRotation;
    12.  
    13.     Debug.Log(
    14.       "Base: " + this.headController.HeadBaseOrientation.eulerAngles +
    15.       " | Local excess: " + excessRotation.eulerAngles +
    16.       " | Global excess: " + globalRotation.eulerAngles
    17.     );
    18.   }
    19. }
    Guess what the output is?

    Base: (89.1, 0.0, 0.0) | Local excess: (0.0, 0.3, 0.0) | Global excess: (89.0, 17.7, 17.7)

    So taking a rotation of 0.3 degrees on the Y axis and rotating it around 89.1 degrees on the X axis turns it into a 17.7 degree rotation still on the Y axis? WTF?

    Attempt 2: Global Rotation = Inv(Chest Rotation) * Head Rotation?

    Base: (89.9, 0.0, 0.0) | Local excess: (0.0, 0.1, 0.0) | Global excess: (270.1, 45.5, 314.5)

    Oh hey, now 0.1 degrees on the Y axis rotated by -90 degrees becomes 45.5 still on the Y axis? WTF^2?

    Attempt 3: Global Rotation = Head Rotation * Chest Rotation?

    Quaternion rotations are not commutative (of course), so flipping the operands gives what?

    Base: (89.6, 180.0, 180.0) | Local excess: (0.0, 0.1, 0.0) | Global excess: (89.6, 180.1, 180.0)

    Oh look, now the 0.1 degree rotation becomes a backflip.

    Expected result

    The expected outcome would be:

    Base: (90.0, 0.0, 0.0) | Local excess: (0.0, 0.123, 0.0) | Global excess: (0.0, 0.0, 0.123)

    (rotation around Y simply becomes a rotation around Z when the coordinate system is rotated by 90 degrees on the X axis)

    What am I doing wrong?
     
  2. Cygon4

    Cygon4

    Joined:
    Sep 17, 2012
    Posts:
    382
    On second thought, maybe debugging the resulting rotations by looking at the euler angles is not wise?

    The following code achieves what I want. However, I would greatly prefer a pure quaternion solution, thus, I shall call it the barf solution for now:

    Code (CSharp):
    1. /// <summary>
    2. ///   Called when the player has tried to rotate the head farther than
    3. ///   its movement range allows
    4. /// </summary>
    5. /// <param name="excessRotation">Excess rotation that was clamped off</param>
    6. protected virtual void OnExcessHeadRotation(Quaternion excessRotation) {
    7.   if(!this.ApplyExcessHeadMovementToBody) {
    8.     return;
    9.   }
    10.  
    11.   updateLookDirection();
    12.  
    13.   // Calculate the clamped and unclamped head orientations in actor space
    14.   Quaternion clampedHeadOrientation, unclampedHeadOrientation;
    15.   {
    16.     Quaternion inverseActorRotation = Quaternion.Inverse(transform.rotation);
    17.     Quaternion baseOrientation = this.headController.HeadBaseOrientation;
    18.  
    19.     // Translate the current head orientation into the actor's coordinate system
    20.     clampedHeadOrientation = inverseActorRotation * (
    21.       baseOrientation * this.headController.LocalHeadOrientation
    22.     );
    23.  
    24.     // Get the unclamped head orientation in the head's local coordinate system
    25.     Quaternion localUnclampedHeadOrientation;
    26.     {
    27.       Vector3 unclampedHeadEulers = (
    28.         this.headController.LocalHeadOrientation.eulerAngles + excessRotation.eulerAngles
    29.       );
    30.  
    31.       localUnclampedHeadOrientation = (
    32.         Quaternion.AngleAxis(unclampedHeadEulers.y, Vector3.up) *
    33.         Quaternion.AngleAxis(unclampedHeadEulers.x, Vector3.right)
    34.       );
    35.     }
    36.  
    37.     // Translate the unclamped head orientation into the actor's coordinate system
    38.     unclampedHeadOrientation = inverseActorRotation * (
    39.       baseOrientation * localUnclampedHeadOrientation
    40.     );
    41.   }
    42.  
    43.   // And finally, we rotate the actor around its own Y axis by the amount of
    44.   // clamped-off head rotation that is present in the actor's coordinate system.
    45.   float yawRotation = (
    46.     unclampedHeadOrientation.eulerAngles.y - clampedHeadOrientation.eulerAngles.y
    47.   );
    48.   transform.Rotate(transform.up, yawRotation);
    49. }