【问题标题】:Conversion between Euler <=> Quaternion like in Unity3d engineEuler <=> Quaternion 之间的转换就像 Unity3d 引擎中一样
【发布时间】:2012-08-23 09:26:10
【问题描述】:

我使用了两个示例(也来自这个站点),但结果与 Unity 中的结果不同。

Quaternion.Euler 和 .eulerAngles 是 Unity 函数。 FromQ 不执行奇点检查,FromQ2 执行。

结果:

eulers = (100,55,-11):
Quaternion.Euler(eulers) == (0.6, 0.4, -0.4, 0.5)
ToQ(eulers)); == (0.5, -0.4, 0.2, 0.7) // 0.5, -0.4 right but in wrong order

FromQ(ToQ(eulers)) == (55.0, 100.0, -11.0)
FromQ2(ToQ(eulers)) == (-55.5, -6.3, 71.0) // something right

Quaternion.Euler(eulers).eulerAngles == (80.0, 235.0, 169.0)
FromQ2(Quaternion.Euler(eulers)) == (65.8, 1.9, 99.8)
ToQ(eulers).eulerAngles == (70.0, 286.9, 341.4)
FromQ(Quaternion.Euler(eulers)) == (-65.8, 76.0, 4.6)

It must be:
FromQ() = FromQ2() = .eulerAngles,
ToQ() = Quaternion.Euler()

代码在这里:http://pastebin.ru/eAlTHdYf

谁能更正这段代码?我需要返回与 Unity 函数返回的值相同的值的代码。

更新

这里是固定代码:http://pastebin.com/riRLRvch。 两个函数(FromQ 和 ToQ)都运行良好。但我有一个奇点的问题。它无法正确检测到奇点。

For example (90, 0, 50) in quaternion is (0.6, -0.3, 0.3, 0.6).
test = x * y + z * w = 0 (must be close to 0.5 or -0.5)

FromQ 不能计算出正确的结果,所以这里有奇点。 (90, 50, 0) - (0.6, 0.3, -0.3, 0.6) 也是如此。

我只看到一种解决方案 - 将“测试”计算为 xw-yz。但我不确定这是否正确。

如何解决?

【问题讨论】:

  • 在非统一方法中应用欧拉旋转的顺序是什么?来自 Unity 文档:“返回围绕 z 轴旋转 z 度、围绕 x 轴旋转 x 度和围绕 y 轴旋转 y 度(按此顺序)的旋转。”如果您的比较方法没有应用相同的 ZXY 顺序,那么您的 quats 可能不匹配。
  • 可能是。我真的不明白我的任务的数学解决方案,所以我无法确定我正在使用哪个阶函数来计算角度。代码没有给我答案。
  • 我是程序员,数学不好,自己也找不到解决办法。

标签: c# math unity3d quaternions euler-angles


【解决方案1】:

我找到了解决办法

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;
}

【讨论】:

  • 你测试过这个吗?对我来说,它在 Unity3D(OSX 上的 3.5.3f3)中产生的结果略有不同。例如。四元数 q = 新四元数(-0.4850f, 0.3952f, -0.6186f, -0.4753f);输出 ("F4"): Unity3D: (71.8047, 45.9338, 139.0386) Vlad: (71.8019, 45.9317, 139.0367)
  • @user1323995 我通过将“强力”欧拉角转换为四元数并返回来对其进行了测试,它工作得足够精确。
【解决方案2】:

这个问题已经快三年了,但我需要相同的代码,而且这里发布的代码似乎不正确,所以我对其进行了调整,发现:

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;
}

根据一些快速测试,这与 Quaternion.Euler 100% 匹配

【讨论】:

    【解决方案3】:

    这可能只值得部分回答,但这里是“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;
    } 
    

    您问题的“FromQ”部分是另一回事。欧拉角comparison is a pain in the behind

    【讨论】:

    • 我也修复了 FromQ。见开头。
    【解决方案4】:

    这是我的解决方案。这与 Unity 的 Quaternion.Euler 和 quaternion.eulerAngles 非常接近。差异足够小,对任何应用程序都无关紧要。

    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 和 360 之间" - 实际上介于 -360 和 360 之间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 2016-09-03
    • 1970-01-01
    • 2014-01-10
    • 1970-01-01
    • 2018-07-30
    相关资源
    最近更新 更多