I followed D.W.'s approach and found a solution as follows:
In order to use an online derivative calculator, I expressed the cubic spline with points P0
, P1
, P2
and P3
using variables a
through h
:
P0 = (a, e)
P1 = (b, f)
P2 = (c, g)
P3 = (d, h)
The x-y-coordinates and their derivatives are as follows:
x(t) = a*(1-t)^3+3*b*(1-t)^2*t+3*c*(1-t)*t^2+d*t^3
x'(t) = 3*((d-3*c+3*b-a)*t^2+2*(c-2*b+a)*t+(b-a))
x''(t) = 6*((d-3*c+3*b-a)*t+(c-2*b+a))
y(t) = e*(1-t)^3+3*f*(1-t)^2*t+3*g*(1-t)*t^2+h*t^3
y'(t) = 3*((h-3*g+3*f-e)*t^2+2*(g-2*f+e)*t+(f-e))
y''(t) = 6*((h-3*g+3*f-e)*t+(g-2*f+e))
In order to keep the equations in a more compact form, I introduced the following substitutions:
m = d-3*c+3*b-a
n = c-2*b+a
o = b-a
p = h-3*g+3*f-e
q = g-2*f+e
r = f-e
This leads to the following simplified derivatives:
x'(t) = 3*(m*t^2+2*n*t+o)
x''(t) = 6*(m*t+n)
y'(t) = 3*(p*t^2+2*q*t+r)
y''(t) = 6*(p*t+q)
Using the mentioned curvature equation and the online derivative calculator I obtained the following expression with its derivative:
k(t) = (x' * y'' - y' * x'') / (x'^2 + y'^2)^(3/2)
= ((3*(m*t^2+2*n*t+o)) * (6*(p*t+q)) - (3*(p*t^2+2*q*t+r)) * (6*(m*t+n))) / ((3*(m*t^2+2*n*t+o))^2 + (3*(p*t^2+2*q*t+r))^2)^(3/2)
k'(t) = (-18*m*(p*t^2+2*q*t+r)+18*p*(m*t^2+2*n*t+o)-18*(m*t+n)*(2*p*t+2*q)+18*(2*m*t+2*n)*(p*t+q))/(9*(p*t^2+2*q*t+r)^2+9*(m*t^2+2*n*t+o)^2)^(3/2)-(3*(18*(p*t+q)*(m*t^2+2*n*t+o)-18*(m*t+n)*(p*t^2+2*q*t+r))*(18*(2*p*t+2*q)*(p*t^2+2*q*t+r)+18*(2*m*t+2*n)*(m*t^2+2*n*t+o)))/(2*(9*(p*t^2+2*q*t+r)^2+9*(m*t^2+2*n*t+o)^2)^(5/2))
Unfortunately, the online tool is not able to yield roots for this expression. But if you look closely (preferably at some rendered representation), you'll find a difference with almost identical denominators (...)^(3/2)
and 2*(...)^(5/2)
. Multiplying the left-hand side with 2*(...)
yields a common denominator and the following nominator, which should be set to zero:
2*(-18*m*(p*t^2+2*q*t+r)+18*p*(m*t^2+2*n*t+o)-18*(m*t+n)*(2*p*t+2*q)+18*(2*m*t+2*n)*(p*t+q))*(9*(p*t^2+2*q*t+r)^2+9*(m*t^2+2*n*t+o)^2) - (3*(18*(p*t+q)*(m*t^2+2*n*t+o)-18*(m*t+n)*(p*t^2+2*q*t+r))*(18*(2*p*t+2*q)*(p*t^2+2*q*t+r)+18*(2*m*t+2*n)*(m*t^2+2*n*t+o)))
This tool can simplify this expression and yields the following polynomial in text form:
((1296*m*p^2+1296*m^3)*q-1296*n*p^3-1296*m^2*n*p)*t^5
+ ((1620*m*p^2+1620*m^3)*r+3240*m*p*q^2+(3240*m^2*n-3240*n*p^2)*q-1620*o*p^3+((-1620*m^2*o)-3240*m*n^2)*p)*t^4
+ ((5184*m*p*q+1296*n*p^2+6480*m^2*n)*r+1296*m*q^3-1296*n*p*q^2+((-6480*o*p^2)-1296*m^2*o+1296*m*n^2)*q+((-5184*m*n*o)-1296*n^3)*p)*t^3
+ (1296*m*p*r^2+(1944*m*q^2+6480*n*p*q-1296*o*p^2+1296*m^2*o+8424*m*n^2)*r-8424*o*p*q^2-6480*m*n*o*q+((-1296*m*o^2)-1944*n^2*o)*p)*t^2
+ (2592*n*p*r^2+(3888*n*q^2-2592*o*p*q+2592*m*n*o+3888*n^3)*r-3888*o*q^3+((-2592*m*o^2)-3888*n^2*o)*q)*t
+ (-324*m*r^3+(1944*n*q+324*o*p)*r^2+((-1944*o*q^2)-324*m*o^2+1944*n^2*o)*r-1944*n*o^2*q+324*o^3*p)
Now you can choose any solver for 5th-degree polynomials. In my case, I could easily use the roots
function from Python's NumPy:
poly = [
(1296*m*p**2+1296*m**3)*q-1296*n*p**3-1296*m**2*n*p,
(1620*m*p**2+1620*m**3)*r+3240*m*p*q**2+(3240*m**2*n-3240*n*p**2)*q-1620*o*p**3+((-1620*m**2*o)-3240*m*n**2)*p,
(5184*m*p*q+1296*n*p**2+6480*m**2*n)*r+1296*m*q**3-1296*n*p*q**2+((-6480*o*p**2)-1296*m**2*o+1296*m*n**2)*q+((-5184*m*n*o)-1296*n**3)*p,
1296*m*p*r**2+(1944*m*q**2+6480*n*p*q-1296*o*p**2+1296*m**2*o+8424*m*n**2)*r-8424*o*p*q**2-6480*m*n*o*q+((-1296*m*o**2)-1944*n**2*o)*p,
2592*n*p*r**2+(3888*n*q**2-2592*o*p*q+2592*m*n*o+3888*n**3)*r-3888*o*q**3+((-2592*m*o**2)-3888*n**2*o)*q,
-324*m*r**3+(1944*n*q+324*o*p)*r**2+((-1944*o*q**2)-324*m*o**2+1944*n**2*o)*r-1944*n*o**2*q+324*o**3*p,
]
roots = np.roots(poly)
The resulting list contains all values t
, where the curvature k(t)
is at a local minimum or maximum. There could, however, be imaginary solutions that should be ignored.
Example:
Regarding D.W.'s hint about endpoints: I'm not sure if the curvature could be extrem at these points. But if in doubt, make sure to check the endpoints explicitly.