首页
/ three.js 中的 CinquefoilKnot:五瓣结曲线的参数方程、实现与管状几何实战

three.js 中的 CinquefoilKnot:五瓣结曲线的参数方程、实现与管状几何实战

2026-09-06 13:49:07作者:姚月梅Lane

CinquefoilKnot 是 three.js 附加库(addons)中 CurveExtras.js 提供的一条参数化空间曲线,即经典的 (2,5) 环面结(五瓣结)。本文基于官方文档 CinquefoilKnot.html.md 展开,覆盖其构造参数、getPoint 数学实现与基类 Curve 的弧长能力,并结合官方示例 webgl_geometry_extrude_splines.html 演示如何用该曲线驱动 TubeGeometry 生成管状网格,读完后你将能在自己的项目中直接构建并渲染一条五瓣结曲线。

spline extrusion 示例中由 CinquefoilKnot 等曲线驱动生成的管状几何

一、定位与继承关系:它是 Curve 的一个参数化实现

文档明确给出的继承链为 Curve → CinquefoilKnot。基类 Curve 是 three.js 中所有解析曲线的抽象基类,CinquefoilKnot 只需覆写 getPoint( t, optionalTarget ) 这一个抽象方法,即可获得基类提供的全部通用能力。

CinquefoilKnot 定义于 CurveExtras.js(注释为 "A Cinquefoil Knot"),它与同文件中的 TrefoilKnot(三叶结,p=3, q=2)、TorusKnot(p=3, q=4)同属环面结家族,区别只在于参数 pq 的取值。从源码结构看,CinquefoilKnot 固定使用 p=2、q=5,正对应数学上的 (2,5) 环面结——曲线在环面中心轴方向绕行 2 圈、沿环面大圆方向绕行 5 圈,形成五个对称"花瓣"的闭环结构。

二、安装与导入:Addon 必须显式导入

CinquefoilKnot 属于 addon,不在 three 核心包内,需要显式从附加库路径导入(详见官方文档 Installation#Addons 的说明):

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

若使用 importmap,参考官方示例中的配置(webgl_geometry_extrude_splines.html):

<script type="importmap">
    {
        "imports": {
            "three": "../build/three.module.js",
            "three/addons/": "./jsm/"
        }
    }
</script>

CurveExtras.js 是 addon 侧的聚合入口,文件末尾统一导出 GrannyKnot、HeartCurve、VivianiCurve、KnotCurve、HelixCurve、TrefoilKnot、TorusKnot、CinquefoilKnot、TrefoilPolynomialKnot、FigureEightPolynomialKnot 及若干 DecoratedTorusKnot 变体(见 CurveExtras.js 导出列表)。

三、构造参数与属性:new CinquefoilKnot( scale = 10 )

构造函数只有一个可选参数 scale

const curve = new CinquefoilKnot();      // scale 使用默认值 10
const bigKnot = new CinquefoilKnot(20); // 显式指定 scale = 20

对应源码(CurveExtras.js):

class CinquefoilKnot extends Curve {

    /**
     * Constructs a new Cinquefoil Knot.
     *
     * @param {number} [scale=10] - The curve's scale.
     */
    constructor( scale = 10 ) {

        super();

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

    }
    // ...
}

.scale : number

曲线的整体缩放因子,默认值为 10。它作用于 getPoint 返回向量的最后一步(multiplyScalar( this.scale )),即对 x/y/z 三个分量统一乘以 scale。修改 scale 相当于把曲线在三维空间中均匀放大或缩小;曲线本身的拓扑形状(五瓣结构)不改变。官方示例中就实例化了 new Curves.CinquefoilKnot( 20 ) 来展示放大后的形态(webgl_geometry_extrude_splines.html)。

四、核心方法 .getPoint( t, optionalTarget ) 的数学实现

文档签名:

.getPoint( t : number, optionalTarget : Vector3 ) : Vector3
  • t:曲线位置插值因子,必须位于 [0, 1] 区间;
  • optionalTarget:可选的目标向量,计算结果写入该向量并返回(避免每次调用产生新对象,适合高频调用场景);
  • 返回:曲线上对应位置的三维坐标,覆写基类 Curve#getPoint

完整实现(CurveExtras.js):

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

    const point = optionalTarget;

    const p = 2;   // 绕环面中心轴的圈数
    const q = 5;   // 绕环面管截面的圈数("五瓣"来源)

    t *= Math.PI * 2; // 将 [0,1] 映射到 [0, 2π],保证曲线闭环

    const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
    const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
    const z = Math.sin( q * t );

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

}

实现要点拆解:

  1. 参数重映射t *= Math.PI * 2 把归一化的 [0,1] 参数换算到 [0, 2π]。由于 p=2、q=5 都是整数,cos/sin 项在 t=0 与 t=1 处取值完全一致,曲线天然首尾闭合(在 t=0 处 p、q 方向同时完成整数周期);
  2. 标准环面结方程(2 + cos(qt))·cos(pt)(2 + cos(qt))·sin(pt) 构成环面上半径为 2 的管圆截面轨迹,sin(qt) 提供离环面的高度摆动;这是 (2,5) 环面结的参数方程,CinquefoilKnot 与同文件 TorusKnot(p=3, q=4)只是 p、q 常数不同;
  3. 可选目标向量:默认参数 optionalTarget = new Vector3() 保证不传参时也可独立调用,而传入复用向量(如渲染循环中的临时对象)可消除每帧垃圾回收压力。

五、继承自 Curve 的实用能力:弧长参数化与等距采样

