<p>从代数的角度来看,用四元数来表示旋转并不困难。就我个人而言,我发现很难从视觉上解释四元数,但使用它们进行旋转的公式非常简单。我将在这里提供一组基本的引用函数。<sup>1</sup>(另请参见<a href="https://stackoverflow.com/a/42180896/577088">hosolmaz</a>给出的这个可爱的答案,他将这些函数打包在一起,创建了一个方便的四元数类。)</p>
<p>您可以将四元数(出于我们的目的)视为标量加上三维向量——抽象地说,<code>w + xi + yj + zk</code>,这里用一个简单的元组<code>(w, x, y, z)</code>表示。三维旋转的空间完全由四元数的子空间表示,即<em>单位</em>四元数的空间,因此要确保四元数是标准化的。您可以按照将任何4个向量规格化的方式执行此操作(即,幅值应接近1;如果不是,请按幅值缩小值):</p>
<pre><code>def normalize(v, tolerance=0.00001):
mag2 = sum(n * n for n in v)
if abs(mag2 - 1.0) > tolerance:
mag = sqrt(mag2)
v = tuple(n / mag for n in v)
return v
</code></pre>
<p>请注意,为了简单起见,下面的函数假设四元数值<em>已经规范化</em>。实际上,您需要不时地对它们进行重新规范化,但处理这一问题的最佳方法将取决于问题域。这些函数只提供最基本的功能,仅供参考。</p>
<p>每个旋转都由一个单位四元数表示,旋转的串联对应于单位四元数的<em>乘法</em>。公式<sup>2</sup>如下:</p>
<pre><code>def q_mult(q1, q2):
w1, x1, y1, z1 = q1
w2, x2, y2, z2 = q2
w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2
z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2
return w, x, y, z
</code></pre>
<p>要将向量旋转四元数,也需要四元数的共轭。很简单:</p>
<pre><code>def q_conjugate(q):
w, x, y, z = q
return (w, -x, -y, -z)
</code></pre>
<p>现在,四元数向量乘法非常简单,只需将向量转换为四元数(通过设置<code>w = 0</code>,并保持<code>x</code>、<code>y</code>和<code>z</code>不变),然后乘法<code>q * v * q_conjugate(q)</code>:</p>
<pre><code>def qv_mult(q1, v1):
q2 = (0.0,) + v1
return q_mult(q_mult(q1, q2), q_conjugate(q1))[1:]
</code></pre>
<p>最后,您需要知道如何从轴角度旋转转换为四元数。也很简单!在这里通过调用<code>normalize</code>“清理”输入和输出是有意义的。</p>
<pre><code>def axisangle_to_q(v, theta):
v = normalize(v)
x, y, z = v
theta /= 2
w = cos(theta)
x = x * sin(theta)
y = y * sin(theta)
z = z * sin(theta)
return w, x, y, z
</code></pre>
<p>后面:</p>
<pre><code>def q_to_axisangle(q):
w, v = q[0], q[1:]
theta = acos(w) * 2.0
return normalize(v), theta
</code></pre>
<p>下面是一个快速使用示例。围绕x、y和z轴旋转90度的序列将使y轴上的向量返回到其原始位置。此代码执行这些旋转:</p>
<pre><code>x_axis_unit = (1, 0, 0)
y_axis_unit = (0, 1, 0)
z_axis_unit = (0, 0, 1)
r1 = axisangle_to_q(x_axis_unit, numpy.pi / 2)
r2 = axisangle_to_q(y_axis_unit, numpy.pi / 2)
r3 = axisangle_to_q(z_axis_unit, numpy.pi / 2)
v = qv_mult(r1, y_axis_unit)
v = qv_mult(r2, v)
v = qv_mult(r3, v)
print v
# output: (0.0, 1.0, 2.220446049250313e-16)
</code></pre>
<p>请记住,此旋转序列不会将<em>所有</em>向量返回到同一位置;例如,对于x轴上的向量,它将对应于围绕y轴的90度旋转。(此处请记住右手法则;围绕y轴的正旋转将x轴上的向量推入<em>负</em>z区域。)</p>
<pre><code>v = qv_mult(r1, x_axis_unit)
v = qv_mult(r2, v)
v = qv_mult(r3, v)
print v
# output: (4.930380657631324e-32, 2.220446049250313e-16, -1.0)
</code></pre>
<p>一如既往,如果你发现这里有什么问题,请告诉我。</p>
<hr/>
<p><sup>1。它们改编自OpenGL教程<a href="https://web.archive.org/web/20150515143824/http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation" rel="nofollow noreferrer">archived here</a>。</sup></p>
<p><sup>2。四元数乘法公式看起来像老鼠窝,但推导起来很简单(如果繁琐的话)。先注意<code>ii = jj = kk = -1</code>;然后是<code>ij = k</code>,<code>jk = i</code>,<code>ki = j</code>;最后是<code>ji = -k</code>,<code>kj = -i</code>,<code>ik = -j</code>。然后将这两个四元数相乘,然后根据16次相乘的结果将这些项分布并重新排列。这也有助于说明为什么可以使用四元数来表示旋转;最后六个恒等式遵循右手规则,在从</em><code>i</code>到<code>j</code>的旋转和围绕</em><code>k</code>的旋转之间创建双射,以此类推。</sup></p>