Kochanek–Bartels-like Rotation Splines#
Remember Kochanek–Bartels splines in Euclidean space? We can try to “translate” those to quaternions by using De Casteljau’s algorithm with Slerp. We only need a way to create the appropriate incoming and outgoing control quaternions, similarly to what we did to create Catmull–Rom-like rotation splines.
We are only considering the more general non-uniform case here. The uniform case can be obtained by simply using time instances \(t_i\) with a step size of 1.
In the notebook about non-uniform Euclidean Kochanek–Bartels splines we showed the following equations for the incoming tangent vector \(\boldsymbol{\dot{x}}_i^{(-)}\) and the outgoing tangent vector \(\boldsymbol{\dot{x}}_i^{(+)}\) at vertex \(\boldsymbol{x}_i\) (which corresponds to the parameter value \(t_i\)):
\begin{align*} a_i &= (1 - T_i) (1 + C_i) (1 + B_i)\\ b_i &= (1 - T_i) (1 - C_i) (1 - B_i)\\ c_i &= (1 - T_i) (1 - C_i) (1 + B_i)\\ d_i &= (1 - T_i) (1 + C_i) (1 - B_i) \end{align*}
\begin{align*} \boldsymbol{\dot{x}}_i^{(+)} &= \frac{ a_i (t_{i+1} - t_i) \, \boldsymbol{v}_{i-1} + b_i (t_i - t_{i-1}) \, \boldsymbol{v}_i }{t_{i+1} - t_{i-1}}\\ \boldsymbol{\dot{x}}_i^{(-)} &= \frac{ c_i (t_{i+1} - t_i) \, \boldsymbol{v}_{i-1} + d_i (t_i - t_{i-1}) \, \boldsymbol{v}_i }{t_{i+1} - t_{i-1}}, \end{align*}
where \(\boldsymbol{v}_i = \frac{\boldsymbol{x}_{i+1} - \boldsymbol{x}_i}{t_{i+1} - t_i}\).
Given those tangent vectors, we know the equations for the incoming control value \(\boldsymbol{\tilde{x}}_i^{(-)}\) and the outgoing control value \(\boldsymbol{\tilde{x}}_i^{(+)}\) from the notebook about non-uniform Euclidean Catmull–Rom splines:
\begin{align*} \boldsymbol{\tilde{x}}_i^{(+)} &= \boldsymbol{x}_i + \frac{(t_{i+1} - t_i)}{3} \boldsymbol{\dot{x}}_i^{(+)} \\ \boldsymbol{\tilde{x}}_i^{(-)} &= \boldsymbol{x}_i - \frac{(t_i - t_{i-1})}{3} \boldsymbol{\dot{x}}_i^{(-)} \end{align*}
We can try to “translate” those equations to quaternions (using some vector operations in the tangent space):
\begin{align*} \vec{\rho}_{i} &= \frac{\ln(\delta_{i})}{t_{i+1} - t_i} \\ \vec{\omega}_i^{(+)} &= \frac{ a_i (t_{i+1} - t_i) \, \vec{\rho}_{i-1} + b_i (t_i - t_{i-1}) \, \vec{\rho}_{i} }{ t_{i+1} - t_{i-1} } \\ \vec{\omega}_i^{(-)} &= \frac{ c_i (t_{i+1} - t_i) \, \vec{\rho}_{i-1} + d_i (t_i - t_{i-1}) \, \vec{\rho}_{i} }{ t_{i+1} - t_{i-1} } \\ \tilde{q}_i^{(+)} &\overset{?}{=} \exp\left(\frac{t_{i+1} - t_i}{3} \, \vec{\omega}_i^{(+)}\right) \, q_i \\ \tilde{q}_i^{(-)} &\overset{?}{=} \exp\left(\frac{t_i - t_{i-1}}{3} \, \vec{\omega}_i^{(-)}\right)^{-1} \, q_i, \end{align*}
where \(\delta_i = q_{i+1} {q_i}^{-1}\) is the relative rotation from \(q_i\) to \(q_{i+1}\), \(\vec{\rho}_{i}\) is the angular velocity along the great arc from \(q_i\) to \(q_{i+1}\) within the parameter interval from \(t_i\) to \(t_{i+1}\), \(\vec{\omega}_i^{(-)}\) is the incoming angular velocity of the Kochanek–Bartels-like quaternion curve at the control point \(q_i\) (which is reached at parameter value \(t_i\)) and \(\vec{\omega}_i^{(+)}\) is the outgoing angular velocity. Finally, \(\tilde{q}_i^{(-)}\) and \(\tilde{q}_i^{(+)}\) are the control quaternions before and after \(q_i\), respectively.
A Python implementation of these equations is available in the class splines.quaternion.KochanekBartels.
[1]:
from splines.quaternion import KochanekBartels
Examples#
This is all a bit abstract, so let’s try a few of those TCB values to see their influence on the rotation spline.
For comparison, you can have a look at the examples for Euclidean Kochanek–Bartels splines.
As so often, we import NumPy and a few helpers from helper.py:
[2]:
import numpy as np
from helper import angles2quat, animate_rotations, display_animation
Let’s define a few example rotations …
[3]:
rotations = [
angles2quat(0, 0, 0),
angles2quat(90, 0, -45),
angles2quat(-45, 45, -90),
angles2quat(135, -35, 90),
angles2quat(90, 0, 0),
]
… and a helper function that allows us to try out different TCB values:
[4]:
def show_tcb(tcb):
"""Show an animation of rotations with the given TCB values."""
if not isinstance(tcb, dict):
tcb = {'': tcb}
result = {}
for name, tcb in tcb.items():
s = KochanekBartels(
rotations,
alpha=0.5,
endconditions='closed',
tcb=tcb,
)
times = np.linspace(s.grid[0], s.grid[-1], 100, endpoint=False)
result[name] = s.evaluate(times)
display_animation(animate_rotations(result))
When using the default TCB values, a Catmull–Rom-like spline is generated:
[5]:
show_tcb([0, 0, 0])
We can vary tension (T) …
[6]:
show_tcb({
'T = 1': [1, 0, 0],
'T = 0.5': [0.5, 0, 0],
'T = -0.5': [-0.5, 0, 0],
'T = -1': [-1, 0, 0],
})
… continuity (C) …
[7]:
show_tcb({
'C = -1': [0, -1, 0],
'C = -0.5': [0, -0.5, 0],
'C = 0.5': [0, 0.5, 0],
'C = 1': [0, 1, 0],
})
… and bias (B):
[8]:
show_tcb({
'B = 1': [0, 0, 1],
'B = 0.5': [0, 0, 0.5],
'B = -0.5': [0, 0, -0.5],
'B = -1': [0, 0, -1],
})
Using the largest tension value (\(T = 1\)) produces the same rotations as using the smallest continuity value (\(C = -1\)). However, the timing is different. With large tension values, rotation slows down close to the control points. With small continuity, angular velocity varies less.
[9]:
show_tcb({
'T = 1': [1, 0, 0],
'C = -1': [0, -1, 0],
})
Just like in the Euclidean case, \(B = -1\) followed by \(B = 1\) can be used to create linear – i.e. Slerp – segments.
[10]:
show_tcb({
'Catmull–Rom': [0, 0, 0],
'2 linear segments': [
(0, 0, 1),
(0, 0, 0),
(0, 0, -1),
(0, 0, 1),
(0, 0, -1),
],
'C = -1': [0, -1, 0],
})