首页
/ three.js HeartCurve 心形曲线详解:源码实现、getPoint 采样原理与 TubeGeometry 应用

three.js HeartCurve 心形曲线详解:源码实现、getPoint 采样原理与 TubeGeometry 应用

2026-09-07 20:45:50作者:邬祺芯Juliet

HeartCurve 是 three.js 附加模块(addon)中基于参数方程生成心形(heart-shaped)路径的曲线类,继承自核心基类 Curve。本文从它在 CurveExtras.js 中的实现出发,完整讲解导入方式、构造参数、scale 属性、getPoint 采样语义,并结合官方示例说明如何把心形曲线用作样条路径生成管线网格,让读者既能直接运行,也能深入理解其数学与几何本质。

HeartCurve 概览

HeartCurve 位于附加曲线集合 CurveExtras.js 中,该文件汇集了多条参数化曲线(Parametric Curve),包括 GrannyKnot、VivianiCurve、HelixCurve、TrefoilKnot 等,其公式来自 MathWorld、Wikipedia 等公开数学资料(见 CurveExtras.js 头部注释)。

HeartCurve 属于派生类,其继承关系为:

Curve(src/extras/core/Curve.js)
   └── HeartCurve(examples/jsm/curves/CurveExtras.js)

核心 Curve 类是 three.js 中所有解析式(analytic)曲线的抽象基类,内部维护了用于弧长累加计算的 arcLengthDivisions(默认 200)、脏标记 needsUpdate 以及私有缓存 cacheArcLengths(见 Curve.js)。HeartCurve 只需实现抽象的 getPoint() 方法即可被整条曲线工具链使用。

Import:附加模块需显式导入

HeartCurve 属于 three.js 的 addon(附加模块),与核心 src 目录中的模块不同,它不会被自动打包,必须从附加路径显式导入:

import { HeartCurve } from 'three/addons/curves/CurveExtras.js';

在当前仓库中,发布后的 three/addons/ 目录即对应于源码目录 examples/jsm/,因此本类实际定义于 examples/jsm/curves/CurveExtras.js,并通过文件末尾的具名导出表对外暴露(见 导出语句):

export {
	GrannyKnot,
	HeartCurve,
	VivianiCurve,
	// ...
};

若项目使用 ES Module 构建工具(如 Vite、Rollup、webpack),直接走上述 import 路径即可;若在 <script type="module"> 场景中使用 CDN 上的 three/addons/,也保持同样的模块路径。

构造函数与 scale 属性

new HeartCurve( scale : number )

构造一个新的心形曲线。scale(缩放因子)用于整体控制心形的尺寸,默认值为 5

从源码可见其实现非常简洁(CurveExtras.js):

class HeartCurve extends Curve {

	constructor( scale = 5 ) {

		super();

		/**
		 * The curve's scale.
		 *
		 * @type {number}
		 * @default 5
		 */
		this.scale = scale;

	}
	// ...
}

要点:

  • 构造函数先调用 super() 完成基类 Curve 的状态初始化(type = 'Curve'arcLengthDivisions = 200 等);
  • scale 既作为构造参数,又被保存为公开实例属性 .scale,默认为 5
  • 传入负值同样合法,会把心形整体镜像翻转;传入 0 则所有采样点退化到原点。

.scale : number

曲线缩放因子,默认 5。它不参与曲线的形状生成逻辑,而是在 getPoint 计算出原始心形点之后,通过 multiplyScalar( this.scale ) 统一放大(或缩小)整条曲线。由于是公开可写属性,实例化后仍可随时修改:

const heart = new HeartCurve(); // 默认 scale = 5
heart.scale = 2;                // 运行时缩小,后续采样立即生效

数学实现:getPoint 与心形参数方程

getPoint 是 HeartCurve 的灵魂,它把参数 t ∈ [0,1] 映射到三维空间中的一点。方法签名与基类抽象一致:

getPoint( t, optionalTarget = new Vector3() )

其源码实现如下(CurveExtras.js):

