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

时间:2009-07-23 13:45:46

标签: math vector quaternions

我有两个向量u和v。有没有办法找到一个表示从u到v的旋转的四元数?

8 个答案:

答案 0 :(得分:103)

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

不要忘记将q标准化。

理查德对于没有独特的旋转是正确的,但上面应该给出“最短的弧度”,这可能是你需要的。

答案 1 :(得分:58)

中途矢量解决方案

我想出了我认为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(向量之间的角度)来实现,看起来好像我们可以从点和交叉积的结果直接构造表示这种旋转的四元数;但是,就其而言, theta = angle / 2 ,这意味着这样做会导致所需旋转的两倍。

一种解决方案是计算 u v 之间的向量,并使用 u 的点和叉积和中途向量构造一个四元数,表示旋转两次 u 中途之间的角度vector,它将我们带到 v

有一种特殊情况,其中 u == -v 并且无法计算唯一的中间向量。这是预期的,因为无限多的“最短弧”旋转可以将我们从 u 带到 v ,并且我们必须简单地围绕与<正交的任何向量旋转180度。 strong> u (或 v )作为我们的特例解决方案。这是通过将 u 的规范化交叉积与任何其他 u 进行对比来完成的。

伪代码遵循(显然,实际上特殊情况必须考虑浮点不准确性 - 可能通过检查点积与某个阈值而不是绝对值)。

另请注意, u == v 时会出现 no 特殊情况(生成标识四元数 - 请亲自查看)。

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

答案 2 :(得分:5)

所陈述的问题没有明确定义:给定的一对向量没有唯一的旋转。考虑这种情况,例如,其中u = &lt; 1,0,0&gt; 和v = &lt; 0,1,0&gt; 。从u到v的一次旋转将是围绕z轴的 pi / 2 旋转。从u到v的另一个旋转是围绕向量&lt; 1,1,0&gt; pi 旋转。

答案 3 :(得分:4)

为什么不使用纯四元数来表示向量?如果你首先将它们标准化,那就更好了。
    q 1 =(0 u x u y u z )'
    q 2 =(0 v x v y v z )'
    q 1 q rot = q 2
预先乘以q 1 -1
    q rot = q 1 -1 q 2
    其中q 1 -1 = q 1 conj / q norm
这可以被认为是“左派”。 正确的划分,这不是你想要的是:
    q rot,right = q 2 -1 q 1

答案 4 :(得分:2)

一些答案​​似乎并未考虑叉积可能为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)

答案 5 :(得分:1)

我对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度旋转)

答案 6 :(得分:0)

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

 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
 }

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

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

答案 7 :(得分:0)

仅使用归一化四元数,我们就可以用以下术语表达 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 dot v, u x v).

所以将 q 表示为 q(q_0, q_1, q_2, q_3) 我们有

q_r = (1 - q_0, q_1, q_2, q_3).normalize()

相关问题