@rig-c/play 1.16.0 → 1.17.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 CHANGED
@@ -643,6 +643,63 @@ physics state) and nothing when no physics hangs under a re-posed bone. Three li
643
643
  - A later `seek` replays from the start with the sets then active, as it
644
644
  always has, so it does not reproduce a carried switch.
645
645
 
646
+ **Easing a drawn set in and out** (#174). A drawn set snaps its bones to the
647
+ drawn angles in one frame. `ease` turns them there over a duration instead,
648
+ and swaps the attachments halfway, the way an alternate drawing takes over
649
+ at a middle angle:
650
+
651
+ ```ts
652
+ player.setSets(['wave_r'], { ease: 0.5 }); // also setSet, addSet, removeSet
653
+ player.setSets([], { ease: 0.5 }); // and back
654
+ ```
655
+
656
+ - **The curve.** Each bone whose drawn rotation the call adds, removes or
657
+ changes turns from what it shows to its new local rotation along the
658
+ shorter way: `start + shortestΔ(start, end) · smoothstep(u)`, with
659
+ `u = elapsed / ease`. Going on, the start is the clip's value at the time
660
+ (it moves with the clip) and the end the drawn local; going off, the start
661
+ is the drawn local and the end the clip's value. So the progress from start
662
+ to end never leaves `[0, 1]` and never turns back, while the clip plays
663
+ underneath. On `@rig-c/parts`' public wind example with its gesture set
664
+ (`wave_r`, 0.5 s), it is 0 to 1 and non-decreasing on every frame, on and
665
+ off; on a clip that holds the arm still, the sleeve's world angle goes
666
+ −119.32° → −110° and back with no overshoot.
667
+ - **The swap.** The held pose draws the attachments it showed until the frame
668
+ whose `u` is nearest ½, then the set's (a tie goes to the later frame; the
669
+ next step is predicted from the current one, so a constant `dt` lands on
670
+ the nearest frame). On the wind example at `frame(1/60)`, 0.5 s swaps on
671
+ frame 15 and 0.25 s on frame 8. `activeSets`, `set` and `slotIdentity().sets`
672
+ name the call's sets from the call on; `slotIdentity().slots` reports what
673
+ the pose draws, so before the swap it shows the previous attachments.
674
+ - **The end.** On the frame the elapsed time reaches `ease`, the player is in
675
+ exactly the instant switch's state: from then on every bone matches an
676
+ instant switch made at the same frame (0° measured) and the slot identity
677
+ too.
678
+ - **Time.** The elapsed time is the player's step (`dt × timeScale`): a
679
+ paused player, or a step of 0, holds the ease where it is. Before `play`
680
+ there is nothing to step and the switch is instant; so is a call that
681
+ re-poses no bone. A `seek` or a skin change lands an ease in flight at
682
+ once.
683
+ - **A call during an ease** starts a new one from the angles shown (no jump:
684
+ reversing a 0.5 s ease at 0.3 makes no frame-to-frame step larger than the
685
+ ease's own), and the attachment swap starts again from what is drawn. A
686
+ call without `ease` during an ease lands it at once.
687
+ - `ease: 0`, or no `ease`, is the instant switch, frame for frame. Anything
688
+ but a finite number at or above 0 is refused by name before anything
689
+ changes.
690
+
691
+ With physics: `ease` and `physics: 'carry'` carry the chains under the eased
692
+ bones on every eased frame — before each step, the turn the frame's blend
693
+ makes is read as #173's switch is (a trial pose at the previous time) and
694
+ the chains are moved by it — so the ease's own turn is not read as motion.
695
+ `physics: 'reset'` with `ease` restarts them once, at the call. On `drape`
696
+ (0.5 s raise, sleeve tips in the arm's frame): the ease alone moves the
697
+ sleeve up to 30 units a frame relative to the arm (the chain reads the turn
698
+ as motion), ease + carry at most 3.9 on the `idle` clip (its idle ranges are
699
+ 1.5 hanging and 4.2 raised) and 2.3 on `shift` (0.85 and 0.07), where
700
+ gravity re-settles the chain in the turning arm's frame (with gravity at 0,
701
+ 0.83 against 0.84).
702
+
646
703
  `sets.json` keys are read strictly. Besides `slots`, `hides` and `drawn`, a
647
704
  set may carry `drawnLength`, `registration` and `threshold`, which parts
648
705
  writes and which place nothing under `check --set`: play reads past them by
package/dist/index.js CHANGED
@@ -8962,6 +8962,20 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8962
8962
  return { order };
8963
8963
  }
