@rig-c/play 1.14.1 → 1.16.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 +101 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +353 -18
- package/dist/index.js.map +1 -1
- package/dist/loadModel.d.ts +7 -0
- package/dist/loadModel.d.ts.map +1 -1
- package/dist/player.d.ts +100 -5
- package/dist/player.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -556,6 +556,104 @@ active (`null`), track evaluation runs with zero extra work and produces
|
|
|
556
556
|
byte-identical poses. An unknown set name throws by name; on a build without
|
|
557
557
|
`sets.json`, `sets()` is empty (`[]`) and `setSet` throws.
|
|
558
558
|
|
|
559
|
+
**Several sets at once** (#168): sets that touch disjoint slots can be active
|
|
560
|
+
together — a mouth viseme driven by audio beside an arm gesture driven by
|
|
561
|
+
input, without building every combination as its own set:
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
player.setSets(['viseme_o', 'gesture_chin']); // exactly these, together
|
|
565
|
+
player.removeSet('viseme_o'); // the gesture stays
|
|
566
|
+
player.addSet('viseme_a'); // beside the active ones; already active = no change
|
|
567
|
+
player.activeSets; // ['viseme_a', 'gesture_chin'] in sets.json order
|
|
568
|
+
player.set; // null: several are active (one active → its name)
|
|
569
|
+
player.setSets([]); // restore base / track attachments
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
A set *touches* every slot it covers, every slot it `hides` and the
|
|
573
|
+
`<base>_still` companion it hides. Two active sets touching the same slot are
|
|
574
|
+
refused by name — `sets "viseme_o" and "talk_point" both touch slot "mouth"` —
|
|
575
|
+
and nothing changes; precedence is never guessed. Disjoint sets compose as the
|
|
576
|
+
union of each alone, whatever the order they were named or added in, and every
|
|
577
|
+
rule above (applied to the held pose, after track evaluation, uncovered slots
|
|
578
|
+
untouched) holds for the composition. `setSet(name)` keeps meaning *exactly this
|
|
579
|
+
one* (`setSets([name])`) and `setSet(null)` is `setSets([])`; an unknown or
|
|
580
|
+
repeated name throws by name. `slotIdentity()` reports the list as `sets`
|
|
581
|
+
beside `set`.
|
|
582
|
+
|
|
583
|
+
**Drawn bones** (#170): a set from `@rig-c/parts` that replaces a limb
|
|
584
|
+
(a *gesture* subset set) carries `drawn` — the world rotation, in degrees
|
|
585
|
+
(CCW, Y up), each of its bones was painted at — and its attachments are
|
|
586
|
+
placed in the frame of the bones posed there:
|
|
587
|
+
|
|
588
|
+
```json
|
|
589
|
+
"wave": {
|
|
590
|
+
"slots": { "arm_upper": "arm_upper_wave", "arm_lower": "arm_lower_wave" },
|
|
591
|
+
"drawn": { "shoulder": 50, "elbow": 95 }
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
While such a set is active its bones are posed at those angles, the way
|
|
596
|
+
`rig-parts check --set` poses them: in document order, each drawn bone's
|
|
597
|
+
local rotation is the drawn angle less its world rotation at local 0 under
|
|
598
|
+
its already-posed parents (no constraint applied), computed once when the set
|
|
599
|
+
is activated. On a track that local rotation replaces the clip's rotation
|
|
600
|
+
of that bone on every pose, before `adjust` (so `adjust` still has the last
|
|
601
|
+
word); every other bone keeps playing. Activating or clearing a drawn set
|
|
602
|
+
poses the held pose again at the same time. Two active sets drawing the same
|
|
603
|
+
bone are refused by name, as two sets touching one slot are. A `drawn` naming
|
|
604
|
+
a bone the document does not have is refused when `sets.json` is loaded.
|
|
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
|
+
`sets.json` keys are read strictly. Besides `slots`, `hides` and `drawn`, a
|
|
647
|
+
set may carry `drawnLength`, `registration` and `threshold`, which parts
|
|
648
|
+
writes and which place nothing under `check --set`: play reads past them by
|
|
649
|
+
name and gives them no meaning. Any other key, on a set or on the document
|
|
650
|
+
(which takes `spec`, `sets` and `threshold`), is refused by name rather than
|
|
651
|
+
ignored.
|
|
652
|
+
|
|
653
|
+
To switch one driver's set while another stays — a new viseme while a gesture
|
|
654
|
+
holds — name the new list in one call (`setSets(['viseme_a', 'gesture_chin'])`)
|
|
655
|
+
or `removeSet` then `addSet`; the first re-poses once, the second twice.
|
|
656
|
+
|
|
559
657
|
### Slot identity snapshot (`slotIdentity()`)
|
|
560
658
|
|
|
561
659
|
To inspect which attachment key, owning skin and visibility state each slot
|
|
@@ -566,7 +664,8 @@ holds on the current frame — for a parts explorer or review overlay — withou
|
|
|
566
664
|
const snap = player.slotIdentity();
|
|
567
665
|
// {
|
|
568
666
|
// generation: number, // matches player.generation and player.posedFrame().generation
|
|
569
|
-
// set: string | null, // active set
|
|
667
|
+
// set: string | null, // the one active set (player.set); null when none or several
|
|
668
|
+
// sets: readonly string[], // every active set (player.activeSets), sets.json order
|
|
570
669
|
// skin: string | null, // active skin name (player.skin ?? null)
|
|
571
670
|
// slots: readonly [ // every slot in posed draw order (z = 0 .. slots.length - 1)
|
|
572
671
|
// {
|
|
@@ -589,7 +688,7 @@ ones included) appears in `snap.slots`:
|
|
|
589
688
|
and the skin that owns it (`skin`).
|
|
590
689
|
- Non-drawn slots report `attachment: null`, `skin: null`, and why they are
|
|
591
690
|
absent from `posedFrame().order`:
|
|
592
|
-
- `'set-hide'` — hidden by
|
|
691
|
+
- `'set-hide'` — hidden by an active attachment set (`set.hides` or a
|
|
593
692
|
`<base>_still` companion of a slot the set covers or hides).
|
|
594
693
|
- `'timeline-null'` — cleared by an active attachment timeline key (`null`).
|
|
595
694
|
- `'no-attachment'` — no attachment in the setup pose or under the active skin
|
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
|
@@ -8579,6 +8579,9 @@ function readSetupMesh(assets, target) {
|
|
|
8579
8579
|
return readSetupMesh$1(underNoSkin(assets.document.doc), target);
|
|
8580
8580
|
}
|
|
8581
8581
|
const SETS_SPECS = ["spine-parts-sets/1", "rig-parts-sets/1"];
|
|
8582
|
+
const SET_KEYS_NOT_POSED = ["drawnLength", "registration", "threshold"];
|
|
8583
|
+
const SET_KEYS = ["slots", "hides", "drawn", ...SET_KEYS_NOT_POSED];
|
|
8584
|
+
const SETS_DOCUMENT_KEYS = ["spec", "sets", "threshold"];
|
|
8582
8585
|
function isRecord(value) {
|
|
8583
8586
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8584
8587
|
}
|
|
@@ -8597,10 +8600,16 @@ function readSetsText(text, where, doc) {
|
|
|
8597
8600
|
`${where}: spec is ${JSON.stringify(raw.spec)}, not "${SETS_SPECS[0]}" or "${SETS_SPECS[1]}"`
|
|
8598
8601
|
);
|
|
8599
8602
|
}
|
|
8603
|
+
for (const key of Object.keys(raw)) {
|
|
8604
|
+
if (!SETS_DOCUMENT_KEYS.includes(key)) {
|
|
8605
|
+
throw new Error(`${where}: unknown key ${JSON.stringify(key)} (known: ${SETS_DOCUMENT_KEYS.join(", ")})`);
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8600
8608
|
if (!isRecord(raw.sets) && !Array.isArray(raw.sets)) {
|
|
8601
8609
|
throw new Error(`${where}: "sets" must be an object or an array`);
|
|
8602
8610
|
}
|
|
8603
8611
|
const knownSlots = new Set(doc.slots.map((slot) => slot.name));
|
|
8612
|
+
const knownBones = new Set(doc.bones.map((bone2) => bone2.name));
|
|
8604
8613
|
const attachmentsBySlot = /* @__PURE__ */ new Map();
|
|
8605
8614
|
for (const skin of doc.skins) {
|
|
8606
8615
|
for (const [slotName, table] of Object.entries(skin.attachments)) {
|
|
@@ -8636,6 +8645,12 @@ function readSetsText(text, where, doc) {
|
|
|
8636
8645
|
throw new Error(`${where}: duplicate set "${name}"`);
|
|
8637
8646
|
}
|
|
8638
8647
|
seenNames.add(name);
|
|
8648
|
+
for (const key of Object.keys(item)) {
|
|
8649
|
+
if (Array.isArray(raw.sets) && key === "name") continue;
|
|
8650
|
+
if (!SET_KEYS.includes(key)) {
|
|
8651
|
+
throw new Error(`${where}: set "${name}" has unknown key ${JSON.stringify(key)} (known: ${SET_KEYS.join(", ")})`);
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8639
8654
|
if (!isRecord(item.slots)) {
|
|
8640
8655
|
throw new Error(`${where}: set "${name}" must have a "slots" object`);
|
|
8641
8656
|
}
|
|
@@ -8670,11 +8685,28 @@ function readSetsText(text, where, doc) {
|
|
|
8670
8685
|
if (!hides.includes(hidden)) hides.push(hidden);
|
|
8671
8686
|
}
|
|
8672
8687
|
}
|
|
8688
|
+
let drawn;
|
|
8689
|
+
if (item.drawn !== void 0) {
|
|
8690
|
+
if (!isRecord(item.drawn)) {
|
|
8691
|
+
throw new Error(`${where}: set "${name}" "drawn" must be an object of bone name → degrees`);
|
|
8692
|
+
}
|
|
8693
|
+
drawn = {};
|
|
8694
|
+
for (const [boneName, degrees] of Object.entries(item.drawn)) {
|
|
8695
|
+
if (!knownBones.has(boneName)) {
|
|
8696
|
+
throw new Error(`${where}: set "${name}" draws unknown bone "${boneName}"`);
|
|
8697
|
+
}
|
|
8698
|
+
if (typeof degrees !== "number" || !Number.isFinite(degrees)) {
|
|
8699
|
+
throw new Error(`${where}: set "${name}" drawn bone "${boneName}" must be a finite number of degrees`);
|
|
8700
|
+
}
|
|
8701
|
+
drawn[boneName] = degrees;
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8673
8704
|
out.push(
|
|
8674
8705
|
Object.freeze({
|
|
8675
8706
|
name,
|
|
8676
8707
|
slots: Object.freeze(slots),
|
|
8677
|
-
hides: Object.freeze(hides)
|
|
8708
|
+
hides: Object.freeze(hides),
|
|
8709
|
+
...drawn === void 0 || Object.keys(drawn).length === 0 ? {} : { drawn: Object.freeze(drawn) }
|
|
8678
8710
|
})
|
|
8679
8711
|
);
|
|
8680
8712
|
}
|
|
@@ -8930,6 +8962,28 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
|
|
|
8930
8962
|
return { order };
|
|
8931
8963
|
}
|
|
8932
8964
|
const DEFAULT_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
|
|
8965
|
+
function setPhysicsOption(call, options) {
|
|
8966
|
+
if (options === void 0) return void 0;
|
|
8967
|
+
if (options === null || typeof options !== "object") {
|
|
8968
|
+
throw new Error(`${call}: options must be an object like { physics: 'carry' }, got ${JSON.stringify(options)}`);
|
|
8969
|
+
}
|
|
8970
|
+
const physics = options.physics;
|
|
8971
|
+
if (physics === void 0 || physics === "carry" || physics === "reset") return physics;
|
|
8972
|
+
throw new Error(`${call}: options.physics must be 'carry' or 'reset', got ${JSON.stringify(physics)}`);
|
|
8973
|
+
}
|
|
8974
|
+
function drawnChanged(before, after) {
|
|
8975
|
+
const changed = /* @__PURE__ */ new Set();
|
|
8976
|
+
if (before !== null) {
|
|
8977
|
+
for (const [bone2, rotation] of before) if (after?.get(bone2) !== rotation) changed.add(bone2);
|
|
8978
|
+
}
|
|
8979
|
+
if (after !== null) {
|
|
8980
|
+
for (const [bone2, rotation] of after) if (before?.get(bone2) !== rotation) changed.add(bone2);
|
|
8981
|
+
}
|
|
8982
|
+
return changed;
|
|
8983
|
+
}
|
|
8984
|
+
function physicsContextOf(stepper) {
|
|
8985
|
+
return "first" in stepper ? stepper.track.ctx : stepper.path.ctx;
|
|
8986
|
+
}
|
|
8933
8987
|
function predicateOf(names) {
|
|
8934
8988
|
const allowed = new Set(names);
|
|
8935
8989
|
return (slot) => allowed.has(slot);
|
|
@@ -8942,6 +8996,47 @@ function hiddenBySet(slotName, set) {
|
|
|
8942
8996
|
}
|
|
8943
8997
|
return false;
|
|
8944
8998
|
}
|
|
8999
|
+
function touchedBySet(view, set) {
|
|
9000
|
+
const touched = /* @__PURE__ */ new Set();
|
|
9001
|
+
for (const slot of view.slots) {
|
|
9002
|
+
if (slot.name in set.slots || hiddenBySet(slot.name, set)) touched.add(slot.name);
|
|
9003
|
+
}
|
|
9004
|
+
return touched;
|
|
9005
|
+
}
|
|
9006
|
+
function normaliseDegrees(deg) {
|
|
9007
|
+
let v = (deg % 360 + 360) % 360;
|
|
9008
|
+
if (v > 180) v -= 360;
|
|
9009
|
+
return v;
|
|
9010
|
+
}
|
|
9011
|
+
function drawnLocals(view, drawn) {
|
|
9012
|
+
const names = new Set(view.bones.map((b) => b.name));
|
|
9013
|
+
const realParent = (parent) => parent !== void 0 && parent.endsWith("_d") && names.has(parent.slice(0, -2)) ? parent.slice(0, -2) : parent;
|
|
9014
|
+
const bones = view.bones.map((b) => ({ ...b, parent: realParent(b.parent) }));
|
|
9015
|
+
const locals = /* @__PURE__ */ new Map();
|
|
9016
|
+
for (let i = 0; i < bones.length; i++) {
|
|
9017
|
+
const target = drawn[bones[i].name];
|
|
9018
|
+
if (target === void 0) continue;
|
|
9019
|
+
bones[i] = { ...bones[i], rotation: 0 };
|
|
9020
|
+
const pose = poseRawSetup({ ...view, bones, slots: [], constraints: [] });
|
|
9021
|
+
const world = pose.bones.find((b) => b.name === bones[i].name);
|
|
9022
|
+
if (world === void 0) throw new Error(`drawn bone ${JSON.stringify(bones[i].name)} was not posed`);
|
|
9023
|
+
const rotation = normaliseDegrees(target - normaliseDegrees(Math.atan2(world.c, world.a) * 180 / Math.PI));
|
|
9024
|
+
bones[i] = { ...bones[i], rotation };
|
|
9025
|
+
locals.set(bones[i].name, rotation);
|
|
9026
|
+
}
|
|
9027
|
+
return locals;
|
|
9028
|
+
}
|
|
9029
|
+
function composeSets(sets) {
|
|
9030
|
+
if (sets.length === 0) return null;
|
|
9031
|
+
if (sets.length === 1) return sets[0];
|
|
9032
|
+
const drawn = sets.filter((s) => s.drawn !== void 0);
|
|
9033
|
+
return {
|
|
9034
|
+
name: sets.map((s) => s.name).join("+"),
|
|
9035
|
+
slots: Object.assign({}, ...sets.map((s) => s.slots)),
|
|
9036
|
+
hides: sets.flatMap((s) => s.hides),
|
|
9037
|
+
...drawn.length === 0 ? {} : { drawn: Object.assign({}, ...drawn.map((s) => s.drawn)) }
|
|
9038
|
+
};
|
|
9039
|
+
}
|
|
8945
9040
|
function applySetToPose(view, raw, set) {
|
|
8946
9041
|
const coveredNames = new Set(Object.keys(set.slots));
|
|
8947
9042
|
const overrideSlots = view.slots.flatMap((slot) => {
|
|
@@ -9182,7 +9277,14 @@ class RigPlayer {
|
|
|
9182
9277
|
cachedIdentityGen = -1;
|
|
9183
9278
|
setList;
|
|
9184
9279
|
setsByName;
|
|
9280
|
+
/** The active sets in declaration order (#168); `activeSet` is their composition. */
|
|
9281
|
+
activeList = [];
|
|
9185
9282
|
activeSet = null;
|
|
9283
|
+
touchedSets = /* @__PURE__ */ new Map();
|
|
9284
|
+
/** The active sets' drawn bones as local rotations, applied before `adjust` on every pose (#170); null when none draws. */
|
|
9285
|
+
drawnPose = null;
|
|
9286
|
+
/** Test-only (#173's planted control): `carry` maps every state through the identity instead of the anchor's turn. Not API. */
|
|
9287
|
+
carryPlant = false;
|
|
9186
9288
|
adjustFn = null;
|
|
9187
9289
|
/** Current preview patches, reapplied through the public core API on replay; no edit history. */
|
|
9188
9290
|
physicsPatches = /* @__PURE__ */ new Map();
|
|
@@ -9193,6 +9295,7 @@ class RigPlayer {
|
|
|
9193
9295
|
draws = () => true;
|
|
9194
9296
|
/** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
|
|
9195
9297
|
adjustHook = (locals, ctx) => {
|
|
9298
|
+
if (this.drawnPose !== null) for (const [bone2, rotation] of this.drawnPose) locals.bone(bone2).rotation = rotation;
|
|
9196
9299
|
if (this.adjustFn !== null) this.adjustFn(locals, ctx);
|
|
9197
9300
|
};
|
|
9198
9301
|
listeners = {
|
|
@@ -9726,8 +9829,7 @@ class RigPlayer {
|
|
|
9726
9829
|
this.reopen();
|
|
9727
9830
|
this.stepTo(time);
|
|
9728
9831
|
} else if (this.rawPose !== null) {
|
|
9729
|
-
this.
|
|
9730
|
-
this.setRawPose(this.setup);
|
|
9832
|
+
this.setRawPose(this.drawnSetup());
|
|
9731
9833
|
} else {
|
|
9732
9834
|
this.poseGeneration++;
|
|
9733
9835
|
this.cachedIdentity = null;
|
|
@@ -9741,9 +9843,17 @@ class RigPlayer {
|
|
|
9741
9843
|
sets() {
|
|
9742
9844
|
return this.setList;
|
|
9743
9845
|
}
|
|
9744
|
-
/**
|
|
9846
|
+
/**
|
|
9847
|
+
* The active attachment set's name when exactly one is active; `null` when
|
|
9848
|
+
* the base attachments are active or when several sets are (read
|
|
9849
|
+
* `activeSets` for the list).
|
|
9850
|
+
*/
|
|
9745
9851
|
get set() {
|
|
9746
|
-
return this.
|
|
9852
|
+
return this.activeList.length === 1 ? this.activeList[0].name : null;
|
|
9853
|
+
}
|
|
9854
|
+
/** Every active attachment set's name, in `sets.json` declaration order (`[]` when none). */
|
|
9855
|
+
get activeSets() {
|
|
9856
|
+
return this.activeList.map((s) => s.name);
|
|
9747
9857
|
}
|
|
9748
9858
|
/**
|
|
9749
9859
|
* Switches the active attachment set (#134): every slot the set covers takes
|
|
@@ -9757,34 +9867,258 @@ class RigPlayer {
|
|
|
9757
9867
|
* frames untouched; when no set is active (`null`), track evaluation runs
|
|
9758
9868
|
* with zero overhead and produces byte-identical poses.
|
|
9759
9869
|
*
|
|
9870
|
+
* A set with `drawn` (#170) also poses its bones at those world angles, as
|
|
9871
|
+
* `@rig-c/parts check --set` does, replacing a clip's rotation of them.
|
|
9872
|
+
*
|
|
9873
|
+
* `setSet(name)` means exactly this one set: any other active sets are
|
|
9874
|
+
* cleared (`setSets([name])`), and `setSet(null)` is `setSets([])`.
|
|
9875
|
+
*
|
|
9760
9876
|
* A name not in `sets.json` throws by name; a build without `sets.json` has
|
|
9761
9877
|
* `sets()` empty and `setSet` throws.
|
|
9762
9878
|
*/
|
|
9763
|
-
setSet(name) {
|
|
9879
|
+
setSet(name, options) {
|
|
9764
9880
|
if (this.setList.length === 0) {
|
|
9765
9881
|
throw new Error(`cannot setSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9766
9882
|
}
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9883
|
+
const physics = setPhysicsOption("setSet", options);
|
|
9884
|
+
this.activate(name === null ? [] : [this.namedSet(name)], physics);
|
|
9885
|
+
}
|
|
9886
|
+
/**
|
|
9887
|
+
* Makes exactly `names` the active attachment sets (#168), together: each
|
|
9888
|
+
* set covers and hides its slots as it does alone, provided no two of them
|
|
9889
|
+
* touch the same slot (cover it, hide it, or hide it as a `<base>_still`
|
|
9890
|
+
* companion). Composition does not depend on the order of `names`. An
|
|
9891
|
+
* unknown name, a repeated name or two sets touching one slot throw by name
|
|
9892
|
+
* and leave the active sets as they were; `[]` restores the base.
|
|
9893
|
+
*
|
|
9894
|
+
* `options.physics` (#173) decides what a switch that re-poses `drawn` bones
|
|
9895
|
+
* does to the physics constraints hung under them — on a bone whose drawn
|
|
9896
|
+
* rotation this call adds, removes or changes, or on a descendant of one:
|
|
9897
|
+
* `'carry'` moves each one's remembered state (where its bone was and
|
|
9898
|
+
* pointed) by the rigid turn of its deepest re-posed ancestor, so the chain
|
|
9899
|
+
* keeps its pose relative to the bone; `'reset'` restarts each one at the
|
|
9900
|
+
* new pose, as a physics `reset` key does. Absent, the constraints read the
|
|
9901
|
+
* jump as motion and kick, as they always have. Anything else throws before
|
|
9902
|
+
* anything changes. Nothing to do before `play` or when no physics hangs
|
|
9903
|
+
* under a re-posed bone. A later `seek` replays from the start with the sets
|
|
9904
|
+
* then active and does not reproduce a carried switch. `setSet`, `addSet`
|
|
9905
|
+
* and `removeSet` take the same option.
|
|
9906
|
+
*/
|
|
9907
|
+
setSets(names, options) {
|
|
9908
|
+
if (this.setList.length === 0) {
|
|
9909
|
+
throw new Error(`cannot setSets(${JSON.stringify(names)}): this model has no sets.json`);
|
|
9776
9910
|
}
|
|
9911
|
+
const physics = setPhysicsOption("setSets", options);
|
|
9912
|
+
const sets = names.map((name) => this.namedSet(name));
|
|
9913
|
+
for (let i = 0; i < sets.length; i++) {
|
|
9914
|
+
for (let j = 0; j < i; j++) {
|
|
9915
|
+
if (sets[i] === sets[j]) throw new Error(`set ${JSON.stringify(sets[i].name)} is named twice in setSets`);
|
|
9916
|
+
}
|
|
9917
|
+
}
|
|
9918
|
+
this.activate(sets, physics);
|
|
9919
|
+
}
|
|
9920
|
+
/**
|
|
9921
|
+
* Activates `name` beside the sets already active (#168): a set already
|
|
9922
|
+
* active is left as it is; one touching a slot an active set touches throws
|
|
9923
|
+
* by name (the slot and both sets) and changes nothing.
|
|
9924
|
+
*/
|
|
9925
|
+
addSet(name, options) {
|
|
9926
|
+
if (this.setList.length === 0) {
|
|
9927
|
+
throw new Error(`cannot addSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9928
|
+
}
|
|
9929
|
+
const physics = setPhysicsOption("addSet", options);
|
|
9930
|
+
const found = this.namedSet(name);
|
|
9931
|
+
if (this.activeList.includes(found)) return;
|
|
9932
|
+
this.activate([...this.activeList, found], physics);
|
|
9933
|
+
}
|
|
9934
|
+
/**
|
|
9935
|
+
* Deactivates `name`, keeping every other active set (#168). An unknown name
|
|
9936
|
+
* throws by name; a set that is not active is left as it is.
|
|
9937
|
+
*/
|
|
9938
|
+
removeSet(name, options) {
|
|
9939
|
+
if (this.setList.length === 0) {
|
|
9940
|
+
throw new Error(`cannot removeSet(${JSON.stringify(name)}): this model has no sets.json`);
|
|
9941
|
+
}
|
|
9942
|
+
const physics = setPhysicsOption("removeSet", options);
|
|
9943
|
+
const found = this.namedSet(name);
|
|
9944
|
+
if (!this.activeList.includes(found)) return;
|
|
9945
|
+
this.activate(this.activeList.filter((s) => s !== found), physics);
|
|
9946
|
+
}
|
|
9947
|
+
/** The declared set named `name`, or a throw naming it and the available sets. */
|
|
9948
|
+
namedSet(name) {
|
|
9777
9949
|
const found = this.setsByName.get(name);
|
|
9778
9950
|
if (found === void 0) {
|
|
9779
9951
|
throw new Error(
|
|
9780
9952
|
`no set named ${JSON.stringify(name)} in sets.json (available: ${this.setList.map((s) => s.name).join(", ")})`
|
|
9781
9953
|
);
|
|
9782
9954
|
}
|
|
9783
|
-
|
|
9955
|
+
return found;
|
|
9956
|
+
}
|
|
9957
|
+
/**
|
|
9958
|
+
* Poses again after the drawn bones changed (#170): through the tracks at
|
|
9959
|
+
* the same time when the player has played, else the setup pose with the
|
|
9960
|
+
* drawn bones at their rotations.
|
|
9961
|
+
*/
|
|
9962
|
+
reposeDrawn() {
|
|
9963
|
+
const stepper = this.tracks ?? this.track;
|
|
9964
|
+
if (stepper !== null && this.pose !== null) {
|
|
9965
|
+
this.setRawPose(stepper.step(0));
|
|
9966
|
+
return;
|
|
9967
|
+
}
|
|
9968
|
+
this.setRawPose(this.drawnSetup());
|
|
9969
|
+
}
|
|
9970
|
+
/** The setup pose with the active sets' drawn bones at their rotations (#170); the plain setup pose when none draws. */
|
|
9971
|
+
drawnSetup() {
|
|
9972
|
+
const setup = this.setup ?? (this.setup = poseRawSetup(this.view));
|
|
9973
|
+
if (this.drawnPose === null) return setup;
|
|
9974
|
+
return poseRawSetup({
|
|
9975
|
+
...this.view,
|
|
9976
|
+
bones: this.view.bones.map((b) => {
|
|
9977
|
+
const rotation = this.drawnPose?.get(b.name);
|
|
9978
|
+
return rotation === void 0 ? b : { ...b, rotation };
|
|
9979
|
+
})
|
|
9980
|
+
});
|
|
9981
|
+
}
|
|
9982
|
+
/** The slots `set` touches, computed once per set. */
|
|
9983
|
+
touched(set) {
|
|
9984
|
+
let touched = this.touchedSets.get(set.name);
|
|
9985
|
+
if (touched === void 0) {
|
|
9986
|
+
touched = touchedBySet(this.view, set);
|
|
9987
|
+
this.touchedSets.set(set.name, touched);
|
|
9988
|
+
}
|
|
9989
|
+
return touched;
|
|
9990
|
+
}
|
|
9991
|
+
/**
|
|
9992
|
+
* 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
|
|
9995
|
+
* changed bones or a descendant of one is carried or reset. Nothing to do
|
|
9996
|
+
* before `play` (the setup pose carries no physics state) or when no
|
|
9997
|
+
* constraint hangs under a changed bone.
|
|
9998
|
+
*/
|
|
9999
|
+
handOverPhysics(before, after, mode) {
|
|
10000
|
+
const stepper = this.tracks ?? this.track;
|
|
10001
|
+
if (stepper === null || this.pose === null || this.rawPose === null) return;
|
|
10002
|
+
const changed = drawnChanged(before, after);
|
|
10003
|
+
if (changed.size === 0) return;
|
|
10004
|
+
const parentOf = new Map(this.view.bones.map((b) => [b.name, b.parent]));
|
|
10005
|
+
const anchorOf = (bone2) => {
|
|
10006
|
+
for (let b = bone2; b !== void 0; b = parentOf.get(b)) if (changed.has(b)) return b;
|
|
10007
|
+
return void 0;
|
|
10008
|
+
};
|
|
10009
|
+
const ctx = physicsContextOf(stepper);
|
|
10010
|
+
const affected = [];
|
|
10011
|
+
for (const c of this.view.constraints) {
|
|
10012
|
+
if (c.kind !== "physics") continue;
|
|
10013
|
+
const bone2 = c.record?.bone;
|
|
10014
|
+
const anchor = bone2 === void 0 ? void 0 : anchorOf(bone2);
|
|
10015
|
+
if (anchor === void 0) continue;
|
|
10016
|
+
const state = ctx.states.get(c.name);
|
|
10017
|
+
if (state === void 0 || state.pending) continue;
|
|
10018
|
+
affected.push({ name: c.name, anchor });
|
|
10019
|
+
}
|
|
10020
|
+
if (affected.length === 0) return;
|
|
10021
|
+
if (mode === "reset") {
|
|
10022
|
+
for (const { name } of affected) {
|
|
10023
|
+
const s = ctx.states.get(name);
|
|
10024
|
+
s.remaining = 0;
|
|
10025
|
+
s.lastTime = ctx.time;
|
|
10026
|
+
s.pending = true;
|
|
10027
|
+
s.xOffset = s.xLag = s.xVelocity = 0;
|
|
10028
|
+
s.yOffset = s.yLag = s.yVelocity = 0;
|
|
10029
|
+
s.rotateOffset = s.rotateLag = s.rotateVelocity = 0;
|
|
10030
|
+
s.scaleOffset = s.scaleLag = s.scaleVelocity = 0;
|
|
10031
|
+
}
|
|
10032
|
+
return;
|
|
10033
|
+
}
|
|
10034
|
+
const anchors = new Set(affected.map((a) => a.anchor));
|
|
10035
|
+
const worlds = (pose) => {
|
|
10036
|
+
const out = /* @__PURE__ */ new Map();
|
|
10037
|
+
for (const b of pose.bones) if (anchors.has(b.name)) out.set(b.name, b);
|
|
10038
|
+
return out;
|
|
10039
|
+
};
|
|
10040
|
+
const old = worlds(this.rawPose);
|
|
10041
|
+
const snapshot = captureStepperState(stepper);
|
|
10042
|
+
const now = worlds(stepper.step(0));
|
|
10043
|
+
restoreStepperState(stepper, snapshot);
|
|
10044
|
+
const maps = /* @__PURE__ */ new Map();
|
|
10045
|
+
for (const anchor of anchors) {
|
|
10046
|
+
const o = old.get(anchor);
|
|
10047
|
+
const n = now.get(anchor);
|
|
10048
|
+
if (o === void 0 || n === void 0) continue;
|
|
10049
|
+
if (this.carryPlant) {
|
|
10050
|
+
maps.set(anchor, [1, 0, 0, 1, 0, 0]);
|
|
10051
|
+
continue;
|
|
10052
|
+
}
|
|
10053
|
+
const det = o.a * o.d - o.b * o.c;
|
|
10054
|
+
if (!Number.isFinite(det) || det === 0) continue;
|
|
10055
|
+
const ia = o.d / det;
|
|
10056
|
+
const ib = -o.b / det;
|
|
10057
|
+
const ic = -o.c / det;
|
|
10058
|
+
const id = o.a / det;
|
|
10059
|
+
const a = n.a * ia + n.b * ic;
|
|
10060
|
+
const b = n.a * ib + n.b * id;
|
|
10061
|
+
const c = n.c * ia + n.d * ic;
|
|
10062
|
+
const d = n.c * ib + n.d * id;
|
|
10063
|
+
maps.set(anchor, [a, b, c, d, n.worldX - (a * o.worldX + b * o.worldY), n.worldY - (c * o.worldX + d * o.worldY)]);
|
|
10064
|
+
}
|
|
10065
|
+
const states = physicsContextOf(stepper).states;
|
|
10066
|
+
for (const { name, anchor } of affected) {
|
|
10067
|
+
const m = maps.get(anchor);
|
|
10068
|
+
const s = states.get(name);
|
|
10069
|
+
if (m === void 0 || s === void 0) continue;
|
|
10070
|
+
const [a, b, c, d, x, y] = m;
|
|
10071
|
+
const ux = s.ux;
|
|
10072
|
+
s.ux = a * ux + b * s.uy + x;
|
|
10073
|
+
s.uy = c * ux + d * s.uy + y;
|
|
10074
|
+
const cx = s.cx;
|
|
10075
|
+
s.cx = a * cx + b * s.cy + x;
|
|
10076
|
+
s.cy = c * cx + d * s.cy + y;
|
|
10077
|
+
const tx = s.tx;
|
|
10078
|
+
s.tx = a * tx + b * s.ty;
|
|
10079
|
+
s.ty = c * tx + d * s.ty;
|
|
10080
|
+
}
|
|
10081
|
+
}
|
|
10082
|
+
/**
|
|
10083
|
+
* Makes `sets` (distinct) the active sets: refuses any two touching one slot
|
|
10084
|
+
* before changing anything, then applies their composition to the held pose.
|
|
10085
|
+
*/
|
|
10086
|
+
activate(sets, physics) {
|
|
10087
|
+
for (let i = 0; i < sets.length; i++) {
|
|
10088
|
+
for (let j = 0; j < i; j++) {
|
|
10089
|
+
const other = this.touched(sets[j]);
|
|
10090
|
+
for (const slot of this.touched(sets[i])) {
|
|
10091
|
+
if (other.has(slot)) {
|
|
10092
|
+
throw new Error(
|
|
10093
|
+
`sets ${JSON.stringify(sets[j].name)} and ${JSON.stringify(sets[i].name)} both touch slot ${JSON.stringify(slot)}; active sets must cover and hide disjoint slots`
|
|
10094
|
+
);
|
|
10095
|
+
}
|
|
10096
|
+
}
|
|
10097
|
+
const otherDrawn = sets[j].drawn;
|
|
10098
|
+
for (const bone2 of Object.keys(sets[i].drawn ?? {})) {
|
|
10099
|
+
if (otherDrawn !== void 0 && bone2 in otherDrawn) {
|
|
10100
|
+
throw new Error(
|
|
10101
|
+
`sets ${JSON.stringify(sets[j].name)} and ${JSON.stringify(sets[i].name)} both pose bone ${JSON.stringify(bone2)}; active sets must draw disjoint bones`
|
|
10102
|
+
);
|
|
10103
|
+
}
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
const drawnBefore = this.activeSet?.drawn;
|
|
10108
|
+
this.activeList = this.setList.filter((s) => sets.includes(s));
|
|
10109
|
+
this.activeSet = composeSets(this.activeList);
|
|
9784
10110
|
this.clearSeekCache();
|
|
10111
|
+
const drawn = this.activeSet?.drawn;
|
|
10112
|
+
if (drawn !== drawnBefore && !(drawn === void 0 && drawnBefore === void 0)) {
|
|
10113
|
+
const posedBefore = this.drawnPose;
|
|
10114
|
+
this.drawnPose = drawn === void 0 ? null : drawnLocals(this.view, drawn);
|
|
10115
|
+
if (physics !== void 0) this.handOverPhysics(posedBefore, this.drawnPose, physics);
|
|
10116
|
+
this.reposeDrawn();
|
|
10117
|
+
return;
|
|
10118
|
+
}
|
|
9785
10119
|
const base = this.rawPose ?? (this.setup ?? (this.setup = poseRawSetup(this.view)));
|
|
9786
10120
|
this.rawPose = base;
|
|
9787
|
-
this.pose = applySetToPose(this.view, base,
|
|
10121
|
+
this.pose = this.activeSet === null ? base : applySetToPose(this.view, base, this.activeSet);
|
|
9788
10122
|
this.poseGeneration++;
|
|
9789
10123
|
this.cachedIdentity = null;
|
|
9790
10124
|
}
|
|
@@ -10020,7 +10354,8 @@ class RigPlayer {
|
|
|
10020
10354
|
}
|
|
10021
10355
|
const snapshot = Object.freeze({
|
|
10022
10356
|
generation: this.poseGeneration,
|
|
10023
|
-
set: this.
|
|
10357
|
+
set: this.set,
|
|
10358
|
+
sets: Object.freeze(this.activeSets),
|
|
10024
10359
|
skin: this.skinName ?? null,
|
|
10025
10360
|
slots: Object.freeze(slots)
|
|
10026
10361
|
});
|