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

sdf*_*az1 99 math vector quaternions

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

Pol*_*878 111

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

不要忘记将q标准化.

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

  • 请注意,这不处理平行向量的情况(两个方向相同或指向相反方向).`crossproduct`在这些情况下无效,所以你首先需要分别检查`dot(v1,v2)> 0.999999`和`dot(v1,v2)< - 0.999999`,并返回一个标识quat for parallel向量,或返回相反向量的180度旋转(绕任何轴). (28认同)
  • 可以在[ogre3d源代码]中找到一个很好的实现(https://bitbucket.org/sinbad/ogre/src/9db75e3ba05c/OgreMain/include/OgreVector3.h#cl-651) (10认同)
  • @JosephThomson 获得“v.Length^2”通常比“v.Length”本身更快,这就是为什么我相信它是这样措辞的。 (4认同)
  • @sinisterchipmunk实际上,如果v1 = v2,则交叉产品将是(0,0,0)而w将是正数,这将标准化为同一性.根据http://www.gamedev.net/topic/429507-finding-the-quaternion-betwee-two-vectors/page__p__3856228#entry3856228,对于v1 = -v2及其附近,它应该也可以正常工作. (3认同)
  • 怎么有人用这种技术工作?例如,`sqrt((v1.Length ^ 2)*(v2.Length ^ 2))`简化为`v1.Length*v2.Length`.我无法得到任何变化来产生明智的结果. (3认同)
  • 是的,这很有效.参见[源代码](https://github.com/toji/gl-matrix/blob/f0583ef53e94bc7e78b78c8a24f09ed5e2f7a20c/src/gl-matrix/quat.js#L54).L61处理向量是否面向相反的方向(返回PI,否则它将按@ jpa的注释返回标识).L67处理并行向量:数学上不必要,但速度更快.L72是Polaris878的答案,假设两个向量都是单位长度(避免使用sqrt).另见[单元测试](https://github.com/toji/gl-matrix/blob/f0583ef53e94bc7e78b78c8a24f09ed5e2f7a20c/spec/gl-matrix/quat-spec.js#L28). (2认同)
  • @sinisterchipmunk你是对的,它确实有效,虽然两个和'sqrt`的力量是多余的.我已经设法弄清楚这个等式正在做什么 - 它取零旋转四元数和四元数的平均值来旋转所需角度​​的两倍,当标准化时,产生所需的旋转.本质上,它找到了中间四元数,而不是像我的方法那样的中间向量.这种方法实际上可能略微更有效,因为它似乎需要更少的浮点运算. (2认同)
  • 在三角项中,矢量部分是vec = | v1 |*| v2 |*sin(弧)*轴,其中轴是作为归一化叉积获得的旋转轴.标量部分是w = | v1 |*| v2 |*(1 + cos(弧)).现在因为sin(弧)= 2*sin(弧/ 2)*cos(弧/ 2)和1 + cos(弧)= 2*cos(弧/ 2)^ 2,所以计算的矢量是所需的单位四元数角度的一半,按因子2*| v1 |*| v2 |*cos(arc/2)缩放.这意味着Peter Ehrlich的程序仅在a和b已经标准化时才有效.在他的博客文章中很好地解释了这一点. (2认同)
  • 答案中的方法 [DOES NOT](/sf/ask/3882313921/) 有效! (2认同)

Jos*_*son 60

中途矢量解决方案

我想出了我认为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
Run Code Online (Sandbox Code Playgroud)

并且两个归一化向量的点和叉积是:

dot     == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z
Run Code Online (Sandbox Code Playgroud)

看到从uv的旋转可以通过围绕垂直向量旋转theta(矢量之间的角度)来实现,看起来好像我们可以直接构造一个四元数来表示点和交叉乘积的结果的旋转; 然而,就其而言,θ=角度/ 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));
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

中途四元数解决方案

这实际上是在接受的答案中提出的解决方案,它似乎比中途矢量解决方案略快(我的测量速度快了约20%,但不记得我的话).我在这里添加它,以防其他像我这样的人对解释感兴趣.

基本上,不是使用中间向量计算四元数,而是可以计算四元数,这将导致所需旋转的两倍(如另一个解决方案中详述),并找到四元数和零度之间的四元数.

正如我之前解释的那样,所需旋转加倍的四元数是:

q.w   == dot(u, v)
q.xyz == cross(u, v)
Run Code Online (Sandbox Code Playgroud)

零旋转的四元数是:

q.w   == 1
q.xyz == (0, 0, 0)
Run Code Online (Sandbox Code Playgroud)

计算中间四元数只需要对四元数求和并对结果进行归一化,就像使用向量一样.但是,与矢量的情况一样,四元数必须具有相同的幅度,否则结果将偏向具有较大幅度的四元数.

由两个向量的点和叉积构成的四元数将具有与那些乘积相同的量级: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)
Run Code Online (Sandbox Code Playgroud)

然后规范化结果.伪代码如下:

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)));
}
Run Code Online (Sandbox Code Playgroud)

  • +1:好极了!这充当了魅力.应该是接受的答案. (11认同)
  • 有效!谢谢!但是,我找到了另一个类似且解释良好的[链接](http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final)来执行上述操作。我想我应该分享记录;) (2认同)

Ric*_*lap 7

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

  • 事实上,不是有无数种可能的答案吗?因为在将“from”向量与“to”向量对齐后,您仍然可以围绕它的轴自由旋转结果?您是否知道通常可以使用哪些额外信息来限制此选择并使问题得到明确定义? (2认同)

Imb*_*dir 6

我对四元数不太擅长。然而,我为此苦苦挣扎了几个小时,无法使 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 )
Run Code Online (Sandbox Code Playgroud)

如果 v1 和 v2 是并行的,例如 v1 == v2 或 v1 == -v2 (有一定的容差),则必须采用特殊情况,我认为解决方案应该是 Quaternion(1, 0,0,0) (无旋转)或四元数(0, *v1)(180 度旋转)


mad*_*man 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

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