feat: Sprite3d — 3D billboard sprites + shared FrameAnimation engine#1513
Merged
Conversation
Add `Sprite3d`, the 3D counterpart of `Sprite`: a textured quad rendered under a `Camera3d` for the 2.5D workflow (characters, pickups, foliage, signs). Headline feature is billboarding via `billboard: false | "cylindrical" | "spherical"`. As a thin `Mesh` subclass it rides the same world-space pipeline (depth testing, frustum culling) and material flags (`lit`, `emissive`, `alphaCutoff`). Frame animation is shared with `Sprite` through a new `FrameAnimation` engine, so 2D and 3D animation behave identically (timing, looping, chaining, speed). The engine owns the animation state; each host exposes it via accessors and applies the current frame through an `applyFrame` callback. The host owns its `isDirty` flag (set in `_applyFrame`); the engine never touches it and `update()` returns a pure `changed` signal. Highlights: - billboard modes (fixed / cylindrical / spherical), Camera3d-only - `Camera3d.getBasis`/`getRight`/`getUp`/`getForward` basis accessors - packed `TextureAtlas` parity — rotated AND trimmed regions mapped onto the quad (UV permutation + logical-frame sub-rect placement) - `alphaCutoff` defaults to 0.5 (opaque mesh pass) so transparent sprite backgrounds cut out cleanly; `0` for a fully-opaque quad - `flipX()` / `flipY()` mirror the local quad — works across billboard modes and rotated/trimmed regions, persists through animation cycles - `Mesh` texture resolver is now framewidth/frameheight-aware - pooled `current.offset` released on destroy (also fixes a pre-existing Sprite leak) - new Billboard Sprites example (same character in all three modes) - CHANGELOG + Working-in-3D wiki updated Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
Sprite3d, the 3D counterpart ofSprite: a textured quad rendered under aCamera3dfor the 2.5D workflow (characters, pickups, foliage, signs, particles). As a thinMeshsubclass it rides the same world-space pipeline (depth testing, frustum culling) and material flags (lit,emissive,alphaCutoff).Frame animation is now shared between
SpriteandSprite3dvia a newFrameAnimationengine, so 2D and 3D animation behave identically.Highlights
billboard: false | "cylindrical" | "spherical"(Camera3d-only). Same up/forward convention as a loaded glTF scene, so they compose without flipping.FrameAnimationengine — one implementation drives both hosts (addAnimation/setCurrentAnimation/play/pause/stop, looping/chaining/speed). Engine owns the animation state; hosts expose it via accessors and apply frames through anapplyFramecallback.isDirty(set in_applyFrame); the engine never reads/writes it, andupdate()returns a purechangedsignal. Both hosts'update()are uniform (engine → super.update()).TextureAtlasregions including rotated and trimmed frames map onto the quad (90° UV permutation + logical-frame sub-rect placement).Mesh's resolver is nowframewidth/frameheight-aware.alphaCutoffdefaults to0.5to cleanly cut out a sprite's transparent background (correct depth, no sorting);0for a fully-opaque quad.flipX()/flipY()— mirror the local quad geometry; works across all billboard modes + rotated/trimmed regions, and persists through animation cycles.Camera3d.getBasis/getRight/getUp/getForward— world-space basis accessors.current.offsetreleased ondestroy()(also fixes a pre-existingSpriteleak).Example
New Billboard Sprites example: the same animated character shown in all three billboard modes side by side (FIXED goes edge-on; UPRIGHT/FACE keep facing the camera) with floating name tags. Uses the shared cityscene atlas (packer-rotated + trimmed
capguy/walkframes), so it also validates atlas mapping + transparency end-to-end.Tests
SpriteandSprite3d(every accessor + proxied method, adversarial cases, dirty-flag ownership, thechangedreturn).FrameAnimationin isolation (proves the engine sets noisDirty).Sprite3d: billboard orientation (deterministicworldToScreen), trim/rotation/combined atlas mapping, alphaCutoff, flipX/flipY (incl. flip × rotated/trimmed atlas, full-cycle persistence, on-screen mirror), resource cleanup.Full suite: 4399 passing, lint 0 errors, types clean, examples
tscclean.Docs
CHANGELOG (19.8
_unreleased_) and the Working in 3D wiki page updated (Sprite3d section: billboard modes, animation, atlas parity, transparency/alphaCutoff, flipX/flipY, add-to-world examples).🤖 Generated with Claude Code