Hermite Basis Interpolation
Hermite interpolation is a technique for creating smooth curves that pass directly through a set of control points. Unlike Bezier curves where control points influence the curve but don't lie on it, Hermite splines guarantee the curve passes through each point while maintaining smooth tangents.
The Core Concept
A cubic Hermite spline interpolates between two points using four pieces of information:
- P0: The starting point
- P1: The ending point
- M0: The tangent (velocity) at the starting point
- M1: The tangent (velocity) at the ending point
The key insight is that knowing both the position and direction at each endpoint gives us enough information to construct a unique smooth curve between them.
The Basis Functions
The interpolation is controlled by four basis functions, each responsible for blending one of the inputs. As the parameter t goes from 0 to 1, these functions determine how much each input contributes to the final position:
The four basis functions are:
- h00(t) = 2t³ - 3t² + 1 (blue) — Starts at 1, smoothly decreases to 0. Controls how much P0 influences the curve.
- h01(t) = -2t³ + 3t² (green) — Starts at 0, smoothly increases to 1. Controls how much P1 influences the curve.
- h10(t) = t³ - 2t² + t (orange) — Zero at both ends with a positive bump. Lets M0 "push" the curve in its direction.
- h11(t) = t³ - t² (purple) — Zero at both ends with a negative dip. Lets M1 "pull" the curve as it arrives.
Why These Functions Work
The basis functions are carefully designed with specific properties:
At t = 0 (start of curve):
- h00(0) = 1, all others = 0 → curve starts exactly at P0
- h10(0) = 0 but h10'(0) = 1 → derivative equals M0
At t = 1 (end of curve):
- h01(1) = 1, all others = 0 → curve ends exactly at P1
- h11(1) = 0 but h11'(1) = 1 → derivative equals M1
This guarantees the curve passes through both points with the specified tangents.
Implementation
function hermiteBasis(t: number): [number, number, number, number] {
const t2 = t * t;
const t3 = t2 * t;
return [
2 * t3 - 3 * t2 + 1, // h00: value at p0
t3 - 2 * t2 + t, // h10: tangent at p0
-2 * t3 + 3 * t2, // h01: value at p1
t3 - t2, // h11: tangent at p1
];
}To interpolate between two points, multiply each input by its corresponding basis function and sum:
interface Point {
x: number;
y: number;
}
function hermiteInterpolate(
p0: Point,
p1: Point,
m0: Point,
m1: Point,
t: number,
): Point {
const [h00, h10, h01, h11] = hermiteBasis(t);
return {
x: h00 * p0.x + h10 * m0.x + h01 * p1.x + h11 * m1.x,
y: h00 * p0.y + h10 * m0.y + h01 * p1.y + h11 * m1.y,
};
}Visual Comparison
The difference between linear interpolation and Hermite interpolation is dramatic:
Both shapes pass through the exact same seven control points (shown in red). The linear version looks like a geometric polygon, while the Hermite version looks organic and natural.
The key insight: we're not adding more points to make it smooth. We're using the mathematical properties of the basis functions to calculate where the curve should go between the points we have.
Computing Tangents with Catmull-Rom
The challenge with Hermite splines is: where do the tangent vectors come from? One elegant solution is Catmull-Rom style tangents, where the tangent at each point is proportional to the vector between its neighbors:
function computeTangent(
points: Point[],
index: number,
closed: boolean,
tension: number = 0.5,
): Point {
const n = points.length;
let prev: Point, next: Point;
if (closed) {
// Wrap around for closed curves
prev = points[(index - 1 + n) % n];
next = points[(index + 1) % n];
} else {
// Clamp to endpoints for open curves
prev = index > 0 ? points[index - 1] : points[index];
next = index < n - 1 ? points[index + 1] : points[index];
}
return {
x: (next.x - prev.x) * tension,
y: (next.y - prev.y) * tension,
};
}The tension parameter controls curve tightness:
- Lower values (0.2-0.3) create tighter curves that hug the control points
- Higher values (0.5-0.7) create looser, more flowing curves
Generating a Complete Path
To create a smooth closed curve through multiple points:
function hermiteClosedPath(
points: Point[],
samplesPerSegment: number = 8,
tension: number = 0.5,
): string {
const n = points.length;
// Compute tangents for all points
const tangents = points.map((_, i) =>
computeTangent(points, i, true, tension),
);
// Generate interpolated points
const allPoints: Point[] = [];
for (let i = 0; i < n; i++) {
const p0 = points[i];
const p1 = points[(i + 1) % n];
const m0 = tangents[i];
const m1 = tangents[(i + 1) % n];
for (let j = 0; j < samplesPerSegment; j++) {
const t = j / samplesPerSegment;
allPoints.push(hermiteInterpolate(p0, p1, m0, m1, t));
}
}
// Build SVG path
let path = `M${allPoints[0].x.toFixed(1)},${allPoints[0].y.toFixed(1)} `;
for (let i = 1; i < allPoints.length; i++) {
path += `L${allPoints[i].x.toFixed(1)},${allPoints[i].y.toFixed(1)} `;
}
return path + "Z";
}When to Use Hermite Interpolation
Hermite splines are ideal when:
- You need curves that pass exactly through control points
- You want C1 continuity (smooth first derivatives) at joints
- You're chaining multiple curve segments together
- You need control over the direction the curve enters/exits each point
They're commonly used for:
- Animation paths and easing functions
- Procedural shape generation
- Font rendering
- Camera paths in 3D graphics
Related Techniques
- Bezier curves: Control points influence the curve but don't lie on it
- B-splines: Smoother (C2) but points don't lie exactly on the curve
- Catmull-Rom splines: A specific type of Hermite spline where tangents are automatically computed from neighbors
See Also
- Procedural Ink Splotches with SVG — A practical application of Hermite interpolation for generating organic shapes