This page was generated from doc/rotation/end-conditions-natural.ipynb. Interactive online version: Binder badge.

“Natural” End Conditions§

In the notebook about “natural” end conditions for Euclidean splines we have derived the following equations for calculating the second and penultimate control points of cubic Bézier splines:

\begin{align*} \boldsymbol{\tilde{x}}_0^{(+)} &= \frac{\boldsymbol{x}_0 + \boldsymbol{\tilde{x}}_1^{(-)}}{2} \\ \boldsymbol{\tilde{x}}_{N-1}^{(-)} &= \frac{\boldsymbol{x}_{N-1} + \boldsymbol{\tilde{x}}_{N-2}^{(+)}}{2} \end{align*}

These equations can be “translated” to quaternions like this:

\begin{align*} \tilde{q}_0^{(+)} &= \left(\tilde{q}_1^{(-)} {q_0}^{-1}\right)^\frac{1}{2} q_0 \\ \tilde{q}_{N-1}^{(-)} &= \left(\tilde{q}_{N-2}^{(+)} {q_{N-1}}^{-1}\right)^\frac{1}{2} q_{N-1} \end{align*}

When considering that the control polygon starts with the quaternions \(\left( q_0, \tilde{q}_0^{(+)}, \tilde{q}_1^{(-)}, q_1, \tilde{q}_1^{(+)}, \dots \right)\) and ends with \(\left( \dots, q_{N-2}, \tilde{q}_{N-2}^{(+)}, \tilde{q}_{N-1}^{(-)}, q_{N-1} \right)\), we can see that the equations are symmetrical. The resulting control quaternion is calculated as the rotation half-way between the first and third control quaternion, counting either from the beginning (\(q_0\)) or the end (\(q_{N-1}\)) of the spline.

[1]:
def natural_end_condition(first, third):
    """Return second control quaternion given the first and third.

    This also works when counting from the end of the spline.

    """
    return first.rotation_to(third)**(1 / 2) * first

Examples§

Let’s first import NumPy, a few helpers from helper.py and the splines.quaternion.DeCasteljau class:

[2]:
import numpy as np
from helper import angles2quat, animate_rotations, display_animation
from splines.quaternion import DeCasteljau

Furthermore, let’s define a helper function for evaluating a single spline segment:

[3]:
def calculate_rotations(control_quaternions):
    times = np.linspace(0, 1, 50)
    return DeCasteljau(
        segments=[control_quaternions],
    ).evaluate(times)
[4]:
q0 = angles2quat(45, 0, 0)
q1 = angles2quat(-45, 0, 0)
[5]:
q1_control = angles2quat(-45, 0, -90)
[6]:
ani = animate_rotations({
    'natural begin': calculate_rotations(
        [q0, natural_end_condition(q0, q1_control), q1_control, q1]),
})
[7]:
display_animation(ani, default_mode='reflect')
[8]:
q0_control = angles2quat(45, 0, 90)
[9]:
ani = animate_rotations({
    'natural end': calculate_rotations(
        [q0, q0_control, natural_end_condition(q1, q0_control), q1]),
})
[10]:
display_animation(ani, default_mode='reflect')