Conversion between Euler <=> Quaternion like in Unity3d engine

后端 未结 4 1117
予麋鹿
予麋鹿 2020-12-17 04:38

I\'ve used two examples (from this site too), but results are not the same as those that said Unity.

Quaternion.Euler and .eulerAngles are Unity functions. FromQ doe

相关标签:
4条回答
  • 2020-12-17 05:10

    Here's my solution. This is very very close to Unity's Quaternion.Euler and quaternion.eulerAngles. The discrepancies are small enough that they shouldn't matter for any application.

    public static Vector3 QuaternionToEuler(Quaternion q)
    {
        Vector3 euler;
    
        // if the input quaternion is normalized, this is exactly one. Otherwise, this acts as a correction factor for the quaternion's not-normalizedness
        float unit = (q.x * q.x) + (q.y * q.y) + (q.z * q.z) + (q.w * q.w);
    
        // this will have a magnitude of 0.5 or greater if and only if this is a singularity case
        float test = q.x * q.w - q.y * q.z;
    
        if (test > 0.4995f * unit) // singularity at north pole
        {
            euler.x = Mathf.PI / 2;
            euler.y = 2f * Mathf.Atan2(q.y, q.x);
            euler.z = 0;
        }
        else if (test < -0.4995f * unit) // singularity at south pole
        {
            euler.x = -Mathf.PI / 2;
            euler.y = -2f * Mathf.Atan2(q.y, q.x);
            euler.z = 0;
        }
        else // no singularity - this is the majority of cases
        {
            euler.x = Mathf.Asin(2f * (q.w * q.x - q.y * q.z));
            euler.y = Mathf.Atan2(2f * q.w * q.y + 2f * q.z * q.x, 1 - 2f * (q.x * q.x + q.y * q.y));
            euler.z = Mathf.Atan2(2f * q.w * q.z + 2f * q.x * q.y, 1 - 2f * (q.z * q.z + q.x * q.x));
        }
    
        // all the math so far has been done in radians. Before returning, we convert to degrees...
        euler *= Mathf.Rad2Deg;
    
        //...and then ensure the degree values are between 0 and 360
        euler.x %= 360;
        euler.y %= 360;
        euler.z %= 360;
    
        return euler;
    }
    
    public static Quaternion EulerToQuaternion(Vector3 euler)
    {
        float xOver2 = euler.x * Mathf.Deg2Rad * 0.5f;
        float yOver2 = euler.y * Mathf.Deg2Rad * 0.5f;
        float zOver2 = euler.z * Mathf.Deg2Rad * 0.5f;
    
        float sinXOver2 = Mathf.Sin(xOver2);
        float cosXOver2 = Mathf.Cos(xOver2);
        float sinYOver2 = Mathf.Sin(yOver2);
        float cosYOver2 = Mathf.Cos(yOver2);
        float sinZOver2 = Mathf.Sin(zOver2);
        float cosZOver2 = Mathf.Cos(zOver2);
    
        Quaternion result;
        result.x = cosYOver2 * sinXOver2 * cosZOver2 + sinYOver2 * cosXOver2 * sinZOver2;
        result.y = sinYOver2 * cosXOver2 * cosZOver2 - cosYOver2 * sinXOver2 * sinZOver2;
        result.z = cosYOver2 * cosXOver2 * sinZOver2 - sinYOver2 * sinXOver2 * cosZOver2;
        result.w = cosYOver2 * cosXOver2 * cosZOver2 + sinYOver2 * sinXOver2 * sinZOver2;
    
        return result;
    }
    
    0 讨论(0)
  • 2020-12-17 05:21

    This might only be worth a partial answer but here is "ToQ() = Quaternion.Euler()":

    public static Quaternion ToQ(Vector3 v)
    {
        return ToQ(v.y,v.x,v.z);
    }
    
    public static Quaternion ToQ(float yaw, float pitch, float roll)
    {
        yaw*=Mathf.Deg2Rad;
        pitch*=Mathf.Deg2Rad;
        roll*=Mathf.Deg2Rad;
        float rollOver2 = roll * 0.5f;
        float sinRollOver2 = (float)Math.Sin((double)rollOver2);
        float cosRollOver2 = (float)Math.Cos((double)rollOver2);
        float pitchOver2 = pitch * 0.5f;
        float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
        float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
        float yawOver2 = yaw * 0.5f;
        float sinYawOver2 = (float)Math.Sin((double)yawOver2);
        float cosYawOver2 = (float)Math.Cos((double)yawOver2);
        Quaternion result;
        result.w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
        result.x = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
        result.y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
        result.z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
    
        return result;
    } 
    

    The 'FromQ' part of your question is a different matter. Euler angle comparison is a pain in the behind.

    0 讨论(0)
  • 2020-12-17 05:23

    I've found solution

    public static Quaternion ToQ (Vector3 v)
    {
        return ToQ (v.y, v.x, v.z);
    }
    
    public static Quaternion ToQ (float yaw, float pitch, float roll)
    {
        yaw *= Mathf.Deg2Rad;
        pitch *= Mathf.Deg2Rad;
        roll *= Mathf.Deg2Rad;
        float rollOver2 = roll * 0.5f;
        float sinRollOver2 = (float)Math.Sin ((double)rollOver2);
        float cosRollOver2 = (float)Math.Cos ((double)rollOver2);
        float pitchOver2 = pitch * 0.5f;
        float sinPitchOver2 = (float)Math.Sin ((double)pitchOver2);
        float cosPitchOver2 = (float)Math.Cos ((double)pitchOver2);
        float yawOver2 = yaw * 0.5f;
        float sinYawOver2 = (float)Math.Sin ((double)yawOver2);
        float cosYawOver2 = (float)Math.Cos ((double)yawOver2);
        Quaternion result;
        result.w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
        result.x = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
        result.y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
        result.z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
    
        return result;
    }
    
    public static Vector3 FromQ2 (Quaternion q1)
    {
        float sqw = q1.w * q1.w;
        float sqx = q1.x * q1.x;
        float sqy = q1.y * q1.y;
        float sqz = q1.z * q1.z;
        float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
        float test = q1.x * q1.w - q1.y * q1.z;
        Vector3 v;
    
        if (test>0.4995f*unit) { // singularity at north pole
            v.y = 2f * Mathf.Atan2 (q1.y, q1.x);
            v.x = Mathf.PI / 2;
            v.z = 0;
            return NormalizeAngles (v * Mathf.Rad2Deg);
        }
        if (test<-0.4995f*unit) { // singularity at south pole
            v.y = -2f * Mathf.Atan2 (q1.y, q1.x);
            v.x = -Mathf.PI / 2;
            v.z = 0;
            return NormalizeAngles (v * Mathf.Rad2Deg);
        }
        Quaternion q = new Quaternion (q1.w, q1.z, q1.x, q1.y);
        v.y = (float)Math.Atan2 (2f * q.x * q.w + 2f * q.y * q.z, 1 - 2f * (q.z * q.z + q.w * q.w));     // Yaw
        v.x = (float)Math.Asin (2f * (q.x * q.z - q.w * q.y));                             // Pitch
        v.z = (float)Math.Atan2 (2f * q.x * q.y + 2f * q.z * q.w, 1 - 2f * (q.y * q.y + q.z * q.z));      // Roll
        return NormalizeAngles (v * Mathf.Rad2Deg);
    }
    
    static Vector3 NormalizeAngles (Vector3 angles)
    {
        angles.x = NormalizeAngle (angles.x);
        angles.y = NormalizeAngle (angles.y);
        angles.z = NormalizeAngle (angles.z);
        return angles;
    }
    
    static float NormalizeAngle (float angle)
    {
        while (angle>360)
            angle -= 360;
        while (angle<0)
            angle += 360;
        return angle;
    }
    
    0 讨论(0)
  • 2020-12-17 05:28

    This question is almost three years old, but I needed the same code and the ones posted here seemed to be incorrect, so I tweaked them and found this:

    public static Quaternion Euler(float yaw, float pitch, float roll) {
            yaw*=Mathf.Deg2Rad;
            pitch*=Mathf.Deg2Rad;
            roll*=Mathf.Deg2Rad;
    
            double yawOver2 = yaw * 0.5f;
            float cosYawOver2 = (float)System.Math.Cos(yawOver2);
            float sinYawOver2 = (float)System.Math.Sin(yawOver2);
            double pitchOver2 = pitch * 0.5f;
            float cosPitchOver2 = (float)System.Math.Cos(pitchOver2);
            float sinPitchOver2 = (float)System.Math.Sin(pitchOver2);
            double rollOver2 = roll * 0.5f;
            float cosRollOver2 = (float)System.Math.Cos(rollOver2);
            float sinRollOver2 = (float)System.Math.Sin(rollOver2);            
            Quaternion result;
            result.w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
            result.x = sinYawOver2 * cosPitchOver2 * cosRollOver2 + cosYawOver2 * sinPitchOver2 * sinRollOver2;
            result.y = cosYawOver2 * sinPitchOver2 * cosRollOver2 - sinYawOver2 * cosPitchOver2 * sinRollOver2;
            result.z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
    
            return result;
    }
    

    According to a few quick tests, this matches Quaternion.Euler 100%

    0 讨论(0)
提交回复
热议问题