backend/gsap API

包 Luna-Flow/geometry3d/backend/gsap 把前端的 DrawList 画成 SVG 多边形,并用 GSAP 时间轴驱动动画。三角形按由远到近排序(画家算法),写入 <svg> 元素中可复用的 <polygon> 节点。GsapPlayer 封装了一个 GSAP 时间轴,它以当前时间回调 MoonBit。该包只支持 js 目标,使用 moonbit-community/rabbita/dom,并要求 GSAP 3 位于 globalThis.gsap。

import {
  "Luna-Flow/geometry3d/frontend",
  "Luna-Flow/geometry3d/backend/gsap",
  "moonbit-community/rabbita/dom",
}

supported_targets = "js"

GSAP 设计解释了排序方式及其局限;GSAP 教程搭建了一个播放器页面。

配置

GsapSvgColor

GsapSvgColor 是 RGB 颜色,各通道取值于 [0,255][0, 255]。

pub struct GsapSvgColor {
  red : Int
  green : Int
  blue : Int
}

GsapSvgColor::rgb

GsapSvgColor::rgb(r, g, b) 构造一个颜色,并把每个通道截断到 [0,255][0, 255]。

pub fn GsapSvgColor::rgb(Int, Int, Int) -> Self

GsapSvgRenderConfig

GsapSvgRenderConfig 包含 SVG 尺寸、背景色、前景色和明暗级数。

pub struct GsapSvgRenderConfig {
  width : Int
  height : Int
  background_color : GsapSvgColor
  foreground_color : GsapSvgColor
  shade_levels : Int
}

这些字段在包外是只读的。每次渲染调用都会规范化配置:非正的尺寸变为 640 × 480,颜色通道被截断,shade_levels 被截断到 [2,256][2, 256]。

GsapSvgRenderConfig::default, GsapSvgRenderConfig::sized

GsapSvgRenderConfig::default() 为 640 × 480,GsapSvgRenderConfig::sized(w, h) 使用给定尺寸(非正值变为 640 或 480)。两者都使用背景色 rgb(7, 12, 22)、前景色 rgb(112, 226, 255) 和 256 个明暗级别。

pub fn GsapSvgRenderConfig::default() -> Self
pub fn GsapSvgRenderConfig::sized(Int, Int) -> Self
test "svg config" {
  let config = @gsap.GsapSvgRenderConfig::sized(-1, 360)
  debug_inspect((config.width, config.height), content="(640, 360)")
  let fg = config.foreground_color
  debug_inspect([fg.red, fg.green, fg.blue], content="[112, 226, 255]")
}

渲染

render_draw_list

render_draw_list(svg, list, config) 更新 <svg> 元素,使其显示一个绘制列表。

pub fn render_draw_list(@dom.Element, @frontend.DrawList, GsapSvgRenderConfig) -> Unit

它按三个顶点的平均深度对三角形排序,最远的在前,深度相同时保持列表顺序。它设置元素的 width、height、viewBox(0 0 W H)、role="img" 和 data-geometry3d-backend="gsap-svg" 属性。它确保该元素的直接子节点中有背景 <rect data-gsap-svg-background> 和分组 <g data-gsap-svg-triangles>(第一次时会替换所有子节点)。然后把分组增减到每个三角形对应一个 <polygon>,并写入每个多边形的 points 和 fill。填充色是前景色乘以量化后的亮度 q/(L−1)q/(L - 1),其中 q=round⁡(clamp⁡(I)(L−1))q = \operatorname{round}(\operatorname{clamp}(I)(L - 1)),与 Canvas 后端相同。多边形在多次调用之间复用,因此重复渲染不会重新创建 DOM 节点。

render_scene

render_scene(svg, scene, view, config) 依次运行 @frontend.build_draw_list 和 render_draw_list。

