demo_gsap 设计

设计目标

demo_gsap 表明,由于每幅画面都是时间的纯函数,geometry3d 场景可以像任何 GSAP 动画一样被拖动、倒放和重播。它是一个完整的播放器页面,使用的 JavaScript 尽可能少。

数学背景

场景作为时间的函数

GSAP 报告时间轴时间 t∈[0,8]t \in [0, 8] 秒,程序绘制 render(scene(t))\text{render}(\text{scene}(t))。没有任何东西依赖上一帧,因此跳转到任意 tt、倒放或以其他速度播放时,显示的都恰好是该 tt 的画面。

推拉距离 d(t)=3.2+66.37⋅12(1+sin⁡(πt/2))d(t) = 3.2 + 66.37 \cdot \tfrac12\big(1 + \sin(\pi t/2)\big) 的周期为 4 秒,因此 8 秒的循环包含两个完整周期,并在 t=8t = 8 处无缝衔接。焦距随 f=23 d/3.2f = 23\, d / 3.2 mm 变化,如 demo 设计中所推导,这使立方体的投影大小保持不变。圆环角度 1.8 t1.8\,t 每个循环前进 14.414.4 rad,这不是整圈数(2π⋅2.292\pi \cdot 2.29),因此循环重新开始时圆环会轻微跳动。

设计决策

由 GSAP 掌管时钟

所有播放状态(暂停、方向、速度、重复、位置)都保存在 GSAP 时间轴中,控件直接调用 GsapPlayer 的方法。MoonBit 程序只保存所选场景。切换场景会立即重新渲染当前时刻,因此暂停中的播放器也会更新。

薄薄的 JavaScript 粘合层

读取 event.target.value、设置范围输入以及写入文字由三个 extern "js" 函数完成。它们是页面特有的、不会被复用,因此不属于后端。

正确性与不变量

  • 时间轴时刻 tt 所显示的画面与播放头如何到达 tt 无关。
  • 推拉场景在循环中是周期性的;圆环场景不是(见上文)。

被否决的方案

  • 在 MoonBit 中维护帧计数器 会破坏跳转和倒放。
  • 用 rabbita 在 MoonBit 中构建控件 会增加代码而不改变行为;静态 HTML 更简单。

边界

demo_gsap 不导出 API,按发布形态不能离线工作,也不解决后端画家排序带来的瑕疵(见 GSAP 设计)。