Spline Curves
Spline Curves
This chapter introduces the B-spline tools in WebCAD, including spline construction, evaluation, and boundary types.
Overview
The SplineUtils module provides low-level computation for B-splines and NURBS (Non-Uniform Rational B-Splines). These functions are mainly used internally for spline entity calculation and rendering.
import {
createBSplineWithDomain,
BSplineConstructor,
boundaryTypes,
getParameterDomain,
isNullOrUndefined,
detectDataType
} from 'vjcad';B-Spline Basics
What Is a B-Spline
A B-spline (Basis Spline) is a parametric curve defined by control points and basis functions. It has the following characteristics:
- Local control: modifying one control point only affects a local part of the curve
- Continuity: arbitrary-order continuity can be maintained
- Flexibility: the knot vector can precisely control the curve shape
Core Concepts
| Concept | Description |
|---|---|
| Control Points | A point sequence that defines the curve shape |
| Degree | Polynomial degree of the basis function |
| Knot Vector | A sequence dividing the parameter space |
| Weights | Weight of each control point in NURBS |
| Boundary | How the curve endpoints are handled |
createBSplineWithDomain() - Create a B-Spline
Creates a B-spline curve object with parameter-domain information.
const spline = createBSplineWithDomain(points, degree, knots, weights, boundary, options);Parameters
| Parameter | Type | Description |
|---|---|---|
points | number[][] | Control point array, such as [[x1,y1], [x2,y2], ...] |
degree | number | number[] | Curve degree, usually 2 or 3 |
knots | number[] | number[][] | Knot vector, optional, uniform by default |
weights | number[] | number[][] | Weight array, optional, used for NURBS |
boundary | string | string[] | Boundary type, see below |
options | object | Additional options |
Basic Example
import { createBSplineWithDomain } from 'vjcad';
// Define control points
const controlPoints = [
[0, 0],
[50, 100],
[100, 100],
[150, 50],
[200, 0]
];
// Create a cubic B-spline
const spline = createBSplineWithDomain(
controlPoints,
3, // degree: cubic
undefined, // knots: use default uniform knots
undefined, // weights: no weights, non-rational
'clamped' // boundary: clamped
);
// Evaluate point at parameter t = 0.5
const result = [];
spline.evaluate(result, 0.5);
console.log(result); // [x, y]Boundary Types (boundaryTypes)
Boundary types determine the behavior of the curve at its endpoints.
const boundaryTypes = {
open: 'open', // Open boundary
closed: 'closed', // Closed boundary
clamped: 'clamped' // Clamped boundary
};open
The curve does not pass through the first and last control points.
const spline = createBSplineWithDomain(points, 3, null, null, 'open');clamped - Most Common
The curve passes through the first and last control points and is tangent to the control polygon at the endpoints.
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');closed
The curve connects head to tail and forms a closed loop.
const spline = createBSplineWithDomain(points, 3, null, null, 'closed');Spline Evaluation
evaluate() - Evaluate Point on the Curve
// Create spline
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// Prepare output array
const point = [];
// Evaluate point at parameter t
spline.evaluate(point, 0.5);
console.log(point[0], point[1]); // x, yGet Parameter Domain
// Get valid parameter range
const domain = spline.domain;
console.log(domain); // [[tMin, tMax]]
// For 1D splines, domain[0] contains the parameter range
const tMin = domain[0][0];
const tMax = domain[0][1];Discretize Curve
function discretizeSpline(spline: any, numPoints: number = 100): Point2D[] {
const domain = spline.domain[0];
const tMin = domain[0];
const tMax = domain[1];
const points: Point2D[] = [];
for (let i = 0; i <= numPoints; i++) {
const t = tMin + (tMax - tMin) * i / numPoints;
const result: number[] = [];
spline.evaluate(result, t);
points.push(new Point2D(result[0], result[1]));
}
return points;
}NURBS Curves
NURBS (Non-Uniform Rational B-Spline) adds weights, allowing exact representation of circular arcs, ellipses, and other conic curves.
Create a NURBS Curve
// Control points
const points = [
[0, 0],
[50, 100],
[100, 0]
];
// Weights (greater than 1 pulls the curve toward the control point)
const weights = [1, 2, 1]; // Middle point weight is 2
const nurbs = createBSplineWithDomain(
points,
2, // quadratic
null, // default knots
weights, // weights
'clamped'
);Effect of Weights
| Weight | Effect |
|---|---|
w = 1 | Standard B-spline behavior |
w > 1 | Curve is pulled toward that control point |
w < 1 | Curve is pushed away from that control point |
w = 0 | Control point has no effect |
w → ∞ | Curve passes through that control point |
Knot Vector
The knot vector determines the basis-function shape and parameterization behavior.
Uniform Knots
If no knot vector is specified, a uniform distribution is used:
// Uniform knots (auto-generated)
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');Custom Knots
For n control points and degree p, the knot-vector length is n + p + 1:
// 5 control points, cubic curve, requires 9 knots
const points = [[0,0], [25,50], [50,50], [75,25], [100,0]];
const knots = [0, 0, 0, 0, 0.5, 1, 1, 1, 1]; // Clamped knots
const spline = createBSplineWithDomain(points, 3, knots, null, 'open');Characteristics of Clamped Knots
- The first and last knots repeat
p + 1times (pis the degree) - The curve passes through the first and last control points
- The curve is tangent to the control polygon at the endpoints
Spline Transformation
transform() - Matrix Transformation
Apply a transformation matrix to a spline curve:
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// 4x4 transform matrix (2D homogeneous coordinates)
// | m0 m3 m6 |
// | m1 m4 m7 |
// | m2 m5 m8 |
const matrix = [
2, 0, 0, // Scale x by 2
0, 2, 0, // Scale y by 2
0, 0, 1 // Homogeneous coordinate
];
spline.transform(matrix);Numerical Derivatives
numericalDerivative() - Numerical Differentiation
Uses finite differences to calculate curve derivatives:
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// First derivative at t = 0.5
const derivative = [];
spline.numericalDerivative(derivative, 1, 0, 0.5);
// derivative contains tangent directionHelper Functions
detectDataType()
Detects the type of input data:
import { detectDataType } from 'vjcad';
const data = [[1, 2], [3, 4]];
const type = detectDataType(data);
// type = 'Arr' (array of arrays)isNullOrUndefined()
Checks whether a value is null or undefined:
import { isNullOrUndefined } from 'vjcad';
isNullOrUndefined(null); // true
isNullOrUndefined(undefined); // true
isNullOrUndefined(0); // false
isNullOrUndefined(''); // falseSplineEnt Entity
In real applications, SplineEnt is usually used to create and manipulate spline curves:
import { SplineEnt, Point2D } from 'vjcad';
// Create spline entity
const spline = new SplineEnt();
spline.degree = 3;
spline.fitPoints = [
new Point2D(0, 0),
new Point2D(50, 100),
new Point2D(100, 50),
new Point2D(150, 100),
new Point2D(200, 0)
];
// Get discretized line entities for rendering
const lineEnts = spline.getNurbsEnts();Application Examples
Smooth Curve Drawing
import { createBSplineWithDomain, Point2D } from 'vjcad';
function createSmoothCurve(waypoints: Point2D[]): Point2D[] {
// Convert Point2D into array format
const points = waypoints.map(p => [p.x, p.y]);
// Create cubic B-spline
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// Discretize into 100 points
const result: Point2D[] = [];
const domain = spline.domain[0];
const tMin = domain[0];
const tMax = domain[1];
for (let i = 0; i <= 100; i++) {
const t = tMin + (tMax - tMin) * i / 100;
const pt: number[] = [];
spline.evaluate(pt, t);
result.push(new Point2D(pt[0], pt[1]));
}
return result;
}Animation Path
function getPositionOnPath(
spline: any,
progress: number // 0 to 1
): Point2D {
const domain = spline.domain[0];
const t = domain[0] + (domain[1] - domain[0]) * progress;
const pt: number[] = [];
spline.evaluate(pt, t);
return new Point2D(pt[0], pt[1]);
}Next Steps
- Boundary Detection - using
BoundaryDetector - Selection Detection - selection-rectangle intersection tests
- Entity System -
EntityBasebase class