Closed mweatherley closed 7 months ago
Additional context which originated the issue: on discord help channel.
Looks like a bug to me. Docs state the classic properties of cardinals. I will need to review the history to see if this issue existed before I reworded the spline docs, it's possible I introduced this.
Let's fix the implementation.
The issue
There is a mismatch between what the documentation says and how
CubicCardinalSpline
actually works. Namely, this:As implemented, this is not true. Instead, the curve passes through only the interior control points — that is, it doesn't actually go through the points at the ends.
Example
Here is a complete example (coutesy of @afonsolage):
This produces the following output:
As one can see, the curve only extends between 0.3 and 0.4, where the interior control points are defined. If you want it to actually pass through all those points, you need to extend your control sequence to a length of six, so that your desired endpoints become interior.
Background
Cardinal splines connect a series of points
P_i
with a sequence of cubic segments betweenP_i
andP_{i+1}
so that:B_i
fromP_i
toP_{i+1}
starts atP_i
and ends atP_{i+1}
B_i
andB_{i+1}
atP_{i+1}
coincideProperty (1) happens by using
P_i
andP_{i+1}
as the outermost control points of the Bézier curveB_i
. Property (2) is ensured by making the third control point ofB_i
, the second control point ofB_{i+1}
, andP_{i+1}
all collinear; the point of doing this is that it makes the union of cubic segments differentiable (i.e. it doesn't have "kinks").The general construction is such that the derivative at
P_i
is given by the difference betweenP_{i-1}
andP_{i+1}
— i.e., the tangent to the curve atP_i
is parallel to the line between the points preceding and following it. I believe this part is implemented at present; however, this aspect of the construction leaves one thing open: what do we do at the endpoints (whereP_{i+1}
orP_{i-1}
doesn't exist)?A standard choice is to just use the line from
P_0
toP_1
to determine the tangent atP_0
(and analogously for the other endpoint). Of course, there are other choices that one could make as well (e.g. just requiring the tangents for those to be specified manually).Presently, either by accident or on purpose, we just exclude those points. Either way, we are currently misleading about it.
Which way, spline implementor?
There are basically two paths forward here:
I think it's pretty clear that I would have not written such a long issue if I did not prefer (2) (which I believe does a good job of meeting expectations and providing ergonomics for curve interpolation between a number of points), but there is a downside: in principle, the current implementation allows the user to indirectly specify the tangent at the endpoints by choosing their control points carefully. (2) is also a breaking change.
I am unsure if this is a bug or if the implementation is this way intentionally, so I have written it as a docs issue.