我有两个向量u和v。是否有一种方法可以找到表示从u旋转到v的四元数?
我有两个向量u和v。是否有一种方法可以找到表示从u旋转到v的四元数?
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
不要忘记对q进行归一化。
Richard关于不存在唯一旋转的说法是正确的,但以上方法应该会给出“最短弧”,这可能是你所需要的。
crossproduct
函数将不会返回有效的结果,因此您需要首先检查分别是否满足dot(v1, v2) > 0.999999
和 dot(v1, v2) < -0.999999
的条件,如果是,则应分别返回标识四元数或绕任意轴的180度旋转对于平行向量和相反向量。 - sinisterchipmunksqrt((v1.Length ^ 2) * (v2.Length ^ 2))
可以简化为v1.Length * v2.Length
。我无法通过任何这种变化来产生有意义的结果。 - Joseph Thomsonv.Length^2
比获取v.Length
本身要快,这就是我认为它被表述成这样的原因。 - lvella我想出了一个解决方案,我相信这正是Imbrondir试图呈现的(尽管有一个小错误,这可能是为什么sinisterchipmunk在验证时遇到困难的原因)。
考虑到我们可以构造表示绕轴旋转的四元数,就像这样:
q.w == cos(angle / 2)
q.x == sin(angle / 2) * axis.x
q.y == sin(angle / 2) * axis.y
q.z == sin(angle / 2) * axis.z
两个已规范化向量的点乘积和叉乘积分别是:
dot == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z
由于从向量 u 到 v 的旋转可以通过绕垂直向量旋转角度 (即两向量间的夹角) 来实现,因此看起来我们可以直接从点积和叉积的结果构建表示该旋转的四元数;然而,目前为止,theta = angle / 2,这意味着这样做会导致所需旋转的两倍。
解决方法是计算出介于 u 和 v 中间的向量,并使用 u 和 中间向量 的点积和叉积构建一个表示旋转的四元数,它将使我们旋转 两倍 于 u 和 中间向量 的夹角,从而使我们达到 v!
有一种特殊情况,当 u == -v 时,无法计算出唯一的中间向量。 这是符合预期的,因为有无限多的“最短弧”旋转方式可以将我们从 u 旋转到 v,因此我们必须沿着与 u(或v)正交的任意向量旋转 180 度作为我们的特殊情况解。这可以通过使用 u 与任何其他不与 u 平行的向量进行规范化的叉积来完成。
伪代码如下(显然,在实际情况中,特殊情况必须考虑到浮点精度问题——可能是通过将点积与某个阈值而不是绝对值进行比较来实现)。
还要注意,当 u==v 时,没有 特殊情况(产生单位四元数-可以亲自检查一下)。
// N.B. the arguments are _not_ axis and angle, but rather the
// raw scalar-vector components.
Quaternion(float w, Vector3 xyz);
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
// It is important that the inputs are of equal length when
// calculating the half-way vector.
u = normalized(u);
v = normalized(v);
// Unfortunately, we have to check for when u == -v, as u + v
// in this case will be (0, 0, 0), which cannot be normalized.
if (u == -v)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
Vector3 half = normalized(u + v);
return Quaternion(dot(u, half), cross(u, half));
}
orthogonal
函数返回给定向量的任何垂直向量。此实现使用与最正交的基向量的叉积。
Vector3 orthogonal(Vector3 v)
{
float x = abs(v.x);
float y = abs(v.y);
float z = abs(v.z);
Vector3 other = x < y ? (x < z ? X_AXIS : Z_AXIS) : (y < z ? Y_AXIS : Z_AXIS);
return cross(v, other);
}
这实际上是被接受回答中提出的解决方案,与半程向量的解法相比似乎稍微快一些(按我的测量结果约快20%,但别完全信任我)。我在此添加它,以防其他像我一样感兴趣的人需要解释。
基本上,您可以计算导致所需旋转两倍的四元数(如其他解决方案中所述),然后找到该四元数与零度之间的四元数中点。
如我之前所述,旋转两倍所需四元数为:
q.w == dot(u, v)
q.xyz == cross(u, v)
零旋转的四元数是:
q.w == 1
q.xyz == (0, 0, 0)
计算两个四元数的中间值只需要对它们求和并归一化结果,就像处理向量一样。但是,与向量一样,这两个四元数必须具有相同的大小,否则结果将偏向于具有更大大小的四元数。q.w = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)
然后对结果进行标准化。伪代码如下:
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
float k_cos_theta = dot(u, v);
float k = sqrt(length_2(u) * length_2(v));
if (k_cos_theta / k == -1)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
return normalized(Quaternion(k_cos_theta + k, cross(u, v)));
}
问题陈述不够明确:对于给定的向量对,不存在唯一的旋转。例如,考虑当u = <1, 0, 0>和v = <0, 1, 0>时。从u到v的一个旋转是绕z轴旋转π / 2。另一个从u到v的旋转是绕向量<1, 1, 0>旋转π。
我对四元数不是很擅长。然而,我为此苦苦挣扎了数小时,也无法使Polaris878的解决方案生效。我尝试了预先归一化v1和v2,归一化q,归一化q.xyz。但仍然无法理解。结果仍然没有给我正确的结果。
最终,我找到了一个可行的解决方案。如果有人需要帮助,这是我的工作(Python)代码:
def diffVectors(v1, v2):
""" Get rotation Quaternion between 2 vectors """
v1.normalize(), v2.normalize()
v = v1+v2
v.normalize()
angle = v.dot(v2)
axis = v.cross(v2)
return Quaternion( angle, *axis )
quat = diffVectors(v1, v2); assert quat * v1 == v2
。 - sinisterchipmunkv.normalize()
。因此答案的标量部分将是 v.dot(v2) = (v1+v2).dot(v2) = 1 + v1.dot(v2),而向量部分将是 v.cross(v2) = (v1+v2).cross(v2) = v1.cross(v2)。 - Don Hatch要找到将u
旋转到v
的最小旋转四元数,请使用以下方法:
align(quat(1, 0, 0, 0), u, v)
function align(Q, u, v)
U = quat(0, ux, uy, uz)
V = quat(0, vx, vy, vz)
return normalize(length(U*V)*Q - V*Q*U)
R = align(Q, u, v)
最重要的是,R
是最接近 Q
的四元数,其本地 u
方向在全局指向 v
方向。因此,R
是最接近 Q
的四元数,它将旋转 u
到 v
。
这可以用来给出所有可能的旋转,这些旋转从 u
到 v
,取决于选择的 Q
。如果您想要从 u
到 v
的最小旋转,就像其他解决方案一样,请使用 Q = quat(1, 0, 0, 0)
。
最常见的情况是,我发现您想要执行的实际操作是将一个轴与另一个轴进行一般的对齐。
// If you find yourself often doing something like
quatFromTo(toWorldSpace(Q, localFrom), worldTo)*Q
// you should instead consider doing
align(Q, localFrom, worldTo)
假如您想要得到四元数 Y
,该四元数仅代表了绕 y 轴的旋转(也就是 Q
的偏航角),我们可以使用以下方法计算出 Y
。
Y = align(quat(Qw, Qx, Qy, Qz), vec(0, 1, 0), vec(0, 1, 0))
// simplifies to
Y = normalize(quat(Qw, 0, Qy, 0))
如果您想要重复执行相同的对齐操作,因为此操作与将四元数投影到嵌入4D空间中的2D平面相同,我们可以将此操作表示为与4x4投影矩阵A*Q
相乘。
A = I - leftQ(V)*rightQ(U)/length(U*V)
// which expands to
A = mat4(
1 + ux*vx + uy*vy + uz*vz, uy*vz - uz*vy, uz*vx - ux*vz, ux*vy - uy*vx,
uy*vz - uz*vy, 1 + ux*vx - uy*vy - uz*vz, uy*vx + ux*vy, uz*vx + ux*vz,
uz*vx - ux*vz, uy*vx + ux*vy, 1 - ux*vx + uy*vy - uz*vz, uz*vy + uy*vz,
ux*vy - uy*vx, uz*vx + ux*vz, uz*vy + uy*vz, 1 - ux*vx - uy*vy + uz*vz)
// A can be applied to Q with the usual matrix-vector multiplication
R = normalize(A*Q)
//LeftQ is a 4x4 matrix which represents the multiplication on the left
//RightQ is a 4x4 matrix which represents the multiplication on the Right
LeftQ(w, x, y, z) = mat4(
w, -x, -y, -z,
x, w, -z, y,
y, z, w, -x,
z, -y, x, w)
RightQ(w, x, y, z) = mat4(
w, -x, -y, -z,
x, w, z, -y,
y, -z, w, x,
z, y, -x, w)
I = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)
从算法角度来看,最快的解决方案在伪代码中。
Quaternion shortest_arc(const vector3& v1, const vector3& v2 )
{
// input vectors NOT unit
Quaternion q( cross(v1, v2), dot(v1, v2) );
// reducing to half angle
q.w += q.magnitude(); // 4 multiplication instead of 6 and more numerical stable
// handling close to 180 degree case
//... code skipped
return q.normalized(); // normalize if you need UNIT quaternion
}
请确认您需要使用单位四元数(通常,这是插值所需的)。
注意: 某些操作可以比单位四元数更快地使用非单位四元数。
有些答案似乎没有考虑到叉积可能为0的情况。下面的代码片段使用角轴表示:
//v1, v2 are assumed to be normalized
Vector3 axis = v1.cross(v2);
if (axis == Vector3::Zero())
axis = up();
else
axis = axis.normalized();
return toQuaternion(axis, ang);
toQuaternion
可以按照以下方式实现:
static Quaternion toQuaternion(const Vector3& axis, float angle)
{
auto s = std::sin(angle / 2);
auto u = axis.normalized();
return Quaternion(std::cos(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}
:
Quaternion::FromTwoVectors(from, to)
toQuaternion(axis, ang)
-> you forgot to specify what is ang
- Maksym Ganenkofunction fromVectors(u, v) {
d = dot(u, v)
w = cross(u, v)
return Quaternion(d + sqrt(d * d + dot(w, w)), w).normalize()
}
如果已知向量 u 到向量 v 是单位向量,则该函数简化为
function fromUnitVectors(u, v) {
return Quaternion(1 + dot(u, v), cross(u, v)).normalize()
}
仅使用归一化四元数,我们可以用以下术语表达Joseph Thompson的答案。
设q_v =(0,u_x,v_y,v_z)和q_w =(0,v_x,v_y,v_z),并考虑
q = q_v * q_w =(-u点积v,uxv)。
因此,将q表示为q(q_0,q_1,q_2,q_3),我们有
q_r =(1-q_0,q_1,q_2,q_3)。normalize()