spine-html 1.11.1 → 1.12.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/README.md +46 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +373 -20
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +70 -7
- package/dist/player.d.ts.map +1 -1
- package/dist/posedFrame.d.ts +5 -0
- package/dist/posedFrame.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -520,6 +520,52 @@ active (`null`), track evaluation runs with zero extra work and produces
|
|
|
520
520
|
byte-identical poses. An unknown set name throws by name; on a build without
|
|
521
521
|
`sets.json`, `sets()` is empty (`[]`) and `setSet` throws.
|
|
522
522
|
|
|
523
|
+
### Slot identity snapshot (`slotIdentity()`)
|
|
524
|
+
|
|
525
|
+
To inspect which attachment key, owning skin and visibility state each slot
|
|
526
|
+
holds on the current frame — for a parts explorer or review overlay — without
|
|
527
|
+
`seek`, `repose`, or restarting physics (#144):
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
const snap = player.slotIdentity();
|
|
531
|
+
// {
|
|
532
|
+
// generation: number, // matches player.generation and player.posedFrame().generation
|
|
533
|
+
// set: string | null, // active set name (player.set)
|
|
534
|
+
// skin: string | null, // active skin name (player.skin ?? null)
|
|
535
|
+
// slots: readonly [ // every slot in posed draw order (z = 0 .. slots.length - 1)
|
|
536
|
+
// {
|
|
537
|
+
// slot: string,
|
|
538
|
+
// z: number,
|
|
539
|
+
// attachment: string | null, // resolved attachment key when drawn, null when not drawn
|
|
540
|
+
// skin: string | null, // skin owning the resolved attachment ('default' or named skin), or null
|
|
541
|
+
// reason: 'hidden' | 'no-attachment' | 'timeline-null' | 'set-hide' | null,
|
|
542
|
+
// },
|
|
543
|
+
// ...
|
|
544
|
+
// ],
|
|
545
|
+
// }
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Every slot in the posed draw order (`z = 0 .. slots.length - 1`, hidden and null
|
|
549
|
+
ones included) appears in `snap.slots`:
|
|
550
|
+
|
|
551
|
+
- Drawn slots (`reason: null`) appear in the same `z` order as
|
|
552
|
+
`player.posedFrame().order`, naming the resolved attachment key (`attachment`)
|
|
553
|
+
and the skin that owns it (`skin`).
|
|
554
|
+
- Non-drawn slots report `attachment: null`, `skin: null`, and why they are
|
|
555
|
+
absent from `posedFrame().order`:
|
|
556
|
+
- `'set-hide'` — hidden by the active attachment set (`set.hides` or a
|
|
557
|
+
`<base>_still` companion of a slot the set covers or hides).
|
|
558
|
+
- `'timeline-null'` — cleared by an active attachment timeline key (`null`).
|
|
559
|
+
- `'no-attachment'` — no attachment in the setup pose or under the active skin
|
|
560
|
+
(or a non-drawable attachment kind such as clipping/boundingbox/path/point).
|
|
561
|
+
- `'hidden'` — filtered out by `player.slots` or on an inactive bone under the
|
|
562
|
+
current skin.
|
|
563
|
+
- `snap.generation` ticks monotonically with `player.generation` and
|
|
564
|
+
`player.posedFrame().generation` whenever the held pose, active set, active
|
|
565
|
+
skin or slot filter changes. The returned snapshot is frozen and cached per
|
|
566
|
+
generation, so reading it repeatedly on a held pose is allocation-free and
|
|
567
|
+
mutates no pixel or physics state.
|
|
568
|
+
|
|
523
569
|
### Several tracks
|
|
524
570
|
|
|
525
571
|
A base on track 0, a layer over it, a one-shot played once and mixed back out —
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './Canva
|
|
|
2
2
|
export { DomRenderer, type MeshBackend, type MeshSourceRect } from './DomRenderer.js';
|
|
3
3
|
export { type PageLayout, type PageRegion, type RegionEncoding, type RegionImage, revokeRegions, type UnpackRegionsOptions, unpackRegions } from './DomTexture.js';
|
|
4
4
|
export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading, type RigSet } from './loadModel.js';
|
|
5
|
-
export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type TrackEntry } from './player.js';
|
|
5
|
+
export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type SlotIdentity, type SlotIdentityReason, type SlotIdentitySnapshot, type TrackEntry } from './player.js';
|
|
6
6
|
export type { BlendMode, PosedClip, PosedFrame, PosedMesh, PosedRegion, PosedSlot, } from './posedFrame.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzI,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzI,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7W,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8699,9 +8699,16 @@ function predicateOf(names) {
|
|
|
8699
8699
|
const allowed = new Set(names);
|
|
8700
8700
|
return (slot) => allowed.has(slot);
|
|
8701
8701
|
}
|
|
8702
|
+
function hiddenBySet(slotName, set) {
|
|
8703
|
+
if (set.hides.includes(slotName)) return true;
|
|
8704
|
+
if (slotName.endsWith("_still") && !(slotName in set.slots)) {
|
|
8705
|
+
const base = slotName.slice(0, -"_still".length);
|
|
8706
|
+
if (base in set.slots || set.hides.includes(base)) return true;
|
|
8707
|
+
}
|
|
8708
|
+
return false;
|
|
8709
|
+
}
|
|
8702
8710
|
function applySetToPose(view, raw, set) {
|
|
8703
8711
|
const coveredNames = new Set(Object.keys(set.slots));
|
|
8704
|
-
const hiddenNames = new Set(set.hides);
|
|
8705
8712
|
const overrideSlots = view.slots.flatMap((slot) => {
|
|
8706
8713
|
const setup = set.slots[slot.name];
|
|
8707
8714
|
return setup !== void 0 ? [{ ...slot, setup }] : [];
|
|
@@ -8729,14 +8736,7 @@ function applySetToPose(view, raw, set) {
|
|
|
8729
8736
|
const rawShownBySlot = new Map(raw.shown.map((s) => [s.slot, s]));
|
|
8730
8737
|
const rawDrawnBySlot = new Map(raw.drawn.map((d) => [d.slot, d]));
|
|
8731
8738
|
const rawClipBySlot = new Map(raw.clips.map((c) => [c[0], c]));
|
|
8732
|
-
const hiddenSlot = (slotName) =>
|
|
8733
|
-
if (hiddenNames.has(slotName)) return true;
|
|
8734
|
-
if (slotName.endsWith("_still") && !coveredNames.has(slotName)) {
|
|
8735
|
-
const base = slotName.slice(0, -"_still".length);
|
|
8736
|
-
if (coveredNames.has(base) || hiddenNames.has(base)) return true;
|
|
8737
|
-
}
|
|
8738
|
-
return false;
|
|
8739
|
-
};
|
|
8739
|
+
const hiddenSlot = (slotName) => hiddenBySet(slotName, set);
|
|
8740
8740
|
const slots = raw.slots.map((rawRow) => {
|
|
8741
8741
|
const slotName = rawRow[0];
|
|
8742
8742
|
if (hiddenSlot(slotName)) {
|
|
@@ -8777,6 +8777,116 @@ function applySetToPose(view, raw, set) {
|
|
|
8777
8777
|
clips
|
|
8778
8778
|
};
|
|
8779
8779
|
}
|
|
8780
|
+
const SEEK_FINE_STEP_LIMIT = 4096;
|
|
8781
|
+
const SEEK_COARSE_STRIDE = 8;
|
|
8782
|
+
const SEEK_EXACT_LIMIT = 1024;
|
|
8783
|
+
function capturePhysicsContext(ctx) {
|
|
8784
|
+
const states = /* @__PURE__ */ new Map();
|
|
8785
|
+
for (const [name, s] of ctx.states) states.set(name, { ...s });
|
|
8786
|
+
return {
|
|
8787
|
+
phase: ctx.phase,
|
|
8788
|
+
time: ctx.time,
|
|
8789
|
+
last: ctx.last,
|
|
8790
|
+
loop: ctx.loop,
|
|
8791
|
+
env: ctx.env,
|
|
8792
|
+
states
|
|
8793
|
+
};
|
|
8794
|
+
}
|
|
8795
|
+
function restorePhysicsContext(ctx, snap) {
|
|
8796
|
+
ctx.phase = snap.phase;
|
|
8797
|
+
ctx.time = snap.time;
|
|
8798
|
+
ctx.last = snap.last;
|
|
8799
|
+
if (snap.loop === void 0) delete ctx.loop;
|
|
8800
|
+
else ctx.loop = snap.loop;
|
|
8801
|
+
if (snap.env === void 0) delete ctx.env;
|
|
8802
|
+
else ctx.env = snap.env;
|
|
8803
|
+
ctx.states.clear();
|
|
8804
|
+
for (const [name, s] of snap.states) ctx.states.set(name, { ...s });
|
|
8805
|
+
}
|
|
8806
|
+
function captureStepperState(stepper) {
|
|
8807
|
+
if ("first" in stepper) {
|
|
8808
|
+
const internal2 = stepper;
|
|
8809
|
+
return {
|
|
8810
|
+
kind: "track",
|
|
8811
|
+
steps: internal2.steps,
|
|
8812
|
+
latest: internal2.latest,
|
|
8813
|
+
refused: internal2.refused,
|
|
8814
|
+
trackTime: internal2.track.trackTime,
|
|
8815
|
+
last: internal2.track.last,
|
|
8816
|
+
ctx: capturePhysicsContext(internal2.track.ctx)
|
|
8817
|
+
};
|
|
8818
|
+
}
|
|
8819
|
+
const internal = stepper;
|
|
8820
|
+
const entry = internal.mixer.tracks[0] ?? null;
|
|
8821
|
+
return {
|
|
8822
|
+
kind: "tracks",
|
|
8823
|
+
started: internal.started,
|
|
8824
|
+
steps: internal.steps,
|
|
8825
|
+
time: internal.time,
|
|
8826
|
+
refused: internal.refused,
|
|
8827
|
+
animationsChanged: internal.mixer.animationsChanged,
|
|
8828
|
+
propertyIds: new Map(internal.mixer.propertyIds),
|
|
8829
|
+
entry: entry === null ? null : {
|
|
8830
|
+
delay: entry.delay,
|
|
8831
|
+
trackTime: entry.trackTime,
|
|
8832
|
+
trackLast: entry.trackLast,
|
|
8833
|
+
nextTrackLast: entry.nextTrackLast,
|
|
8834
|
+
trackEnd: entry.trackEnd,
|
|
8835
|
+
animationLast: entry.animationLast,
|
|
8836
|
+
nextAnimationLast: entry.nextAnimationLast,
|
|
8837
|
+
mixTime: entry.mixTime,
|
|
8838
|
+
mixDuration: entry.mixDuration,
|
|
8839
|
+
totalAlpha: entry.totalAlpha,
|
|
8840
|
+
alpha: entry.alpha,
|
|
8841
|
+
additive: entry.additive,
|
|
8842
|
+
keepHold: entry.keepHold,
|
|
8843
|
+
timelineMode: [...entry.timelineMode],
|
|
8844
|
+
timelineHoldMix: [...entry.timelineHoldMix],
|
|
8845
|
+
timelinesRotation: [...entry.timelinesRotation]
|
|
8846
|
+
},
|
|
8847
|
+
ctx: capturePhysicsContext(internal.path.ctx)
|
|
8848
|
+
};
|
|
8849
|
+
}
|
|
8850
|
+
function restoreStepperState(stepper, snap) {
|
|
8851
|
+
if (snap.kind === "track") {
|
|
8852
|
+
const internal2 = stepper;
|
|
8853
|
+
internal2.steps = snap.steps;
|
|
8854
|
+
internal2.latest = snap.latest;
|
|
8855
|
+
internal2.refused = snap.refused;
|
|
8856
|
+
internal2.track.trackTime = snap.trackTime;
|
|
8857
|
+
internal2.track.last = snap.last;
|
|
8858
|
+
restorePhysicsContext(internal2.track.ctx, snap.ctx);
|
|
8859
|
+
return;
|
|
8860
|
+
}
|
|
8861
|
+
const internal = stepper;
|
|
8862
|
+
internal.started = snap.started;
|
|
8863
|
+
internal.steps = snap.steps;
|
|
8864
|
+
internal.time = snap.time;
|
|
8865
|
+
internal.refused = snap.refused;
|
|
8866
|
+
internal.mixer.animationsChanged = snap.animationsChanged;
|
|
8867
|
+
internal.mixer.propertyIds.clear();
|
|
8868
|
+
for (const [k, v] of snap.propertyIds) internal.mixer.propertyIds.set(k, v);
|
|
8869
|
+
const entry = internal.mixer.tracks[0] ?? null;
|
|
8870
|
+
if (entry !== null && snap.entry !== null) {
|
|
8871
|
+
entry.delay = snap.entry.delay;
|
|
8872
|
+
entry.trackTime = snap.entry.trackTime;
|
|
8873
|
+
entry.trackLast = snap.entry.trackLast;
|
|
8874
|
+
entry.nextTrackLast = snap.entry.nextTrackLast;
|
|
8875
|
+
entry.trackEnd = snap.entry.trackEnd;
|
|
8876
|
+
entry.animationLast = snap.entry.animationLast;
|
|
8877
|
+
entry.nextAnimationLast = snap.entry.nextAnimationLast;
|
|
8878
|
+
entry.mixTime = snap.entry.mixTime;
|
|
8879
|
+
entry.mixDuration = snap.entry.mixDuration;
|
|
8880
|
+
entry.totalAlpha = snap.entry.totalAlpha;
|
|
8881
|
+
entry.alpha = snap.entry.alpha;
|
|
8882
|
+
entry.additive = snap.entry.additive;
|
|
8883
|
+
entry.keepHold = snap.entry.keepHold;
|
|
8884
|
+
entry.timelineMode = [...snap.entry.timelineMode];
|
|
8885
|
+
entry.timelineHoldMix = [...snap.entry.timelineHoldMix];
|
|
8886
|
+
entry.timelinesRotation = [...snap.entry.timelinesRotation];
|
|
8887
|
+
}
|
|
8888
|
+
restorePhysicsContext(internal.path.ctx, snap.ctx);
|
|
8889
|
+
}
|
|
8780
8890
|
class RigPlayer {
|
|
8781
8891
|
/**
|
|
8782
8892
|
* The renderer this player draws through — its knobs and counters are the
|
|
@@ -8831,6 +8941,10 @@ class RigPlayer {
|
|
|
8831
8941
|
/** The un-overridden pose from the track/tracks (or setup pose when `setSet` is called before `play`). */
|
|
8832
8942
|
rawPose = null;
|
|
8833
8943
|
setup = null;
|
|
8944
|
+
seekCache = null;
|
|
8945
|
+
poseGeneration = 0;
|
|
8946
|
+
cachedIdentity = null;
|
|
8947
|
+
cachedIdentityGen = -1;
|
|
8834
8948
|
setList;
|
|
8835
8949
|
setsByName;
|
|
8836
8950
|
activeSet = null;
|
|
@@ -8862,6 +8976,8 @@ class RigPlayer {
|
|
|
8862
8976
|
this.meshBackend = options.meshBackend;
|
|
8863
8977
|
this.adjustFn = options.adjust ?? null;
|
|
8864
8978
|
if (options.slots !== void 0 && options.slots !== null) this.slots = options.slots;
|
|
8979
|
+
this.poseGeneration = 0;
|
|
8980
|
+
this.cachedIdentity = null;
|
|
8865
8981
|
this.active = this.create(options.mode ?? "dom");
|
|
8866
8982
|
}
|
|
8867
8983
|
/**
|
|
@@ -8928,6 +9044,7 @@ class RigPlayer {
|
|
|
8928
9044
|
const alpha = options.alpha ?? 1;
|
|
8929
9045
|
const additive = options.additive ?? false;
|
|
8930
9046
|
this.paused = false;
|
|
9047
|
+
this.clearSeekCache();
|
|
8931
9048
|
const plain = track === 0 && mix === 0 && alpha === 1 && !additive;
|
|
8932
9049
|
if (!plain) this.beyondBase = true;
|
|
8933
9050
|
if (this.tracks === null && plain && !this.trackMode) {
|
|
@@ -8956,6 +9073,7 @@ class RigPlayer {
|
|
|
8956
9073
|
queue(name, options = {}) {
|
|
8957
9074
|
const track = options.track ?? 0;
|
|
8958
9075
|
this.beyondBase = true;
|
|
9076
|
+
this.clearSeekCache();
|
|
8959
9077
|
const tracks = this.compose();
|
|
8960
9078
|
tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0, alpha: options.alpha ?? 1, additive: options.additive ?? false });
|
|
8961
9079
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
@@ -8968,6 +9086,7 @@ class RigPlayer {
|
|
|
8968
9086
|
*/
|
|
8969
9087
|
clear(track = 0, options = {}) {
|
|
8970
9088
|
this.beyondBase = true;
|
|
9089
|
+
this.clearSeekCache();
|
|
8971
9090
|
const tracks = this.compose();
|
|
8972
9091
|
tracks.setEmptyAnimation(track, options.mix ?? 0);
|
|
8973
9092
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
@@ -8981,6 +9100,7 @@ class RigPlayer {
|
|
|
8981
9100
|
*/
|
|
8982
9101
|
queueClear(track = 0, options = {}) {
|
|
8983
9102
|
this.beyondBase = true;
|
|
9103
|
+
this.clearSeekCache();
|
|
8984
9104
|
const tracks = this.compose();
|
|
8985
9105
|
tracks.addEmptyAnimation(track, options.mix ?? 0, options.delay ?? 0);
|
|
8986
9106
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
@@ -8995,6 +9115,7 @@ class RigPlayer {
|
|
|
8995
9115
|
*/
|
|
8996
9116
|
setAlpha(track, alpha) {
|
|
8997
9117
|
this.beyondBase = true;
|
|
9118
|
+
this.clearSeekCache();
|
|
8998
9119
|
const tracks = this.compose();
|
|
8999
9120
|
tracks.setAlpha(track, alpha);
|
|
9000
9121
|
this.setRawPose(tracks.step(0));
|
|
@@ -9025,6 +9146,7 @@ class RigPlayer {
|
|
|
9025
9146
|
*/
|
|
9026
9147
|
compose() {
|
|
9027
9148
|
if (this.tracks !== null) return this.tracks;
|
|
9149
|
+
this.clearSeekCache();
|
|
9028
9150
|
const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
|
|
9029
9151
|
const single = this.track;
|
|
9030
9152
|
const time = single?.trackTime ?? 0;
|
|
@@ -9043,6 +9165,8 @@ class RigPlayer {
|
|
|
9043
9165
|
setRawPose(raw) {
|
|
9044
9166
|
this.rawPose = raw;
|
|
9045
9167
|
this.pose = this.activeSet === null ? raw : applySetToPose(this.view, raw, this.activeSet);
|
|
9168
|
+
this.poseGeneration++;
|
|
9169
|
+
this.cachedIdentity = null;
|
|
9046
9170
|
}
|
|
9047
9171
|
/**
|
|
9048
9172
|
* The world the rig's physics answers (#121): `wind` and `gravity`, as
|
|
@@ -9061,8 +9185,10 @@ class RigPlayer {
|
|
|
9061
9185
|
environment(environment) {
|
|
9062
9186
|
if (environment === null) {
|
|
9063
9187
|
this.env = null;
|
|
9188
|
+
this.clearSeekCache();
|
|
9064
9189
|
this.stepper()?.setEnvironment(DEFAULT_ENVIRONMENT);
|
|
9065
9190
|
} else if (environment !== void 0) {
|
|
9191
|
+
this.clearSeekCache();
|
|
9066
9192
|
const live = this.stepper();
|
|
9067
9193
|
if (live) {
|
|
9068
9194
|
live.setEnvironment(environment);
|
|
@@ -9101,20 +9227,101 @@ class RigPlayer {
|
|
|
9101
9227
|
this.paused = false;
|
|
9102
9228
|
}
|
|
9103
9229
|
/**
|
|
9104
|
-
* Moves a single animation on track 0 to `time`:
|
|
9105
|
-
*
|
|
9106
|
-
*
|
|
9107
|
-
* on the way are not fired.
|
|
9108
|
-
*
|
|
9109
|
-
* queue was asked for — so after any
|
|
9110
|
-
* player forward instead.
|
|
9230
|
+
* Moves a single animation on track 0 to `time`: snapshot-backed deterministic
|
|
9231
|
+
* seek (#141) that produces the exact pose and physics state of reopening at 0
|
|
9232
|
+
* and stepping forward in `seekStep` increments (the last one shorter) until
|
|
9233
|
+
* `time` is reached. Events and completes crossed on the way are not fired.
|
|
9234
|
+
* It has a meaning for one animation on one track only — the player cannot
|
|
9235
|
+
* replay when a mix, a second track or a queue was asked for — so after any
|
|
9236
|
+
* of those it refuses by name; step the player forward instead.
|
|
9111
9237
|
*/
|
|
9112
9238
|
seek(time) {
|
|
9113
9239
|
if (this.animation === null) throw new Error("seek before play: no animation is open");
|
|
9114
9240
|
if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
|
|
9115
9241
|
if (this.beyondBase) throw new Error("seek is defined for a single animation on track 0; after a mix, another track, a queue or a clear the player cannot replay to a time — step it forward with frame/advance instead");
|
|
9116
|
-
this.
|
|
9117
|
-
this.
|
|
9242
|
+
const cache = this.ensureSeekCache();
|
|
9243
|
+
const stepper = this.tracks ?? this.track;
|
|
9244
|
+
const exactHit = cache.exact.get(time);
|
|
9245
|
+
if (exactHit !== void 0) {
|
|
9246
|
+
restoreStepperState(stepper, exactHit.state);
|
|
9247
|
+
this.rawPose = exactHit.rawPose;
|
|
9248
|
+
this.pose = exactHit.pose;
|
|
9249
|
+
this.poseGeneration++;
|
|
9250
|
+
this.cachedIdentity = null;
|
|
9251
|
+
return;
|
|
9252
|
+
}
|
|
9253
|
+
const steps = Math.floor(time / this.seekStep);
|
|
9254
|
+
while (cache.frontierSteps < steps) {
|
|
9255
|
+
const raw = cache.frontier.step(this.seekStep);
|
|
9256
|
+
cache.frontierSteps++;
|
|
9257
|
+
if (cache.frontierSteps <= SEEK_FINE_STEP_LIMIT || cache.frontierSteps % SEEK_COARSE_STRIDE === 0) {
|
|
9258
|
+
cache.grid.set(cache.frontierSteps, {
|
|
9259
|
+
state: captureStepperState(cache.frontier),
|
|
9260
|
+
rawPose: raw
|
|
9261
|
+
});
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
const gridHit = cache.grid.get(steps);
|
|
9265
|
+
if (gridHit !== void 0) {
|
|
9266
|
+
restoreStepperState(stepper, gridHit.state);
|
|
9267
|
+
this.rawPose = gridHit.rawPose;
|
|
9268
|
+
} else {
|
|
9269
|
+
const baseStep = steps - steps % SEEK_COARSE_STRIDE;
|
|
9270
|
+
const baseSnap = cache.grid.get(baseStep);
|
|
9271
|
+
restoreStepperState(stepper, baseSnap.state);
|
|
9272
|
+
this.rawPose = baseSnap.rawPose;
|
|
9273
|
+
for (let i = baseStep; i < steps; i++) this.rawPose = stepper.step(this.seekStep);
|
|
9274
|
+
}
|
|
9275
|
+
const rest = time - stepper.trackTime;
|
|
9276
|
+
if (rest > 0) this.rawPose = stepper.step(rest);
|
|
9277
|
+
if (this.rawPose !== null) this.setRawPose(this.rawPose);
|
|
9278
|
+
if (this.rawPose !== null && this.pose !== null) {
|
|
9279
|
+
if (cache.exact.size >= SEEK_EXACT_LIMIT) {
|
|
9280
|
+
const oldest = cache.exact.keys().next().value;
|
|
9281
|
+
if (oldest !== void 0) cache.exact.delete(oldest);
|
|
9282
|
+
}
|
|
9283
|
+
cache.exact.set(time, {
|
|
9284
|
+
state: captureStepperState(stepper),
|
|
9285
|
+
rawPose: this.rawPose,
|
|
9286
|
+
pose: this.pose
|
|
9287
|
+
});
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
clearSeekCache() {
|
|
9291
|
+
this.seekCache = null;
|
|
9292
|
+
}
|
|
9293
|
+
ensureSeekCache() {
|
|
9294
|
+
const mode = this.tracks !== null ? "tracks" : "track";
|
|
9295
|
+
if (this.seekCache !== null && this.seekCache.mode === mode) {
|
|
9296
|
+
return this.seekCache;
|
|
9297
|
+
}
|
|
9298
|
+
if (this.tracks === null && this.track === null) {
|
|
9299
|
+
this.reopen();
|
|
9300
|
+
}
|
|
9301
|
+
let frontier;
|
|
9302
|
+
let firstPose;
|
|
9303
|
+
if (mode === "tracks") {
|
|
9304
|
+
const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
|
|
9305
|
+
tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
|
|
9306
|
+
firstPose = tracks.start();
|
|
9307
|
+
this.applyEnvironment(tracks);
|
|
9308
|
+
frontier = tracks;
|
|
9309
|
+
} else {
|
|
9310
|
+
const track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
|
|
9311
|
+
firstPose = track.first;
|
|
9312
|
+
this.applyEnvironment(track);
|
|
9313
|
+
frontier = track;
|
|
9314
|
+
}
|
|
9315
|
+
const grid = /* @__PURE__ */ new Map();
|
|
9316
|
+
grid.set(0, { state: captureStepperState(frontier), rawPose: firstPose });
|
|
9317
|
+
this.seekCache = {
|
|
9318
|
+
mode,
|
|
9319
|
+
frontier,
|
|
9320
|
+
frontierSteps: 0,
|
|
9321
|
+
grid,
|
|
9322
|
+
exact: /* @__PURE__ */ new Map()
|
|
9323
|
+
};
|
|
9324
|
+
return this.seekCache;
|
|
9118
9325
|
}
|
|
9119
9326
|
/** Whatever is open, opened again with track 0's animation as `play` last set it, at pose 0: the single track, or the tracks under `tracks: true`. */
|
|
9120
9327
|
reopen() {
|
|
@@ -9217,6 +9424,7 @@ class RigPlayer {
|
|
|
9217
9424
|
this.source = frameSource(view, this.read.parsed);
|
|
9218
9425
|
this.skinName = name;
|
|
9219
9426
|
this.setup = null;
|
|
9427
|
+
this.clearSeekCache();
|
|
9220
9428
|
if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
|
|
9221
9429
|
const time = this.time;
|
|
9222
9430
|
this.reopen();
|
|
@@ -9224,6 +9432,9 @@ class RigPlayer {
|
|
|
9224
9432
|
} else if (this.rawPose !== null) {
|
|
9225
9433
|
this.setup = poseRawSetup(this.view);
|
|
9226
9434
|
this.setRawPose(this.setup);
|
|
9435
|
+
} else {
|
|
9436
|
+
this.poseGeneration++;
|
|
9437
|
+
this.cachedIdentity = null;
|
|
9227
9438
|
}
|
|
9228
9439
|
}
|
|
9229
9440
|
/**
|
|
@@ -9259,9 +9470,12 @@ class RigPlayer {
|
|
|
9259
9470
|
}
|
|
9260
9471
|
if (name === null) {
|
|
9261
9472
|
this.activeSet = null;
|
|
9473
|
+
this.clearSeekCache();
|
|
9262
9474
|
const base2 = this.rawPose ?? (this.setup ?? (this.setup = poseRawSetup(this.view)));
|
|
9263
9475
|
this.rawPose = base2;
|
|
9264
9476
|
this.pose = base2;
|
|
9477
|
+
this.poseGeneration++;
|
|
9478
|
+
this.cachedIdentity = null;
|
|
9265
9479
|
return;
|
|
9266
9480
|
}
|
|
9267
9481
|
const found = this.setsByName.get(name);
|
|
@@ -9271,9 +9485,12 @@ class RigPlayer {
|
|
|
9271
9485
|
);
|
|
9272
9486
|
}
|
|
9273
9487
|
this.activeSet = found;
|
|
9488
|
+
this.clearSeekCache();
|
|
9274
9489
|
const base = this.rawPose ?? (this.setup ?? (this.setup = poseRawSetup(this.view)));
|
|
9275
9490
|
this.rawPose = base;
|
|
9276
9491
|
this.pose = applySetToPose(this.view, base, found);
|
|
9492
|
+
this.poseGeneration++;
|
|
9493
|
+
this.cachedIdentity = null;
|
|
9277
9494
|
}
|
|
9278
9495
|
/**
|
|
9279
9496
|
* Which slots this player draws (`SlotFilter`), or null for every slot.
|
|
@@ -9289,11 +9506,15 @@ class RigPlayer {
|
|
|
9289
9506
|
if (filter === null) {
|
|
9290
9507
|
this.slotFilter = null;
|
|
9291
9508
|
this.draws = () => true;
|
|
9509
|
+
this.poseGeneration++;
|
|
9510
|
+
this.cachedIdentity = null;
|
|
9292
9511
|
return;
|
|
9293
9512
|
}
|
|
9294
9513
|
if (typeof filter === "function") {
|
|
9295
9514
|
this.slotFilter = filter;
|
|
9296
9515
|
this.draws = filter;
|
|
9516
|
+
this.poseGeneration++;
|
|
9517
|
+
this.cachedIdentity = null;
|
|
9297
9518
|
return;
|
|
9298
9519
|
}
|
|
9299
9520
|
if (!Array.isArray(filter)) throw new Error(`slots must be an array of slot names, a function, or null, got ${typeof filter}`);
|
|
@@ -9305,6 +9526,8 @@ class RigPlayer {
|
|
|
9305
9526
|
const allowed = new Set(filter);
|
|
9306
9527
|
this.slotFilter = [...filter];
|
|
9307
9528
|
this.draws = (slot) => allowed.has(slot);
|
|
9529
|
+
this.poseGeneration++;
|
|
9530
|
+
this.cachedIdentity = null;
|
|
9308
9531
|
}
|
|
9309
9532
|
/**
|
|
9310
9533
|
* The per-pose adjustment of bone locals (`BoneAdjust`), or null for none.
|
|
@@ -9320,6 +9543,7 @@ class RigPlayer {
|
|
|
9320
9543
|
set adjust(fn) {
|
|
9321
9544
|
if (fn !== null && typeof fn !== "function") throw new Error(`adjust must be a function or null, got ${typeof fn}`);
|
|
9322
9545
|
this.adjustFn = fn;
|
|
9546
|
+
this.clearSeekCache();
|
|
9323
9547
|
this.repose();
|
|
9324
9548
|
}
|
|
9325
9549
|
/**
|
|
@@ -9330,6 +9554,7 @@ class RigPlayer {
|
|
|
9330
9554
|
* changed, then `draw`. Nothing before `play`.
|
|
9331
9555
|
*/
|
|
9332
9556
|
repose() {
|
|
9557
|
+
this.clearSeekCache();
|
|
9333
9558
|
const stepper = this.tracks ?? this.track;
|
|
9334
9559
|
if (stepper === null || this.pose === null) return;
|
|
9335
9560
|
this.setRawPose(stepper.step(0));
|
|
@@ -9407,6 +9632,10 @@ class RigPlayer {
|
|
|
9407
9632
|
if (this.setup === null) this.setup = poseRawSetup(this.view);
|
|
9408
9633
|
return this.activeSet === null ? this.setup : applySetToPose(this.view, this.setup, this.activeSet);
|
|
9409
9634
|
}
|
|
9635
|
+
/** Monotonic pose generation counter matching `posedFrame().generation` and `slotIdentity().generation`. */
|
|
9636
|
+
get generation() {
|
|
9637
|
+
return this.poseGeneration;
|
|
9638
|
+
}
|
|
9410
9639
|
/**
|
|
9411
9640
|
* The pose the player holds, as the frame record the renderer draws — under
|
|
9412
9641
|
* this player's `slots` filter, or under `slots` given here instead (a
|
|
@@ -9415,13 +9644,137 @@ class RigPlayer {
|
|
|
9415
9644
|
* posed once).
|
|
9416
9645
|
*/
|
|
9417
9646
|
posedFrame(slots) {
|
|
9418
|
-
if (this.pose === null) return { order: [] };
|
|
9419
9647
|
const draws = slots === void 0 ? this.draws : slots === null ? () => true : typeof slots === "function" ? slots : predicateOf(slots);
|
|
9420
|
-
|
|
9648
|
+
const frame2 = this.pose === null ? { order: [] } : frameOf(this.source, this.pose, this.color, draws);
|
|
9649
|
+
Object.defineProperty(frame2, "generation", {
|
|
9650
|
+
value: this.poseGeneration,
|
|
9651
|
+
enumerable: false,
|
|
9652
|
+
configurable: true
|
|
9653
|
+
});
|
|
9654
|
+
return frame2;
|
|
9655
|
+
}
|
|
9656
|
+
/**
|
|
9657
|
+
* A read-only slot identity snapshot coherent with `posedFrame()` (#144):
|
|
9658
|
+
* for every slot in the posed draw order (`z = 0 .. slots.length - 1`, hidden
|
|
9659
|
+
* and null slots included), its resolved attachment key (`string | null`),
|
|
9660
|
+
* the skin that owns that attachment (`string | null`), and `reason` (`null`
|
|
9661
|
+
* when drawn in `posedFrame().order`, or `'hidden' | 'no-attachment' | 'timeline-null' | 'set-hide'`
|
|
9662
|
+
* when not drawn), together with the active `set`, active `skin`, and frame
|
|
9663
|
+
* `generation`. Read off the held pose without `seek`, `repose`, or physics
|
|
9664
|
+
* restart.
|
|
9665
|
+
*/
|
|
9666
|
+
slotIdentity() {
|
|
9667
|
+
if (this.cachedIdentity !== null && this.cachedIdentityGen === this.poseGeneration) {
|
|
9668
|
+
return this.cachedIdentity;
|
|
9669
|
+
}
|
|
9670
|
+
const setupPose = this.setup ?? (this.setup = poseRawSetup(this.view));
|
|
9671
|
+
const pose = this.posed();
|
|
9672
|
+
const raw = this.rawPose ?? setupPose;
|
|
9673
|
+
const shownBySlot = new Map(pose.shown.map((s) => [s.slot, s]));
|
|
9674
|
+
const setupShownSlots = new Set(setupPose.shown.map((s) => s.slot));
|
|
9675
|
+
const rawRowBySlot = new Map(raw.slots.map((r) => [r[0], r]));
|
|
9676
|
+
const slots = [];
|
|
9677
|
+
for (let z = 0; z < pose.drawOrder.length; z++) {
|
|
9678
|
+
const slotName = pose.drawOrder[z];
|
|
9679
|
+
if (this.activeSet !== null && hiddenBySet(slotName, this.activeSet)) {
|
|
9680
|
+
slots.push(Object.freeze({
|
|
9681
|
+
slot: slotName,
|
|
9682
|
+
z,
|
|
9683
|
+
attachment: null,
|
|
9684
|
+
skin: null,
|
|
9685
|
+
reason: "set-hide"
|
|
9686
|
+
}));
|
|
9687
|
+
continue;
|
|
9688
|
+
}
|
|
9689
|
+
const shown = shownBySlot.get(slotName);
|
|
9690
|
+
const isDrawableKind = shown !== void 0 && (shown.geometry.kind === "region" || shown.geometry.kind === "mesh" || shown.geometry.kind === "linkedmesh");
|
|
9691
|
+
if (shown !== void 0 && isDrawableKind) {
|
|
9692
|
+
const bone2 = this.source.boneOf.get(slotName);
|
|
9693
|
+
const boneActive = bone2 !== void 0 && this.source.active.has(bone2);
|
|
9694
|
+
if (this.draws(slotName) && boneActive) {
|
|
9695
|
+
slots.push(Object.freeze({
|
|
9696
|
+
slot: slotName,
|
|
9697
|
+
z,
|
|
9698
|
+
attachment: shown.name,
|
|
9699
|
+
skin: shown.skin,
|
|
9700
|
+
reason: null
|
|
9701
|
+
}));
|
|
9702
|
+
} else {
|
|
9703
|
+
slots.push(Object.freeze({
|
|
9704
|
+
slot: slotName,
|
|
9705
|
+
z,
|
|
9706
|
+
attachment: null,
|
|
9707
|
+
skin: null,
|
|
9708
|
+
reason: "hidden"
|
|
9709
|
+
}));
|
|
9710
|
+
}
|
|
9711
|
+
continue;
|
|
9712
|
+
}
|
|
9713
|
+
const rawRow = rawRowBySlot.get(slotName);
|
|
9714
|
+
const overriddenBySet = this.activeSet !== null && slotName in this.activeSet.slots;
|
|
9715
|
+
const setupHadAttachment = setupShownSlots.has(slotName);
|
|
9716
|
+
const isTimelineNull = !overriddenBySet && (setupHadAttachment && rawRow?.[1] === null || this.isSlotKeyedNullByTimeline(slotName, raw.animationTime));
|
|
9717
|
+
slots.push(Object.freeze({
|
|
9718
|
+
slot: slotName,
|
|
9719
|
+
z,
|
|
9720
|
+
attachment: null,
|
|
9721
|
+
skin: null,
|
|
9722
|
+
reason: isTimelineNull ? "timeline-null" : "no-attachment"
|
|
9723
|
+
}));
|
|
9724
|
+
}
|
|
9725
|
+
const snapshot = Object.freeze({
|
|
9726
|
+
generation: this.poseGeneration,
|
|
9727
|
+
set: this.activeSet?.name ?? null,
|
|
9728
|
+
skin: this.skinName ?? null,
|
|
9729
|
+
slots: Object.freeze(slots)
|
|
9730
|
+
});
|
|
9731
|
+
this.cachedIdentity = snapshot;
|
|
9732
|
+
this.cachedIdentityGen = this.poseGeneration;
|
|
9733
|
+
return snapshot;
|
|
9734
|
+
}
|
|
9735
|
+
/** Whether an active animation's attachment timeline explicitly keyed `null` on `slotName` at `animationTime`. */
|
|
9736
|
+
isSlotKeyedNullByTimeline(slotName, animationTime) {
|
|
9737
|
+
const checkAnimation = (animName, time) => {
|
|
9738
|
+
if (animName === null) return void 0;
|
|
9739
|
+
const anim = this.view.animations.find((a) => a.name === animName);
|
|
9740
|
+
if (anim === void 0) return void 0;
|
|
9741
|
+
const target = anim.timelines.slots.find((s) => s.name === slotName);
|
|
9742
|
+
if (target === void 0) return void 0;
|
|
9743
|
+
let keyed;
|
|
9744
|
+
for (const tl of target.timelines) {
|
|
9745
|
+
if (tl.kind !== "attachment") continue;
|
|
9746
|
+
let activeKey = null;
|
|
9747
|
+
for (const k of tl.keys) {
|
|
9748
|
+
if (k.time <= time) activeKey = k;
|
|
9749
|
+
else break;
|
|
9750
|
+
}
|
|
9751
|
+
if (activeKey !== null) {
|
|
9752
|
+
keyed = activeKey.name === null || activeKey.name === void 0;
|
|
9753
|
+
}
|
|
9754
|
+
}
|
|
9755
|
+
return keyed;
|
|
9756
|
+
};
|
|
9757
|
+
if (this.tracks !== null) {
|
|
9758
|
+
let result = false;
|
|
9759
|
+
for (let t = 0; t < this.trackCount; t++) {
|
|
9760
|
+
const cur = this.tracks.current(t);
|
|
9761
|
+
if (cur !== null && cur.animation !== null) {
|
|
9762
|
+
const k = checkAnimation(cur.animation, cur.animationTime);
|
|
9763
|
+
if (k !== void 0) result = k;
|
|
9764
|
+
}
|
|
9765
|
+
}
|
|
9766
|
+
return result;
|
|
9767
|
+
}
|
|
9768
|
+
if (this.track !== null && this.animation !== null) {
|
|
9769
|
+
return checkAnimation(this.animation, animationTime) === true;
|
|
9770
|
+
}
|
|
9771
|
+
return false;
|
|
9421
9772
|
}
|
|
9422
9773
|
/** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
|
|
9423
9774
|
dispose() {
|
|
9424
9775
|
this.renderer.dispose();
|
|
9776
|
+
this.clearSeekCache();
|
|
9777
|
+
this.cachedIdentity = null;
|
|
9425
9778
|
this.tracks = null;
|
|
9426
9779
|
this.track = null;
|
|
9427
9780
|
this.pose = null;
|