8964
8964
  const DEFAULT_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
8965
+ function setEaseOption(call, options) {
8966
+ const ease = options?.ease;
8967
+ if (ease === void 0) return 0;
8968
+ if (typeof ease !== "number" || !Number.isFinite(ease) || ease < 0) {
8969
+ throw new Error(`${call}: options.ease must be a finite number of seconds at or above 0, got ${typeof ease === "number" ? String(ease) : JSON.stringify(ease)}`);
8970
+ }
8971
+ return ease;
8972
+ }
8973
+ function easeProgress(ease) {
8974
+ return Math.min(Math.max(ease.elapsed / ease.duration, 0), 1);
8975
+ }
8976
+ function smoothstep(u) {
8977
+ return u * u * (3 - 2 * u);
8978
+ }
8965
8979
  function setPhysicsOption(call, options) {
8966
8980
  if (options === void 0) return void 0;
8967
8981
  if (options === null || typeof options !== "object") {
@@ -9285,6 +9299,12 @@ class RigPlayer {
9285
9299
  drawnPose = null;
9286
9300
  /** Test-only (#173's planted control): `carry` maps every state through the identity instead of the anchor's turn. Not API. */
9287
9301
  carryPlant = false;
9302
+ /** An eased set switch in flight (#174), or null. */
9303
+ easing = null;
9304
+ /** The last positive step `advance` took: what predicts the next one when an ease starts. */
9305
+ lastStep = 0;
9306
+ /** Test-only (#174's planted controls): swap the attachments at u = 0, or ease by w = 1.2u (1 at u = 1). Not API. */
9307
+ easePlant = null;
9288
9308
  adjustFn = null;
9289
9309
  /** Current preview patches, reapplied through the public core API on replay; no edit history. */
9290
9310
  physicsPatches = /* @__PURE__ */ new Map();
@@ -9295,7 +9315,8 @@ class RigPlayer {
9295
9315
  draws = () => true;
9296
9316
  /** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
9297
9317
  adjustHook = (locals, ctx) => {
9298
- if (this.drawnPose !== null) for (const [bone2, rotation] of this.drawnPose) locals.bone(bone2).rotation = rotation;
9318
+ if (this.easing !== null) this.applyEase(this.easing, locals);
9319
+ else if (this.drawnPose !== null) for (const [bone2, rotation] of this.drawnPose) locals.bone(bone2).rotation = rotation;
9299
9320
  if (this.adjustFn !== null) this.adjustFn(locals, ctx);
9300
9321
  };
9301
9322
  listeners = {
@@ -9504,11 +9525,12 @@ class RigPlayer {
9504
9525
  if (time > 0) this.stepTo(time);
9505
9526
  return tracks;
9506
9527
  }
9507
- /** Stores `raw` and applies `activeSet` when one is active. Zero work when `activeSet === null`. */
9528
+ /** Stores `raw` and applies the shown set (`activeSet`, or before an ease's swap the composition it eases from). Zero work when none. */
9508
9529
  setRawPose(raw) {
9509
9530
  this.rawPose = raw;
9510
9531
  this.heldParameters = null;
9511
- this.pose = this.activeSet === null ? raw : applySetToPose(this.view, raw, this.activeSet);
9532
+ const set = this.shownSet();
9533
+ this.pose = set === null ? raw : applySetToPose(this.view, raw, set);
9512
9534
  this.poseGeneration++;
9513
9535
  this.cachedIdentity = null;
9514
9536
  }
@@ -9626,6 +9648,7 @@ class RigPlayer {
9626
9648
  if (this.animation === null) throw new Error("seek before play: no animation is open");
9627
9649
  if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
9628
9650
  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");
9651
+ this.easing = null;
9629
9652
  const cache = this.ensureSeekCache();
9630
9653
  const stepper = this.tracks ?? this.track;
9631
9654
  const exactHit = cache.exact.get(time);
@@ -9781,6 +9804,8 @@ class RigPlayer {
9781
9804
  if (this.pose === null) return;
9782
9805
  const step = dt * this.timeScale;
9783
9806
  if (this.paused || !(step > 0)) return;
9807
+ this.lastStep = step;
9808
+ if (this.easing !== null) this.advanceEase(this.easing, step);
9784
9809
  const tracks = this.tracks;
9785
9810
  if (tracks !== null) {
9786
9811
  const before2 = [];
@@ -9823,6 +9848,7 @@ class RigPlayer {
9823
9848
  this.source = frameSource(view, this.read.parsed);
9824
9849
  this.skinName = name;
9825
9850
  this.setup = null;
9851
+ this.easing = null;
9826
9852
  this.clearSeekCache();
9827
9853
  if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
9828
9854
  const time = this.time;
@@ -9881,7 +9907,8 @@ class RigPlayer {
9881
9907
  throw new Error(`cannot setSet(${JSON.stringify(name)}): this model has no sets.json`);
9882
9908
  }
9883
9909
  const physics = setPhysicsOption("setSet", options);
9884
- this.activate(name === null ? [] : [this.namedSet(name)], physics);
9910
+ const ease = setEaseOption("setSet", options);
9911
+ this.activate(name === null ? [] : [this.namedSet(name)], physics, ease);
9885
9912
  }
9886
9913
  /**
9887
9914
  * Makes exactly `names` the active attachment sets (#168), together: each
@@ -9903,19 +9930,42 @@ class RigPlayer {
9903
9930
  * under a re-posed bone. A later `seek` replays from the start with the sets
9904
9931
  * then active and does not reproduce a carried switch. `setSet`, `addSet`
9905
9932
  * and `removeSet` take the same option.
9933
+ *
9934
+ * `options.ease` (#174, seconds, a finite number at or above 0) eases the
9935
+ * bones the call re-poses instead of snapping them. Each bone whose drawn
9936
+ * rotation the call adds, removes or changes turns from what it shows to
9937
+ * its new local rotation along the shorter way, as
9938
+ * `start + shortestΔ(start, end) · smoothstep(elapsed / ease)`: going on,
9939
+ * from the clip's value at the time to the drawn local; going off, from the
9940
+ * drawn local to the clip's value. The attachments the pose draws stay the
9941
+ * previous ones until the frame whose `elapsed / ease` is nearest ½ (the
9942
+ * next step predicted from the current one; a tie goes to the later frame),
9943
+ * then become the call's; `activeSets` and `slotIdentity().sets` name the
9944
+ * call's sets at once, while `slotIdentity().slots` follows what is drawn.
9945
+ * On the frame the elapsed time reaches `ease` the player is in exactly the
9946
+ * instant switch's state. The elapsed time is the player's step
9947
+ * (`dt × timeScale`), so a paused player or a zero step holds the ease;
9948
+ * before `play`, or when no bone's rotation changes, the switch is instant.
9949
+ * A call during an ease eases again from the angles shown (a call without
9950
+ * `ease` lands it at once), and `seek` or a skin change lands it at once.
9951
+ * With `physics: 'carry'` the chains under the eased bones are carried on
9952
+ * every eased frame, by the turn that frame's blend makes; with `'reset'`
9953
+ * they restart once, at the call. `0` or absent is the instant switch on
9954
+ * the same code path; anything else throws by name before anything changes.
9906
9955
  */
9907
9956
  setSets(names, options) {
9908
9957
  if (this.setList.length === 0) {
9909
9958
  throw new Error(`cannot setSets(${JSON.stringify(names)}): this model has no sets.json`);
9910
9959
  }
9911
9960
  const physics = setPhysicsOption("setSets", options);
9961
+ const ease = setEaseOption("setSets", options);
9912
9962
  const sets = names.map((name) => this.namedSet(name));
9913
9963
  for (let i = 0; i < sets.length; i++) {
9914
9964
  for (let j = 0; j < i; j++) {
9915
9965
  if (sets[i] === sets[j]) throw new Error(`set ${JSON.stringify(sets[i].name)} is named twice in setSets`);
9916
9966
  }
9917
9967
  }
9918
- this.activate(sets, physics);
9968
+ this.activate(sets, physics, ease);
9919
9969
  }
9920
9970
  /**
9921
9971
  * Activates `name` beside the sets already active (#168): a set already
@@ -9927,9 +9977,10 @@ class RigPlayer {
9927
9977
  throw new Error(`cannot addSet(${JSON.stringify(name)}): this model has no sets.json`);
9928
9978
  }
9929
9979
  const physics = setPhysicsOption("addSet", options);
9980
+ const ease = setEaseOption("addSet", options);
9930
9981
  const found = this.namedSet(name);
9931
9982
  if (this.activeList.includes(found)) return;
9932
- this.activate([...this.activeList, found], physics);
9983
+ this.activate([...this.activeList, found], physics, ease);
9933
9984
  }
9934
9985
  /**
9935
9986
  * Deactivates `name`, keeping every other active set (#168). An unknown name
@@ -9940,9 +9991,10 @@ class RigPlayer {
9940
9991
  throw new Error(`cannot removeSet(${JSON.stringify(name)}): this model has no sets.json`);
9941
9992
  }
9942
9993
  const physics = setPhysicsOption("removeSet", options);
9994
+ const ease = setEaseOption("removeSet", options);
9943
9995
  const found = this.namedSet(name);
9944
9996
  if (!this.activeList.includes(found)) return;
9945
- this.activate(this.activeList.filter((s) => s !== found), physics);
9997
+ this.activate(this.activeList.filter((s) => s !== found), physics, ease);
9946
9998
  }
9947
9999
  /** The declared set named `name`, or a throw naming it and the available sets. */
9948
10000
  namedSet(name) {
@@ -9990,16 +10042,16 @@ class RigPlayer {
9990
10042
  }
9991
10043
  /**
9992
10044
  * The physics under the bones a set switch re-poses (#173), before the
9993
- * repose step: the drawn rotations went from `before` to `after` (already
9994
- * `drawnPose`), and every physics constraint whose bone is one of the
10045
+ * repose step: the `changed` bones' rotations went from what the held pose
10046
+ * shows to what the player now applies (`drawnPose`, or an ease's next
10047
+ * blend, #174), and every physics constraint whose bone is one of the
9995
10048
  * changed bones or a descendant of one is carried or reset. Nothing to do
9996
10049
  * before `play` (the setup pose carries no physics state) or when no
9997
10050
  * constraint hangs under a changed bone.
9998
10051
  */
9999
- handOverPhysics(before, after, mode) {
10052
+ handOverPhysics(changed, mode) {
10000
10053
  const stepper = this.tracks ?? this.track;
10001
10054
  if (stepper === null || this.pose === null || this.rawPose === null) return;
10002
- const changed = drawnChanged(before, after);
10003
10055
  if (changed.size === 0) return;
10004
10056
  const parentOf = new Map(this.view.bones.map((b) => [b.name, b.parent]));
10005
10057
  const anchorOf = (bone2) => {
@@ -10079,11 +10131,112 @@ class RigPlayer {
10079
10131
  s.ty = c * tx + d * s.ty;
10080
10132
  }
10081
10133
  }
10134
+ /**
10135
+ * The composition the held pose draws: `activeSet`, except before an ease's
10136
+ * swap (#174), when it is the composition the ease started from.
10137
+ */
10138
+ shownSet() {
10139
+ const easing = this.easing;
10140
+ return easing === null || easing.swapped ? this.activeSet : easing.fromSet;
10141
+ }
10142
+ /** The local an eased bone shows at the ease's current progress, given the clip's local `clip`. */
10143
+ easedLocal(easing, from, to, clip) {
10144
+ const start = from ?? clip;
10145
+ const end = to ?? clip;
10146
+ const u = easeProgress(easing);
10147
+ const w = this.easePlant === "overshoot" ? u < 1 ? 1.2 * u : 1 : smoothstep(u);
10148
+ return start + normaliseDegrees(end - start) * w;
10149
+ }
10150
+ /**
10151
+ * The ease's part of `adjustHook` (#174): every drawn bone the ease does not
10152
+ * turn takes its drawn local, as without an ease; every eased bone reads
10153
+ * the clip's local (what the timelines left) and then takes its blend.
10154
+ */
10155
+ applyEase(easing, locals) {
10156
+ for (const bone2 of easing.bones.keys()) easing.clip.set(bone2, locals.bone(bone2).rotation);
10157
+ if (this.drawnPose !== null) {
10158
+ for (const [bone2, rotation] of this.drawnPose) if (!easing.bones.has(bone2)) locals.bone(bone2).rotation = rotation;
10159
+ }
10160
+ for (const [bone2, { from, to }] of easing.bones) {
10161
+ locals.bone(bone2).rotation = this.easedLocal(easing, from, to, easing.clip.get(bone2));
10162
+ }
10163
+ }
10164
+ /**
10165
+ * Starts an eased switch (#174) to the sets already made active, from what
10166
+ * the held pose shows: an eased bone's start is the local it shows (fixed),
10167
+ * or the clip's value for a bone that showed the clip's; its end is the new
10168
+ * drawn local, or the clip's value for a bone whose set went. Returns false,
10169
+ * changing nothing, when there is nothing to ease — before `play` (no step
10170
+ * to ease over) or when no bone's rotation changes — and the call is then
10171
+ * an instant one.
10172
+ */
10173
+ startEase(duration, shownBefore, drawn, physics) {
10174
+ const stepper = this.tracks ?? this.track;
10175
+ if (stepper === null || this.pose === null) return false;
10176
+ const before = this.drawnPose;
10177
+ const after = drawn === void 0 ? null : drawnLocals(this.view, drawn);
10178
+ const previous = this.easing;
10179
+ const candidates = drawnChanged(before, after);
10180
+ if (previous !== null) for (const bone2 of previous.bones.keys()) candidates.add(bone2);
10181
+ const bones = /* @__PURE__ */ new Map();
10182
+ for (const bone2 of candidates) {
10183
+ const to = after?.get(bone2) ?? null;
10184
+ let from;
10185
+ const eased = previous?.bones.get(bone2);
10186
+ if (previous !== null && eased !== void 0) {
10187
+ from = previous.elapsed > 0 ? this.easedLocal(previous, eased.from, eased.to, previous.clip.get(bone2)) : eased.from;
10188
+ } else {
10189
+ from = before?.get(bone2) ?? null;
10190
+ }
10191
+ if (from === to) continue;
10192
+ bones.set(bone2, { from, to });
10193
+ }
10194
+ if (bones.size === 0) return false;
10195
+ const easing = {
10196
+ duration,
10197
+ elapsed: 0,
10198
+ bones,
10199
+ clip: /* @__PURE__ */ new Map(),
10200
+ fromSet: shownBefore,
10201
+ swapped: false,
10202
+ physics
10203
+ };
10204
+ if (this.easePlant === "swap-at-0" || this.swapsNow(0, this.lastStep / duration)) easing.swapped = true;
10205
+ this.easing = easing;
10206
+ this.drawnPose = after;
10207
+ if (physics === "reset") this.handOverPhysics(new Set(bones.keys()), "reset");
10208
+ this.reposeDrawn();
10209
+ return true;
10210
+ }
10211
+ /**
10212
+ * Whether the frame at progress `u` is the one nearest ½, the next frame
10213
+ * being at `next`: it is when `u` reached ½ or the next one would be
10214
+ * farther from it; a tie (within 1e-9) goes to the later frame.
10215
+ */
10216
+ swapsNow(u, next) {
10217
+ if (u >= 0.5) return true;
10218
+ return next > u && 0.5 - u < next - 0.5 - 1e-9;
10219
+ }
10220
+ /**
10221
+ * One step of an eased switch (#174), before the track steps: the elapsed
10222
+ * time advances by the step, the attachments swap when this is the frame
10223
+ * nearest ½ (`swapsNow`, the next step predicted by this one), and the
10224
+ * ease ends — the instant switch's state, `drawnPose` alone — on the frame
10225
+ * it reaches its duration. With `physics: 'carry'`, the chains under the
10226
+ * eased bones are carried by the turn this frame's blend makes, read by a
10227
+ * trial `step(0)` at the previous time (`handOverPhysics`).
10228
+ */
10229
+ advanceEase(easing, step) {
10230
+ easing.elapsed += step;
10231
+ if (easing.elapsed >= easing.duration) this.easing = null;
10232
+ else if (!easing.swapped && this.swapsNow(easing.elapsed / easing.duration, (easing.elapsed + step) / easing.duration)) easing.swapped = true;
10233
+ if (easing.physics === "carry") this.handOverPhysics(new Set(easing.bones.keys()), "carry");
10234
+ }
10082
10235
  /**
10083
10236
  * Makes `sets` (distinct) the active sets: refuses any two touching one slot
10084
10237
  * before changing anything, then applies their composition to the held pose.
10085
10238
  */
10086
- activate(sets, physics) {
10239
+ activate(sets, physics, ease = 0) {
10087
10240
  for (let i = 0; i < sets.length; i++) {
10088
10241
  for (let j = 0; j < i; j++) {
10089
10242
  const other = this.touched(sets[j]);
@@ -10105,14 +10258,27 @@ class RigPlayer {
10105
10258
  }
10106
10259
  }
10107
10260
  const drawnBefore = this.activeSet?.drawn;
10261
+ const shownBefore = this.shownSet();
10108
10262
  this.activeList = this.setList.filter((s) => sets.includes(s));
10109
10263
  this.activeSet = composeSets(this.activeList);
10110
10264
  this.clearSeekCache();
10111
10265
  const drawn = this.activeSet?.drawn;
10266
+ if (ease > 0 && this.startEase(ease, shownBefore, drawn, physics)) return;
10267
+ const easing = this.easing;
10268
+ if (easing !== null) {
10269
+ this.easing = null;
10270
+ const posedBefore = this.drawnPose;
10271
+ this.drawnPose = drawn === void 0 ? null : drawnLocals(this.view, drawn);
10272
+ const changed = drawnChanged(posedBefore, this.drawnPose);
10273
+ for (const bone2 of easing.bones.keys()) changed.add(bone2);
10274
+ if (physics !== void 0) this.handOverPhysics(changed, physics);
10275
+ this.reposeDrawn();
10276
+ return;
10277
+ }
10112
10278
  if (drawn !== drawnBefore && !(drawn === void 0 && drawnBefore === void 0)) {
10113
10279
  const posedBefore = this.drawnPose;
10114
10280
  this.drawnPose = drawn === void 0 ? null : drawnLocals(this.view, drawn);
10115
- if (physics !== void 0) this.handOverPhysics(posedBefore, this.drawnPose, physics);
10281
+ if (physics !== void 0) this.handOverPhysics(drawnChanged(posedBefore, this.drawnPose), physics);
10116
10282
  this.reposeDrawn();
10117
10283
  return;
10118
10284
  }
@@ -10304,9 +10470,10 @@ class RigPlayer {
10304
10470
  const setupShownSlots = new Set(setupPose.shown.map((s) => s.slot));
10305
10471
  const rawRowBySlot = new Map(raw.slots.map((r) => [r[0], r]));
10306
10472
  const slots = [];
10473
+ const shownSet = this.shownSet();
10307
10474
  for (let z = 0; z < pose.drawOrder.length; z++) {
10308
10475
  const slotName = pose.drawOrder[z];
10309
- if (this.activeSet !== null && hiddenBySet(slotName, this.activeSet)) {
10476
+ if (shownSet !== null && hiddenBySet(slotName, shownSet)) {
10310
10477
  slots.push(Object.freeze({
10311
10478
  slot: slotName,
10312
10479
  z,
@@ -10341,7 +10508,7 @@ class RigPlayer {
10341
10508
  continue;
10342
10509
  }
10343
10510
  const rawRow = rawRowBySlot.get(slotName);
10344
- const overriddenBySet = this.activeSet !== null && slotName in this.activeSet.slots;
10511
+ const overriddenBySet = shownSet !== null && slotName in shownSet.slots;
10345
10512
  const setupHadAttachment = setupShownSlots.has(slotName);
10346
10513
  const isTimelineNull = !overriddenBySet && (setupHadAttachment && rawRow?.[1] === null || this.isSlotKeyedNullByTimeline(slotName, raw.animationTime));
10347
10514
  slots.push(Object.freeze({
@@ -10406,6 +10573,7 @@ class RigPlayer {
10406
10573
  this.renderer.dispose();
10407
10574
  this.clearSeekCache();
10408
10575
  this.cachedIdentity = null;
10576
+ this.easing = null;
10409
10577
  this.tracks = null;
10410
10578
  this.track = null;
10411
10579
  this.pose = null;