Skip to content

feat(gltf): node animation + Sprite-aligned animation API; Light3d as a world renderable#1504

Merged
obiot merged 1 commit into
masterfrom
feat/mesh-animation
Jun 20, 2026
Merged

feat(gltf): node animation + Sprite-aligned animation API; Light3d as a world renderable#1504
obiot merged 1 commit into
masterfrom
feat/mesh-animation

Conversation

@obiot

@obiot obiot commented Jun 20, 2026

Copy link
Copy Markdown
Member

Builds on the glTF/GLB scene loader (#1502) with node/TRS animation and unifies the 2D ↔ 3D animation and lighting APIs.

Animation

  • glTF node animation — assets with animation channels load as a rig-driven GLTFModel that keeps the node hierarchy intact (an animated parent carries its children). Per-frame TRS sampling (LERP/SLERP/STEP; CUBICSPLINE value), allocation-free pose path (preallocated per-node world buffers). The parser emits the node graph + animation clips.
  • Sprite-aligned API on both Sprite and GLTFModel via a shared parseAnimationOptions helper: setCurrentAnimation(name, { loop, speed, onComplete, next }), getAnimationNames, animationspeed, and play / pause / stop. Additive and non-breaking on Sprite (legacy string / function / no-arg forms preserved).

Lighting (unify 3D with 2D)

  • Light3d is now a world Renderable, managed exactly like Light2d: world.addChild(new Light3d({ direction, color, intensity })) and the active stage auto-tracks it; ambient is a Light3d with type: "ambient". LightingEnvironment is removed (folded into the stage's active-light set + a packMeshLights helper).
  • Both lights co-located in src/lighting/; Light2d converted to TypeScript to match Light3d.

Materials / loader

  • Mesh.textureRepeat — honors the glTF sampler wrapS/wrapT (default REPEAT) so tiling UVs (authored outside [0,1]) sample correctly instead of clamping flat. Known limitation tracked in Mesh.textureRepeat mutates the shared per-image TextureAtlas.repeat (wrap mode is image-global) #1503.
  • External glTF resources — external .bin buffers + image uris resolved relative to the asset URL, with crossOrigin forwarded.
  • OBJ/MTL textures auto-loadpreloadMTL loads each material's map_Kd relative to the .mtl (parity with the glTF external-texture path), so an OBJ model's textures come for free; preloading the model + material is enough. A missing texture is warned + skipped (white-pixel fallback), not fatal.

Fixes

  • Camera3d.isVisible no longer NaN-culls sizeless grouping containers (and their whole subtree) — infinite-bounds containers are always visible (matches Camera2d).

Example

New glTF Animated Model example (Kenney Blocky Characters, CC0): walking character under Camera3d, clip selector + speed + play/pause/stop, and drag-to-orbit.

Tests / docs

  • ~125 new tests (parser graph/animations/wrap + robustness, keyframe sampler, GLTFModel, lighting + packMeshLights, Sprite play/pause/stop, parseAnimationOptions, Camera3d infinite-bounds, Mesh.textureRepeat, OBJ map_Kd auto-load). Full suite 4239 passed / 15 skipped / 0 failed; lint + tsc + doc-gen clean.
  • README, CHANGELOG (19.8), and the two 3D wiki pages updated (support matrices reformatted to feature → ✅/❌, modern named-import API style).

Related

🤖 Generated with Claude Code

… a world renderable

Adds glTF node/TRS animation and unifies the 2D/3D animation + lighting APIs.

Animation
- glTF node animation: assets with animation channels load as a rig-driven
  GLTFModel that keeps the node hierarchy intact (an animated parent carries
  its children). Per-frame TRS sampling (LERP/SLERP/STEP; CUBICSPLINE value),
  allocation-free pose path. Parser emits the node graph + animation clips.
- Sprite-aligned API on both Sprite and GLTFModel via a shared
  parseAnimationOptions helper: setCurrentAnimation(name, { loop, speed,
  onComplete, next }), getAnimationNames, animationspeed, play/pause/stop
  (additive + non-breaking on Sprite — legacy string/fn/no-arg forms preserved).

Lighting (unify 3D with 2D)
- Light3d is now a world Renderable managed exactly like Light2d: add it to the
  world and the active stage auto-tracks it; ambient is a Light3d type:"ambient".
  LightingEnvironment removed (folded into stage active-light set + packMeshLights).
- Both lights co-located in src/lighting/; Light2d converted to TypeScript.

Materials / loader
- Mesh.textureRepeat (honors glTF sampler wrapS/wrapT, default REPEAT) so tiling
  UVs sample correctly instead of clamping flat (tracked limitation: #1503).
- External glTF resources: external .bin + image uris resolved relative to the
  asset URL, crossOrigin forwarded.

Fixes
- Camera3d.isVisible no longer NaN-culls sizeless grouping containers (and their
  whole subtree) — infinite-bounds containers are always visible.

Example: glTF Animated Model (Kenney Blocky Characters, CC0) with clip selector,
speed, play/pause/stop, and drag-to-orbit.

~120 new tests (parser graph/animations/wrap, sampler, GLTFModel, lighting,
Sprite play/pause/stop, parseAnimationOptions). Full suite 4237/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@obiot obiot force-pushed the feat/mesh-animation branch from 5b171a9 to e8b01f4 Compare June 20, 2026 01:50
@obiot obiot merged commit 62cd259 into master Jun 20, 2026
6 checks passed
@obiot obiot deleted the feat/mesh-animation branch June 20, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OBJ/MTL: auto-load map_Kd textures relative to the .mtl (make the explicit texture preload optional)

1 participant