getPoint( t, optionalTarget = new Vector3() ) {

	const point = optionalTarget;

	t *= 2 * Math.PI;

	const x = 16 * Math.pow( Math.sin( t ), 3 );
	const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
	const z = 0;

	return point.set( x, y, z ).multiplyScalar( this.scale );

}

代码背后是著名的三维心形曲线参数方程(t 为弧度角):

x(t) = 16 · sin³(t)
y(t) = 13·cos(t) − 5·cos(2t) − 2·cos(3t) − cos(4t)
z(t) = 0

从源码结构可作如下几何推断:

  • 参数 t ∈ [0,1] 在方法开头被缩放为 t ∈ [0, 2π],即整条曲线是闭合的:t=0t=1 计算出的点完全重合;
  • z 恒等于 0,所以心形完全落在 XY 平面内(XZ 为水平方向时心形平放,Y 竖直时心形朝上/朝下取决于观察坐标系);
  • 参考采样点(令 scale = 1 代入验证):t = 0.5(即 π/2)→ (16, 4, 0)t = 1(即 π)→ (0, -17, 0)t = 0.75(即 3π/2)→ (-16, 4, 0)。由此可确认曲线下尖端约在 (0, -17·scale),两侧最宽处约在 x = ±16·scale,是开口朝上的心形轮廓
  • 最终 multiplyScalar( this.scale ) 意味着默认 scale = 5 时,心形横向跨度约 ±80、纵向范围约 [-85, 5] 量级(由上述采样点外推)。实际做布局时可据此估算物体摆放位置。

参数与返回值约定

  • t:插值因子,表示曲线上位置,必须在 [0,1] 范围内。该语义贯穿 Curve 体系的全部采样接口(getPointgetPointAt 等),定义见 Curve.js 基类文档
  • optionalTarget:可选的目标向量,计算得到的坐标会直接写入该向量并返回。这是 three.js 曲线接口中典型的“预分配输出缓冲区”优化手段,可避免高频采样时反复创建 Vector3 对象、降低 GC 压力;不传时方法内部会新建一个 Vector3(注意默认参数 = new Vector3() 在源码中位于签名处)。
  • 返回:曲线上的位置向量(Vector3)。
  • 覆盖关系:本方法覆盖了抽象基类 Curve#getPoint

由基类 Curve 继承的能力

HeartCurve 只实现了 getPoint,其余能力全部继承自 src/extras/core/Curve.js。高频使用的方法包括:

方法 说明
getPoint(t, target?) 按插值因子 t 直接采样(HeartCurve 实现的即此方法,采样点在曲线上非等弧长分布)
getPointAt(u, target?) 等弧长采样:先把 ugetUtoTmapping 换算为 t 再调用 getPoint(见 Curve.js
getPoints(divisions) 均匀按 tdivisions + 1 个点,常用于构建 Line 或折线几何
getSpacedPoints(divisions) 等弧长取点,做粒子排布或均匀运动时更平滑
getLength() / getLengths() 计算弧长,由 arcLengthDivisions 控制分段精度
computeFrenetFrames(...) 计算 Frenet 标架,供 TubeGeometry 等沿曲线扫掠的几何使用

例如若要快速生成一条可渲染的心形线:

import { HeartCurve } from 'three/addons/curves/CurveExtras.js';

const curve = new HeartCurve( 3.5 );
const points = curve.getPoints( 200 ); // 201 个 Vector3 采样点

// 用 Line / LineLoop 渲染
const geometry = new THREE.BufferGeometry().setFromPoints( points );
const line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0xff0000 } ) );

结合官方示例:把 HeartCurve 喂给 TubeGeometry

HeartCurve 在仓库中的实际出场点是官方示例 examples/webgl_geometry_extrude_splines.html。该示例演示沿多条三维样条进行“管道挤出(tube extrusion)”,其中 HeartCurve 被构建为其中一个可切换的路径:

// 导入整个 CurveExtras 命名空间
import * as Curves from 'three/addons/curves/CurveExtras.js';