CinquefoilKnot 自身只实现了 getPoint,但得益于继承 Curve,它免费获得一系列几何处理能力:

  • getPointAt( u, optionalTarget ):按弧长参数化取点——先通过 getUtoTmapping 把等弧长的 u 反查为参数 t 再调用 getPointCurve.js)。由于五瓣结曲线各段速率不均匀,"视觉匀速"的动画(如相机沿曲线行进)应使用 getPointAt 而非 getPoint
  • getPoints( divisions = 5 ) / getSpacedPoints( divisions = 5 ):分别按参数和按弧长采样出 divisions + 1 个点,可用于生成 Line 可视化或提取控制点;
  • getLength() / getLengths():返回曲线总长与累积分段长度。基类默认 arcLengthDivisions = 200Curve.js),即通过 200 段弦长累加近似弧长,并缓存在 cacheArcLengths 中;
  • updateArcLengths():修改 scale 等几何参数后必须调用(或置 needsUpdate = true),以让弧长缓存失效重算,否则 getPointAtgetLength 的结果会停留在旧形状上。

六、实战:用 CinquefoilKnot 生成管状几何与沿线相机

官方示例 webgl_geometry_extrude_splines.html 是观察 CinquefoilKnot 最直观的场景。它把 CurveExtras 中的 14 条曲线(外加 2 条 CatmullRom 样条)放入一个字典,通过 lil-gui 面板切换:

import * as Curves from 'three/addons/curves/CurveExtras.js';

// 保留 Curve 实例的字典
const splines = {
    GrannyKnot: new Curves.GrannyKnot(),
    HeartCurve: new Curves.HeartCurve( 3.5 ),
    /* ... */
    TrefoilKnot: new Curves.TrefoilKnot(),
    TorusKnot: new Curves.TorusKnot( 20 ),
    CinquefoilKnot: new Curves.CinquefoilKnot( 20 ),
    /* ... */
};

选定曲线后用 TubeGeometry 沿曲线扫掠出管状网格(示例源码):

const params = {
    spline: 'GrannyKnot',
    scale: 4,
    extrusionSegments: 100,  // 沿曲线的分段数
    radiusSegments: 3,       // 管截面的径向分段数
    closed: true,            // 首尾是否封闭
};

function addTube() {

    // 清理旧网格与几何体
    if ( mesh !== undefined ) {
        parent.remove( mesh );
        mesh.geometry.dispose();
    }

    const extrudePath = splines[ params.spline ];

    tubeGeometry = new THREE.TubeGeometry(
        extrudePath,                    // 路径曲线(此处可传 CinquefoilKnot 实例)
        params.extrusionSegments,
        2,                              // 管半径
        params.radiusSegments,
        params.closed
    );

    const mesh = new THREE.Mesh( tubeGeometry, material );
    // ...
}

面板中的 extrusionSegments(50~500,步长 50)、radiusSegments(2~12)、closed 等滑块会触发 addTube() 重建几何体;由于 CinquefoilKnot 本身闭环,closed = true 时首尾接缝自然吻合。

同一示例还演示了"相机沿曲线飞行"的经典用法(示例源码):

function render() {

    const time = Date.now();
    const looptime = 20 * 1000;
    const t = ( time % looptime ) / looptime; // 20 秒一个周期

    // 关键:使用 getPointAt 保证相机沿曲线匀速运动
    tubeGeometry.parameters.path.getPointAt( t, position );
    position.multiplyScalar( params.scale );

    // 用 binormal 偏移避免相机陷入管壁
    tubeGeometry.parameters.path.getTangentAt( t, direction );
    normal.copy( binormal ).cross( direction );
    position.add( normal.clone().multiplyScalar( 15 ) );

    // lookAhead 模式:沿弧长向前看 30 个单位
    tubeGeometry.parameters.path.getPointAt(
        ( t + 30 / tubeGeometry.parameters.path.getLength() ) % 1, lookAt );

    splineCamera.matrix.lookAt( splineCamera.position, lookAt, normal );
    // ...
}

这段代码完整展示了 CinquefoilKnot 作为 Curve 子类的三个高频用途:getPointAt(匀速定位)、getTangentAt(切线方向)、getLength(弧长换算),且全部写入复用的目标向量以避免逐帧分配。

七、API 速查表

成员 签名 说明 源码位置
构造函数 new CinquefoilKnot( scale = 10 ) scale 为曲线缩放因子,默认 10 CurveExtras.js
.scale number 曲线缩放因子,默认 10,作用在 getPoint 输出上 同上
.getPoint() ( t, optionalTarget = new Vector3() ) => Vector3 覆写 Curve#getPointt ∈ [0,1],内部映射到 [0, 2π] CurveExtras.js
.getPointAt() 继承自 Curve 按弧长参数取点,动画/相机运动推荐 Curve.js
.getPoints() / .getSpacedPoints() 继承自 Curve 参数采样 / 等距采样,返回 divisions + 1 个点 Curve.js
.getLength() / .updateArcLengths() 继承自 Curve 总长计算(默认 200 段近似);改 scale 后需刷新弧长缓存 Curve.js

八、小结

CinquefoilKnot 是一个"小类、大价值"的典型:实现上只有约 20 行(CurveExtras.js),但它是 (2,5) 环面结参数方程的精确表达,叠加 Curve 基类的弧长参数化、等距采样与长度计算能力,可直接服务于 TubeGeometry 建模、相机路径动画、粒子沿线运动等场景。开发时的三条注意事项:scale 只改变尺寸不改变形状;修改 scale 后调用 updateArcLengths() 刷新弧长缓存;高频取点时传入复用的 optionalTarget 向量以减少分配。更多同类曲线(三叶结、装饰环面结等)与完整交互演示,可参考 CurveExtras.jswebgl_geometry_extrude_splines.html

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