three.js HeartCurve 心形曲线详解:源码实现、getPoint 采样原理与 TubeGeometry 应用
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=0与t=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体系的全部采样接口(getPoint、getPointAt等),定义见 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?) |
按等弧长采样:先把 u 经 getUtoTmapping 换算为 t 再调用 getPoint(见 Curve.js) |
getPoints(divisions) |
均匀按 t 取 divisions + 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”,即可直观看到由心形曲线作为中心线挤出得到的闭合管道——这正是心形曲线最常见的实用场景:
- 用
new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed)沿心形路径扫掠出管状几何,用于立体爱心、告白装饰、道路/轨道模型等; - 用
curve.getSpacedPoints(n)取得等距点,让物体(如粒子、相机、动画物体)沿心形轨迹匀速运动; - 用
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/getSpacedPoints 或 TubeGeometry,即可快速在三维场景中落地“线条爱心”“立体管道爱心”或“沿心形路径运动”等效果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00