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 class splines.quaternion.DeCasteljau:

[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')