平均一系列旋转并使用它们来施加扭矩
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
。
在智能手机上打字,但我希望这个想法变得清晰..并且确实如此有效;)
我正在构建一个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
:
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
。
在智能手机上打字,但我希望这个想法变得清晰..并且确实如此有效;)