【问题标题】:Averaging an array of rotations and using them to apply torque平均旋转阵列并使用它们来施加扭矩
【发布时间】:2020-07-08 00:40:14
【问题描述】:

我正在构建一个 VR 交互系统,并添加一个投掷组件。

它的设置是为了让我有一个持续更新的持有对象的位置数组,当我释放持有的对象时,它会计算最后 5 个位置之间每个时间步长的增量位置并将它们平均。这给我留下了一个平滑的冲力投掷矢量,而不会因释放时无意的手腕轻弹而引入任何抖动。该系统已实施并且运行良好。

我现在正在尝试对旋转做同样的事情,并在释放时添加一个扭矩力。 然而,就我目前的实现而言,它似乎只是选择一个随机轴在发布时旋转。

我的四元数更新数组的实现方式与位置相同,没有问题,问题出在我的四元数平均代码或增量旋转计算代码中:

以下是我计算增量旋转的方法。请注意,rotations 是来自最后一帧的 n 旋转数组,最近的在 0,最旧的在 n

Quaternion[] deltaRotations = new Quaternion[rotations.Length - 1];

for (int i = 0; i < deltaRotations.Length; i++)
{
    deltaRotations[i] = Quaternion.Inverse(rotations[i]) * rotations[i+1];
}

以下是我平均增量旋转的方法。

Quaternion avgRot = Quaternion.identity;

int quatCount = 0;

foreach (Quaternion quat in deltaRotations)
{
    quatCount ++;
    avgRot = Quaternion.Slerp(avgRot, quat, 1 / quatCount);
}

来自Rigidbody.addTorque(avgRot, ForceMode.Impulse) 的合力使其沿看似随机的方向旋转。

你们有什么明显的错误吗?

编辑

根据 Ruzihim 的回答,我已将代码转换为使用旋转的角度/轴表示并对它们进行平均。这确定了物体释放时旋转的正确轴,但似乎每次都给我相同的旋转强度。下面是新的计算旋转力方法的代码,而不是我将 deltaRotations 数组传递给这个函数,而不是传入原始旋转数组并在事后计算增量旋转:

    Vector3 averageRotationsAngleAxis(Quaternion[] rotations) // returns an angle / axis representation of averaged rotations[]
    {
        Vector3[] deltaAxes = new Vector3[rotations.Length];

        for (int i = 0; i < rotations.Length; i++)
        {
            float angle;
            Vector3 axis;
            rotations[i].ToAngleAxis(out angle, out axis);

            deltaAxes[i] = angle * axis;
        }

        Vector3 returnVec = averageVector3(deltaAxes);

        return returnVec;
    }

将 Ruzihim 的答案标记为正确,当我 Debug.Log 使用他的方法投掷旋转的结果时,它们看起来是正确的,问题一定是介于这和脉冲扭矩之间

【问题讨论】:

  • 你能发布你正在使用的旋转数组吗?你的 for 循环比它应该多一个。将 deltaRotations.Length 更改为 deltaRotations.Length - 1 因为你有 i 和 (i + 1)。
  • @jdweng deltaRotations.Length = rotations.Length - 1。这部分对我来说看起来不错,我想如果这是一个问题,OP 会有一个运行时异常
  • @jdweng 你可能会在这里做一些事情,我正在尝试调试。记录旋转数组,它只显示一个而不是我期望的一组 5 个。但是是的,数组长度很好,它的 rotations.Length - 1 因为 5 个旋转之间有 4 个增量旋转
  • 好吧,我已经解决了四元数太少的问题,但是有同样的问题,这里有一组典型的四元数,它们被输入到增量转换和平均代码中:(-0.02236, 0.02097 ,-0.04850,0.99835)(-0.01765,0.03755,0.03755,0.0355,0.01765,0.03755,0.03755,-0.03795,0.99867)(-0.01576,0.03766,-0.03136,0.99867)(-0.02256,0.03473,-0.02588,0.99880)
  • 如果您使用deltaRotation = rotations[i+1] * Quaternion.Inverse(rotations[i]) 而不是deltaRotations[i] = Quaternion.Inverse(rotations[i]) * rotations[i+1] 计算沿全局轴的增量,这可能会起作用。我不是 100% 关于使用 slerp 来“平均”它们,所以我在下面提供了一个替代答案。

标签: c# unity3d quaternions


【解决方案1】:

使用 slerp 平均四元数可能会导致一些意外行为。例如,如果您有 2 个增量旋转,其中一个围绕 y 轴旋转 180 度,一个围绕 y 轴旋转 -180 度,从一个旋转到另一个旋转永远不会产生同一旋转(围绕 y 旋转 0 度)轴)。它实际上总是在某个方向上旋转 180 度。自己试试吧:

Quaternion a = Quaternion.AngleAxis(180f, Vector3.up); 
Quaternion b = Quaternion.AngleAxis(-180f, Vector3.up); 
Quaternion c = Quaternion.Slerp(a, b, 0.5f)

float angle; 
Vector3 axis; 
c.ToAngleAxis(out angle, out axis); 
Debug.Log(angle); 
Debug.Log(axis);

因此,相反,我建议对旋转轴和角度进行平均,特别是使用 global 旋转轴。要获得全局轴上的增量旋转,请从 global axis delta rotation * start rotation = next rotation 开始并以代数方式求解:

                globalAxesDelta * startRot = nextRot
globalAxesDelta * startRot * inv(startRot) = nextRot * inv(startRot)
                           globalAxesDelta = nextRot * inv(startRot)

所以要使用全局轴计算旋转增量,请使用 deltaRotation = rotations[i+1] * Quaternion.Inverse(rotations[i]) 而不是 deltaRotations[i] = Quaternion.Inverse(rotations[i]) * rotations[i+1]。然后,使用ToAngleAxis 将它们转换为轴/角度形式:

Vector3[] deltaAxes = new Vector3[rotations.Length - 1];

for (int i = 0; i < deltaRotations.Length; i++)
{
    Quaternion deltaRotation = rotations[i+1] * Quaternion.Inverse(rotations[i]);
    float angle;  
    Vector3 axis;
    deltaRotation.ToAngleAxis(out angle, out axis);
    deltaAxes[i] = angle * axis;
}

然后平均这些全局轴:

Vector3 avgAxis = Vector3.zero;

foreach (Vector3 angleAxis in deltaAxes)
{
    avgAxis += (1f/deltaRotations.Length) * angleAxis;
}

然后,您可以使用 AddTorque(avgAxis * someTorqueFactor) 之类的方法来沿全局轴应用扭矩。

【讨论】:

  • 好吧,slerp 肯定不是最好的方法。如果您有 2 个增量旋转,其中一个围绕 y 轴旋转 180 度,一个围绕 y 轴旋转 -180 度,从一个旋转到另一个旋转永远不会产生同一旋转(围绕 y 轴旋转 0 度)。它实际上总是在某个方向上旋转 180 度。
  • 自己试试吧:float angle;Vector3 axis;Quaternion a = Quaternion.AngleAxis(180f, Vector3.up);Quaternion b = Quaternion.AngleAxis(-180f, Vector3.up);Quaternion c = Quaternion.Slerp(a, b, 0.5f);c.ToAngleAxis(out angle, out axis);Debug.Log(angle);Debug.Log(axis);
  • 我不知道是否以代数方式平均四元数 - 将它们的每个组件相加并除以项数 - 是否会始终产生任何有用的东西,但它可能会。我认为无论哪种方式,平均轴角表示都更直观。
  • @WyattWehking 这里应该考虑到旋转的角度:deltaAxes[i] = angle * axis;... 不知道为什么不:(
  • 是的,这对我来说也没有意义,我已经编辑了帖子以显示计算平均旋转的更新方法,我会继续试验,但如果你在其中发现任何东西,请告诉我.
【解决方案2】:

一般关于Quaternion真的听Ruzihm! ^^


作为替代 afaik,您还可以平均欧拉角,例如

Quaternion[] deltaRotations = new Quaternion[rotations.Length - 1];

for (int i = 0; i < deltaRotations.Length; i++)
{
    deltaRotations[i] = Quaternion.Inverse(rotations[i]) * rotations[i+1];
}

var averageEuler = Vector3.zero;
foreach(var rot in deltaRotations)
{
    averageEuler += rot.eulerAngles;
}
averageEuler /= deltaRotations.Length;


return Quaternion.Euler(averageEuler);

这应该 afaik 也可以解决 180-180 度的问题,因为 Unity API 已经“规范化”了 eulerAngles


在智能手机上输入,但我希望这个想法变得清晰......并且实际上是这样工作的;)

【讨论】:

  • 欧拉角法绝对比slerp好。我认为这会产生不同的结果,但我不确定,这样可能会感觉更好^^。我希望提问者尝试这两种方法,并就他们在游戏中的感觉是否不同提供反馈
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-04
  • 1970-01-01
相关资源
最近更新 更多