基于JS的 2D/3D 数学几何库开发实践
在前端图形开发中,2D/3D 几何运算是可视化与图形编程的重要基础。
大家好,我是前端丁修,2023 年,很长时间我在从使与 CAD 转 3D 相关的开发工作,2024 主要从事 3D 数据资产平台的开发工作,这两个项目都涉及到了 2D 和 3D 图形开发,与传统页面开发不同的是,项目里涉及到大量通过几何计算来实现的需求,例如 CAD 的识别、构件绘制及标注等,几何库便成了不可或缺的部分。
本文将介绍一个如何使用 JavaScript(TypeScript) 编写数学库,支持基础的 2D/3D 数学运算,如向量、矩阵、四元数等,提供几何对象(点、线、面)及其常用算法(如距离计算、碰撞检测),以便快速进行图形变换和几何操作。
架构与层次结构
数学库的核心是基础对象的定义。通过接口(Interface)、继承和组合,可以构建清晰的层次结构。
基类
定义通用方法(如 equals、clone、serialization、deserialization)。
基础对象
| 对象 | 2D/3D | 特性描述 |
|---|---|---|
| Number | 通用 | 数值 |
| Vector | 通用 | 向量 |
| Matrix | 通用 | 矩阵 |
| Quaternion | 通用 | 四元数,通过四个分量(x, y, z, w)表示 3D 旋转 |
| Euler | 通用 | 欧拉角(pitch、yaw 和 roll) |
| Axis | 通用 | 坐标轴(x, y, z) |
| CCS | 通用 | 坐标系转换,用于转换不同坐标系下的向量和矩阵 |
| BBox | 通用 | 边界框(由最小点和最大点定义) |
| OBB | 通用 | 有向边界框(Oriented Bounding Box),用于快速碰撞检测和空间查询 |
| Transform | 通用 | 变换矩阵,用于平移、旋转、缩放等操作 |
几何对象
| 对象 | 2D/3D | 特性描述 |
|---|---|---|
| Line | 通用 | 无限长直线(由点+方向向量定义) |
| LineSeg | 通用 | 有限线段(由起点+终点定义) |
| Ray | 通用 | 射线(起点+方向向量) |
| Curve | 通用 | 曲线(由点集或参数方程定义) |
| BezierCurve | 通用 | 贝塞尔曲线(由控制点定义) |
| Circle | 2D | 完整圆(圆心+半径) |
| Ellipse | 2D | 椭圆(由焦点+长轴+短轴定义) |
| Arc | 2D/3D | 圆弧(圆心+半径+起始角+终止角) |
| Sphere | 3D | 球体(圆心+半径,3D 扩展) |
| Polygon | 通用 | 多边形(由顶点集定义) |
模块化与扩展性
模块化拆分
为支持按需加载与 Tree Shaking,库的核心模块按功能拆分:
src/
├── core/ # 通用基础工具
│ ├── MathUtils.ts # 数学工具函数(如容差比较)
│ └── types.ts # 公共类型定义(如IVector、IMatrix接口)
│
├── 2d/ # 2D专用模块
│ ├── Vector2.ts # 2D向量
│ ├── Matrix3.ts # 3x3矩阵(用于2D仿射变换)
│ └── Polygon2.ts # 2D多边形碰撞检测
│
├── 3d/ # 3D专用模块
│ ├── Vector3.ts # 3D向量
│ ├── Matrix4.ts # 4x4矩阵
│ └── Quaternion.ts # 四元数
│
└── index.ts # 统一入口(按需导出)
用户可单独引入 2D 或 3D 模块。
浮点数精度问题与容差对象
在几何计算中,由于浮点数精度的限制,计算机处理浮点数时存在微小的误差,例如,0.1 + 0.2 可能不等于 0.3。为了处理这些误差,我们引入了一个容差对象,用于承载几种不同的容差值,并且可以进行修改。
容差对象的定义:
// 容差配置类
class Tolerance {
static global = 1e-6; // 全局容差值,用于默认计算的容差,距离容差
static angle = 1e-4; // 用于角度计算的容差值(约0.0001度)
static radian = 1e-6; // 用于弧度计算的容差值
static epsilon = 1e-6; // 可选,用于比较两个浮点数是否相等的容差值
static distance = 1e-6; // 可选,用于距离计算的容差值,也可使用默认容差。
// 动态修改容差
static setGlobal(value: number) {
Tolerance.global = value;
}
// ...
}
// 使用示例:判断两角度是否相等
function isAngleEqual(a: number, b: number): boolean {
return Math.abs(a - b) < Tolerance.angle;
}
在不同的用途中,容差的默认值设置也不同,如果是科学计算,则需要较高的精度。我们的业务应用场景,对精度要求不高,所以全局、浮点数计算、距离、及弧度的容差,使用 Number.EPSILON 即 1e-6 作为默认值。双精度浮点数(JavaScript 的 Number 类型)的精度约为 1e-15,但实际计算中多次运算会累积误差, 1e-6 足够覆盖大多数场景的误差范围。
对于角度计算的容差,为和弧度计算保持结果一致性,需要进行换算调整。1 弧度 ≈ 57.2958 度,1e-6 弧度对应约 5.7296e-5 度,取近似值 1e-4 度作为角度的默认容差。
以上每个属性都可以通过方法进行获取和设置。
容差的使用,默认使用全局配置,局部可以覆盖。 代码示例:
function isEqual(a: number, b: number, tolerance = Tolerance.global): boolean {
return Math.abs(a - b) < tolerance;
}
另外还可以扩展更多的设置使用方式,通过定义容差对象,我们可以在几何计算中灵活地处理浮点数误差,提高计算的准确性和稳定性。
对象的实现
现在,让我们来看一下这些对象是如何实现的,以及有哪些属性和方法。
类与接口设计
在设计类和接口时,我们使用了 TypeScript 的类和接口定义几何对象。我们还应用了继承和多态的概念,以确保代码的可重用性和灵活性。
通用几何接口
// core/types.ts
export interface IGeometry {
type: 'line' | 'arc' | 'circle'; // 标识几何类型
clone(): IGeometry; // 克隆对象
translate(offset: IVector): void; // 平移操作
}
export interface ICurve extends IGeometry {
length(): number; // 计算曲线长度
getPointAt(t: number): IVector; // 根据参数t获取曲线上点
}
export interface ILine extends IGeometry {
direction: IVector; // 方向向量
distanceToPoint(p: IVector): number; // 计算点到直线的距离
}
基本的数学对象
来看下数学对象的属性和方法 Point
- 坐标属性
- 方法:
- 获取点的坐标
- 设置点的坐标
- 计算两点之间的距离
- 点与向量运算
- 点转换
Vector
- 分量属性
- 方法:
- 获取向量的分量
- 设置向量的分量
- 向量加法
- 向量减法
- 向量缩放
- 向量点乘
- 向量叉乘(仅 3D)
- 计算向量长度
- 向量归一化
- 方向角度判断
- 向量转换
Matrix
- 方法:
- 获取矩阵元素
- 设置矩阵元素
- 矩阵加法
- 矩阵减法
- 矩阵乘法
- 矩阵转置
- 矩阵求逆
- 仿射变换
Euler
- 三个角度(pitch、yaw 和 roll)
- 方法:
- 获取欧拉角的三个分量
- 设置欧拉角的三个分量
- 将欧拉角转换为旋转矩阵
- 将欧拉角转换为四元数
Quaternion
-
四个分量(x, y, z, w)
-
方法:
- 获取四元数的四个分量
- 设置四元数的四个分量
- 四元数乘法
- 四元数与向量的旋转
- 将四元数转换为旋转矩阵
- 将四元数转换为欧拉角
-
BBox
- 属性:
- 最小点
- 最大点
- 方法:
- 获取边界框的最小点和最大点
- 设置边界框的最小点和最大点
- 计算边界框的面积(2D)或体积(3D)
- 判断点是否在边界框内
- 边界框之间关系判断
- 属性:
-
OBB
- 属性:
- 中心点
- 半长轴
- 旋转角度
- 方法:
- 获取有向边界框的中心点、半长轴和旋转角度
- 设置有向边界框的中心点、半长轴和旋转角度
- 计算有向边界框的面积(2D)或体积(3D)
- 属性:
-
Axis
- 属性:
- 方向向量
- 方法:
- 获取坐标轴的方向向量
- 设置坐标轴的方向向量
- 坐标轴变换
- 属性:
基础几何对象
在 2D 和 3D 空间中,我们定义了以下几何对象:
基础几何对象
-
Line
- 属性:
- 点
- 方向向量
- 方法:
- 获取直线的点和方向向量
- 设置直线的点和方向向量
- 计算点到直线的距离
- 判断两直线是否平行
- 属性:
-
LineSeg
- 属性:
- 两个端点
- 方法:
- 获取线段的两个端点
- 设置线段的两个端点
- 计算线段长度
- 判断点是否在线段上
- 属性:
-
Ray
- 属性:
- 起点
- 方向向量
- 方法:
- 获取射线的起点和方向向量
- 设置射线的起点和方向向量
- 计算点到射线的距离
- 属性:
-
Curve
- 属性:
- 点集
- 方法:
- 获取曲线的点集
- 设置曲线的点集
- 计算曲线长度
- 插值计算曲线上任意一点
- 属性:
-
Circle
- 属性:
- 圆心
- 半径
- 方法:
- 获取圆的圆心和半径
- 设置圆的圆心和半径
- 计算圆的周长和面积
- 判断点是否在圆上
- 属性:
-
Arc
- 属性:
- 圆心
- 半径
- 起始角度
- 终止角度
- 方法:
- 获取圆弧的圆心、半径、起始角度和终止角度
- 设置圆弧的圆心、半径、起始角度和终止角度
- 计算圆弧长度
- 属性:
-
Polygon
- 属性:
- 顶点集
- 方法:
- 获取多边形的顶点集
- 设置多边形的顶点集
- 计算多边形的周长和面积
- 判断点是否在多边形内
- 属性:
-
Ellipse
- 属性:
- 焦点 1
- 焦点 2
- 长轴
- 短轴
- 方法:
- 获取椭圆的焦点 1、焦点 2、长轴和短轴
- 设置椭圆的焦点 1、焦点 2、长轴和短轴
- 计算椭圆的周长和面积
- 判断点是否在椭圆上
- 属性:
这些对象和方法为 2D 和 3D 几何计算提供了基础支持。
其他对象
-
Transform
- 属性:
- 平移
- 旋转
- 缩放
- 方法:
- 获取变换的平移、旋转和缩放
- 设置变换的平移、旋转和缩放
- 应用变换到点或向量
- 反转变换
- 属性:
几何对象之间的计算
下三角表格,展示对象之间的几何关系。
最短距离计算
2D 几何对象之间最短距离计算
| 对象 | Line2 | Circle2 | Ellipse2 |
|---|---|---|---|
| Line2 | ✓ | ||
| Circle2 | ✓ | ✓ | |
| Ellipse2 | ✓ | ✓ | ✓ |
3D 几何对象之间最短距离计算
| 对象 | Line3 | Ray3 | Point3 |
|---|---|---|---|
| Ray3 | ✓ | ||
| Point3 | ✓ | ✓ | |
| all Curve3 | ✓ | ✓ | ✓ |
点到直线或曲线等的最短距离
| 对象 | Point2 | Line2 | LineSeg2 | Ray2 | Circle2 | Arc2 | Ellipse2 | Polygon2 |
|---|---|---|---|---|---|---|---|---|
| Point2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
求交计算
2D 对象间的求交运算
| 对象 | Line2 | LineSeg2 | Ray2 | Circle2 | Arc2 | Ellipse2 |
|---|---|---|---|---|---|---|
| Line2 | ✓ | |||||
| LineSeg2 | ✓ | ✓ | ||||
| Ray2 | ✓ | ✓ | ✓ | |||
| Circle2 | ✓ | ✓ | ✓ | ✓ | ||
| Arc2 | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Ellipse2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
3D 对象间的求交运算
| 对象 | Line3 | LineSeg3 | Ray3 | Circle3 | Arc3 |
|---|---|---|---|---|---|
| Line3 | ✓ | ||||
| LineSeg3 | ✓ | ✓ | |||
| Ray3 | ✓ | ✓ | ✓ | ||
| Circle3 | ✓ | ✓ | ✓ | ✓ | |
| Arc3 | ✓ | ✓ | ✓ | ✓ | ✓ |
几何对象之间的算法,只列举了部分,实际应用中,还有很多需要扩展,例如重合判断、交并差计算等等。开发数学库的路还很长,除了前文提到的部分,还有几样工作也需要处理:
单元测试
数学计算很容易出错,一定要写单元测试。可以使用 Jest 或 Mocha 编写测试用例,覆盖所有核心功能,重点测试那些容易出问题的边界情况:
基础运算验证 :测试向量加减、矩阵乘法等基础运算的正确性。
边界条件覆盖:如零向量、单位矩阵、角度为 0 或 π 时的计算。
容差机制测试:验证浮点数比较逻辑在不同容差下的行为。
结合 CI/CD 工具(如 GitHub Actions),实现测试自动化,确保每次提交均通过验证。
可视化调试
几何算法的调试不像页面调试那么方便,可以开发简单的调试页面,输入几何参数(如多边形顶点、旋转角度),实时渲染图形(使用 Canvas 或 WebGL),根据输入数据,观察结果是否正确。
性能优化
图形计算对性能敏感,需要注意以下几点:
- 避免重复计算,合理使用缓存
- 使用适当的数据结构优化查找和遍历
- 考虑使用 Web Workers 处理复杂计算
- 针对热点代码进行性能分析和优化
文档生成
通过代码注释生成结构化文档,包括:
- API 接口说明
- 使用示例
- 算法原理说明
- 性能建议
数学库的开发是一个持续迭代的过程。从单元测试到性能优化,每一步都需兼顾严谨性与实用性。待后期我的数学库,经过应用检验之后,再逐步分享具体几何对象和方法的实现。