// 一个 Curve 实例字典,供 GUI 切换
const splines = {
	GrannyKnot: new Curves.GrannyKnot(),
	HeartCurve: new Curves.HeartCurve( 3.5 ), // 缩放 3.5 的心形
	VivianiCurve: new Curves.VivianiCurve( 70 ),
	// ...其余曲线
};

随后通过下拉参数选择当前路径,并实时生成管道网格(示例核心逻辑):

const extrudePath = splines[ params.spline ];

tubeGeometry = new THREE.TubeGeometry(
	extrudePath,      // 路径曲线,此处可选择 HeartCurve 实例
	params.extrusionSegments, // 默认 100 段
	2,                // 管道半径
	params.radiusSegments,    // 默认 3
	params.closed             // 默认 true
);

示例还额外对整条管道施加了 mesh.scale(默认 4)做整体变换。运行该示例时把 spline 切换为 “HeartCurve”,即可直观看到由心形曲线作为中心线挤出得到的闭合管道——这正是心形曲线最常见的实用场景:

  1. new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed) 沿心形路径扫掠出管状几何,用于立体爱心、告白装饰、道路/轨道模型等;
  2. curve.getSpacedPoints(n) 取得等距点,让物体(如粒子、相机、动画物体)沿心形轨迹匀速运动;
  3. curve.computeFrenetFrames() 驱动“跟随路径的相机”,做心形环绕航拍效果。

提示:TubeGeometry 在内部会密集调用曲线的 getPointAt(等弧长采样)与 computeFrenetFrames,因此会依赖 arcLengthDivisions 对弧长做数值积分近似。若曲线被缩放得非常大而出现采样不均匀/路径偏移,可从 Curve 基类 中提高 heart.arcLengthDivisions(默认 200)来提升精度。

使用建议与易错点

  • 模块导入路径HeartCurve 只能从 three/addons/curves/CurveExtras.js(即 examples/jsm/curves/CurveExtras.js)导入,不在核心 three 包中,直接 import { HeartCurve } from 'three' 会失败;
  • 闭合性:HeartCurve 数学上天然闭合,但基类 Curve 默认 arcLengthDivisions 缓存的长度计算同样按首尾相接处理,因此可放心用于 TubeGeometry(..., closed = true)LineLoop
  • 平面曲线z 恒为 0,若希望心形立在 XZ 地平面(如 “3D 爱心立牌”),请对生成的网格或曲线实例应用旋转变换(如绕 X 轴旋转 -90°),或自行包装一个把 (x, y) 交换到 (x, z) 的派生曲线;
  • 共享 target 陷阱:复用同一个 optionalTarget 连续调用 getPoint 时,旧结果会被新结果覆盖,需要保留历史点时务必拷贝;
  • scale 与场景尺度:默认 scale = 5 后心形宽约 160 单位,相比默认相机(如示例中 z 数百的视距)属中等大小物体;小场景记得传更小参数(官方示例即用 3.5)。

参考实现位置

内容 仓库位置
HeartCurve 完整实现(构造函数 + getPoint) examples/jsm/curves/CurveExtras.js
CurveExtras 导出声明 examples/jsm/curves/CurveExtras.js
Curve 抽象基类与弧长机制 src/extras/core/Curve.js
曲线工具链(CurvePath) src/extras/core/CurvePath.js
HeartCurve 实战用法(GUI + TubeGeometry) examples/webgl_geometry_extrude_splines.html
官方 API 文档页 docs/pages/HeartCurve.html.md

总结

HeartCurve 是 three.js 附加曲线库中一个“小而精”的类:构造与实现总计约 40 行,却完整承接了 Curve 体系强大的采样、弧长与标架计算能力。掌握它的关键在于三点:从 CurveExtras.js 显式导入、理解 t ∈ [0,1] 到参数方程的角度映射、以及 scale 只做整体缩放而不改变形状本身。在此基础上,把心形曲线接入 getPoints/getSpacedPointsTubeGeometry,即可快速在三维场景中落地“线条爱心”“立体管道爱心”或“沿心形路径运动”等效果。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395