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