限制俯仰、偏航和横滚
Constrain pitch, yaw, & roll
我有一个表示为四元数的旋转,我试图限制俯仰、偏航和横滚轴。我尝试这样做:
public struct Orientation
{
public Vector3 up, forward;
public Orientation(Vector3 up, Vector3 forward)
{
this.up = up;
this.forward = forward;
}
}
public static Orientation[] orientations = new Orientation[3]
{
new Orientation(Vector3.right, Vector3.up),
new Orientation(Vector3.up, Vector3.forward),
new Orientation(Vector3.forward, Vector3.right)
};
public enum Axis
{
Pitch,
Yaw,
Roll
};
private Vector3 ConstrainAxis(Vector3 vector, Axis axis, float from, float to)
{
Orientation orientation = orientations[(int)axis];
float theta = (to - from) * 0.5F;
Vector3 cons = Quaternion.AngleAxis(from + theta, orientation.up) * orientation.forward;
Vector3 proj = Vector3.ProjectOnPlane(vector, orientation.up);
return ConstrainVector(cons.normalized, proj.normalized, theta);
}
private Vector3 ConstrainVector(Vector3 from, Vector3 to, float angle)
{
float theta = Mathf.Abs(angle / Vector3.Angle(from, to));
if(theta < 1.0F)
{
return Vector3.Slerp(from, to, theta);
}
return to;
}
事实证明,这只不过是一种过于复杂的约束欧拉角表示的各个组件的方法,它们都受到一个奇怪的抖动问题的影响(万向节锁相关?)。
约束这些轴的最佳方法是什么?
对于关节约束,通常的做法是使用“摆动扭曲”参数化。
为了将当前旋转表示为四元数的“摆动扭曲”,有很好的分解
https://web.archive.org/web/20160909191250/https://www.alinenormoyle.com/weblog/?p=726
“摆动”和“扭曲”的约束可以用四元数完成。
如果我们想将摆动限制在 +-30 度,伪代码如下所示
Quaternion swing;
const double maxMagnitude = sin(0.5 * toRad(30));
const double maxMagnitudeW = sqrt(1.0 - maxMagnitude * maxMagnitude);
if (swing.vec().normSqr() > maxMagnitude * maxMagnitude)
{
swing.vec() = swing.vec().normalized() * maxMagnitude;
swing.w() = maxMagnitudeW;
}
添加到 minorlogic 的答案:保存 targetQuat 的 W 组件的符号很重要。这是扭曲约束的 three.js 实现。似乎还有一些我没有检查过的奇点:
http://www.allenchou.net/2018/05/game-math-swing-twist-interpolation-sterp/
const HEAD_YAW_MAX = 40
const MAX_MAGNITUDE = Math.sin(0.5 * THREE.Math.degToRad(HEAD_YAW_MAX));
const MAX_MAGNITUDE_W = Math.sqrt(1.0 - MAX_MAGNITUDE * MAX_MAGNITUDE);
const MAX_MAG_POW_2 = MAX_MAGNITUDE * MAX_MAGNITUDE;
in update function
const qT = this.headBone.quaternion;
v1.set(qT.x, qT.y, qT.z); //todo check singularity: rotation by 180
v1.projectOnVector(this.headBone.up); //up is direction around twist
// v1.set(0, qT.y, 0); //project on y axis
q1.set(v1.x, v1.y, v1.z, qT.w); //twist
q1.normalize();
q3.copy(q1).conjugate();
q2.multiplyQuaternions(qT, q3); //swing
q2.normalize();
v1.set(q1.x, q1.y, q1.z);
if (v1.lengthSq() > MAX_MAG_POW_2) {
v1.setLength(MAX_MAGNITUDE);
const sign = qT.w < 0 ? -1 : 1;
q1.set(v1.x, v1.y, v1.z, sign * MAX_MAGNITUDE_W);
this.headBone.quaternion.multiplyQuaternions(q2, q1); //swing * twist
}
swing twist参数化算法来源:
Component of a quaternion rotation around an axis
看来这个问题比较热门,所以我会在minorlogic提供的答案的基础上展开。在 minorlogic 的帮助下,我最终创建了这个“QuaternionExtension”class,它既可以将四元数分解为其摆动和扭曲分量,又可以将四元数约束在任意轴上。很抱歉 6 年前没有分享这个,但现在就在这里。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class QuaternionExtension
{
public static void Decompose(this Quaternion quaternion, Vector3 direction, out Quaternion swing, out Quaternion twist)
{
Vector3 vector = new Vector3(quaternion.x, quaternion.y, quaternion.z);
Vector3 projection = Vector3.Project(vector, direction);
twist = new Quaternion(projection.x, projection.y, projection.z, quaternion.w).normalized;
swing = quaternion * Quaternion.Inverse(twist);
}
public static Quaternion Constrain(this Quaternion quaternion, float angle)
{
float magnitude = Mathf.Sin(0.5F * angle);
float sqrMagnitude = magnitude * magnitude;
Vector3 vector = new Vector3(quaternion.x, quaternion.y, quaternion.z);
if (vector.sqrMagnitude > sqrMagnitude)
{
vector = vector.normalized * magnitude;
quaternion.x = vector.x;
quaternion.y = vector.y;
quaternion.z = vector.z;
quaternion.w = Mathf.Sqrt(1.0F - sqrMagnitude) * Mathf.Sign(quaternion.w);
}
return quaternion;
}
}
我有一个表示为四元数的旋转,我试图限制俯仰、偏航和横滚轴。我尝试这样做:
public struct Orientation
{
public Vector3 up, forward;
public Orientation(Vector3 up, Vector3 forward)
{
this.up = up;
this.forward = forward;
}
}
public static Orientation[] orientations = new Orientation[3]
{
new Orientation(Vector3.right, Vector3.up),
new Orientation(Vector3.up, Vector3.forward),
new Orientation(Vector3.forward, Vector3.right)
};
public enum Axis
{
Pitch,
Yaw,
Roll
};
private Vector3 ConstrainAxis(Vector3 vector, Axis axis, float from, float to)
{
Orientation orientation = orientations[(int)axis];
float theta = (to - from) * 0.5F;
Vector3 cons = Quaternion.AngleAxis(from + theta, orientation.up) * orientation.forward;
Vector3 proj = Vector3.ProjectOnPlane(vector, orientation.up);
return ConstrainVector(cons.normalized, proj.normalized, theta);
}
private Vector3 ConstrainVector(Vector3 from, Vector3 to, float angle)
{
float theta = Mathf.Abs(angle / Vector3.Angle(from, to));
if(theta < 1.0F)
{
return Vector3.Slerp(from, to, theta);
}
return to;
}
事实证明,这只不过是一种过于复杂的约束欧拉角表示的各个组件的方法,它们都受到一个奇怪的抖动问题的影响(万向节锁相关?)。
约束这些轴的最佳方法是什么?
对于关节约束,通常的做法是使用“摆动扭曲”参数化。 为了将当前旋转表示为四元数的“摆动扭曲”,有很好的分解 https://web.archive.org/web/20160909191250/https://www.alinenormoyle.com/weblog/?p=726
“摆动”和“扭曲”的约束可以用四元数完成。
如果我们想将摆动限制在 +-30 度,伪代码如下所示
Quaternion swing;
const double maxMagnitude = sin(0.5 * toRad(30));
const double maxMagnitudeW = sqrt(1.0 - maxMagnitude * maxMagnitude);
if (swing.vec().normSqr() > maxMagnitude * maxMagnitude)
{
swing.vec() = swing.vec().normalized() * maxMagnitude;
swing.w() = maxMagnitudeW;
}
添加到 minorlogic 的答案:保存 targetQuat 的 W 组件的符号很重要。这是扭曲约束的 three.js 实现。似乎还有一些我没有检查过的奇点: http://www.allenchou.net/2018/05/game-math-swing-twist-interpolation-sterp/
const HEAD_YAW_MAX = 40
const MAX_MAGNITUDE = Math.sin(0.5 * THREE.Math.degToRad(HEAD_YAW_MAX));
const MAX_MAGNITUDE_W = Math.sqrt(1.0 - MAX_MAGNITUDE * MAX_MAGNITUDE);
const MAX_MAG_POW_2 = MAX_MAGNITUDE * MAX_MAGNITUDE;
in update function
const qT = this.headBone.quaternion;
v1.set(qT.x, qT.y, qT.z); //todo check singularity: rotation by 180
v1.projectOnVector(this.headBone.up); //up is direction around twist
// v1.set(0, qT.y, 0); //project on y axis
q1.set(v1.x, v1.y, v1.z, qT.w); //twist
q1.normalize();
q3.copy(q1).conjugate();
q2.multiplyQuaternions(qT, q3); //swing
q2.normalize();
v1.set(q1.x, q1.y, q1.z);
if (v1.lengthSq() > MAX_MAG_POW_2) {
v1.setLength(MAX_MAGNITUDE);
const sign = qT.w < 0 ? -1 : 1;
q1.set(v1.x, v1.y, v1.z, sign * MAX_MAGNITUDE_W);
this.headBone.quaternion.multiplyQuaternions(q2, q1); //swing * twist
}
swing twist参数化算法来源: Component of a quaternion rotation around an axis
看来这个问题比较热门,所以我会在minorlogic提供的答案的基础上展开。在 minorlogic 的帮助下,我最终创建了这个“QuaternionExtension”class,它既可以将四元数分解为其摆动和扭曲分量,又可以将四元数约束在任意轴上。很抱歉 6 年前没有分享这个,但现在就在这里。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class QuaternionExtension
{
public static void Decompose(this Quaternion quaternion, Vector3 direction, out Quaternion swing, out Quaternion twist)
{
Vector3 vector = new Vector3(quaternion.x, quaternion.y, quaternion.z);
Vector3 projection = Vector3.Project(vector, direction);
twist = new Quaternion(projection.x, projection.y, projection.z, quaternion.w).normalized;
swing = quaternion * Quaternion.Inverse(twist);
}
public static Quaternion Constrain(this Quaternion quaternion, float angle)
{
float magnitude = Mathf.Sin(0.5F * angle);
float sqrMagnitude = magnitude * magnitude;
Vector3 vector = new Vector3(quaternion.x, quaternion.y, quaternion.z);
if (vector.sqrMagnitude > sqrMagnitude)
{
vector = vector.normalized * magnitude;
quaternion.x = vector.x;
quaternion.y = vector.y;
quaternion.z = vector.z;
quaternion.w = Mathf.Sqrt(1.0F - sqrMagnitude) * Mathf.Sign(quaternion.w);
}
return quaternion;
}
}