Search Unity

Is there a conversion method from quaternion to euler?

Discussion in 'Entity Component System' started by xVergilx, Feb 4, 2019.

  1. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Title. I can't find it in math / quaternion. Just making sure that there isn't one that I've missed.

    Edit: This is what I've came up with in a meantime:
    Code (CSharp):
    1.         /// <summary>
    2.         /// Converts quaternion representation to euler
    3.         /// </summary>
    4.         public static float3 ToEuler(this quaternion quaternion) {
    5.             float4 q = quaternion.value;
    6.             double3 res;
    7.  
    8.             double sinr_cosp = +2.0 * (q.w * q.x + q.y * q.z);
    9.             double cosr_cosp = +1.0 - 2.0 * (q.x * q.x + q.y * q.y);
    10.             res.x = math.atan2(sinr_cosp, cosr_cosp);
    11.  
    12.             double sinp = +2.0 * (q.w * q.y - q.z * q.x);
    13.             if (math.abs(sinp) >= 1) {
    14.                 res.y = math.PI / 2 * math.sign(sinp);
    15.             } else {
    16.                 res.y = math.asin(sinp);
    17.             }
    18.  
    19.             double siny_cosp = +2.0 * (q.w * q.z + q.x * q.y);
    20.             double cosy_cosp = +1.0 - 2.0 * (q.y * q.y + q.z * q.z);
    21.             res.z = math.atan2(siny_cosp, cosy_cosp);
    22.  
    23.             return (float3) res;
    24.         }
     
    Last edited: Feb 4, 2019
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    It's an interesting question. I suspect it does not exist because there's not a lot of mathematical reason to convert a quaternion to euler. However, there's definitely a place for it for debugging or feedback so it does seem like something that should be included.
     
    xVergilx likes this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Making a custom editor in quaternions is pretty pointless. Unless designers would want to learn quaternions all the sudden.
     
    Mikael-H likes this.
  4. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Just spent an hour looking for the equivalent of Quaternion.eulerAngles in the Mathematics source. I thought it surely must be in there (guess not...). There are many uses for euler representation. Hope this will be added in the Mathematics repo, saving future noobs like me the time looking for it.
     
    jmcusack likes this.
  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Editing data should store the data in float3 eulers.

    And during the conversion pipeline it should be converted to quaternions. I am not sure what the use case for converting from quaterion -> euler would be?
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Debugging and editors
     
  7. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Yes, mostly in the presentation layer. But I also have couple more reasons in my case.

    1) I have to work with some existing math code that expects euler angles.
    2) Our particular network game uses FixedPoint representation of euler angles for compression and determinism.

    @Joachim_Ante We are actually not talking about the new conversion pipeline. I think you guys are working very hard on it right now and it's occupying a lot of your mind. We are just saying in general the mathematics library is missing a quaterion-to-euler method.
     
    jmcusack and xVergilx like this.
  8. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    There are very practical reasons gotcha's aside. It's a common space efficiency trick in networking to send just one axis of a rotation.
     
  9. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    You can work directly in euler representation with RotationEulerXYZ (or whatever rotation order you need). See the Transform documentation.
     
    SubPixelPerfect and Flipps like this.
  10. kro11

    kro11

    Joined:
    Sep 23, 2019
    Posts:
    105
    How to recreate this with quaternions?

    Code (CSharp):
    1. int max = spawnerData.Spread / 2;
    2.                         int min = -max;
    3.                         Vector3 tempRot = rotation;
    4.  
    5.                         int index = 0;
    6.                         for (int x = min; x < max; x++)
    7.                         {
    8.                             tempRot.x = (rotation.x + 5 * x) % 360;
    9.                             for (int y = min; y < max; y++)
    10.                             {
    11.                                 tempRot.y = (rotation.y + 5 * y) % 360;                          
    12.                                 EntityManager.SetComponentData(projectiles[index], new Translation { Value = newTranslation });
    13.                                 EntityManager.SetComponentData(projectiles[index], new Rotation { Value = Quaternion.Euler(tempRot) });
    14.                                 EntityManager.SetComponentData(projectiles[index], new MoveForward { Speed = 5 });
    15.                                 index++;
    16.                             }
    17.                         }
    I have quaternion only from entity, or do I need to convert it using the first post?
    Code (CSharp):
    1. float3 tempRotAngles = new float3(newRotation.value.x, newRotation.value.y, newRotation.value.z);
    2.                         Debug.Log(newRotation);
    3.  
    4.                         int index = 0;
    5.                         for (int x = min; x < max; x++)
    6.                         {
    7.                             tempRotAngles.x = (tempRot.value.x + 5 * x) % 360;
    8.                             for (int y = min; y < max; y++)
    9.                             {
    10.                                 tempRotAngles.y = (tempRot.value.y + 5 * y) % 360;
    11.                                 EntityManager.SetComponentData(projectiles[index], new Translation { Value = newTranslation });
    12.                                 EntityManager.SetComponentData(projectiles[index], new Rotation { Value = Quaternion.Euler(tempRotAngles) });
    13.                                 EntityManager.SetComponentData(projectiles[index], new MoveForward { Speed = 5 });
    14.                                 index++;
    15.                             }
    16.                         }
    I tried so, but it does not respond to rotations.
     
  11. kro11

    kro11

    Joined:
    Sep 23, 2019
    Posts:
    105
    If I add ref RotationEulerXYZ to Entities.ForEach() loop in ComponentSystem then loop stops working. Am I doing something wrong?
     
  12. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I recently encountered a use case where I'm pretty sure I'd need this. I'm making an orbit camera that has limit angles of how much it can go up and down (the angles are user-defined). The camera can also have an arbitrary "reference plane" and its limit angles must be relative to that plane

    I'd need a system that gets the euler angles of the camera's rotation quaternion relatively to the reference plane's rotation, clamps its angles to the limits relatively to the reference plane, and then reconverts it to quaternion.

    I need to do this clamping from the camera's quaternion, instead of working in euler from the start and converting to quaternion at the end, because there are all sorts of rotation operations that are hell to do in euler and easy to do in quaternions (LookAts, Transformations, etc...). The camera might have to frame/lookat things cinematically and interpolate its rotation between different poses, and this is something we'd do in quaternions. I also want to let the users have to freedom of modifying the camera's quaternion directly if that's what they prefer, and just clamp it at the end of all rotation modifications. Bottom line: we want to be able to work with quaternions!

    Also, I couldn't find a math.AnglesBetween(v1, v2) either. This is another commonly-needed operation.

    Overall, there's a bunch of things I wish were added to the math lib. Here are a few (very untested) examples that I need in almost every project:
    Code (CSharp):
    1. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    2.     public static float Angle(float3 from, float3 to)
    3.     {
    4.         return math.degrees(math.acos(math.dot(math.normalize(from), math.normalize(to))));
    5.     }
    6.  
    7.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    8.     public static quaternion FromToRotation(quaternion from, quaternion to)
    9.     {
    10.         return math.mul(math.inverse(from), to);
    11.     }
    12.  
    13.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    14.     public static float AngleSigned(float3 from, float3 to)
    15.     {
    16.         float angle = math.acos(math.dot(math.normalize(from), math.normalize(to)));
    17.         float3 cross = math.cross(from, to);
    18.         angle *= math.sign(math.dot(math.up(), cross));
    19.         return math.degrees(angle);
    20.     }
    21.  
    22.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    23.     public static float3 ProjectOnPlane(float3 vector, float3 onPlane)
    24.     {
    25.         float3 orthogonalComponent = onPlane * math.dot(vector, onPlane);
    26.         return vector - orthogonalComponent;
    27.     }
    28.  
    29.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    30.     public static float3 ProjectOnNormal(float3 vector, float3 onNormal)
    31.     {
    32.         return onNormal * math.dot(vector, onNormal);
    33.     }
    34.  
    35.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    36.     public static float3 ClampMagnitude(float3 vector, float magnitude)
    37.     {
    38.         float lengthScale = math.length(vector) / magnitude;
    39.         if (lengthScale > 1f)
    40.         {
    41.             vector = vector * (1f / lengthScale);
    42.         }
    43.         return vector;
    44.     }

    __________________________

    I think the alternative would be to take the desired angle limits and convert them to what they would represent in terms of a dot product between two vectors. And then calculate an interpolant that would represent what ratio of the current rotation would represent the clamped rotation, and finally do a myCamRotation = quanterion.Slerp(referencePlaneRotAllignedWithCurrentCamFwd, currentRot, interpolant)

    This would work, but... it seems like a lot of trouble compared to just being able to get angles from a quaternion. And it's not a solution that would seem intuitive to a lot of people. It seems to me like adding a quaternion.ToEuler() to Unity.Mathematics would just be a very useful thing to tons of people
     
    Last edited: Nov 8, 2019
  13. frimarichard

    frimarichard

    Joined:
    Jul 24, 2017
    Posts:
    31
    I just wanted to add that I'm in the exact same boat as Phil, as I require clamping rotation between two values. Any QoL function to achieve this would be fantastic.
     
    deus0 likes this.
  14. danzmarcel

    danzmarcel

    Joined:
    Apr 20, 2017
    Posts:
    1
    I have another use case. I simply want to rotate a cube. the entity has a Rotation component. How can I do this without converting it to euler, modify and convert back from euler?
     
  15. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
  16. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I was multiplying a Vector3 by a Quaternion last week for setting rotations on a medical viz project last week and to my surprise, it worked.
    Code (CSharp):
    1. Plane anteriorCutPlane = new Plane ();
    2. Vector3 newNormalRotation = currImplant.transform.localEulerAngles;
    3. anteriorCutPlane.SetNormalAndPosition (Quaternion.Euler (newNormalRotation) * implantAnteriorCutVector, implantAnteriorCutPoint);
     
  17. ethancorssel

    ethancorssel

    Joined:
    Apr 25, 2016
    Posts:
    6
    My use case is that I want to know the angle between an NPC and the player, to know if the NPC is seeing the front, the left or right side, or the back of the player.

    The way I approach this in MonoBehavior is a simple division between these two angles (in euler), and pass the angle between a loop which triggered the pose between 8 possible angles (360/8).

    Again, all this is made using the euler angles of the NPC and the player, and couldn't implement this in the DOTS way, yet.
     
  18. SebastianAaltonen

    SebastianAaltonen

    Unity Technologies

    Joined:
    Feb 21, 2020
    Posts:
    112
    Quaternion can't represent all euler angles. For example if you rotate by 600 degrees (which is more than one full turn), the quaternion will not be able to represent this. Also if your artist uses values such as -90 degrees, and you convert that to quaternion and back, you might end up getting back 270 degrees instead of -90 (depends on the conversion). Also rounding errors will make even euler numbers such as 50 to messy values such as 49.999 after converting to quaternion and back. Artists don't like issues like this. Better to keep the authoring representation in euler angles and runtime in quaternions, and never convert from runtime representation back to authoring representation (except for debugging purposes of course).
     
  19. Karearea

    Karearea

    Joined:
    Sep 3, 2012
    Posts:
    386
    I have a use case with an aircraft autopilot. I know the target bank angles I'd like, but can't get the current local euler z value from which to get the error to send to the PID controller. Have some checks around heading straight up or down so not worried about gimbal locking issues.

    I've tried other toEuler code I've found but suspect the order is off. Substituting the old Quaternion conversion works fine, so I'll add a polite request towards adding a quaternion.ToEuler() to Unity.Mathematics.
     
  20. Jawsarn

    Jawsarn

    Joined:
    Jan 12, 2017
    Posts:
    245
    Generally sure, but there are cases e.g. Input where you want to keep track on euler angles, e.g. see your own DOTSSample.
     
    Neiist, deus0, jdtec and 4 others like this.
  21. Timboc

    Timboc

    Joined:
    Jun 22, 2015
    Posts:
    238
    Very much agree with this and I can see the issue of providing a toEuler method people may abuse. However, adding another use-case to the table: making a tween library that supports tweening e.g. independent axes of a Rotation (ICD). i.e. the tween library can't arbitrarily replace the user's chosen component with RotationEulerXYZ, yet as the tween works with eulers ( for >180 etc) it has to decompose quaternion -> euler.
    Here is the code I currently use (modified from https://stackoverflow.com/questions...tween-euler-quaternion-like-in-unity3d-engine):
    Code (CSharp):
    1.  
    2. public static float3 GetQuaternionEulerAngles(quaternion rot)
    3. {
    4.     float4 q1 = rot.value;
    5.     float sqw = q1.w * q1.w;
    6.     float sqx = q1.x * q1.x;
    7.     float sqy = q1.y * q1.y;
    8.     float sqz = q1.z * q1.z;
    9.     float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
    10.     float test = q1.x * q1.w - q1.y * q1.z;
    11.     float3 v;
    12.  
    13.     if (test > 0.4995f * unit)
    14.     { // singularity at north pole
    15.         v.y = 2f * math.atan2(q1.y, q1.x);
    16.         v.x = math.PI / 2f;
    17.         v.z = 0;
    18.         return NormalizeAngles(v);
    19.     }
    20.     if (test < -0.4995f * unit)
    21.     { // singularity at south pole
    22.         v.y = -2f * math.atan2(q1.y, q1.x);
    23.         v.x = -math.PI / 2;
    24.         v.z = 0;
    25.         return NormalizeAngles(v);
    26.     }
    27.  
    28.     rot = new quaternion(q1.w, q1.z, q1.x, q1.y);
    29.     v.y = math.atan2(2f * rot.value.x * rot.value.w + 2f * rot.value.y * rot.value.z, 1 - 2f * (rot.value.z * rot.value.z + rot.value.w * rot.value.w));     // Yaw
    30.     v.x = math.asin(2f * (rot.value.x * rot.value.z - rot.value.w * rot.value.y));                             // Pitch
    31.     v.z = math.atan2(2f * rot.value.x * rot.value.y + 2f * rot.value.z * rot.value.w, 1 - 2f * (rot.value.y * rot.value.y + rot.value.z * rot.value.z));      // Roll
    32.     return NormalizeAngles(v);
    33. }
    34.  
    35. static float3 NormalizeAngles(float3 angles)
    36. {
    37.     angles.x = NormalizeAngle(angles.x);
    38.     angles.y = NormalizeAngle(angles.y);
    39.     angles.z = NormalizeAngle(angles.z);
    40.     return angles;
    41. }
    42.  
    43. static float NormalizeAngle(float angle)
    44. {
    45.     while (angle > math.PI * 2f)
    46.         angle -= math.PI * 2f;
    47.     while (angle < 0)
    48.         angle += math.PI * 2f;
    49.     return angle;
    50. }
     
    Last edited: May 5, 2020
  22. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Another use case:

    Calculating the Angular Velocity (euler) that would bring an object from its current rotation (quaternion) to a target rotation over a time t. Imagine you want a rigidbody to reach a certain rotation, but with velocity/torque instead of by modifying its transform.

    I can't implement moving platforms in my Character Controller project without this. And if I'm not mistaken, this would also be required for implementing active ragdolls (physics animation)

    Thankfully, the DOTS physics team are adding it to the physics maths utils soon, but I feel like this really belongs in the basic math library. Plenty of people seem to be running into roadblocks due to the lack of QuaternionToEuler. And I think the "Estimate" in the function name does a good job of informing the user that it's not 100% accurate
     
    Last edited: May 5, 2020
  23. JakeThinkHQ

    JakeThinkHQ

    Joined:
    May 12, 2020
    Posts:
    1
    Having the same issue! What I've run into may be another use case?

    A box gets it's rotation value from a math.slerp method, interpolating between it's current rotation and a target rotation within a Job. Target rotation is a Quaternion that's created from a Vector3.

    I want to create another Job that checks if the Box's X-axis rotation passes 0 and goes into negative. For this I have to use the current box rotation not the target rotation as it might not have reached it yet.
     
  24. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    The camera use case that @PhilSA is exactly the thing I am trying to solve at the moment. Has there been any discussion in Unity internally for ToEuler be added to the mathematics package? I am in very very much need of it!

    I also agree that all those functions you described PhilSA is absolutely needed. It's funny, because I also have all those functions in my own extension of the math class! Those really need to be added!
     
    adammpolak likes this.
  25. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    Has anybody found out the math to convert a quaternion to Euler angles, especially in reference to a plane/normal?
     
  26. WireWhiz

    WireWhiz

    Joined:
    Apr 17, 2019
    Posts:
    16
    I found a way to "project a quaternion on a plane" so to speak. Though it takes several extensions to get there. It gets the radians of a quaternion around an axis and then converts it back into a quaternion, though if you want the angle you just need to remove that. The zero vector can be anything except parallel to the vector, though a 90-degree difference is optimal. This is what I would usually use Euler angles for since when coding for VR you need to rotate the input vectors by the rotation y of the headset or controller.
    Code (CSharp):
    1. public static class MathExtention
    2. {
    3.     public static float3 Project(this float3 vector, float3 normal)
    4.     {
    5.         normal = math.normalize(normal);
    6.         float dist = math.dot(vector, normal);
    7.         float3 projected = dist * normal;
    8.         return projected;
    9.     }
    10.     public static float3 ProjectOnPlane(this float3 vector, float3 normal)
    11.     {
    12.         float3 projected = vector - vector.Project(normal);
    13.         return projected;
    14.     }
    15.     public static float AngleFrom(this float3 vector1, float3 vector2)
    16.     {
    17.         return math.acos(math.dot(math.normalize(vector1), math.normalize(vector2)));
    18.     }
    19.     public static quaternion ProjectOnPlane(this quaternion rotation, float3 vector, float3 zeroVector)
    20.     {
    21.         float3 flatRotatedVector = math.mul(rotation, zeroVector).ProjectOnPlane(vector);
    22.         float angle = (math.dot(math.cross(vector, zeroVector), flatRotatedVector) > 0) ? 1 : -1 * zeroVector.AngleFrom(flatRotatedVector);
    23.        
    24.         return quaternion.AxisAngle(vector, angle);
    25.     }
    26.  
    27. }
    Edit:
    For some reason, this code causes a jump at 0 and 180, I fixed it by subtracting from 2pi instead of setting it negative:
    Code (CSharp):
    1. public static quaternion ProjectOnPlane(this quaternion rotation, float3 vector)
    2.     {
    3.         float3 flatRotatedVector = math.mul(rotation, new float3(0, 0, 1)).ProjectOnPlane(vector);
    4.         float angle = 0;
    5.         if (math.dot(flatRotatedVector, new float3(1, 0, 0)) > 0)
    6.             angle = new float3(0, 0, 1).AngleFrom(flatRotatedVector);
    7.         else
    8.             angle = 2 * math.PI - new float3(0, 0, 1).AngleFrom(flatRotatedVector);
    9.  
    10.         return quaternion.AxisAngle(vector, angle);
    11.     }
     
    Last edited: Jun 25, 2020
  27. HeliosJack

    HeliosJack

    Joined:
    Aug 15, 2013
    Posts:
    41
    just chiming in with my use case, dont know where this has landed. need to do something like this:
    Code (CSharp):
    1. localToWorld.Value = float4x4.TRS(localToWorld.Position, quaternion.EulerXYZ(0, localToWorld.Rotation.toEuler().y, 0), 1)
    in other words, rotate to world level while maintaining heading.
     
  28. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The core of this I shamelessly ripped from an open source quaternion implementation. Still has some smoothing issues as it approaches it's target but otherwise works well.

    Code (csharp):
    1.  
    2. public static float3 AngularVelocityToTarget(quaternion fromRotation, float3 toDirection, float turnSpeed, float3 up)
    3.         {
    4.             var wanted = quaternion.LookRotation(toDirection, up);
    5.             wanted = math.normalizesafe(wanted);
    6.             return AngularVelocityToTarget(fromRotation, wanted, turnSpeed);
    7.         }
    8.  
    9.         public static float3 AngularVelocityToTarget(quaternion fromRotation, quaternion toRotation, float turnSpeed)
    10.         {
    11.             quaternion delta = math.mul(toRotation, math.inverse(fromRotation));
    12.             delta = math.normalizesafe(delta);
    13.  
    14.             delta.ToAngleAxis(out float3 axis, out float angle);
    15.  
    16.             // We get an infinite axis in the event that our rotation is already aligned.
    17.             if (float.IsInfinity(axis.x))
    18.             {
    19.                 return default;
    20.             }
    21.  
    22.             if (angle > 180f)
    23.             {
    24.                 angle -= 360f;
    25.             }
    26.  
    27.             // Here I drop down to 0.9f times the desired movement,
    28.             // since we'd rather undershoot and ease into the correct angle
    29.             // than overshoot and oscillate around it in the event of errors.
    30.             return (math.radians(0.9f) * angle / turnSpeed) * math.normalizesafe(axis);
    31.         }
    32.         public static void ToAngleAxis(this quaternion q, out float3 axis, out float angle)
    33.         {
    34.             q = math.normalizesafe(q);
    35.            
    36.             angle = 2.0f * (float)math.acos(q.value.w);
    37.             angle = math.degrees(angle);
    38.             float den = (float)math.sqrt(1.0 - q.value.w * q.value.w);
    39.             if (den > 0.0001f)
    40.             {
    41.                 axis = q.value.xyz / den;
    42.             }
    43.             else
    44.             {
    45.                 // This occurs when the angle is zero.
    46.                 // Not a problem: just set an arbitrary normalized axis.
    47.                 axis = new float3(1, 0, 0);
    48.             }
    49.         }
    50.  
     
    azmi_unity likes this.
  29. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    The rotation of angular velocity during teleportation
     
    LW likes this.
  30. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    After a lot of searching I found that this implementation at this StackExchange post provides an accurate implementation.

    The best part of this implementation is that it has an identical rotation order to that of Unity. Unity uses YXZ Euler (same with this one), which essentially means rotate around global Y first, then rotate around local X, then finally rotate around local Z.

    This implementation also follows Unity's X rotation very accurately. When converting from quaternion to euler, the X rotation value that this implementation returns will always be in range [-90, 90] degrees.

    Though the difference is that of the Y and Z axis ranges. Unity has a range of [-180, 180] degrees, whereas this implementation uses [0, 360] degrees. It still functions identically due to the properties of trigonometric functions. If you do want the range in of all axes being in range [-180, 180] you can do:

    Code (CSharp):
    1. float3 EA = unityQuaternionToEuler(myQuaternion);
    2.  
    3. EA = ((EA + 180) % 360) - 180;
    Here is the implementation converted to use the DOTS Mathematics package:

    Code (CSharp):
    1.  
    2.     public static quaternion unityEulerToQuaternion(float3 v)
    3.     {
    4.       return unityEulerToQuaternion(v.y, v.x, v.z);
    5.     }
    6.  
    7.     public static quaternion unityEulerToQuaternion(float yaw, float pitch, float roll)
    8.     {
    9.       yaw = math.radians(yaw);
    10.       pitch = math.radians(pitch);
    11.       roll = math.radians(roll);
    12.  
    13.       float rollOver2 = roll * 0.5f;
    14.       float sinRollOver2 = (float)math.sin((double)rollOver2);
    15.       float cosRollOver2 = (float)math.cos((double)rollOver2);
    16.       float pitchOver2 = pitch * 0.5f;
    17.       float sinPitchOver2 = (float)math.sin((double)pitchOver2);
    18.       float cosPitchOver2 = (float)math.cos((double)pitchOver2);
    19.       float yawOver2 = yaw * 0.5f;
    20.       float sinYawOver2 = (float)math.sin((double)yawOver2);
    21.       float cosYawOver2 = (float)math.cos((double)yawOver2);
    22.       float4 result;
    23.       result.w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
    24.       result.x = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
    25.       result.y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
    26.       result.z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
    27.  
    28.       return new quaternion(result);
    29.     }
    30.  
    31.     public static float3 unityQuaternionToEuler(quaternion q2)
    32.     {
    33.       float4 q1 = q2.value;
    34.  
    35.       float sqw = q1.w * q1.w;
    36.       float sqx = q1.x * q1.x;
    37.       float sqy = q1.y * q1.y;
    38.       float sqz = q1.z * q1.z;
    39.       float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
    40.       float test = q1.x * q1.w - q1.y * q1.z;
    41.       float3 v;
    42.  
    43.       if (test > 0.4995f * unit)
    44.       { // singularity at north pole
    45.         v.y = 2f * math.atan2(q1.y, q1.x);
    46.         v.x = math.PI / 2;
    47.         v.z = 0;
    48.         return NormalizeAngles(math.degrees(v));
    49.       }
    50.       if (test < -0.4995f * unit)
    51.       { // singularity at south pole
    52.         v.y = -2f * math.atan2(q1.y, q1.x);
    53.         v.x = -math.PI / 2;
    54.         v.z = 0;
    55.         return NormalizeAngles(math.degrees(v));
    56.       }
    57.  
    58.       quaternion q3 = new quaternion(q1.w, q1.z, q1.x, q1.y);
    59.       float4 q = q3.value;
    60.  
    61.       v.y = math.atan2(2f * q.x * q.w + 2f * q.y * q.z, 1 - 2f * (q.z * q.z + q.w * q.w));   // Yaw
    62.       v.x = math.asin(2f * (q.x * q.z - q.w * q.y));                                         // Pitch
    63.       v.z = math.atan2(2f * q.x * q.y + 2f * q.z * q.w, 1 - 2f * (q.y * q.y + q.z * q.z));   // Roll
    64.  
    65.       return NormalizeAngles(math.degrees(v));
    66.     }
    67.  
    68.     static float3 NormalizeAngles(float3 angles)
    69.     {
    70.       angles.x = NormalizeAngle(angles.x);
    71.       angles.y = NormalizeAngle(angles.y);
    72.       angles.z = NormalizeAngle(angles.z);
    73.       return angles;
    74.     }
    75.  
    76.     static float NormalizeAngle(float angle)
    77.     {
    78.       while (angle > 360)
    79.         angle -= 360;
    80.       while (angle < 0)
    81.         angle += 360;
    82.       return angle;
    83.     }
     
    Last edited: Sep 19, 2020
    Akzwar and Sab_Rango like this.
  31. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Math.cs under Unity.Physics contains all we want, but it's all internal right now so you'd have to copy the functions manually. I do see comments in there saying these will be added to Unity.Mathematics eventually

    Code (CSharp):
    1. using System.Diagnostics;
    2. using System.Runtime.CompilerServices;
    3. using Unity.Mathematics;
    4. using UnityEngine.Assertions;
    5.  
    6. namespace Unity.Physics
    7. {
    8.     // Common math helper functions
    9.     [DebuggerStepThrough]
    10.     public static partial class Math
    11.     {
    12.         // Constants
    13.         [DebuggerStepThrough]
    14.         public static class Constants
    15.         {
    16.             public static float4 One4F => new float4(1);
    17.             public static float4 Min4F => new float4(float.MinValue);
    18.             public static float4 Max4F => new float4(float.MaxValue);
    19.             public static float3 Min3F => new float3(float.MinValue);
    20.             public static float3 Max3F => new float3(float.MaxValue);
    21.  
    22.             // Smallest float such that 1.0 + eps != 1.0
    23.             // Different from float.Epsilon which is the smallest value greater than zero.
    24.             public const float Eps = 1.192092896e-07F;
    25.  
    26.             // These constants are identical to the ones in the Unity Mathf library, to ensure identical behaviour
    27.             internal const float UnityEpsilonNormalSqrt = 1e-15F;
    28.             internal const float UnityEpsilon = 0.00001F;
    29.         }
    30.  
    31.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    32.         public static int NextMultipleOf16(int input) => ((input + 15) >> 4) << 4;
    33.  
    34.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    35.         public static ulong NextMultipleOf16(ulong input) => ((input + 15) >> 4) << 4;
    36.        
    37.         /// Note that alignment must be a power of two for this to work.
    38.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    39.         public static int NextMultipleOf(int input, int alignment) => (input + (alignment - 1)) & (~(alignment - 1));
    40.  
    41.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    42.         public static ulong NextMultipleOf(ulong input, ulong alignment) => (input + (alignment - 1)) & (~(alignment - 1));
    43.  
    44.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    45.         public static int IndexOfMinComponent(float2 v) => v.x < v.y ? 0 : 1;
    46.  
    47.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    48.         public static int IndexOfMinComponent(float3 v) => v.x < v.y ? ((v.x < v.z) ? 0 : 2) : ((v.y < v.z) ? 1 : 2);
    49.  
    50.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    51.         public static int IndexOfMinComponent(float4 v) => math.cmax(math.select(new int4(0, 1, 2, 3), new int4(-1), math.cmin(v) < v));
    52.  
    53.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    54.         public static int IndexOfMaxComponent(float2 v) => v.x > v.y ? 0 : 1;
    55.  
    56.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    57.         public static int IndexOfMaxComponent(float3 v) => v.x > v.y ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2);
    58.  
    59.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    60.         public static int IndexOfMaxComponent(float4 v) => math.cmax(math.select(new int4(0, 1, 2, 3), new int4(-1), math.cmax(v) > v));
    61.  
    62.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    63.         public static float HorizontalMul(float3 v) => v.x * v.y * v.z;
    64.  
    65.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    66.         public static float HorizontalMul(float4 v) => (v.x * v.y) * (v.z * v.w);
    67.  
    68.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    69.         public static float Dotxyz1(float4 lhs, float3 rhs) => math.dot(lhs, new float4(rhs, 1));
    70.  
    71.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    72.         public static double Dotxyz1(double4 lhs, double3 rhs) => math.dot(lhs, new double4(rhs, 1));
    73.  
    74.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    75.         public static float Det(float3 a, float3 b, float3 c) => math.dot(math.cross(a, b), c); // TODO: use math.determinant()?
    76.  
    77.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    78.         public static float RSqrtSafe(float v) => math.select(math.rsqrt(v), 0.0f, math.abs(v) < 1e-10f);
    79.  
    80.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    81.         public static float NormalizeWithLength(float3 v, out float3 n)
    82.         {
    83.             float lengthSq = math.lengthsq(v);
    84.             float invLength = math.rsqrt(lengthSq);
    85.             n = v * invLength;
    86.             return lengthSq * invLength;
    87.         }
    88.  
    89.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    90.         public static bool IsNormalized(float3 v)
    91.         {
    92.             float lenZero = math.lengthsq(v) - 1.0f;
    93.             float absLenZero = math.abs(lenZero);
    94.             return absLenZero < Constants.UnityEpsilon;
    95.         }
    96.  
    97.         // Return two normals perpendicular to the input vector
    98.         public static void CalculatePerpendicularNormalized(float3 v, out float3 p, out float3 q)
    99.         {
    100.             float3 vSquared = v * v;
    101.             float3 lengthsSquared = vSquared + vSquared.xxx; // y = ||j x v||^2, z = ||k x v||^2
    102.             float3 invLengths = math.rsqrt(lengthsSquared);
    103.  
    104.             // select first direction, j x v or k x v, whichever has greater magnitude
    105.             float3 dir0 = new float3(-v.y, v.x, 0.0f);
    106.             float3 dir1 = new float3(-v.z, 0.0f, v.x);
    107.             bool cmp = (lengthsSquared.y > lengthsSquared.z);
    108.             float3 dir = math.select(dir1, dir0, cmp);
    109.  
    110.             // normalize and get the other direction
    111.             float invLength = math.select(invLengths.z, invLengths.y, cmp);
    112.             p = dir * invLength;
    113.             float3 cross = math.cross(v, dir);
    114.             q = cross * invLength;
    115.         }
    116.  
    117.         // Calculate the eigenvectors and eigenvalues of a symmetric 3x3 matrix
    118.         public static void DiagonalizeSymmetricApproximation(float3x3 a, out float3x3 eigenVectors, out float3 eigenValues)
    119.         {
    120.             float GetMatrixElement(float3x3 m, int row, int col)
    121.             {
    122.                 switch (col)
    123.                 {
    124.                     case 0: return m.c0[row];
    125.                     case 1: return m.c1[row];
    126.                     case 2: return m.c2[row];
    127.                     default: UnityEngine.Assertions.Assert.IsTrue(false); return 0.0f;
    128.                 }
    129.             }
    130.  
    131.             void SetMatrixElement(ref float3x3 m, int row, int col, float x)
    132.             {
    133.                 switch (col)
    134.                 {
    135.                     case 0: m.c0[row] = x; break;
    136.                     case 1: m.c1[row] = x; break;
    137.                     case 2: m.c2[row] = x; break;
    138.                     default: UnityEngine.Assertions.Assert.IsTrue(false); break;
    139.                 }
    140.             }
    141.  
    142.             eigenVectors = float3x3.identity;
    143.             float epsSq = 1e-14f * (math.lengthsq(a.c0) + math.lengthsq(a.c1) + math.lengthsq(a.c2));
    144.             const int maxIterations = 10;
    145.             for (int iteration = 0; iteration < maxIterations; iteration++)
    146.             {
    147.                 // Find the row (p) and column (q) of the off-diagonal entry with greater magnitude
    148.                 int p = 0, q = 1;
    149.                 {
    150.                     float maxEntry = math.abs(a.c1[0]);
    151.                     float mag02 = math.abs(a.c2[0]);
    152.                     float mag12 = math.abs(a.c2[1]);
    153.                     if (mag02 > maxEntry)
    154.                     {
    155.                         maxEntry = mag02;
    156.                         p = 0;
    157.                         q = 2;
    158.                     }
    159.                     if (mag12 > maxEntry)
    160.                     {
    161.                         maxEntry = mag12;
    162.                         p = 1;
    163.                         q = 2;
    164.                     }
    165.  
    166.                     // Terminate if it's small enough
    167.                     if (maxEntry * maxEntry < epsSq)
    168.                     {
    169.                         break;
    170.                     }
    171.                 }
    172.  
    173.                 // Calculate jacobia rotation
    174.                 float3x3 j = float3x3.identity;
    175.                 {
    176.                     float apq = GetMatrixElement(a, p, q);
    177.                     float tau = (GetMatrixElement(a, q, q) - GetMatrixElement(a, p, p)) / (2.0f * apq);
    178.                     float t = math.sqrt(1.0f + tau * tau);
    179.                     if (tau > 0.0f)
    180.                     {
    181.                         t = 1.0f / (tau + t);
    182.                     }
    183.                     else
    184.                     {
    185.                         t = 1.0f / (tau - t);
    186.                     }
    187.                     float c = math.rsqrt(1.0f + t * t);
    188.                     float s = t * c;
    189.  
    190.                     SetMatrixElement(ref j, p, p, c);
    191.                     SetMatrixElement(ref j, q, q, c);
    192.                     SetMatrixElement(ref j, p, q, s);
    193.                     SetMatrixElement(ref j, q, p, -s);
    194.                 }
    195.  
    196.                 // Rotate a
    197.                 a = math.mul(math.transpose(j), math.mul(a, j));
    198.                 eigenVectors = math.mul(eigenVectors, j);
    199.             }
    200.             eigenValues = new float3(a.c0.x, a.c1.y, a.c2.z);
    201.         }
    202.  
    203.         // Returns the twist angle of the swing-twist decomposition of q about i, j, or k corresponding to index = 0, 1, or 2 respectively.
    204.         public static float CalculateTwistAngle(quaternion q, int twistAxisIndex)
    205.         {
    206.             // q = swing * twist, twist = normalize(twistAxis * twistAxis dot q.xyz, q.w)
    207.             float dot = q.value[twistAxisIndex];
    208.             float w = q.value.w;
    209.             float lengthSq = dot * dot + w * w;
    210.             float invLength = RSqrtSafe(lengthSq);
    211.             float sinHalfAngle = dot * invLength;
    212.             float cosHalfAngle = w * invLength;
    213.             float halfAngle = math.atan2(sinHalfAngle, cosHalfAngle);
    214.             return halfAngle + halfAngle;
    215.         }
    216.  
    217.         // Returns a quaternion q with q * from = to
    218.         public static quaternion FromToRotation(float3 from, float3 to)
    219.         {
    220.             Assert.IsTrue(math.abs(math.lengthsq(from) - 1.0f) < 1e-4f);
    221.             Assert.IsTrue(math.abs(math.lengthsq(to) - 1.0f) < 1e-4f);
    222.             float3 cross = math.cross(from, to);
    223.             CalculatePerpendicularNormalized(from, out float3 safeAxis, out float3 unused); // for when angle ~= 180
    224.             float dot = math.dot(from, to);
    225.             float3 squares = new float3(0.5f - new float2(dot, -dot) * 0.5f, math.lengthsq(cross));
    226.             float3 inverses = math.select(math.rsqrt(squares), 0.0f, squares < 1e-10f);
    227.             float2 sinCosHalfAngle = squares.xy * inverses.xy;
    228.             float3 axis = math.select(cross * inverses.z, safeAxis, squares.z < 1e-10f);
    229.             return new quaternion(new float4(axis * sinCosHalfAngle.x, sinCosHalfAngle.y));
    230.         }
    231.  
    232.        
    233.         // Note: taken from Unity.Animation/Core/MathExtensions.cs, which will be moved to Unity.Mathematics at some point
    234.         //       after that, this should be removed and the Mathematics version should be used
    235.         #region toEuler
    236.         static float3 toEuler(quaternion q, math.RotationOrder order = math.RotationOrder.Default)
    237.         {
    238.             const float epsilon = 1e-6f;
    239.  
    240.             //prepare the data
    241.             var qv = q.value;
    242.             var d1 = qv * qv.wwww * new float4(2.0f); //xw, yw, zw, ww
    243.             var d2 = qv * qv.yzxw * new float4(2.0f); //xy, yz, zx, ww
    244.             var d3 = qv * qv;
    245.             var euler = new float3(0.0f);
    246.  
    247.             const float CUTOFF = (1.0f - 2.0f * epsilon) * (1.0f - 2.0f * epsilon);
    248.  
    249.             switch (order)
    250.             {
    251.                 case math.RotationOrder.ZYX:
    252.                 {
    253.                     var y1 = d2.z + d1.y;
    254.                     if (y1 * y1 < CUTOFF)
    255.                     {
    256.                         var x1 = -d2.x + d1.z;
    257.                         var x2 = d3.x + d3.w - d3.y - d3.z;
    258.                         var z1 = -d2.y + d1.x;
    259.                         var z2 = d3.z + d3.w - d3.y - d3.x;
    260.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), math.atan2(z1, z2));
    261.                     }
    262.                     else //zxz
    263.                     {
    264.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    265.                         var abcd = new float4(d2.z, d1.y, d2.y, d1.x);
    266.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    267.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    268.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), 0.0f);
    269.                     }
    270.  
    271.                     break;
    272.                 }
    273.  
    274.                 case math.RotationOrder.ZXY:
    275.                 {
    276.                     var y1 = d2.y - d1.x;
    277.                     if (y1 * y1 < CUTOFF)
    278.                     {
    279.                         var x1 = d2.x + d1.z;
    280.                         var x2 = d3.y + d3.w - d3.x - d3.z;
    281.                         var z1 = d2.z + d1.y;
    282.                         var z2 = d3.z + d3.w - d3.x - d3.y;
    283.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), math.atan2(z1, z2));
    284.                     }
    285.                     else //zxz
    286.                     {
    287.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    288.                         var abcd = new float4(d2.z, d1.y, d2.y, d1.x);
    289.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    290.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    291.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), 0.0f);
    292.                     }
    293.  
    294.                     break;
    295.                 }
    296.  
    297.                 case math.RotationOrder.YXZ:
    298.                 {
    299.                     var y1 = d2.y + d1.x;
    300.                     if (y1 * y1 < CUTOFF)
    301.                     {
    302.                         var x1 = -d2.z + d1.y;
    303.                         var x2 = d3.z + d3.w - d3.x - d3.y;
    304.                         var z1 = -d2.x + d1.z;
    305.                         var z2 = d3.y + d3.w - d3.z - d3.x;
    306.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), math.atan2(z1, z2));
    307.                     }
    308.                     else //yzy
    309.                     {
    310.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    311.                         var abcd = new float4(d2.x, d1.z, d2.y, d1.x);
    312.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    313.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    314.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), 0.0f);
    315.                     }
    316.  
    317.                     break;
    318.                 }
    319.  
    320.                 case math.RotationOrder.YZX:
    321.                 {
    322.                     var y1 = d2.x - d1.z;
    323.                     if (y1 * y1 < CUTOFF)
    324.                     {
    325.                         var x1 = d2.z + d1.y;
    326.                         var x2 = d3.x + d3.w - d3.z - d3.y;
    327.                         var z1 = d2.y + d1.x;
    328.                         var z2 = d3.y + d3.w - d3.x - d3.z;
    329.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), math.atan2(z1, z2));
    330.                     }
    331.                     else //yxy
    332.                     {
    333.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    334.                         var abcd = new float4(d2.x, d1.z, d2.y, d1.x);
    335.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    336.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    337.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), 0.0f);
    338.                     }
    339.  
    340.                     break;
    341.                 }
    342.  
    343.                 case math.RotationOrder.XZY:
    344.                 {
    345.                     var y1 = d2.x + d1.z;
    346.                     if (y1 * y1 < CUTOFF)
    347.                     {
    348.                         var x1 = -d2.y + d1.x;
    349.                         var x2 = d3.y + d3.w - d3.z - d3.x;
    350.                         var z1 = -d2.z + d1.y;
    351.                         var z2 = d3.x + d3.w - d3.y - d3.z;
    352.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), math.atan2(z1, z2));
    353.                     }
    354.                     else //xyx
    355.                     {
    356.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    357.                         var abcd = new float4(d2.x, d1.z, d2.z, d1.y);
    358.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    359.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    360.                         euler = new float3(math.atan2(x1, x2), math.asin(y1), 0.0f);
    361.                     }
    362.  
    363.                     break;
    364.                 }
    365.  
    366.                 case math.RotationOrder.XYZ:
    367.                 {
    368.                     var y1 = d2.z - d1.y;
    369.                     if (y1 * y1 < CUTOFF)
    370.                     {
    371.                         var x1 = d2.y + d1.x;
    372.                         var x2 = d3.z + d3.w - d3.y - d3.x;
    373.                         var z1 = d2.x + d1.z;
    374.                         var z2 = d3.x + d3.w - d3.y - d3.z;
    375.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), math.atan2(z1, z2));
    376.                     } else //xzx
    377.                     {
    378.                         y1 = math.clamp(y1, -1.0f, 1.0f);
    379.                         var abcd = new float4(d2.z, d1.y, d2.x, d1.z);
    380.                         var x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
    381.                         var x2 = math.csum(abcd * abcd * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
    382.                         euler = new float3(math.atan2(x1, x2), -math.asin(y1), 0.0f);
    383.                     }
    384.  
    385.                     break;
    386.                 }
    387.             }
    388.  
    389.             return eulerReorderBack(euler, order);
    390.         }
    391.  
    392.         static float3 eulerReorderBack(float3 euler, math.RotationOrder order)
    393.         {
    394.             switch (order)
    395.             {
    396.                 case math.RotationOrder.XZY:
    397.                     return euler.xzy;
    398.                 case math.RotationOrder.YZX:
    399.                     return euler.zxy;
    400.                 case math.RotationOrder.YXZ:
    401.                     return euler.yxz;
    402.                 case math.RotationOrder.ZXY:
    403.                     return euler.yzx;
    404.                 case math.RotationOrder.ZYX:
    405.                     return euler.zyx;
    406.                 case math.RotationOrder.XYZ:
    407.                 default:
    408.                     return euler;
    409.             }
    410.         }
    411.         #endregion
    412.  
    413.         /// <summary>
    414.         /// Convert a quaternion orientation to Euler angles.
    415.         /// Use this method to calculate angular velocity needed to achieve a target orientation.
    416.         /// </summary>
    417.         /// <param name="q">An orientation.</param>
    418.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    419.         internal static float3 ToEulerAngles(this quaternion q, math.RotationOrder order = math.RotationOrder.XYZ)
    420.         {
    421.             return toEuler(q, order);
    422.         }
    423.  
    424.         // Returns the angle in degrees between /from/ and /to/. This is always the smallest
    425.         internal static float Angle(float3 from, float3 to)
    426.         {
    427.             // sqrt(a) * sqrt(b) = sqrt(a * b) -- valid for real numbers
    428.             var denominator = math.sqrt(math.lengthsq(from) * math.lengthsq(to));
    429.             if (denominator < Constants.UnityEpsilonNormalSqrt)
    430.                 return 0F;
    431.  
    432.             var dot = math.clamp(math.dot(from, to) / denominator, -1F, 1F);
    433.             return math.degrees(math.acos(dot));
    434.         }
    435.  
    436.         // The smaller of the two possible angles between the two vectors is returned, therefore the result will never be greater than 180 degrees or smaller than -180 degrees.
    437.         // If you imagine the from and to vectors as lines on a piece of paper, both originating from the same point, then the /axis/ vector would point up out of the paper.
    438.         // The measured angle between the two vectors would be positive in a clockwise direction and negative in an anti-clockwise direction.
    439.         internal static float SignedAngle(float3 from, float3 to, float3 axis)
    440.         {
    441.             var unsignedAngle = Angle(from, to);
    442.             var sign = math.sign(math.dot(math.cross(from, to), axis));
    443.             return unsignedAngle * sign;
    444.         }
    445.  
    446.         // Projects a vector onto a plane defined by a normal orthogonal to the plane.
    447.         internal static float3 ProjectOnPlane(float3 vector, float3 planeNormal)
    448.         {
    449.             var sqrMag = math.dot(planeNormal, planeNormal);
    450.             if (sqrMag < Constants.UnityEpsilon)
    451.                 return vector;
    452.  
    453.             var dot = math.dot(vector, planeNormal);
    454.             return vector - planeNormal * (dot / sqrMag);
    455.         }
    456.  
    457.         /// <summary>
    458.         /// Physics internally represents all rigid bodies in world space.
    459.         /// If a static body is in a hierarchy, its local-to-world matrix must be decomposed when building the physics world.
    460.         /// This method returns a world-space RigidTransform that would be decomposed for such a rigid body.
    461.         /// </summary>
    462.         /// <returns>A world-space RigidTransform as used by physics.</returns>
    463.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    464.         public static RigidTransform DecomposeRigidBodyTransform(in float4x4 localToWorld) =>
    465.             new RigidTransform(DecomposeRigidBodyOrientation(localToWorld), localToWorld.c3.xyz);
    466.  
    467.         /// <summary>
    468.         /// Physics internally represents all rigid bodies in world space.
    469.         /// If a static body is in a hierarchy, its local-to-world matrix must be decomposed when building the physics world.
    470.         /// This method returns a world-space orientation that would be decomposed for such a rigid body.
    471.         /// </summary>
    472.         /// <returns>A world-space orientation as used by physics.</returns>
    473.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    474.         public static quaternion DecomposeRigidBodyOrientation(in float4x4 localToWorld) =>
    475.             quaternion.LookRotationSafe(localToWorld.c2.xyz, localToWorld.c1.xyz);
    476.  
    477.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    478.         internal static float3 DecomposeScale(this float4x4 matrix) =>
    479.             new float3(math.length(matrix.c0.xyz), math.length(matrix.c1.xyz), math.length(matrix.c2.xyz));
    480.     }
    481. }
    482.  
     
  32. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    I have a 3D space game where I want to "thrust" in the direction the user is pointed.

    I want to take the current rotation (quaternion) and increase the linear velocity (float3) in that direction.

    @mike_acton @SebastianAaltonen
     
    jmcusack likes this.
  33. nobeerleft

    nobeerleft

    Joined:
    Mar 29, 2012
    Posts:
    27
    Can’t you just rotate a unit forward vector by the quat to get the rotated forward?
     
    adammpolak likes this.
  34. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @nobeerleft so I think you just solved it...?

    Code (CSharp):
    1. Entities
    2. .WithAll<PlayerTag>()
    3. .ForEach(Entity entity, ref Rotation rotation, ref PhysicsVelocity physicsVelocity)
    4.  
    5. if (inputData.thrust == 1)
    6. {
    7. physicsVelocity.Linear += math.mul(rotation.Value, new float3(0,0,1)).xyz;
    8. }
    I am not sure why
    math.mul(rotation.Value, new float3(0,0,1)).xyz
    works but it does... so super duper thank you @nobeerleft .

    Is "unit forward vector" new float3(0,0,1) somehow linked to the orientation of quaternions in Unity ECS?
     
  35. nobeerleft

    nobeerleft

    Joined:
    Mar 29, 2012
    Posts:
    27
    nah, the new vector is just “forward” (whatever you wanted forward to be) in rotated space ( ie the rotated orientation)
     
    adammpolak likes this.
  36. azmi_unity

    azmi_unity

    Joined:
    Dec 13, 2020
    Posts:
    62
    PhysicsVelocity.CalculateVelocityToTarget
    which uses the internal "ToEulerAngles" method gives wrong values for certain orientations--it gives angular velocities that is not the shortest path. (Euler rotation order messing it up?).

    But this code works fine.
     
  37. backwheelbates

    backwheelbates

    Joined:
    Jan 14, 2014
    Posts:
    232
    This is a great topic. Any advice on how to decompose a quaternion into Euler angles using different rotate orders?

    Edit
    @PhilSA , I found the solution in your link to math.cs. Thanks!!
     
    Last edited: Jan 22, 2022
  38. goodnewsjimdotcom

    goodnewsjimdotcom

    Joined:
    May 24, 2017
    Posts:
    342
    I've tested your code. It works appropriately from about 20 tests.

    My test was as follows:

    I have a 3d space shooter who shoots a double laser with quaterntion q3 fine.

    I added the following code:

    Vector3 r4 = unityQuaternionToEuler(q3);
    quaternion qx = unityEulerToQuaternion(r4);

    I replaced one of two lasers with qx and left the original as q3.

    The result was exactly the same as both being q3 that worked. qx and q3 worked the same.

    qx is the conversion of my quat to vector3 and back. qx has a different value that q3 and that is fine since quaternions have many correct answers for the same quat. qx is the same as q3 effectively though their data is different, and this is proper and right!

    The one thing I did not test for is gimbal lock. If the infinitesimal chance this happens in my game, it is irrelevant since lots of random goofy stuff happens because the dimensions are in flux.

    Thank you for the code, I should be MMO today or tomorrow.

    ,GoodNewsJim



     
  39. JollyTheory

    JollyTheory

    Joined:
    Dec 30, 2018
    Posts:
    156
    Note to anybody following @PhilSA comment, beware that Enum RotationOrder.Default is not XYZ, it's ZXY. The documentation says: "This is the default rotation order in Unity".

    Here's the final code I used, matches quaternion.Euler():
    Code (CSharp):
    1. float3 quaternionToEuler(quaternion q)
    2. {
    3.     float epsilon = 1e-6f;
    4.     float CUTOFF = (1.0f - 2.0f * epsilon) * (1.0f - 2.0f * epsilon);
    5.     float4 qv = q.value;
    6.     float4 d1 = qv * qv.wwww * 2.0f;
    7.     float4 d2 = qv * qv.yzxw * 2.0f;
    8.     float4 d3 = qv * qv;
    9.     float3 euler = 0.0f;
    10.     float y1 = d2.y - d1.x;
    11.     if (y1 * y1 < CUTOFF)
    12.     {
    13.         float x1 = d2.x + d1.z;
    14.         float x2 = d3.y + d3.w - d3.x - d3.z;
    15.         float z1 = d2.z + d1.y;
    16.         float z2 = d3.z + d3.w - d3.x - d3.y;
    17.         euler = float3(atan2(x1, x2), -asin(y1), atan2(z1, z2));
    18.     }
    19.     else
    20.     {
    21.         y1 = clamp(y1, -1.0f, 1.0f);
    22.         float4 abcd = float4(d2.z, d1.y, d2.y, d1.x);
    23.         float x1 = 2.0f * (abcd.x * abcd.w + abcd.y * abcd.z);
    24.         float4 x = abcd * abcd * float4(-1.0f, 1.0f, -1.0f, 1.0f);
    25.         float x2 = (x.x + x.y) + (x.z + x.w);
    26.         euler = float3(atan2(x1, x2), -asin(y1), 0.0f);
    27.     }
    28.     return euler.yzx;
    29. }
     
    bb8_1 likes this.
  40. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759