This page was generated from doc/rotation/kochanek-bartels.ipynb. Interactive online version: Binder badge.

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.

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],
})