I’m trying to use a Hermite curve in a project, with an admittedly limited understanding of how the math actually works, and I’ve run into some behavior I don’t understand. I've demonstrated my confusion with a minimal code sample below, but basically I would expect points along a subcurve of a hermite curve (i.e. a subcurve defined using points and tangents on the original curve) to fit the original curve, but this seems to be false.
The following c# code defines a Hermite curve class that provides functions for computing the position and the tangent of a point at some ratio along the curve. I copy/pasted the math for both functions from other places on the internet.
A small test harness then performs the test that I would expect to succeed, but doesn’t. It is unclear to me if there is a bug in my code, a mistake in my math, or if I misunderstand something about how Hermite curves work and this test actually should not pass.
Any insight is appreciated.
using System;
using System.Numerics;
class Program
{
class HermiteCurve
{
Vector2 start;
Vector2 startTangent;
Vector2 end;
Vector2 endTangent;
public HermiteCurve(Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent)
{
this.start = start;
this.startTangent = startTangent;
this.end = end;
this.endTangent = endTangent;
}
public Vector2 GetPoint(float t)
{
var t2 = t * t;
var t3 = t2 * t;
return
( 2f*t3 - 3f*t2 + 1f) * start +
(-2f*t3 + 3f*t2) * end +
(t3 - 2f*t2 + t) * startTangent +
(t3 - t2) * endTangent;
}
public Vector2 GetTangent(float t)
{
var t2 = t * t;
return
(6f*t2 - 6*t) * start +
(-6f*t2 + 6*t) * end +
(3f*t2 - 4f*t + 1) * startTangent +
(3f*t2 - 2f*t) * endTangent;
}
}
static void Main(string[] args)
{
Vector2 p0 = new Vector2(0, 0);
Vector2 m0 = new Vector2(1, 0);
Vector2 p1 = new Vector2(1, 1);
Vector2 m1 = new Vector2(0, 1);
HermiteCurve curve = new HermiteCurve(p0, m0, p1, m1);
Vector2 p0prime = curve.GetPoint(0.5f);
Vector2 m0prime = curve.GetTangent(0.5f);
HermiteCurve curvePrime = new HermiteCurve(p0prime, m0prime, p1, m1);
Vector2 curvePoint = curve.GetPoint(0.75f);
Vector2 curveTangent = curve.GetTangent(0.75f);
Vector2 curvePrimePoint = curvePrime.GetPoint(0.5f);
Vector2 curvePrimeTangent = curvePrime.GetTangent(0.5f);
// Why does this check fail?
if (curvePoint != curvePrimePoint || curveTangent != curvePrimeTangent)
{
Console.WriteLine("fail");
Console.WriteLine("curvePosition - x: " + curvePoint.X + " y: " + curvePoint.Y);
Console.WriteLine("curvePrimePosition - x: " + curvePrimePoint.X + " y: " + curvePrimePoint.Y);
Console.WriteLine("curveTangent - x: " + curveTangent.X + " y: " + curveTangent.Y);
Console.WriteLine("curvePrimeTangent - x: " + curvePrimeTangent.X + " y: " + curvePrimeTangent.Y);
}
}
}
Program output:
fail
curvePosition - x: 0.890625 y: 0.703125
curvePrimePosition - x: 0.96875 y: 0.71875
curveTangent - x: 0.8125 y: 1.3125
curvePrimeTangent - x: 0.25 y: 0.375
The short answer is that the math simply does not work the way you want it to.
It's been ages since I toyed around with polynomial curves, so just for fun, I hacked together a Python program that computes control points for a "split" hermite curve, as well as your "wrong" curve. In practice, you might be better off using the de Casteljau algorithm.
This implementation is probably horrible in a gazillion ways, but at least it seems to produce correct results. :-)