pub fn render_scene(@dom.Element, @frontend.Scene, @frontend.RenderView, GsapSvgRenderConfig) -> Unit
fn draw_on(svg : @dom.Element, angle : Double) -> Unit {
  let view = @frontend.RenderView::perspective(
    @view.Camera3::default(4.5),
    @view.PerspectiveProjection::new(@view.Viewport::new(640, 480), 400.0),
  )
  let scene = @frontend.Scene::single(
    @core.cube_mesh(1.0),
    @core.Transform3::rotation(0.4, angle, 0.0),
    @frontend.Light::default(),
  )
  @gsap.render_scene(svg, scene, view, @gsap.GsapSvgRenderConfig::sized(640, 480))
}

播放

GsapTimeline

GsapTimeline 是指向 JavaScript GSAP 时间轴对象的不透明句柄。

type GsapTimeline

GsapPlayer

GsapPlayer 是一个默认暂停、时长固定的 GSAP 时间轴,它把当前时间报告给 MoonBit 回调。

pub struct GsapPlayer {
  timeline : GsapTimeline
  duration_seconds : Double
}

GsapPlayer::new

GsapPlayer::new(duration, on_frame, repeat?) 创建一个暂停的时间轴,它以线性缓动把时钟从 00 补间到 duration 秒,并在每次更新时调用 on_frame(t)。它还会立即调用一次 on_frame(0.0)。

pub fn GsapPlayer::new(Double, (Double) -> Unit, repeat? : Int) -> Self

非正的时长变为 1 秒。repeat 是 GSAP 的重复次数:-1(默认)表示无限重复,0 表示只播放一次。如果 globalThis.gsap.timeline 不可用,它会抛出 JavaScript 错误而中止。

GsapPlayer::play, GsapPlayer::pause, GsapPlayer::reverse, GsapPlayer::restart, GsapPlayer::kill

这些方法转发到时间轴的 play()、pause()、reverse()、restart() 和 kill()。

pub fn GsapPlayer::play(Self) -> Unit
pub fn GsapPlayer::pause(Self) -> Unit
pub fn GsapPlayer::reverse(Self) -> Unit
pub fn GsapPlayer::restart(Self) -> Unit
pub fn GsapPlayer::kill(Self) -> Unit

调用 kill 之后不得再使用该播放器。

GsapPlayer::seek, GsapPlayer::time

player.seek(t) 把播放头移到 t 秒,并截断到 [0,duration][0, \mathit{duration}];player.time() 返回播放头的局部时间。

pub fn GsapPlayer::seek(Self, Double) -> Unit
pub fn GsapPlayer::time(Self) -> Double

GsapPlayer::progress, GsapPlayer::set_progress

player.progress() 以单次迭代的比例返回播放头位置;player.set_progress(p) 设置该位置,并把 p 截断到 [0,1][0, 1]。

pub fn GsapPlayer::progress(Self) -> Double
pub fn GsapPlayer::set_progress(Self, Double) -> Unit

GsapPlayer::time_scale, GsapPlayer::set_time_scale

player.time_scale() 返回播放速度因子;player.set_time_scale(k) 设置该因子,非正的 k 会被替换为 1.0。

pub fn GsapPlayer::time_scale(Self) -> Double
pub fn GsapPlayer::set_time_scale(Self, Double) -> Unit

要倒放,请使用 reverse,而不是负的速度因子。

GsapPlayer::set_repeat

player.set_repeat(n) 设置重复次数,小于 -1 的值会被提升为 -1(无限重复)。

pub fn GsapPlayer::set_repeat(Self, Int) -> Unit

GsapPlayer::is_paused

player.is_paused() 返回时间轴是否处于暂停状态。

pub fn GsapPlayer::is_paused(Self) -> Bool
fn start_player(svg : @dom.Element) -> @gsap.GsapPlayer {
  let player = @gsap.GsapPlayer::new(8.0, fn(t) { draw_on(svg, t * 0.8) }, repeat=-1)
  player.set_time_scale(0.5)
  player.play()
  player
}