基于JavaScript(TypeScript)的2D/3D数学几何库开发实践

583 阅读10分钟

基于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通用贝塞尔曲线(由控制点定义)
Circle2D完整圆(圆心+半径)
Ellipse2D椭圆(由焦点+长轴+短轴定义)
Arc2D/3D圆弧(圆心+半径+起始角+终止角)
Sphere3D球体(圆心+半径,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.EPSILON1e-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 几何对象之间最短距离计算

对象Line2Circle2Ellipse2
Line2
Circle2
Ellipse2

3D 几何对象之间最短距离计算

对象Line3Ray3Point3
Ray3
Point3
all Curve3

点到直线或曲线等的最短距离

对象Point2Line2LineSeg2Ray2Circle2Arc2Ellipse2Polygon2
Point2

求交计算

2D 对象间的求交运算

对象Line2LineSeg2Ray2Circle2Arc2Ellipse2
Line2
LineSeg2
Ray2
Circle2
Arc2
Ellipse2

3D 对象间的求交运算

对象Line3LineSeg3Ray3Circle3Arc3
Line3
LineSeg3
Ray3
Circle3
Arc3

几何对象之间的算法,只列举了部分,实际应用中,还有很多需要扩展,例如重合判断、交并差计算等等。开发数学库的路还很长,除了前文提到的部分,还有几样工作也需要处理:

单元测试

数学计算很容易出错,一定要写单元测试。可以使用 Jest 或 Mocha 编写测试用例,覆盖所有核心功能,重点测试那些容易出问题的边界情况:

基础运算验证 :测试向量加减、矩阵乘法等基础运算的正确性。

边界条件覆盖:如零向量、单位矩阵、角度为 0 或 π 时的计算。

容差机制测试:验证浮点数比较逻辑在不同容差下的行为。

结合 CI/CD 工具(如 GitHub Actions),实现测试自动化,确保每次提交均通过验证。

可视化调试

几何算法的调试不像页面调试那么方便,可以开发简单的调试页面,输入几何参数(如多边形顶点、旋转角度),实时渲染图形(使用 Canvas 或 WebGL),根据输入数据,观察结果是否正确。

性能优化

图形计算对性能敏感,需要注意以下几点:

  • 避免重复计算,合理使用缓存
  • 使用适当的数据结构优化查找和遍历
  • 考虑使用 Web Workers 处理复杂计算
  • 针对热点代码进行性能分析和优化

文档生成

通过代码注释生成结构化文档,包括:

  • API 接口说明
  • 使用示例
  • 算法原理说明
  • 性能建议

数学库的开发是一个持续迭代的过程。从单元测试到性能优化,每一步都需兼顾严谨性与实用性。待后期我的数学库,经过应用检验之后,再逐步分享具体几何对象和方法的实现。