平均一系列旋转并使用它们来施加扭矩

Averaging an array of rotations and using them to apply torque

我正在构建一个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 使用他的方法进行投掷旋转的结果时,它们看起来是正确的,问题一定是介于那个和脉冲扭矩之间

使用 slerp 对四元数进行平均可能会导致一些意外行为。例如,如果您有 2 个增量旋转,一个绕 y 轴旋转 180 度,一个绕 y 轴旋转 -180 度,从一个旋转到另一个旋转永远不会产生恒等旋转(旋转 0 度绕 y 轴轴)。它实际上总是在某个方向上旋转 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 旋转轴。要获得全局轴的增量旋转,从 开始并用代数求解:

                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:

将它们转换为 axis/angle 形式
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) 之类的东西来沿全局轴应用扭矩。

总的来说Quaternion真的听! ^^


作为替代 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


在智能手机上打字,但我希望这个想法变得清晰..并且确实如此有效;)