@rig-c/play 1.15.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 +97 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +316 -14
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +123 -6
- package/dist/player.d.ts.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -603,6 +603,103 @@ poses the held pose again at the same time. Two active sets drawing the same
|
|
|
603
603
|
bone are refused by name, as two sets touching one slot are. A `drawn` naming
|
|
604
604
|
a bone the document does not have is refused when `sets.json` is loaded.
|
|
605
605
|
|
|
606
|
+
**Physics under a drawn bone** (#173). A physics chain hung from a bone a set
|
|
607
|
+
re-poses — a sleeve under a raised arm — reads the jump to the drawn angle as
|
|
608
|
+
motion: the constraint remembers where its bone was and pointed, so the
|
|
609
|
+
switch kicks it (on the test rig `drape`, a 110° raise throws the sleeve's
|
|
610
|
+
tip about 297 units off the arm in the switch itself, against an idle
|
|
611
|
+
frame-to-frame range of 1.5). Every set call takes an option for the chains
|
|
612
|
+
under the bones the switch re-poses:
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
player.setSets(['raise'], { physics: 'carry' }); // also setSet, addSet, removeSet
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
- `'carry'` moves each affected constraint's remembered state by the rigid
|
|
619
|
+
turn of its deepest re-posed ancestor, so the chain keeps its pose relative
|
|
620
|
+
to the arm and goes on swinging from there (the switch itself moves the
|
|
621
|
+
sleeve 0 relative to the arm; gravity then re-settles it in the turned pose,
|
|
622
|
+
which is physics and not the switch).
|
|
623
|
+
- `'reset'` restarts each affected constraint at the new pose, as a physics
|
|
624
|
+
`reset` key does: the chain snaps straight along its bones (17 units on the
|
|
625
|
+
same raise) and settles from there.
|
|
626
|
+
- Absent, nothing changes: today's behaviour, frame for frame. This stays the
|
|
627
|
+
default.
|
|
628
|
+
|
|
629
|
+
`'carry'` is recommended: on `drape` it moves the sleeve 0 relative to the arm
|
|
630
|
+
at the switch and 0 at the deactivation, where `'reset'` moves it 17 units at
|
|
631
|
+
the switch and 118 at the deactivation (no option: 297 and 298).
|
|
632
|
+
|
|
633
|
+
A constraint is affected when its bone is a bone whose drawn rotation the call
|
|
634
|
+
adds, removes or changes, or a descendant of one; every other chain is not
|
|
635
|
+
touched. The option does nothing before `play` (the setup pose carries no
|
|
636
|
+
physics state) and nothing when no physics hangs under a re-posed bone. Three limits:
|
|
637
|
+
|
|
638
|
+
- Carry moves the positions and direction the constraint remembers; a
|
|
639
|
+
constraint with `x`/`y` keeps its translation offsets along the world axes,
|
|
640
|
+
unturned.
|
|
641
|
+
- A drawn bone nested under a carried chain reads its new world from the
|
|
642
|
+
chain's state before the carry.
|
|
643
|
+
- A later `seek` replays from the start with the sets then active, as it
|
|
644
|
+
always has, so it does not reproduce a carried switch.
|
|
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
|
+
|
|
606
703
|
`sets.json` keys are read strictly. Besides `slots`, `hides` and `drawn`, a
|
|
607
704
|
set may carry `drawnLength`, `registration` and `threshold`, which parts
|
|
608
705
|
writes and which place nothing under `check --set`: play reads past them by
|
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, readSetupMesh, type RegionLoading, type RigSet, type SetupMesh, type SetupMeshTarget } from './loadModel.js';
|
|
5
|
-
export { type AdjustableBones, type BoneAdjust, type BoneAdjustContext, type BoneLocals, type BoneWorld, type PhysicsParameters, type PhysicsParameterPatch, type PhysicsParameterRead, 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';
|
|
5
|
+
export { type AdjustableBones, type BoneAdjust, type BoneAdjustContext, type BoneLocals, type BoneWorld, type PhysicsParameters, type PhysicsParameterPatch, type PhysicsParameterRead, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SetActivationOptions, 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,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9L,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,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;
|
|
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,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9L,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,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,oBAAoB,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/e,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8962,6 +8962,42 @@ 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
|
+
}
|
|
8979
|
+
function setPhysicsOption(call, options) {
|
|
8980
|
+
if (options === void 0) return void 0;
|
|
8981
|
+
if (options === null || typeof options !== "object") {
|
|
8982
|
+
throw new Error(`${call}: options must be an object like { physics: 'carry' }, got ${JSON.stringify(options)}`);
|
|
8983
|
+
}
|
|
8984
|
+
const physics = options.physics;
|
|
8985
|
+
if (physics === void 0 || physics === "carry" || physics === "reset") return physics;
|
|
8986
|
+
throw new Error(`${call}: options.physics must be 'carry' or 'reset', got ${JSON.stringify(physics)}`);
|
|
8987
|
+
}
|
|
8988
|
+
function drawnChanged(before, after) {
|
|
8989
|
+
const changed = /* @__PURE__ */ new Set();
|
|
8990
|
+
if (before !== null) {
|
|
8991
|
+
for (const [bone2, rotation] of before) if (after?.get(bone2) !== rotation) changed.add(bone2);
|
|
8992
|
+
}
|
|
8993
|
+
if (after !== null) {
|
|
8994
|
+
for (const [bone2, rotation] of after) if (before?.get(bone2) !== rotation) changed.add(bone2);
|
|
8995
|
+
}
|
|
8996
|
+
return changed;
|
|
8997
|
+
}
|
|
8998
|
+
function physicsContextOf(stepper) {
|
|
8999
|
+
return "first" in stepper ? stepper.track.ctx : stepper.path.ctx;
|
|
9000
|
+
}
|
|
8965
9001
|
function predicateOf(names) {
|
|
8966
9002
|
const allowed = new Set(names);
|
|
8967
9003
|
return (slot) => allowed.has(slot);
|
|
@@ -9261,6 +9297,14 @@ class RigPlayer {
|
|
|
9261
9297
|
touchedSets = /* @__PURE__ */ new Map();
|
|
9262
9298
|
/** The active sets' drawn bones as local rotations, applied before `adjust` on every pose (#170); null when none draws. */
|
|
9263
9299
|
drawnPose = null;
|
|
9300
|
+
/** Test-only (#173's planted control): `carry` maps every state through the identity instead of the anchor's turn. Not API. */
|
|
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;
|
|
9264
9308
|
adjustFn = null;
|
|
9265
9309
|
/** Current preview patches, reapplied through the public core API on replay; no edit history. */
|
|
9266
9310
|
physicsPatches = /* @__PURE__ */ new Map();
|
|
@@ -9271,7 +9315,8 @@ class RigPlayer {
|
|
|
9271
9315
|
draws = () => true;
|
|
9272
9316
|
/** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
|
|
9273
9317
|
adjustHook = (locals, ctx) => {
|
|
9274
|
-
if (this.
|
|
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;
|
|
9275
9320
|
if (this.adjustFn !== null) this.adjustFn(locals, ctx);
|
|
9276
9321
|
};
|
|
9277
9322
|
listeners = {
|
|
@@ -9480,11 +9525,12 @@ class RigPlayer {
|
|
|
9480
9525
|
if (time > 0) this.stepTo(time);
|
|
9481
9526
|
return tracks;
|
|
9482
9527
|
}
|
|
9483
|
-
/** Stores `raw` and applies `activeSet
|
|
9528
|
+
/** Stores `raw` and applies the shown set (`activeSet`, or before an ease's swap the composition it eases from). Zero work when none. */
|
|
9484
9529
|
setRawPose(raw) {
|
|
9485
9530
|
this.rawPose = raw;
|
|
9486
9531
|
this.heldParameters = null;
|
|
9487
|
-
|
|
9532
|
+
const set = this.shownSet();
|
|
9533
|
+
this.pose = set === null ? raw : applySetToPose(this.view, raw, set);
|
|
9488
9534
|
this.poseGeneration++;
|
|
9489
9535
|
this.cachedIdentity = null;
|
|
9490
9536
|
}
|
|
@@ -9602,6 +9648,7 @@ class RigPlayer {
|
|
|
9602
9648
|
if (this.animation === null) throw new Error("seek before play: no animation is open");
|
|
9603
9649
|
if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
|
|
9604
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;
|
|
9605
9652
|
const cache = this.ensureSeekCache();
|
|
9606
9653
|
const stepper = this.tracks ?? this.track;
|
|
9607
9654
|
const exactHit = cache.exact.get(time);
|
|
@@ -9757,6 +9804,8 @@ class RigPlayer {
|
|
|
9757
9804
|
if (this.pose === null) return;
|
|
9758
9805
|
const step = dt * this.timeScale;
|
|
9759
9806
|
if (this.paused || !(step > 0)) return;
|
|
9807
|
+
this.lastStep = step;
|
|
9808
|
+
if (this.easing !== null) this.advanceEase(this.easing, step);
|
|
9760
9809
|
const tracks = this.tracks;
|
|
9761
9810
|
if (tracks !== null) {
|
|
9762
9811
|
const before2 = [];
|
|
@@ -9799,6 +9848,7 @@ class RigPlayer {
|
|
|
9799
9848
|
this.source = frameSource(view, this.read.parsed);
|
|
9800
9849
|
this.skinName = name;
|
|
9801
9850
|
this.setup = null;
|
|
9851
|
+
this.easing = null;
|
|
9802
9852
|
this.clearSeekCache();
|
|
9803
9853
|
if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
|
|
9804
9854
|
const time = this.time;
|
|
@@ -9852,11 +9902,13 @@ class RigPlayer {
|
|
|
9852
9902
|
* A name not in `sets.json` throws by name; a build without `sets.json` has
|
|
9853
9903
|
* `sets()` empty and `setSet` throws.
|
|
9854
9904
|
*/
|
|
9855
|
-
setSet(name) {
|
|
9905
|
+
setSet(name, options) {
|
|
9856
9906
|
if (this.setList.length === 0) {
|
|
9857
9907
|
throw new Error(`cannot setSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9858
9908
|
}
|
|
9859
|
-
|
|
9909
|
+
const physics = setPhysicsOption("setSet", options);
|
|
9910
|
+
const ease = setEaseOption("setSet", options);
|
|
9911
|
+
this.activate(name === null ? [] : [this.namedSet(name)], physics, ease);
|
|
9860
9912
|
}
|
|
9861
9913
|
/**
|
|
9862
9914
|
* Makes exactly `names` the active attachment sets (#168), together: each
|
|
@@ -9865,43 +9917,84 @@ class RigPlayer {
|
|
|
9865
9917
|
* companion). Composition does not depend on the order of `names`. An
|
|
9866
9918
|
* unknown name, a repeated name or two sets touching one slot throw by name
|
|
9867
9919
|
* and leave the active sets as they were; `[]` restores the base.
|
|
9920
|
+
*
|
|
9921
|
+
* `options.physics` (#173) decides what a switch that re-poses `drawn` bones
|
|
9922
|
+
* does to the physics constraints hung under them — on a bone whose drawn
|
|
9923
|
+
* rotation this call adds, removes or changes, or on a descendant of one:
|
|
9924
|
+
* `'carry'` moves each one's remembered state (where its bone was and
|
|
9925
|
+
* pointed) by the rigid turn of its deepest re-posed ancestor, so the chain
|
|
9926
|
+
* keeps its pose relative to the bone; `'reset'` restarts each one at the
|
|
9927
|
+
* new pose, as a physics `reset` key does. Absent, the constraints read the
|
|
9928
|
+
* jump as motion and kick, as they always have. Anything else throws before
|
|
9929
|
+
* anything changes. Nothing to do before `play` or when no physics hangs
|
|
9930
|
+
* under a re-posed bone. A later `seek` replays from the start with the sets
|
|
9931
|
+
* then active and does not reproduce a carried switch. `setSet`, `addSet`
|
|
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.
|
|
9868
9955
|
*/
|
|
9869
|
-
setSets(names) {
|
|
9956
|
+
setSets(names, options) {
|
|
9870
9957
|
if (this.setList.length === 0) {
|
|
9871
9958
|
throw new Error(`cannot setSets(${JSON.stringify(names)}): this model has no sets.json`);
|
|
9872
9959
|
}
|
|
9960
|
+
const physics = setPhysicsOption("setSets", options);
|
|
9961
|
+
const ease = setEaseOption("setSets", options);
|
|
9873
9962
|
const sets = names.map((name) => this.namedSet(name));
|
|
9874
9963
|
for (let i = 0; i < sets.length; i++) {
|
|
9875
9964
|
for (let j = 0; j < i; j++) {
|
|
9876
9965
|
if (sets[i] === sets[j]) throw new Error(`set ${JSON.stringify(sets[i].name)} is named twice in setSets`);
|
|
9877
9966
|
}
|
|
9878
9967
|
}
|
|
9879
|
-
this.activate(sets);
|
|
9968
|
+
this.activate(sets, physics, ease);
|
|
9880
9969
|
}
|
|
9881
9970
|
/**
|
|
9882
9971
|
* Activates `name` beside the sets already active (#168): a set already
|
|
9883
9972
|
* active is left as it is; one touching a slot an active set touches throws
|
|
9884
9973
|
* by name (the slot and both sets) and changes nothing.
|
|
9885
9974
|
*/
|
|
9886
|
-
addSet(name) {
|
|
9975
|
+
addSet(name, options) {
|
|
9887
9976
|
if (this.setList.length === 0) {
|
|
9888
9977
|
throw new Error(`cannot addSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9889
9978
|
}
|
|
9979
|
+
const physics = setPhysicsOption("addSet", options);
|
|
9980
|
+
const ease = setEaseOption("addSet", options);
|
|
9890
9981
|
const found = this.namedSet(name);
|
|
9891
9982
|
if (this.activeList.includes(found)) return;
|
|
9892
|
-
this.activate([...this.activeList, found]);
|
|
9983
|
+
this.activate([...this.activeList, found], physics, ease);
|
|
9893
9984
|
}
|
|
9894
9985
|
/**
|
|
9895
9986
|
* Deactivates `name`, keeping every other active set (#168). An unknown name
|
|
9896
9987
|
* throws by name; a set that is not active is left as it is.
|
|
9897
9988
|
*/
|
|
9898
|
-
removeSet(name) {
|
|
9989
|
+
removeSet(name, options) {
|
|
9899
9990
|
if (this.setList.length === 0) {
|
|
9900
9991
|
throw new Error(`cannot removeSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9901
9992
|
}
|
|
9993
|
+
const physics = setPhysicsOption("removeSet", options);
|
|
9994
|
+
const ease = setEaseOption("removeSet", options);
|
|
9902
9995
|
const found = this.namedSet(name);
|
|
9903
9996
|
if (!this.activeList.includes(found)) return;
|
|
9904
|
-
this.activate(this.activeList.filter((s) => s !== found));
|
|
9997
|
+
this.activate(this.activeList.filter((s) => s !== found), physics, ease);
|
|
9905
9998
|
}
|
|
9906
9999
|
/** The declared set named `name`, or a throw naming it and the available sets. */
|
|
9907
10000
|
namedSet(name) {
|
|
@@ -9947,11 +10040,203 @@ class RigPlayer {
|
|
|
9947
10040
|
}
|
|
9948
10041
|
return touched;
|
|
9949
10042
|
}
|
|
10043
|
+
/**
|
|
10044
|
+
* The physics under the bones a set switch re-poses (#173), before 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
|
|
10048
|
+
* changed bones or a descendant of one is carried or reset. Nothing to do
|
|
10049
|
+
* before `play` (the setup pose carries no physics state) or when no
|
|
10050
|
+
* constraint hangs under a changed bone.
|
|
10051
|
+
*/
|
|
10052
|
+
handOverPhysics(changed, mode) {
|
|
10053
|
+
const stepper = this.tracks ?? this.track;
|
|
10054
|
+
if (stepper === null || this.pose === null || this.rawPose === null) return;
|
|
10055
|
+
if (changed.size === 0) return;
|
|
10056
|
+
const parentOf = new Map(this.view.bones.map((b) => [b.name, b.parent]));
|
|
10057
|
+
const anchorOf = (bone2) => {
|
|
10058
|
+
for (let b = bone2; b !== void 0; b = parentOf.get(b)) if (changed.has(b)) return b;
|
|
10059
|
+
return void 0;
|
|
10060
|
+
};
|
|
10061
|
+
const ctx = physicsContextOf(stepper);
|
|
10062
|
+
const affected = [];
|
|
10063
|
+
for (const c of this.view.constraints) {
|
|
10064
|
+
if (c.kind !== "physics") continue;
|
|
10065
|
+
const bone2 = c.record?.bone;
|
|
10066
|
+
const anchor = bone2 === void 0 ? void 0 : anchorOf(bone2);
|
|
10067
|
+
if (anchor === void 0) continue;
|
|
10068
|
+
const state = ctx.states.get(c.name);
|
|
10069
|
+
if (state === void 0 || state.pending) continue;
|
|
10070
|
+
affected.push({ name: c.name, anchor });
|
|
10071
|
+
}
|
|
10072
|
+
if (affected.length === 0) return;
|
|
10073
|
+
if (mode === "reset") {
|
|
10074
|
+
for (const { name } of affected) {
|
|
10075
|
+
const s = ctx.states.get(name);
|
|
10076
|
+
s.remaining = 0;
|
|
10077
|
+
s.lastTime = ctx.time;
|
|
10078
|
+
s.pending = true;
|
|
10079
|
+
s.xOffset = s.xLag = s.xVelocity = 0;
|
|
10080
|
+
s.yOffset = s.yLag = s.yVelocity = 0;
|
|
10081
|
+
s.rotateOffset = s.rotateLag = s.rotateVelocity = 0;
|
|
10082
|
+
s.scaleOffset = s.scaleLag = s.scaleVelocity = 0;
|
|
10083
|
+
}
|
|
10084
|
+
return;
|
|
10085
|
+
}
|
|
10086
|
+
const anchors = new Set(affected.map((a) => a.anchor));
|
|
10087
|
+
const worlds = (pose) => {
|
|
10088
|
+
const out = /* @__PURE__ */ new Map();
|
|
10089
|
+
for (const b of pose.bones) if (anchors.has(b.name)) out.set(b.name, b);
|
|
10090
|
+
return out;
|
|
10091
|
+
};
|
|
10092
|
+
const old = worlds(this.rawPose);
|
|
10093
|
+
const snapshot = captureStepperState(stepper);
|
|
10094
|
+
const now = worlds(stepper.step(0));
|
|
10095
|
+
restoreStepperState(stepper, snapshot);
|
|
10096
|
+
const maps = /* @__PURE__ */ new Map();
|
|
10097
|
+
for (const anchor of anchors) {
|
|
10098
|
+
const o = old.get(anchor);
|
|
10099
|
+
const n = now.get(anchor);
|
|
10100
|
+
if (o === void 0 || n === void 0) continue;
|
|
10101
|
+
if (this.carryPlant) {
|
|
10102
|
+
maps.set(anchor, [1, 0, 0, 1, 0, 0]);
|
|
10103
|
+
continue;
|
|
10104
|
+
}
|
|
10105
|
+
const det = o.a * o.d - o.b * o.c;
|
|
10106
|
+
if (!Number.isFinite(det) || det === 0) continue;
|
|
10107
|
+
const ia = o.d / det;
|
|
10108
|
+
const ib = -o.b / det;
|
|
10109
|
+
const ic = -o.c / det;
|
|
10110
|
+
const id = o.a / det;
|
|
10111
|
+
const a = n.a * ia + n.b * ic;
|
|
10112
|
+
const b = n.a * ib + n.b * id;
|
|
10113
|
+
const c = n.c * ia + n.d * ic;
|
|
10114
|
+
const d = n.c * ib + n.d * id;
|
|
10115
|
+
maps.set(anchor, [a, b, c, d, n.worldX - (a * o.worldX + b * o.worldY), n.worldY - (c * o.worldX + d * o.worldY)]);
|
|
10116
|
+
}
|
|
10117
|
+
const states = physicsContextOf(stepper).states;
|
|
10118
|
+
for (const { name, anchor } of affected) {
|
|
10119
|
+
const m = maps.get(anchor);
|
|
10120
|
+
const s = states.get(name);
|
|
10121
|
+
if (m === void 0 || s === void 0) continue;
|
|
10122
|
+
const [a, b, c, d, x, y] = m;
|
|
10123
|
+
const ux = s.ux;
|
|
10124
|
+
s.ux = a * ux + b * s.uy + x;
|
|
10125
|
+
s.uy = c * ux + d * s.uy + y;
|
|
10126
|
+
const cx = s.cx;
|
|
10127
|
+
s.cx = a * cx + b * s.cy + x;
|
|
10128
|
+
s.cy = c * cx + d * s.cy + y;
|
|
10129
|
+
const tx = s.tx;
|
|
10130
|
+
s.tx = a * tx + b * s.ty;
|
|
10131
|
+
s.ty = c * tx + d * s.ty;
|
|
10132
|
+
}
|
|
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
|
+
}
|
|
9950
10235
|
/**
|
|
9951
10236
|
* Makes `sets` (distinct) the active sets: refuses any two touching one slot
|
|
9952
10237
|
* before changing anything, then applies their composition to the held pose.
|
|
9953
10238
|
*/
|
|
9954
|
-
activate(sets) {
|
|
10239
|
+
activate(sets, physics, ease = 0) {
|
|
9955
10240
|
for (let i = 0; i < sets.length; i++) {
|
|
9956
10241
|
for (let j = 0; j < i; j++) {
|
|
9957
10242
|
const other = this.touched(sets[j]);
|
|
@@ -9973,12 +10258,27 @@ class RigPlayer {
|
|
|
9973
10258
|
}
|
|
9974
10259
|
}
|
|
9975
10260
|
const drawnBefore = this.activeSet?.drawn;
|
|
10261
|
+
const shownBefore = this.shownSet();
|
|
9976
10262
|
this.activeList = this.setList.filter((s) => sets.includes(s));
|
|
9977
10263
|
this.activeSet = composeSets(this.activeList);
|
|
9978
10264
|
this.clearSeekCache();
|
|
9979
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
|
+
}
|
|
9980
10278
|
if (drawn !== drawnBefore && !(drawn === void 0 && drawnBefore === void 0)) {
|
|
10279
|
+
const posedBefore = this.drawnPose;
|
|
9981
10280
|
this.drawnPose = drawn === void 0 ? null : drawnLocals(this.view, drawn);
|
|
10281
|
+
if (physics !== void 0) this.handOverPhysics(drawnChanged(posedBefore, this.drawnPose), physics);
|
|
9982
10282
|
this.reposeDrawn();
|
|
9983
10283
|
return;
|
|
9984
10284
|
}
|
|
@@ -10170,9 +10470,10 @@ class RigPlayer {
|
|
|
10170
10470
|
const setupShownSlots = new Set(setupPose.shown.map((s) => s.slot));
|
|
10171
10471
|
const rawRowBySlot = new Map(raw.slots.map((r) => [r[0], r]));
|
|
10172
10472
|
const slots = [];
|
|
10473
|
+
const shownSet = this.shownSet();
|
|
10173
10474
|
for (let z = 0; z < pose.drawOrder.length; z++) {
|
|
10174
10475
|
const slotName = pose.drawOrder[z];
|
|
10175
|
-
if (
|
|
10476
|
+
if (shownSet !== null && hiddenBySet(slotName, shownSet)) {
|
|
10176
10477
|
slots.push(Object.freeze({
|
|
10177
10478
|
slot: slotName,
|
|
10178
10479
|
z,
|
|
@@ -10207,7 +10508,7 @@ class RigPlayer {
|
|
|
10207
10508
|
continue;
|
|
10208
10509
|
}
|
|
10209
10510
|
const rawRow = rawRowBySlot.get(slotName);
|
|
10210
|
-
const overriddenBySet =
|
|
10511
|
+
const overriddenBySet = shownSet !== null && slotName in shownSet.slots;
|
|
10211
10512
|
const setupHadAttachment = setupShownSlots.has(slotName);
|
|
10212
10513
|
const isTimelineNull = !overriddenBySet && (setupHadAttachment && rawRow?.[1] === null || this.isSlotKeyedNullByTimeline(slotName, raw.animationTime));
|
|
10213
10514
|
slots.push(Object.freeze({
|
|
@@ -10272,6 +10573,7 @@ class RigPlayer {
|
|
|
10272
10573
|
this.renderer.dispose();
|
|
10273
10574
|
this.clearSeekCache();
|
|
10274
10575
|
this.cachedIdentity = null;
|
|
10576
|
+
this.easing = null;
|
|
10275
10577
|
this.tracks = null;
|
|
10276
10578
|
this.track = null;
|
|
10277
10579
|
this.pose = null;
|