@flighthq/spritesheet 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/spritesheet.d.ts +5 -0
- package/dist/spritesheet.d.ts.map +1 -0
- package/dist/spritesheet.js +28 -0
- package/dist/spritesheet.js.map +1 -0
- package/dist/spritesheetAnimation.d.ts +4 -0
- package/dist/spritesheetAnimation.d.ts.map +1 -0
- package/dist/spritesheetAnimation.js +48 -0
- package/dist/spritesheetAnimation.js.map +1 -0
- package/dist/spritesheetData.d.ts +37 -0
- package/dist/spritesheetData.d.ts.map +1 -0
- package/dist/spritesheetData.js +39 -0
- package/dist/spritesheetData.js.map +1 -0
- package/dist/spritesheetFrame.d.ts +3 -0
- package/dist/spritesheetFrame.d.ts.map +1 -0
- package/dist/spritesheetFrame.js +11 -0
- package/dist/spritesheetFrame.js.map +1 -0
- package/dist/spritesheetFrom.d.ts +6 -0
- package/dist/spritesheetFrom.d.ts.map +1 -0
- package/dist/spritesheetFrom.js +90 -0
- package/dist/spritesheetFrom.js.map +1 -0
- package/dist/spritesheetPlayer.d.ts +17 -0
- package/dist/spritesheetPlayer.d.ts.map +1 -0
- package/dist/spritesheetPlayer.js +269 -0
- package/dist/spritesheetPlayer.js.map +1 -0
- package/dist/spritesheetTimelineSource.d.ts +3 -0
- package/dist/spritesheetTimelineSource.d.ts.map +1 -0
- package/dist/spritesheetTimelineSource.js +38 -0
- package/dist/spritesheetTimelineSource.js.map +1 -0
- package/dist/spritesheetValidation.d.ts +5 -0
- package/dist/spritesheetValidation.d.ts.map +1 -0
- package/dist/spritesheetValidation.js +97 -0
- package/dist/spritesheetValidation.js.map +1 -0
- package/package.json +45 -0
- package/src/spritesheet.test.ts +61 -0
- package/src/spritesheetAnimation.test.ts +128 -0
- package/src/spritesheetData.test.ts +149 -0
- package/src/spritesheetFrame.test.ts +50 -0
- package/src/spritesheetFrom.test.ts +259 -0
- package/src/spritesheetPlayer.test.ts +661 -0
- package/src/spritesheetTimelineSource.test.ts +60 -0
- package/src/spritesheetValidation.test.ts +169 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { clearSignal, createSignal, emitSignal } from '@flighthq/signals';
|
|
2
|
+
export function acquireSpritesheetPlayer() {
|
|
3
|
+
if (playerPool.length > 0) {
|
|
4
|
+
const p = playerPool.pop();
|
|
5
|
+
// Reset to a clean idle state (signals are already disconnected by releaseSpritesheetPlayer).
|
|
6
|
+
p.animation = null;
|
|
7
|
+
p.complete = true;
|
|
8
|
+
p.elapsed = 0;
|
|
9
|
+
p.frameIndex = 0;
|
|
10
|
+
p.paused = false;
|
|
11
|
+
p.queue.length = 0;
|
|
12
|
+
p.speed = 1;
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
return createSpritesheetPlayer();
|
|
16
|
+
}
|
|
17
|
+
export function cloneSpritesheetPlayer(player) {
|
|
18
|
+
return {
|
|
19
|
+
animation: player.animation,
|
|
20
|
+
complete: player.complete,
|
|
21
|
+
elapsed: player.elapsed,
|
|
22
|
+
frameIndex: player.frameIndex,
|
|
23
|
+
onComplete: createSignal(),
|
|
24
|
+
onLoop: createSignal(),
|
|
25
|
+
paused: player.paused,
|
|
26
|
+
queue: [...player.queue],
|
|
27
|
+
speed: player.speed,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function createSpritesheetPlayer(obj) {
|
|
31
|
+
return {
|
|
32
|
+
animation: obj?.animation ?? null,
|
|
33
|
+
complete: obj?.complete ?? true,
|
|
34
|
+
elapsed: obj?.elapsed ?? 0,
|
|
35
|
+
frameIndex: obj?.frameIndex ?? 0,
|
|
36
|
+
onComplete: obj?.onComplete ?? createSignal(),
|
|
37
|
+
onLoop: obj?.onLoop ?? createSignal(),
|
|
38
|
+
paused: obj?.paused ?? false,
|
|
39
|
+
queue: obj?.queue ?? [],
|
|
40
|
+
speed: obj?.speed ?? 1,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function disposeSpritesheetPlayer(player) {
|
|
44
|
+
clearSignal(player.onComplete);
|
|
45
|
+
clearSignal(player.onLoop);
|
|
46
|
+
player.animation = null;
|
|
47
|
+
player.complete = true;
|
|
48
|
+
player.queue.length = 0;
|
|
49
|
+
}
|
|
50
|
+
export function getSpritesheetPlayerFrame(player, spritesheet) {
|
|
51
|
+
const { animation, frameIndex } = player;
|
|
52
|
+
if (animation === null || animation.frames.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const spriteFrameIndex = animation.frames[frameIndex];
|
|
55
|
+
return spritesheet.frames[spriteFrameIndex] ?? null;
|
|
56
|
+
}
|
|
57
|
+
// Returns the `SpritesheetFrame` for a neighbor frame offset relative to the current playback head,
|
|
58
|
+
// without mutating the player. Positive `frameOffset` looks ahead; negative looks behind.
|
|
59
|
+
// The offset wraps within the animation's frame list. Returns null when no animation is set or
|
|
60
|
+
// the animation has no frames.
|
|
61
|
+
// Primary use: onion-skin preview in editors (show neighboring frames without advancing the head).
|
|
62
|
+
export function getSpritesheetPlayerFrameAt(player, spritesheet, frameOffset) {
|
|
63
|
+
const { animation, frameIndex } = player;
|
|
64
|
+
if (animation === null || animation.frames.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
const n = animation.frames.length;
|
|
67
|
+
// Wrap the offset within [0, n) using positive modulo.
|
|
68
|
+
const targetIndex = (((frameIndex + frameOffset) % n) + n) % n;
|
|
69
|
+
const spriteFrameIndex = animation.frames[targetIndex];
|
|
70
|
+
return spritesheet.frames[spriteFrameIndex] ?? null;
|
|
71
|
+
}
|
|
72
|
+
export function pauseSpritesheetPlayer(player) {
|
|
73
|
+
player.paused = true;
|
|
74
|
+
}
|
|
75
|
+
export function playSpritesheetAnimation(player, animation, restart = true) {
|
|
76
|
+
if (!restart && animation === player.animation)
|
|
77
|
+
return;
|
|
78
|
+
player.animation = animation;
|
|
79
|
+
player.complete = animation === null;
|
|
80
|
+
player.elapsed = 0;
|
|
81
|
+
player.frameIndex = 0;
|
|
82
|
+
player.queue.length = 0;
|
|
83
|
+
}
|
|
84
|
+
export function queueSpritesheetAnimation(player, animation) {
|
|
85
|
+
player.queue.push(animation);
|
|
86
|
+
}
|
|
87
|
+
// Returns a player acquired from the pool to the pool for reuse.
|
|
88
|
+
// Disconnects all signals before returning — the next acquire starts with fresh signal state.
|
|
89
|
+
// Every `acquireSpritesheetPlayer` call must have a matching `releaseSpritesheetPlayer`.
|
|
90
|
+
export function releaseSpritesheetPlayer(player) {
|
|
91
|
+
clearSignal(player.onComplete);
|
|
92
|
+
clearSignal(player.onLoop);
|
|
93
|
+
player.animation = null;
|
|
94
|
+
player.complete = true;
|
|
95
|
+
player.elapsed = 0;
|
|
96
|
+
player.frameIndex = 0;
|
|
97
|
+
player.paused = false;
|
|
98
|
+
player.queue.length = 0;
|
|
99
|
+
player.speed = 1;
|
|
100
|
+
playerPool.push(player);
|
|
101
|
+
}
|
|
102
|
+
export function resumeSpritesheetPlayer(player) {
|
|
103
|
+
player.paused = false;
|
|
104
|
+
}
|
|
105
|
+
export function seekSpritesheetPlayerToFrame(player, frameIndex) {
|
|
106
|
+
const { animation } = player;
|
|
107
|
+
if (animation === null || animation.frames.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
const clamped = Math.max(0, Math.min(frameIndex, animation.frames.length - 1));
|
|
110
|
+
player.frameIndex = clamped;
|
|
111
|
+
// Sync elapsed to the start of the target virtual frame index that maps to this display frame.
|
|
112
|
+
// For simplicity, seek to the first virtual index that produces this display frame.
|
|
113
|
+
player.elapsed = resolveVirtualIndexStartTime(animation, clamped);
|
|
114
|
+
}
|
|
115
|
+
export function seekSpritesheetPlayerToTime(player, time) {
|
|
116
|
+
const { animation } = player;
|
|
117
|
+
if (animation === null || animation.frames.length === 0)
|
|
118
|
+
return;
|
|
119
|
+
const totalTime = resolveAnimationTotalTime(animation);
|
|
120
|
+
player.elapsed = Math.max(0, Math.min(time, totalTime));
|
|
121
|
+
player.frameIndex = resolveFrameIndexFromElapsed(animation, player.elapsed);
|
|
122
|
+
}
|
|
123
|
+
export function stopSpritesheetPlayer(player) {
|
|
124
|
+
player.elapsed = 0;
|
|
125
|
+
player.frameIndex = 0;
|
|
126
|
+
player.complete = true;
|
|
127
|
+
player.queue.length = 0;
|
|
128
|
+
}
|
|
129
|
+
export function updateSpritesheetPlayer(player, deltaTime) {
|
|
130
|
+
const { animation } = player;
|
|
131
|
+
if (animation === null || player.complete || player.paused || animation.frames.length === 0)
|
|
132
|
+
return false;
|
|
133
|
+
const { loop } = animation;
|
|
134
|
+
const totalTime = resolveAnimationTotalTime(animation);
|
|
135
|
+
const prevLoopCount = Math.floor(player.elapsed / totalTime);
|
|
136
|
+
player.elapsed += deltaTime * player.speed;
|
|
137
|
+
if (!loop && player.elapsed >= totalTime) {
|
|
138
|
+
if (player.queue.length > 0) {
|
|
139
|
+
const next = player.queue.shift();
|
|
140
|
+
player.animation = next;
|
|
141
|
+
player.elapsed = 0;
|
|
142
|
+
player.frameIndex = 0;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
player.elapsed = totalTime;
|
|
146
|
+
// On completion, show the last virtual frame in the direction sequence.
|
|
147
|
+
const lastVi = resolveVirtualFrameCount(animation) - 1;
|
|
148
|
+
player.frameIndex = resolveVirtualIndexToDisplayIndex(animation, lastVi);
|
|
149
|
+
player.complete = true;
|
|
150
|
+
emitSignal(player.onComplete);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (Math.floor(player.elapsed / totalTime) > prevLoopCount)
|
|
154
|
+
emitSignal(player.onLoop);
|
|
155
|
+
const timeInLoop = player.elapsed % totalTime;
|
|
156
|
+
const vi = resolveVirtualIndexFromTime(animation, timeInLoop);
|
|
157
|
+
player.frameIndex = resolveVirtualIndexToDisplayIndex(animation, vi);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
// Returns a cached cumulative-duration array for variable-timing animations.
|
|
161
|
+
// The array has virtualCount+1 entries: [0, d0, d0+d1, ..., total].
|
|
162
|
+
function getCumulativeDurations(animation) {
|
|
163
|
+
const cached = cumulativeDurationsCache.get(animation);
|
|
164
|
+
if (cached !== undefined)
|
|
165
|
+
return cached;
|
|
166
|
+
const { frames, frameDuration, frameDurations } = animation;
|
|
167
|
+
const n = frames.length;
|
|
168
|
+
const virtualCount = resolveVirtualFrameCount(animation);
|
|
169
|
+
const arr = new Float64Array(virtualCount + 1);
|
|
170
|
+
let t = 0;
|
|
171
|
+
for (let vi = 0; vi < virtualCount; vi++) {
|
|
172
|
+
arr[vi] = t;
|
|
173
|
+
// Map virtual index to the forward frame index for the duration lookup.
|
|
174
|
+
const fi = vi < n ? vi : 2 * (n - 1) - vi;
|
|
175
|
+
t += frameDurations[fi] ?? frameDuration;
|
|
176
|
+
}
|
|
177
|
+
arr[virtualCount] = t || 1;
|
|
178
|
+
cumulativeDurationsCache.set(animation, arr);
|
|
179
|
+
return arr;
|
|
180
|
+
}
|
|
181
|
+
// Computes the total duration of one full virtual loop of the animation (ms).
|
|
182
|
+
// For variable-duration animations, delegates to the cached cumulative array (O(1) after warm-up).
|
|
183
|
+
function resolveAnimationTotalTime(animation) {
|
|
184
|
+
const { frameDuration, frameDurations } = animation;
|
|
185
|
+
if (frameDurations !== null) {
|
|
186
|
+
// Use the precomputed cumulative array: total is the last entry.
|
|
187
|
+
const arr = getCumulativeDurations(animation);
|
|
188
|
+
return arr[arr.length - 1];
|
|
189
|
+
}
|
|
190
|
+
const virtualCount = resolveVirtualFrameCount(animation);
|
|
191
|
+
return virtualCount * frameDuration || 1;
|
|
192
|
+
}
|
|
193
|
+
// Resolves a display frame index from elapsed time (used by seekSpritesheetPlayerToTime).
|
|
194
|
+
function resolveFrameIndexFromElapsed(animation, elapsed) {
|
|
195
|
+
const totalTime = resolveAnimationTotalTime(animation);
|
|
196
|
+
const timeInLoop = elapsed % totalTime;
|
|
197
|
+
const vi = resolveVirtualIndexFromTime(animation, timeInLoop);
|
|
198
|
+
return resolveVirtualIndexToDisplayIndex(animation, vi);
|
|
199
|
+
}
|
|
200
|
+
// Returns the virtual frame count for an animation. Pingpong directions double the frame count
|
|
201
|
+
// minus two endpoints: [0,1,2] → virtual [0,1,2,1] = 4 virtual frames (2*3-2=4).
|
|
202
|
+
function resolveVirtualFrameCount(animation) {
|
|
203
|
+
const n = animation.frames.length;
|
|
204
|
+
const isPingpong = animation.direction === 'pingpong' || animation.direction === 'pingpong_reverse';
|
|
205
|
+
if (isPingpong && n > 1)
|
|
206
|
+
return 2 * n - 2;
|
|
207
|
+
return n;
|
|
208
|
+
}
|
|
209
|
+
// Given time within a single loop, returns the virtual frame index (direction-agnostic step count).
|
|
210
|
+
// For variable-duration animations, uses the precomputed cumulative array for O(log n) binary search.
|
|
211
|
+
function resolveVirtualIndexFromTime(animation, timeInLoop) {
|
|
212
|
+
const { frameDuration, frameDurations } = animation;
|
|
213
|
+
const virtualCount = resolveVirtualFrameCount(animation);
|
|
214
|
+
if (frameDurations !== null) {
|
|
215
|
+
// Binary search in the precomputed cumulative array.
|
|
216
|
+
const arr = getCumulativeDurations(animation);
|
|
217
|
+
let lo = 0;
|
|
218
|
+
let hi = virtualCount - 1;
|
|
219
|
+
while (lo < hi) {
|
|
220
|
+
const mid = (lo + hi + 1) >> 1;
|
|
221
|
+
if (arr[mid] <= timeInLoop) {
|
|
222
|
+
lo = mid;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
hi = mid - 1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return lo;
|
|
229
|
+
}
|
|
230
|
+
return Math.min(Math.floor(timeInLoop / frameDuration), virtualCount - 1);
|
|
231
|
+
}
|
|
232
|
+
// Returns the elapsed time at the start of the given virtual frame index (for seeking).
|
|
233
|
+
// Uses the precomputed cumulative array when available.
|
|
234
|
+
function resolveVirtualIndexStartTime(animation, virtualIndex) {
|
|
235
|
+
const { frameDuration, frameDurations } = animation;
|
|
236
|
+
if (frameDurations !== null) {
|
|
237
|
+
const arr = getCumulativeDurations(animation);
|
|
238
|
+
return arr[virtualIndex];
|
|
239
|
+
}
|
|
240
|
+
return virtualIndex * frameDuration;
|
|
241
|
+
}
|
|
242
|
+
// Given a virtual frame index (0..virtualCount-1), returns the display frame index
|
|
243
|
+
// (0..frames.length-1) accounting for direction.
|
|
244
|
+
function resolveVirtualIndexToDisplayIndex(animation, virtualIndex) {
|
|
245
|
+
const { direction, frames } = animation;
|
|
246
|
+
const last = frames.length - 1;
|
|
247
|
+
switch (direction) {
|
|
248
|
+
case 'forward':
|
|
249
|
+
return virtualIndex;
|
|
250
|
+
case 'reverse':
|
|
251
|
+
return last - virtualIndex;
|
|
252
|
+
case 'pingpong':
|
|
253
|
+
// Virtual: [0,1,...,last, last-1,...,1] — forward then backward (no endpoint repeat).
|
|
254
|
+
return virtualIndex <= last ? virtualIndex : 2 * last - virtualIndex;
|
|
255
|
+
case 'pingpong_reverse':
|
|
256
|
+
// Virtual: [last,...,0, 1,...,last-1] — reverse then forward.
|
|
257
|
+
return virtualIndex <= last ? last - virtualIndex : virtualIndex - last;
|
|
258
|
+
default:
|
|
259
|
+
return virtualIndex;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Pool for short-lived players — use acquire/release as paired brackets.
|
|
263
|
+
const playerPool = [];
|
|
264
|
+
// Cached cumulative virtual-frame start times per animation instance.
|
|
265
|
+
// Key: SpritesheetAnimation entity. Value: Float64Array of length (virtualCount + 1) where
|
|
266
|
+
// entry[i] = start time of virtual frame i, entry[virtualCount] = total loop time.
|
|
267
|
+
// Built lazily on first access for variable-duration animations; uniform animations never need it.
|
|
268
|
+
const cumulativeDurationsCache = new WeakMap();
|
|
269
|
+
//# sourceMappingURL=spritesheetPlayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spritesheetPlayer.js","sourceRoot":"","sources":["../src/spritesheetPlayer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Spritesheet, SpritesheetAnimation, TimelineSource } from '@flighthq/types';
|
|
2
|
+
export declare function createSpritesheetTimelineSource(spritesheet: Readonly<Spritesheet>, animation: Readonly<SpritesheetAnimation>): TimelineSource;
|
|
3
|
+
//# sourceMappingURL=spritesheetTimelineSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spritesheetTimelineSource.d.ts","sourceRoot":"","sources":["../src/spritesheetTimelineSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyB,WAAW,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAUhH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,EAClC,SAAS,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GACxC,cAAc,CA0BhB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createBitmap } from '@flighthq/displayobject';
|
|
2
|
+
import { addNodeChild, invalidateNodeLocalTransform } from '@flighthq/node';
|
|
3
|
+
// Exposes a spritesheet animation as a TimelineSource so a MovieClip can play it (the spritesheet side of
|
|
4
|
+
// the timeline frame-source contract — `@flighthq/timeline` consumes `TimelineSource`, this produces one,
|
|
5
|
+
// and neither depends on the other beyond the shared interface in `@flighthq/types`). Each frame swaps
|
|
6
|
+
// the displayed atlas region and offset on a bitmap the source lazily creates as a child of the target
|
|
7
|
+
// the first time it constructs onto that target; tracking the bitmap per target keeps the source
|
|
8
|
+
// shareable across many MovieClips.
|
|
9
|
+
//
|
|
10
|
+
// Bind it with `setMovieClipSource(clip, createSpritesheetTimelineSource(sheet, anim))`, then `playMovieClip`.
|
|
11
|
+
export function createSpritesheetTimelineSource(spritesheet, animation) {
|
|
12
|
+
const bitmaps = new WeakMap();
|
|
13
|
+
return {
|
|
14
|
+
totalFrames: animation.frames.length,
|
|
15
|
+
labels: [],
|
|
16
|
+
frameRate: 1000 / animation.frameDuration,
|
|
17
|
+
constructFrame(target, frame) {
|
|
18
|
+
const atlas = spritesheet.atlas;
|
|
19
|
+
if (atlas === null)
|
|
20
|
+
return;
|
|
21
|
+
let bitmap = bitmaps.get(target);
|
|
22
|
+
if (bitmap === undefined) {
|
|
23
|
+
bitmap = createBitmap();
|
|
24
|
+
bitmap.data.image = atlas.image;
|
|
25
|
+
addNodeChild(target, bitmap);
|
|
26
|
+
bitmaps.set(target, bitmap);
|
|
27
|
+
}
|
|
28
|
+
const sheetFrame = spritesheet.frames[animation.frames[frame - 1]];
|
|
29
|
+
if (sheetFrame === undefined)
|
|
30
|
+
return;
|
|
31
|
+
bitmap.data.sourceRectangle = atlas.regions[sheetFrame.id];
|
|
32
|
+
bitmap.x = sheetFrame.offsetX - animation.originX;
|
|
33
|
+
bitmap.y = sheetFrame.offsetY - animation.originY;
|
|
34
|
+
invalidateNodeLocalTransform(bitmap);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=spritesheetTimelineSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spritesheetTimelineSource.js","sourceRoot":"","sources":["../src/spritesheetTimelineSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAG5E,0GAA0G;AAC1G,0GAA0G;AAC1G,uGAAuG;AACvG,uGAAuG;AACvG,iGAAiG;AACjG,oCAAoC;AACpC,EAAE;AACF,+GAA+G;AAC/G,MAAM,UAAU,+BAA+B,CAC7C,WAAkC,EAClC,SAAyC;IAEzC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAyB,CAAC;IACrD,OAAO;QACL,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM;QACpC,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,IAAI,GAAG,SAAS,CAAC,aAAa;QACzC,cAAc,CAAC,MAAqB,EAAE,KAAa;YACjD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC;YAChC,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO;YAE3B,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,GAAG,YAAY,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;gBAChC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC9B,CAAC;YAED,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YACnE,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO;YACrC,MAAM,CAAC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAC3D,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;YAClD,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;YAClD,4BAA4B,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Spritesheet, SpritesheetValidationDiagnostic } from '@flighthq/types';
|
|
2
|
+
import type { SpritesheetData } from './spritesheetData';
|
|
3
|
+
export declare function validateSpritesheet(spritesheet: Readonly<Spritesheet>): SpritesheetValidationDiagnostic[] | null;
|
|
4
|
+
export declare function validateSpritesheetData(data: Readonly<SpritesheetData>): SpritesheetValidationDiagnostic[] | null;
|
|
5
|
+
//# sourceMappingURL=spritesheetValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spritesheetValidation.d.ts","sourceRoot":"","sources":["../src/spritesheetValidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAEpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOzD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,+BAA+B,EAAE,GAAG,IAAI,CAwChH;AAQD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,+BAA+B,EAAE,GAAG,IAAI,CA0CjH"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Validates a runtime `Spritesheet` for structural integrity:
|
|
2
|
+
// - Frames referencing atlas region IDs that do not exist in the atlas.
|
|
3
|
+
// - Animations referencing frame indices outside the sheet's frame array bounds.
|
|
4
|
+
// - Animations with zero frames.
|
|
5
|
+
// Returns null when the spritesheet is valid, or an array of diagnostics when issues are found.
|
|
6
|
+
export function validateSpritesheet(spritesheet) {
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
const { atlas, animations, frames } = spritesheet;
|
|
9
|
+
// Check frame → atlas region resolution.
|
|
10
|
+
if (atlas !== null) {
|
|
11
|
+
const regionIds = new Set(atlas.regions.map((r) => r.id));
|
|
12
|
+
for (let fi = 0; fi < frames.length; fi++) {
|
|
13
|
+
if (!regionIds.has(frames[fi].id)) {
|
|
14
|
+
diagnostics.push({
|
|
15
|
+
animationName: null,
|
|
16
|
+
frameIndex: fi,
|
|
17
|
+
message: `Frame ${fi} references atlas region id ${frames[fi].id} which does not exist in the atlas.`,
|
|
18
|
+
severity: 'error',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Check animation → frame index resolution.
|
|
24
|
+
for (const [animName, anim] of Object.entries(animations)) {
|
|
25
|
+
if (anim.frames.length === 0) {
|
|
26
|
+
diagnostics.push({
|
|
27
|
+
animationName: animName,
|
|
28
|
+
frameIndex: null,
|
|
29
|
+
message: `Animation "${animName}" has no frames.`,
|
|
30
|
+
severity: 'warning',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (let ai = 0; ai < anim.frames.length; ai++) {
|
|
34
|
+
const frameRef = anim.frames[ai];
|
|
35
|
+
if (frameRef < 0 || frameRef >= frames.length) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
animationName: animName,
|
|
38
|
+
frameIndex: ai,
|
|
39
|
+
message: `Animation "${animName}" references frame index ${frameRef} which is out of range (sheet has ${frames.length} frames).`,
|
|
40
|
+
severity: 'error',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return diagnostics.length > 0 ? diagnostics : null;
|
|
46
|
+
}
|
|
47
|
+
// Validates a `SpritesheetData` descriptor for structural integrity:
|
|
48
|
+
// - Frames with empty names (warning — not an error, positional fallback is used at hydration).
|
|
49
|
+
// - Animations referencing frame names not found in the frame list.
|
|
50
|
+
// - Animations with zero frame names (warning — all frames are used as fallback, which may be intentional).
|
|
51
|
+
// - Per-frame duration arrays whose length does not match the frame name count.
|
|
52
|
+
// Returns null when valid, or an array of diagnostics when issues are found.
|
|
53
|
+
export function validateSpritesheetData(data) {
|
|
54
|
+
const diagnostics = [];
|
|
55
|
+
const { animations, frames } = data;
|
|
56
|
+
// Build a set of all frame names for fast lookup (skip empty names).
|
|
57
|
+
const frameNameSet = new Set();
|
|
58
|
+
for (const fd of frames) {
|
|
59
|
+
if (fd.name !== '') {
|
|
60
|
+
frameNameSet.add(fd.name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const ad of animations) {
|
|
64
|
+
if (ad.frameNames.length === 0) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
animationName: ad.name,
|
|
67
|
+
frameIndex: null,
|
|
68
|
+
message: `Animation "${ad.name}" has no frameNames — all sheet frames will be used as the frame list.`,
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
for (let ai = 0; ai < ad.frameNames.length; ai++) {
|
|
74
|
+
const fname = ad.frameNames[ai];
|
|
75
|
+
if (!frameNameSet.has(fname)) {
|
|
76
|
+
diagnostics.push({
|
|
77
|
+
animationName: ad.name,
|
|
78
|
+
frameIndex: ai,
|
|
79
|
+
message: `Animation "${ad.name}" references frame name "${fname}" which is not present in the data frame list.`,
|
|
80
|
+
severity: 'error',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Check per-frame duration array length matches frame name count.
|
|
85
|
+
if (ad.frameDurations !== null && ad.frameDurations.length !== ad.frameNames.length) {
|
|
86
|
+
diagnostics.push({
|
|
87
|
+
animationName: ad.name,
|
|
88
|
+
frameIndex: null,
|
|
89
|
+
message: `Animation "${ad.name}" has ${ad.frameDurations.length} frameDurations but ${ad.frameNames.length} frameNames — lengths should match.`,
|
|
90
|
+
severity: 'warning',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return diagnostics.length > 0 ? diagnostics : null;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=spritesheetValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spritesheetValidation.js","sourceRoot":"","sources":["../src/spritesheetValidation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/spritesheet",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/displayobject": "0.1.0",
|
|
31
|
+
"@flighthq/entity": "0.1.0",
|
|
32
|
+
"@flighthq/geometry": "0.1.0",
|
|
33
|
+
"@flighthq/image": "0.1.0",
|
|
34
|
+
"@flighthq/node": "0.1.0",
|
|
35
|
+
"@flighthq/signals": "0.1.0",
|
|
36
|
+
"@flighthq/textureatlas": "0.1.0",
|
|
37
|
+
"@flighthq/tileset": "0.1.0",
|
|
38
|
+
"@flighthq/types": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.3.0"
|
|
42
|
+
},
|
|
43
|
+
"description": "Spritesheet frame animation playback",
|
|
44
|
+
"sideEffects": false
|
|
45
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cloneSpritesheet, createSpritesheet, getSpritesheetAnimation } from './spritesheet';
|
|
2
|
+
import { createSpritesheetAnimation } from './spritesheetAnimation';
|
|
3
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
4
|
+
|
|
5
|
+
describe('cloneSpritesheet', () => {
|
|
6
|
+
it('returns a distinct entity with copies of all frames', () => {
|
|
7
|
+
const sheet = createSpritesheet();
|
|
8
|
+
const frame = createSpritesheetFrame({ id: 1, offsetX: 5, offsetY: 10, pivotX: 2, pivotY: 3, rotated: true });
|
|
9
|
+
sheet.frames.push(frame);
|
|
10
|
+
sheet.animations['walk'] = createSpritesheetAnimation();
|
|
11
|
+
|
|
12
|
+
const clone = cloneSpritesheet(sheet);
|
|
13
|
+
|
|
14
|
+
expect(clone).not.toBe(sheet);
|
|
15
|
+
expect(clone.frames).not.toBe(sheet.frames);
|
|
16
|
+
expect(clone.frames[0]).not.toBe(sheet.frames[0]);
|
|
17
|
+
expect(clone.frames[0].id).toBe(1);
|
|
18
|
+
expect(clone.frames[0].pivotX).toBe(2);
|
|
19
|
+
expect(clone.frames[0].rotated).toBe(true);
|
|
20
|
+
expect(clone.animations['walk']).toBe(sheet.animations['walk']);
|
|
21
|
+
expect(clone.atlas).toBe(sheet.atlas);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('createSpritesheet', () => {
|
|
26
|
+
it('initializes with null atlas, empty animations and frames', () => {
|
|
27
|
+
const sheet = createSpritesheet();
|
|
28
|
+
expect(sheet.atlas).toBeNull();
|
|
29
|
+
expect(sheet.frames).toHaveLength(0);
|
|
30
|
+
expect(Object.keys(sheet.animations)).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('uses provided animations and frames directly', () => {
|
|
34
|
+
const animations = { idle: createSpritesheetAnimation() };
|
|
35
|
+
const frames = [createSpritesheetFrame({ id: 0 })];
|
|
36
|
+
const sheet = createSpritesheet({ animations, frames });
|
|
37
|
+
expect(sheet.animations).toBe(animations);
|
|
38
|
+
expect(sheet.frames).toBe(frames);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('getSpritesheetAnimation', () => {
|
|
43
|
+
it('returns null when no animations exist', () => {
|
|
44
|
+
const sheet = createSpritesheet();
|
|
45
|
+
expect(getSpritesheetAnimation(sheet, 'walk')).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns null when label does not match', () => {
|
|
49
|
+
const sheet = createSpritesheet();
|
|
50
|
+
sheet.animations['idle'] = createSpritesheetAnimation();
|
|
51
|
+
expect(getSpritesheetAnimation(sheet, 'walk')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns the matching animation by label', () => {
|
|
55
|
+
const sheet = createSpritesheet();
|
|
56
|
+
const walk = createSpritesheetAnimation();
|
|
57
|
+
sheet.animations['idle'] = createSpritesheetAnimation();
|
|
58
|
+
sheet.animations['walk'] = walk;
|
|
59
|
+
expect(getSpritesheetAnimation(sheet, 'walk')).toBe(walk);
|
|
60
|
+
});
|
|
61
|
+
});
|