寻找表示从一个向量到另一个向量的旋转的四元数


Answers:


115
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

不要忘记标准化q。

理查德(Richard)认为没有唯一的轮换是正确的,但是上面的代码应该给出“最短弧线”,这可能就是您所需要的。


30
请注意,这不适用于并行向量(相同方向或相反方向)。crossproduct在这些情况下将是无效的,因此您首先需要分别检查dot(v1, v2) > 0.999999dot(v1, v2) < -0.999999,并为并行矢量返回同一性quat,或者为相反矢量返回180度旋转(绕任何轴)。
sinisterchipmunk 2012年

11
一个好的执行本中可以找到OGRE3D源代码
若昂·波特拉

4
@sinisterchipmunk实际上,如果v1 = v2,叉积将为(0,0,0),而w为正,这将归一化为恒等式。根据gamedev.net/topic/…的说法,它对于v1 = -v2及其附近也应该可以正常工作。
2012年

3
谁能使这种技术起作用?首先,sqrt((v1.Length ^ 2) * (v2.Length ^ 2))简化为v1.Length * v2.Length。我无法获得任何变化以产生明智的结果。
Joseph Thomson

2
是的,这可行。参见源代码。L61处理向量是否面对相反的方向(返回PI,否则按@jpa的评论返回标识)。L67处理并行向量:数学上不必要,但速度更快。L72是Polaris878的答案,假设两个向量均为单位长度(避免sqrt)。另请参阅单元测试
sinisterchipmunk

63

半路矢量解决方案

我提出了我相信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

看到从uv的旋转可以通过围绕垂直矢量旋转theta(矢量之间的角度)来实现,看来我们可以根据点和叉积的结果直接构造一个表示该旋转的四元数; 但是,就目前而言,theta = angle / 2,这意味着这样做将导致所需旋转次数增加一倍。

一种解决方案是计算uv之间的向量,并使用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)

就像向量一样,计算四元数四元仅是对四元数求和并将结果归一化的问题。但是,与向量一样,四元数必须具有相同的大小,否则结果将偏向具有较大值的四元数。

由两个向量的点和叉积构成的四元数将具有与这些乘积相同的大小:length(u) * length(v)。与其将所有四个分量都除以该因子,我们还可以扩大单位四元数。并且,如果您想知道为什么使用来接受答案似乎很复杂sqrt(length(u) ^ 2 * length(v) ^ 2),那是因为向量的平方长度比长度的平方要快,所以我们可以节省一个sqrt计算。结果是:

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)));
}

12
+1:太好了!这起到了魅力。应该是公认的答案。
Rekin 2013年

1
四元数语法在某些示例(Quaternion(xyz,w)和Quaternion(w,xyz))上已启用。似乎在最后一个代码块中,弧度和度也混合在一起表示角度(180对k_cos_theta + k)。
吉列尔莫·布拉斯科

1
四元数(float,Vector3)是从标量向量构造的,而四元数(Vector3,float)是从轴角度构造的。可能会造成混淆,但我认为这是正确的。如果您仍然认为这是错误的,请纠正我!
2014年

有效!谢谢!但是,我发现了另一个类似的且经过充分解释的链接可以执行上述操作。以为我应该分享记录;)
罪人2014年

1
@JosephThomson中途四元数解似乎来自这里
legends2k 2014年

6

所陈述的问题尚未明确定义:给定的一对矢量没有唯一的旋转。考虑这种情况,例如u = <1,0,0>和v = <0,1,0>。从u到v的一圈是围绕z轴的pi / 2圈。从u到v的另一个旋转将是围绕向量<1,1,0>pi旋转。


1
实际上,没有无限可能的答案吗?因为在将“ from”向量与“ to”向量对齐之后,您仍然可以围绕轴自由旋转结果吗?您是否知道通常可以使用哪些额外信息来约束此选择并使问题得到明确定义?
Doug McClean 2013年

5

为什么不使用纯四元数表示向量?最好先将它们标准化。
q 1 =(0 U X ü ÿ Ù Ž) '
q 2 =(0V X v ÿ v Ž)'
q 1 q = Q 2
预乘法其中q 1 -1
q = Q 1 -1 q 2
其中q 1 -1 = q 1 conj / q 范数
这可以被认为是“左分裂”。右除法,这不是您想要的:
q 腐烂,右 = q 2 -1 q 1


2
我迷路了,不是从q1到q2的旋转计算为q_2 = q_rot q_1 q_rot ^ -1吗?
yota

4

我对Quaternion不太好。但是,我为此奋斗了几个小时,无法使用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 )

如果v1和v2与v1 == v2或v1 == -v2并存(具有一定的容忍度),则必须进行特殊处理,我认为解决方案应为Quaternion(1、0、0、0)(不旋转)或四元数(0,* v1)(旋转180度)


我有一个有效的实现,但是您的这个更漂亮,所以我真的希望它能起作用。不幸的是,它未能通过我的所有测试案例。我的测试都看起来像quat = diffVectors(v1, v2); assert quat * v1 == v2
sinisterchipmunk 2012年

由于angle从点产品中获得价值,所以这根本不可能奏效。
sam hocevar 2014年

Quaternion()函数在哪里?
June Wang

3

一些答案似乎没有考虑叉积可能为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);
}

如果您使用的是Eigen库,则还可以执行以下操作:

Quaternion::FromTwoVectors(from, to)

toQuaternion(axis, ang)->您忘记指定了什么ang
Maksym Ganenko '19

第二个参数是angle四元数的轴角表示的一部分,以弧度为单位。
Shital Shah

要求您让四元数从一个向量旋转到另一个向量。您没有角度,您必须先计算它。您的答案应包含角度的计算。干杯!
Maksym Ganenko,

这是C ++吗?ux()是什么?
June Wang,

是的,这是C ++。u是Eigen库中的向量类型(如果使用的是一种)。
Shital Shah

2

从算法的角度来看,最快的解决方案是伪代码

 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
 }

确保需要单位四元数(通常,插值是必需的)。

注意:非单位四元数可以比单元更快地用于某些操作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.