packages/core/src/features/sprites/model.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import { defineAnimation } from '../../lib/animation/definition.js';
2 import type { PibblAnimationDefinition } from '../../lib/animation/types.js';
3
4 /** Pixel rectangle within an atlas page or original frame. See {@link SpriteFrame}. */
5 export interface SpriteRectangle { /** Horizontal pixel offset. */ readonly x: number; /** Vertical pixel offset. */ readonly y: number; /** Width in pixels. */ readonly width: number; /** Height in pixels. */ readonly height: number }
6 /** Normalized frame information returned by a format adapter. See {@link SpriteFrame}. */
7 export interface SpriteFrameData {
8 /** Unique frame name in this sheet. */ readonly name: string;
9 /** Zero-based atlas page index; defaults to zero. */ readonly page?: number;
10 /** Packed rectangle within the atlas page. */ readonly rect: SpriteRectangle;
11 /** Original frame dimensions before trimming. */ readonly sourceSize?: Readonly<{ /** Width in pixels. */ width: number; /** Height in pixels. */ height: number }>;
12 /** Trimmed content offset within the original frame. */ readonly trim?: Readonly<{ /** Horizontal pixel offset. */ x: number; /** Vertical pixel offset. */ y: number }>;
13 /** Whether the packed rectangle is rotated clockwise. */ readonly rotated?: boolean;
14 }
15 /** One normalized timed frame reference. Durations are milliseconds. See {@link SpriteFrame}. */
16 export interface SpriteAnimationFrameData { /** Name of the frame used by this animation step. */ readonly frame: string; /** Positive duration of the animation step in milliseconds. */ readonly duration: number }
17 /** Adapter result. Image paths resolve relative to the metadata URL. See {@link SpriteFrame}. */
18 export interface SpriteSheetData {
19 /** Atlas image URLs, relative to the metadata URL. */ readonly images: readonly string[];
20 /** Normalized frame records for this sheet. */ readonly frames: readonly SpriteFrameData[];
21 /** Named sequences of timed frame references. */ readonly animations?: Readonly<Record<string, readonly SpriteAnimationFrameData[]>>;
22 }
23 /** Import adapter; loaders invoke this directly without a format registry. See {@link SpriteFrame}. */
24 export interface SpriteSheetFormat { /** Convert decoded metadata to normalized sheet data.
25 * @param data - Parsed atlas JSON.
26 * @returns Validated adapter data for loading the sheet.
27 */ readonly parse: (data: unknown) => SpriteSheetData }
28
29 export interface FrameRecord extends SpriteFrameData { readonly image: HTMLImageElement; readonly owner: { disposed: boolean } }
30 const records = new WeakMap<SpriteFrame, FrameRecord>();
31 /** Immutable atlas frame. Obtain frames from loadSpriteSheet; images belong to the sheet. See {@link SpriteFrame}. */
32 export class SpriteFrame {
33 private readonly identity = true;
34 /** Original logical frame width, before transparent borders were trimmed.
35 * @returns Width in logical pixels.
36 */
37 get width(): number { return frameRecord(this).sourceSize?.width ?? frameRecord(this).rect.width; }
38 /** Original logical frame height, before transparent borders were trimmed.
39 * @returns Height in logical pixels.
40 */
41 get height(): number { return frameRecord(this).sourceSize?.height ?? frameRecord(this).rect.height; }
42 /** Stable name supplied by the atlas or numeric grid index.
43 * @returns The frame name.
44 */
45 get name(): string { void this.identity; return frameRecord(this).name; }
46 }
47 export function makeFrame(record: FrameRecord): SpriteFrame {
48 const frame = new SpriteFrame(); records.set(frame, record); Object.freeze(frame); return frame;
49 }
50 export function frameRecord(frame: SpriteFrame): FrameRecord {
51 const record = records.get(frame);
52 if (!record) throw new TypeError('Expected a SpriteFrame from loadSpriteSheet.');
53 if (record.owner.disposed) throw new Error('Sprite sheet has been disposed.');
54 return record;
55 }
56 /** Author a finite discrete animation using FPS or explicit positive millisecond durations. See {@link SpriteFrame}.
57 * @param options - Frames with a shared FPS or individual millisecond durations.
58 * @returns A seekable discrete animation definition.
59 */
60 export function defineSpriteAnimation(options: {
61 readonly frames: readonly SpriteFrame[];
62 readonly fps: number;
63 } | {
64 readonly frames: readonly Readonly<{ frame: SpriteFrame; duration: number }>[];
65 }): PibblAnimationDefinition<SpriteFrame> {
66 if (!options.frames.length) throw new RangeError('Sprite animation requires at least one frame.');
67 if ('fps' in options && (!Number.isFinite(options.fps) || options.fps <= 0)) throw new RangeError('Sprite FPS must be finite and positive.');
68 const entries = options.frames.map(item => {
69 const entry = 'fps' in options ? { frame: item as SpriteFrame, duration: 1000 / options.fps } : item as { frame: SpriteFrame; duration: number };
70 frameRecord(entry.frame);
71 if (!Number.isFinite(entry.duration) || entry.duration <= 0) throw new RangeError('Sprite duration must be finite and positive.');
72 return { ...entry };
73 });
74 let duration = 0;
75 const ends = entries.map(entry => duration += entry.duration);
76 return defineAnimation({ duration, sample: ({ localTime }) => {
77 let low = 0, high = ends.length - 1;
78 while (low < high) { const mid = (low + high) >>> 1; if (localTime < ends[mid]) high = mid; else low = mid + 1; }
79 return entries[low].frame;
80 } });
81 }
82
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.