样条曲线
大约 5 分钟
样条曲线
本章介绍 WebCAD 中的 B 样条曲线(B-Spline)工具函数,包括样条曲线的构造、求值和边界类型等。
概述
SplineUtils 模块提供了 B 样条曲线和 NURBS(非均匀有理 B 样条)的底层计算功能。这些功能主要用于内部样条实体的计算和渲染。
import {
createBSplineWithDomain,
BSplineConstructor,
boundaryTypes,
getParameterDomain,
isNullOrUndefined,
detectDataType
} from 'vjcad';B 样条曲线基础
什么是 B 样条曲线
B 样条曲线(Basis Spline)是一种参数化曲线,由控制点和基函数定义。它具有以下特点:
- 局部性:修改一个控制点只影响曲线的局部
- 连续性:可以保证任意阶的连续性
- 灵活性:通过节点向量可以精确控制曲线形状
核心概念
| 概念 | 说明 |
|---|---|
| 控制点 (Control Points) | 定义曲线形状的点序列 |
| 次数 (Degree) | 基函数的多项式次数 |
| 节点向量 (Knot Vector) | 参数空间的分割点序列 |
| 权重 (Weights) | NURBS 中每个控制点的权重 |
| 边界类型 (Boundary) | 曲线端点的处理方式 |
createBSplineWithDomain() - 创建 B 样条
创建一个带有参数域属性的 B 样条曲线对象。
const spline = createBSplineWithDomain(points, degree, knots, weights, boundary, options);参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
points | number[][] | 控制点数组,如 [[x1,y1], [x2,y2], ...] |
degree | number | number[] | 曲线次数(通常为 2 或 3) |
knots | number[] | number[][] | 节点向量(可选,默认均匀分布) |
weights | number[] | number[][] | 权重数组(可选,用于 NURBS) |
boundary | string | string[] | 边界类型(见下文) |
options | object | 其他选项 |
基本示例
import { createBSplineWithDomain } from 'vjcad';
// 定义控制点
const controlPoints = [
[0, 0],
[50, 100],
[100, 100],
[150, 50],
[200, 0]
];
// 创建 3 次 B 样条曲线
const spline = createBSplineWithDomain(
controlPoints,
3, // degree: 3 次曲线
undefined, // knots: 使用默认均匀节点
undefined, // weights: 无权重(非有理)
'clamped' // boundary: 夹紧边界
);
// 求值:计算参数 t=0.5 处的点
const result = [];
spline.evaluate(result, 0.5);
console.log(result); // [x, y]边界类型 (boundaryTypes)
边界类型决定了曲线在端点处的行为。
const boundaryTypes = {
open: 'open', // 开放边界
closed: 'closed', // 闭合边界
clamped: 'clamped' // 夹紧边界
};open - 开放边界
曲线不通过第一个和最后一个控制点。
const spline = createBSplineWithDomain(points, 3, null, null, 'open');clamped - 夹紧边界(最常用)
曲线通过第一个和最后一个控制点,且在端点处与控制多边形相切。
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');closed - 闭合边界
曲线首尾相连,形成闭合环。
const spline = createBSplineWithDomain(points, 3, null, null, 'closed');样条求值
evaluate() - 计算曲线上的点
// 创建样条
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// 准备输出数组
const point = [];
// 计算参数 t 处的点(t 通常在 [0, 1] 或节点域内)
spline.evaluate(point, 0.5);
console.log(point[0], point[1]); // x, y 坐标获取参数域
// 获取有效参数范围
const domain = spline.domain;
console.log(domain); // [[tMin, tMax]]
// 对于一维样条,domain[0] 包含参数范围
const tMin = domain[0][0];
const tMax = domain[0][1];离散化曲线
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 曲线
NURBS(Non-Uniform Rational B-Spline)通过添加权重,可以精确表示圆弧、椭圆等圆锥曲线。
创建 NURBS
// 控制点
const points = [
[0, 0],
[50, 100],
[100, 0]
];
// 权重(大于 1 会将曲线拉向该控制点)
const weights = [1, 2, 1]; // 中间点权重为 2
const nurbs = createBSplineWithDomain(
points,
2, // 2 次
null, // 默认节点
weights, // 权重
'clamped'
);权重的作用
| 权重值 | 效果 |
|---|---|
| w = 1 | 标准 B 样条行为 |
| w > 1 | 曲线被拉向该控制点 |
| w < 1 | 曲线远离该控制点 |
| w = 0 | 控制点对曲线无影响 |
| w → ∞ | 曲线通过该控制点 |
节点向量
节点向量决定了基函数的形状和参数化方式。
均匀节点
如果不指定节点向量,将使用均匀分布:
// 均匀节点(自动生成)
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');自定义节点
对于 n 个控制点、p 次曲线,节点向量长度为 n + p + 1:
// 5 个控制点,3 次曲线,需要 9 个节点
const points = [[0,0], [25,50], [50,50], [75,25], [100,0]];
const knots = [0, 0, 0, 0, 0.5, 1, 1, 1, 1]; // 夹紧节点
const spline = createBSplineWithDomain(points, 3, knots, null, 'open');夹紧节点的特点
- 首尾节点重复 p+1 次(p 为次数)
- 确保曲线通过首尾控制点
- 确保曲线在端点处与控制多边形相切
样条变换
transform() - 矩阵变换
对样条曲线应用变换矩阵:
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// 4x4 变换矩阵(2D 齐次坐标)
// | m0 m3 m6 |
// | m1 m4 m7 |
// | m2 m5 m8 |
const matrix = [
2, 0, 0, // 缩放 x 方向 2 倍
0, 2, 0, // 缩放 y 方向 2 倍
0, 0, 1 // 齐次坐标
];
spline.transform(matrix);数值导数
numericalDerivative() - 数值微分
使用有限差分法计算曲线的导数:
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// 计算 t=0.5 处的一阶导数
const derivative = [];
spline.numericalDerivative(derivative, 1, 0, 0.5);
// derivative 包含切线方向辅助函数
detectDataType()
检测输入数据的类型:
import { detectDataType } from 'vjcad';
const data = [[1, 2], [3, 4]];
const type = detectDataType(data);
// type = 'Arr' (数组的数组)isNullOrUndefined()
检查值是否为 null 或 undefined:
import { isNullOrUndefined } from 'vjcad';
isNullOrUndefined(null); // true
isNullOrUndefined(undefined); // true
isNullOrUndefined(0); // false
isNullOrUndefined(''); // falseSplineEnt 实体
在实际应用中,通常使用 SplineEnt 实体类来创建和操作样条曲线:
import { SplineEnt, Point2D } from 'vjcad';
// 创建样条实体
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)
];
// 获取离散化的线段用于渲染
const lineEnts = spline.getNurbsEnts();应用示例
平滑曲线绘制
import { createBSplineWithDomain, Point2D } from 'vjcad';
function createSmoothCurve(waypoints: Point2D[]): Point2D[] {
// 将 Point2D 转换为数组格式
const points = waypoints.map(p => [p.x, p.y]);
// 创建 3 次 B 样条
const spline = createBSplineWithDomain(points, 3, null, null, 'clamped');
// 离散化为 100 个点
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;
}动画路径
function getPositionOnPath(
spline: any,
progress: number // 0 到 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]);
}