@rig-c/core 2.60.0 β 2.61.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 +5 -2
- package/docs/AUTHORING.md +42 -2
- package/docs/INGEST.md +1 -0
- package/docs/MOTION.md +1 -0
- package/package.json +1 -1
- package/src/cli/core_commands.ts +38 -2
- package/src/cli/shared.ts +20 -3
- package/src/cli/spine_commands.ts +1 -1
- package/src/compile.ts +59 -5
- package/src/render.ts +16 -0
- package/src/render_core.ts +23 -5
- package/src/render_shared.ts +33 -0
package/README.md
CHANGED
|
@@ -433,7 +433,10 @@ frames. It draws with rigc's own rasteriser (the one `check` measures with), so
|
|
|
433
433
|
it needs no browser and no network, and the `frames.json` it leaves beside the
|
|
434
434
|
directories makes the result a frame set like any other β the world box every
|
|
435
435
|
frame is a picture of. `--animation <name>` narrows it to one, `--fps` and
|
|
436
|
-
`--max` change the rate and the frame size.
|
|
436
|
+
`--max` change the rate and the frame size. A set drawn at any rate but the
|
|
437
|
+
default is written to `<anim>@<fps>fps/` rather than `<anim>/`, so two rates sit
|
|
438
|
+
side by side: `--fps 24` writes `<anim>@24fps/`, and the default 12 writes
|
|
439
|
+
`<anim>/`. Each set's summary line ends with the directory it wrote.
|
|
437
440
|
|
|
438
441
|
> π¨ **`--skin <name>` if your rig has more than one.** With no `--skin` no skin
|
|
439
442
|
> is set at all, so every slot resolves through the **default** skin alone β and
|
|
@@ -603,7 +606,7 @@ commands take it and what its default is.
|
|
|
603
606
|
| `ingest <skeleton.json> --out <dir>` | `build` run backwards: reads a Spine 4.3 skeleton and writes the rig spec and motion spec that **rebuild it**, plus a findings report naming everything it could not carry. a skeleton that declares no stage is carried as declaring none, `--stage x,y,w,h` adds a box to one β and is refused, rather than ignored, beside one that declares a box β and `--images <dir>` writes the spec's own images directory β the opposite direction from `build --images`, which overrides it β so the rebuild carries no flag at all |
|
|
604
607
|
| `split --rig β¦ --motion β¦ --slot <name> --out <dir>` | the answer to a mesh refused because its triangles are **several pieces that share no vertex** β two legs after the triangles bridging them were deleted. It reads the two specs and writes them back into an absent or empty `--out` with one mesh per piece: piece 1 stays in the slot, piece k gets a new slot `<slot>_<k>` after it, each with the original's uvs and weights unchanged, its triangles renumbered outline first where the hull rule needs it, and every deform key re-sliced to the new indices β plus `split.json`, where every original vertex and triangle went. One piece is said so and nothing is written; what it cannot carry is refused by name ([AUTHORING Β§3.4.11](docs/AUTHORING.md)) |
|
|
605
608
|
| `explain --rig β¦ --motion β¦` | the compiled rig as a table β every bone with its resolved parent, the slots in draw order, every timeline key by key. Writes nothing. What to reach for when a rig compiles and still looks wrong |
|
|
606
|
-
| `render --candidate <dir>` | PNG frames plus a contact sheet, in `render
|
|
609
|
+
| `render --candidate <dir>` | PNG frames plus a contact sheet, in `render/`, one directory per set: `<anim>@<fps>fps/` rather than `<anim>/` at any rate but the default β `--fps 24` writes `<anim>@24fps/`, and the default 12 writes `<anim>/`. `--hide <slot,β¦>` or `--slot <slot,β¦>` draws part of the rig on the **same grid** as the whole, so the two frames overlay and the difference is the part; `frames.json` records the subset and `check` refuses such a set as a reference. `--geometry` adds a `geometry.json` per set: every frame's bone world transforms and skinned attachment vertices, with the rest geometry, on the frames' own grid. `--folds` adds a `folds.json` per set and prints one line per mesh with a triangle turned over against the setup pose: the frames, the worst frame's count and which triangles, their world box and their setup-pose area. `--pass-through <cloth>:<leg>` adds a `pass-through.json` per set and prints the frames where a cloth drawn over a leg has its side inside the leg's drawn outline, with the depth and the world box β a reading, not a gate. `--seam <slot>:<slot>` adds a `seam.json` per set and prints, per pair, the frames where two slots that meet open background inside the joint (a gap) or show the overlap band of one past the other (a ghost), with the worst frame's cell count and world box β a reading, not a gate; the exit status never moves. `--angle-range <bone>:<relative-to>` prints how far one bone turns against another over each set: the lowest and highest angle and the frames they fall on β a reading, not a gate. `--viewport <x,y,w,h>` or `--viewport-from <frames.json>` draws over a given box instead of a fitted one, so two builds land on one pixel grid; a slot that reaches past it is named by set and frame, and `frames.json` records it. `--environment <schedule.json>` draws the rig under the wind and gravity its physics answers, in time β the same frames on the core and on spine-core; an inspection of the response the rig states, not a scene |
|
|
607
610
|
| `preview --candidate <dir>` | one self-contained `.html` that plays it, headed by the line `validate <dir>` prints for it and the rigc version β a refused candidate is still previewed, and its header says so in the gate's words. Repeat `--candidate` for one page with a pane per candidate, in the order given; a green `build` ends by naming this command for its own `--out` |
|
|
608
611
|
| `vote --candidate a --candidate b` | one `.html` that asks a human which; `vote --record <file>` checks the answer into `votes.jsonl` |
|
|
609
612
|
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
package/docs/AUTHORING.md
CHANGED
|
@@ -1242,7 +1242,11 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
1242
1242
|
gate can know you did not mean them. `render` writes
|
|
1243
1243
|
`render/<animation>/f0000.pngβ¦` with a `contact.png` grid of **every** frame
|
|
1244
1244
|
beside them (open that one first β spacing is a comparison across frames) and a
|
|
1245
|
-
`frames.json` sidecar naming the world box they are pictures of.
|
|
1245
|
+
`frames.json` sidecar naming the world box they are pictures of. It also says
|
|
1246
|
+
whether the physics ran: under each set's line, `physics: N constraint(s)
|
|
1247
|
+
stepped at <fps> fps`, and the same two figures on that set in `frames.json` β
|
|
1248
|
+
or, once, `physics: none in this rig` for a rig that declares no physics
|
|
1249
|
+
constraint (Β§8.4). `contact.png`
|
|
1246
1250
|
is for spacing across frames; a defect is read on **one frame at full size**,
|
|
1247
1251
|
because a tile is too small to say which part a pixel belongs to. When a frame
|
|
1248
1252
|
looks wrong, render it again with `--hide <slot>` β the **largest attachment**
|
|
@@ -6843,6 +6847,14 @@ it had nothing to step (`nanStepping=skipped`). What it does not see is a skin o
|
|
|
6843
6847
|
than the default, which A10 does not pose. The sentence, and where to look for the
|
|
6844
6848
|
number, is under *nothing to draw* in Β§3.4.
|
|
6845
6849
|
|
|
6850
|
+
β¨οΈ **An invocation missing a flag is one line, before any of these.** `build`,
|
|
6851
|
+
`explain` and `validate` given a cut spelled out with `--rig`, `--motion` or
|
|
6852
|
+
`--out` absent print `rigc: <command> needs --<flag> <value> (or --cut <name>
|
|
6853
|
+
--cuts <cuts.json>)` on stderr and exit 2 β the line alone, with no command
|
|
6854
|
+
catalogue under it. An input flag that names a **directory** is a compile error
|
|
6855
|
+
with a remedy in it: `CE_COMPILE_JSON_READ_FAILED` says what the flag wants and
|
|
6856
|
+
names the one file there that reads as it (Β§5.1.2). It is never read as that file.
|
|
6857
|
+
|
|
6846
6858
|
### 5.1 Compile errors β before the gate
|
|
6847
6859
|
|
|
6848
6860
|
A `CompileError` names the object and the field, and nothing is written. Its line
|
|
@@ -6919,7 +6931,7 @@ retain the minimal inputs and report that code rather than inventing author data
|
|
|
6919
6931
|
| <a id="CE_COMPILE_RGBA_CHANNEL_COUNT"></a>`CE_COMPILE_RGBA_CHANNEL_COUNT` | βrgba value needs 4 channels, got <value>β | Supply four rgba channels. |
|
|
6920
6932
|
| <a id="CE_COMPILE_RGBA2_CHANNEL_COUNT"></a>`CE_COMPILE_RGBA2_CHANNEL_COUNT` | βrgba2 value needs 7 channels, got <value>β | Supply seven rgba2 channels. |
|
|
6921
6933
|
| <a id="CE_COMPILE_COLOUR_CHANNEL_COUNT"></a>`CE_COMPILE_COLOUR_CHANNEL_COUNT` | β<value> value needs <value> channel[ / s], got <value>β | Supply the channel count named for this colour shape. |
|
|
6922
|
-
| <a id="CE_COMPILE_JSON_READ_FAILED"></a>`CE_COMPILE_JSON_READ_FAILED` | βcannot read <value>: <value>β<br>Also: β<value> (line <value>, column <value>)β | Fix the named JSON file's path, permissions or JSON syntax. |
|
|
6934
|
+
| <a id="CE_COMPILE_JSON_READ_FAILED"></a>`CE_COMPILE_JSON_READ_FAILED` | βcannot read <value>: <value>β<br>Also: β<value> (line <value>, column <value>)β<br>Also, when `--rig`, `--motion` or `--manifest` names a directory: β<value> names a directory; it wants <value> β <value> exists thereβ Β· ββ¦ β <value> holds no .json fileβ Β· ββ¦ β <value> holds <value> .json file(s) and none reads as one: <value>β Β· ββ¦ β <value> .json files in <value> read as one, so name it: <value>β, a list cut at eight names with β, β¦ <value> in allβ | Fix the named JSON file's path, permissions or JSON syntax. For a directory, name the file the line names β the one `.json` there that reads as that input (a rig spec through the rig spec's parser, a motion spec through the motion spec's; for `--manifest`, any `.json`) β or pick one of the files it lists. A directory is never read as its file: rigc compiles what it was given. |
|
|
6923
6935
|
| <a id="CE_COMPILE_IMAGE_MISSING"></a>`CE_COMPILE_IMAGE_MISSING` | βimage "<value>" is not on disk at <value>β<br>Also: β<value>.pngβ | Supply the PNG at the named images path or correct the image name. |
|
|
6924
6936
|
| <a id="CE_COMPILE_IMAGE_PNG_INVALID"></a>`CE_COMPILE_IMAGE_PNG_INVALID` | β<value> is <value>, not a PNG: its first <value> byte(s) are <value>, where a PNG's first 8 are <value><value>. rigc reads PNG and nothing else β its page-size and alpha readers, its renderer and its region lift all decode PNG, and it links no decoder for any other format β so nothing in this file was measured. Re-export it as PNGβ<br>β<value> is a PNG whose IHDR chunk declares <value> byte(s) of data, where the format's is 13β<br>β<value> is a PNG whose IHDR chunk states its size as <value>x<value>: <value> 0, where the format requires each dimension to be at least 1, so the file describes no texel and nothing in it can be measured or drawn. Re-export itβ<br>β<value> is a PNG whose first chunk is <value> rather than the IHDR the format requires firstβ<br>β<value> is a truncated PNG: it is <value> byte(s) long and <value>, so no reader can decode it. Re-export or re-copy the file wholeβ<br>β<value> is a truncated PNG: it is <value> byte(s) long and ends <value> byte(s) into the 8-byte PNG signature, so no reader can decode it. Re-export or re-copy the file wholeβ<br>β<value> is a truncated PNG: it is <value> byte(s) long and ends after the signature with no IEND chunk, so no reader can decode it. Re-export or re-copy the file wholeβ<br>β<value> is a truncated PNG: it is <value> byte(s) long and its <value> chunk at byte <value> declares <value> byte(s) of data, which with its CRC runs to byte <value>, so no reader can decode it. Re-export or re-copy the file wholeβ<br>β<value> is a truncated PNG: it is <value> byte(s) long and {ends after <value> with no IEND chunk / ends <value> byte(s) into the header of the chunk after <value>}, so no reader can decode it. Re-export or re-copy the file wholeβ<br>β<value> is empty (0 bytes), and a PNG's first 8 bytes are <value>β<br>β[; its name ends in .png, and it is the bytes that decide what a file is / ]β<br>β[its width and its height are / [its width is / its height is]]β<br>βa <value> imageβ<br>βa WebP image (a RIFF/WEBP container whose first chunk is <value>)β<br>βimage "<value>": <value>β<br>βin no image format rigc recognises by its signature (it names <value> when it meets them)β<br>Here the values named in the message include: βKTX2β, βGIFβ, βJPEGβ. | Supply a readable PNG with the required header and dimensions at the named path. |
|
|
6925
6937
|
| <a id="CE_COMPILE_ATLAS_INPUT_MISSING"></a>`CE_COMPILE_ATLAS_INPUT_MISSING` | β--atlas-in names <value>, which is not on diskβ | Correct --atlas-in or supply the atlas file at that path. |
|
|
@@ -8496,6 +8508,34 @@ from its own frame and no earlier (`RE02`); and plants on the core the readings
|
|
|
8496
8508
|
mapping rejects β the schedule ignored, its y read up, each change a frame late or
|
|
8497
8509
|
early β each red against spine-core (`RE03`).
|
|
8498
8510
|
|
|
8511
|
+
**Whether the physics ran.** Every render β with this flag or without β says, under
|
|
8512
|
+
each set's line, how many physics constraints the poser stepped on at least one of
|
|
8513
|
+
its frames, and at what rate:
|
|
8514
|
+
|
|
8515
|
+
```text
|
|
8516
|
+
.. idle 25 frame(s), 2.000s + contact.png -> render/idle
|
|
8517
|
+
.. physics: 3 constraint(s) stepped at 12 fps
|
|
8518
|
+
```
|
|
8519
|
+
|
|
8520
|
+
and `frames.json` carries the same two figures on that set, `"physics": { "stepped":
|
|
8521
|
+
3, "fps": 12 }`. The rate is the set's own: one physics update a frame. The count is
|
|
8522
|
+
read off the posed state, not off the declaration: a constraint counts when, after
|
|
8523
|
+
a step that moved the skeleton's clock, its own clock reads the skeleton's β which
|
|
8524
|
+
the physics update sets and every way of not running leaves behind. So a constraint
|
|
8525
|
+
that is inactive on every frame, or at a mix of 0, is not counted, and the line names
|
|
8526
|
+
it: `physics: 0 constraint(s) stepped at 12 fps β 1 of the 1 declared took none:
|
|
8527
|
+
"iris_settle" (inactive, or a mix of 0, on every frame)`; a set of one pose β an
|
|
8528
|
+
animation of no length, or the setup pose of a skeleton with none β takes no step
|
|
8529
|
+
and says so. A rig that declares no physics constraint prints `physics: none in this
|
|
8530
|
+
rig β it declares no physics constraint, so no frame steps one` once, before the
|
|
8531
|
+
sets, and its `frames.json` carries no `physics` field, as every frame set written
|
|
8532
|
+
before the field existed carries none. This is what tells a reading β a fold count
|
|
8533
|
+
from `--folds`, say (Β§8.3) β taken with the physics from one taken without. Both
|
|
8534
|
+
posers read the same constraints on every frame, and `cli.ts`, `cli.ts --poser
|
|
8535
|
+
spine` and `cli_core.ts` write the same files (`PH01`, `PH02`); the step removed β
|
|
8536
|
+
on the core, or spine-core's `PhysicsConstraint.update` made a no-op β reads 0
|
|
8537
|
+
(`PH03`), and a constraint at mix 0 is named (`PH04`).
|
|
8538
|
+
|
|
8499
8539
|
**The files.** `frames.json` and each `geometry.json` record the schedule as
|
|
8500
8540
|
`environment`, its steps as read, `t`, `wind`, `gravity` in that order. The
|
|
8501
8541
|
framing reads it too: a gust can carry a coat past the box a still rig fills, so
|
package/docs/INGEST.md
CHANGED
|
@@ -191,6 +191,7 @@ rigc render
|
|
|
191
191
|
.. atlas β¦/examples/spineboy/export/spineboy.atlas
|
|
192
192
|
.. poser spine-core β no skeleton.model.json beside β¦/examples/spineboy/export/spineboy-pro.json β a Spine export, not a rigc build
|
|
193
193
|
.. 200x186px at 8 fps, 1 set(s) -> β¦/render/sb
|
|
194
|
+
.. physics: none in this rig β it declares no physics constraint, so no frame steps one
|
|
194
195
|
.. walk 9 frame(s), 1.000s + contact.png -> β¦/render/sb/walk@8fps
|
|
195
196
|
rigc: wrote β¦/render/sb/frames.json
|
|
196
197
|
```
|
package/docs/MOTION.md
CHANGED
|
@@ -1067,6 +1067,7 @@ rigc render --candidate spine --fps 24 --max 200
|
|
|
1067
1067
|
|
|
1068
1068
|
```
|
|
1069
1069
|
.. 134x200px at 24 fps, 1 set(s) -> β¦/semaphore/render
|
|
1070
|
+
.. physics: none in this rig β it declares no physics constraint, so no frame steps one
|
|
1070
1071
|
.. raise 14 frame(s), 0.542s + contact.png -> β¦/semaphore/render/raise@24fps
|
|
1071
1072
|
```
|
|
1072
1073
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.61.0",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -736,6 +736,8 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
736
736
|
const only = readAnimationFlag(flags, [...facts.animations]);
|
|
737
737
|
const skin = readSkinFlag(flags, [...facts.skins]);
|
|
738
738
|
const subset = readSlotSubsetFlags(flags, facts, skin);
|
|
739
|
+
// The physics constraints the skeleton declares, off the Spine file both posers read (issue #1467).
|
|
740
|
+
const physicsDeclared = environmentResponse(skeletonText).declared;
|
|
739
741
|
const passPairs = readPassThroughFlag(passThrough, facts);
|
|
740
742
|
const seamPairs = readSeamFlag(seam, facts);
|
|
741
743
|
// The angle reading (issue #1414) reads bones, which a subset of the drawn slots does not change, so it takes --slot/--hide.
|
|
@@ -810,7 +812,14 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
810
812
|
// The bones ride along for --geometry and --angle-range, the whole attachments for --geometry and the two readings off them.
|
|
811
813
|
const wantsBones = geometry || anglePairs !== undefined;
|
|
812
814
|
const wantsGeometry = geometry || folds || passPairs !== undefined || seamPairs !== undefined;
|
|
813
|
-
|
|
815
|
+
// The physics constraints each frame's step stepped ride along whenever the rig declares one (issue #1467): the
|
|
816
|
+
// `physics:` line and frames.json's `physics` field are read off them.
|
|
817
|
+
const sampling = {
|
|
818
|
+
...pose,
|
|
819
|
+
...(wantsBones ? { bones: true } : {}),
|
|
820
|
+
...(wantsGeometry ? { geometry: true } : {}),
|
|
821
|
+
...(physicsDeclared.length > 0 ? { physics: true } : {}),
|
|
822
|
+
};
|
|
814
823
|
const sampled: Map<string, Frame[]> =
|
|
815
824
|
only === undefined
|
|
816
825
|
? sampleAll(poser, fps, sampling)
|
|
@@ -916,6 +925,8 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
916
925
|
);
|
|
917
926
|
}
|
|
918
927
|
console.log(` .. ${viewport.width}x${viewport.height}px at ${fps} fps, ${sampled.size} set(s) -> ${outRoot}`);
|
|
928
|
+
// Whether physics ran (issue #1467): said once for a rig with nothing to step, and per set below for one with.
|
|
929
|
+
if (physicsDeclared.length === 0) console.log(` .. ${PHYSICS_NONE_LINE}`);
|
|
919
930
|
if (!facts.declaresStage) console.log(` .. ${STAGELESS_FRAMING.render}`);
|
|
920
931
|
// A given box (issue #1414): said with the box it replaced, and every slot it leaves part of out, by set and frame.
|
|
921
932
|
const outside = given === undefined ? [] : outsideViewport(sampled, viewport);
|
|
@@ -959,6 +970,7 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
959
970
|
const seamReading = seamReadings.get(name);
|
|
960
971
|
if (seamReading !== undefined) writeFileSync(join(dir, SEAM_FILE), seamText(seamReading));
|
|
961
972
|
const duration = frames[frames.length - 1].time;
|
|
973
|
+
const physics = physicsDeclared.length === 0 ? undefined : physicsSteppedOf(frames, fps);
|
|
962
974
|
sets.push({
|
|
963
975
|
dir: dirName,
|
|
964
976
|
animation: name === SETUP_POSE_DIR && facts.animations.length === 0 ? null : name,
|
|
@@ -967,6 +979,7 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
967
979
|
written: frames.length,
|
|
968
980
|
stride: 1,
|
|
969
981
|
duration,
|
|
982
|
+
...(physics === undefined ? {} : { physics: { stepped: physics.stepped.length, fps } }),
|
|
970
983
|
});
|
|
971
984
|
const how = frames.length === 1 ? 'a single pose' : `${duration.toFixed(3)}s`;
|
|
972
985
|
const folding = foldReading?.meshes.filter((m) => m.worst !== null).length ?? 0;
|
|
@@ -977,6 +990,7 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
977
990
|
const seamNote = seamReading === undefined ? '' : ` + ${SEAM_FILE} (${opening} of ${seamReading.pairs.length} pair(s) open)`;
|
|
978
991
|
const extras = `${sheet ? ` + ${SHEET_FILE}` : ''}${geometryOut === undefined ? '' : ` + ${GEOMETRY_FILE}`}${foldNote}${passNote}${seamNote}`;
|
|
979
992
|
console.log(` .. ${name.padEnd(16)} ${frames.length} frame(s), ${how}${extras} -> ${dir}`);
|
|
993
|
+
if (physics !== undefined) console.log(` .. ${physicsLine(physics.stepped, physicsDeclared, fps, frames.length)}`);
|
|
980
994
|
for (const mesh of foldReading?.meshes ?? []) {
|
|
981
995
|
const key = `${mesh.slot}\u0000${mesh.attachment}`;
|
|
982
996
|
const fault = faultsSaid.has(key) ? null : setupFaultLine(mesh);
|
|
@@ -1022,6 +1036,28 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
1022
1036
|
console.log(`rigc: wrote ${join(outRoot, FRAMES_SIDECAR)}`);
|
|
1023
1037
|
}
|
|
1024
1038
|
|
|
1039
|
+
/** `render`'s line for a rig that declares no physics constraint (issue #1467): nothing was stepped because nothing could be. */
|
|
1040
|
+
export const PHYSICS_NONE_LINE = 'physics: none in this rig β it declares no physics constraint, so no frame steps one';
|
|
1041
|
+
|
|
1042
|
+
/** The physics constraints any frame of a set stepped (issue #1467), by name in code-unit order. */
|
|
1043
|
+
export function physicsSteppedOf(frames: readonly Frame[], fps: number): { stepped: string[]; fps: number } {
|
|
1044
|
+
const names = new Set<string>();
|
|
1045
|
+
for (const frame of frames) for (const name of frame.physics ?? []) names.add(name);
|
|
1046
|
+
return { stepped: [...names].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)), fps };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* `render`'s `physics:` line for one set (issue #1467): how many constraints were stepped at the set's rate, and β when
|
|
1051
|
+
* fewer than the rig declares β the ones that took no step, by name, with why a constraint takes none.
|
|
1052
|
+
*/
|
|
1053
|
+
export function physicsLine(stepped: readonly string[], declared: readonly string[], fps: number, frames: number): string {
|
|
1054
|
+
const head = `physics: ${stepped.length} constraint(s) stepped at ${fps} fps`;
|
|
1055
|
+
const idle = declared.filter((name) => !stepped.includes(name));
|
|
1056
|
+
if (idle.length === 0) return head;
|
|
1057
|
+
const why = frames === 1 ? 'a single pose takes no step' : 'inactive, or a mix of 0, on every frame';
|
|
1058
|
+
return `${head} β ${idle.length} of the ${declared.length} declared took none: ${idle.map((n) => JSON.stringify(n)).join(', ')} (${why})`;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1025
1061
|
/** Where `inspect` writes when `--out` is not given, and the name it takes inside a directory `--out` names. */
|
|
1026
1062
|
export const DEFAULT_INSPECT_OUT = 'inspect.png';
|
|
1027
1063
|
|
|
@@ -1351,7 +1387,7 @@ export function cmdExplain(flags: Record<string, string>): void {
|
|
|
1351
1387
|
const leverFold = flags['lever-fold'] === undefined ? null : Number(flags['lever-fold']);
|
|
1352
1388
|
const leverWhy = leverFold === null ? null : leverFractionWhy(leverFold);
|
|
1353
1389
|
if (leverWhy !== null) throw new UsageError(`--lever-fold ${JSON.stringify(flags['lever-fold'])}: ${leverWhy}`);
|
|
1354
|
-
const { label, opts } = resolveCut(flags);
|
|
1390
|
+
const { label, opts } = resolveCut(flags, 'explain');
|
|
1355
1391
|
console.log(`rigc explain ${label}`);
|
|
1356
1392
|
// See the identical pair of lines in `cmdBuild` for why both paths are named
|
|
1357
1393
|
// here rather than only the one the header's `label` happens to carry.
|
package/src/cli/shared.ts
CHANGED
|
@@ -94,6 +94,19 @@ export type CutTable = Record<string, CutEntry>;
|
|
|
94
94
|
|
|
95
95
|
export class UsageError extends Error {}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* A usage error that is one required flag missing from a spelled-out cut β
|
|
99
|
+
* printed as its one line and exit 2, without the usage block under it (issue
|
|
100
|
+
* #1466). The catalogue under `--out is required` buried the only line that
|
|
101
|
+
* said what to add, which is `PoseError`'s reason for printing alone. Still a
|
|
102
|
+
* `UsageError`, so every reader that asks whether an invocation was refused
|
|
103
|
+
* reads it as one.
|
|
104
|
+
*/
|
|
105
|
+
export class MissingFlagError extends UsageError {}
|
|
106
|
+
|
|
107
|
+
/** The `<value>` each flag of a spelled-out cut takes, for the line that names a missing one. */
|
|
108
|
+
const CUT_FLAG_VALUES = { rig: '<rig.json>', motion: '<motion.json>', out: '<dir>' } as const;
|
|
109
|
+
|
|
97
110
|
/**
|
|
98
111
|
* `explain` refusing a pair it cannot pose β a usage error in kind, printed
|
|
99
112
|
* without the usage block for `PoseError`'s and `IngestError`'s reason: the
|
|
@@ -264,7 +277,7 @@ function entryToOptions(dir: string, name: string, entry: CutEntry): CompileOpti
|
|
|
264
277
|
* Resolve the cut a command was pointed at, either spelled out on the command
|
|
265
278
|
* line or looked up by name in a cuts.json.
|
|
266
279
|
*/
|
|
267
|
-
export function resolveCut(flags: Record<string, string
|
|
280
|
+
export function resolveCut(flags: Record<string, string>, command: 'build' | 'explain' | 'validate'): { label: string; opts: CompileOptions } {
|
|
268
281
|
const explicit =
|
|
269
282
|
flags.rig !== undefined || flags.manifest !== undefined || flags.motion !== undefined || flags.out !== undefined;
|
|
270
283
|
if (explicit) {
|
|
@@ -272,7 +285,7 @@ export function resolveCut(flags: Record<string, string>): { label: string; opts
|
|
|
272
285
|
throw new UsageError('--rig/--motion/--out and --cut/--cuts are two ways to say the same thing; pick one');
|
|
273
286
|
}
|
|
274
287
|
for (const key of ['rig', 'motion', 'out'] as const) {
|
|
275
|
-
if (flags[key] === undefined) throw new
|
|
288
|
+
if (flags[key] === undefined) throw new MissingFlagError(`${command} needs --${key} ${CUT_FLAG_VALUES[key]} (or --cut <name> --cuts <cuts.json>)`);
|
|
276
289
|
}
|
|
277
290
|
const opts: CompileOptions = {
|
|
278
291
|
rigPath: resolve(flags.rig),
|
|
@@ -2291,7 +2304,7 @@ function pageRectangle(region: AtlasRegion): string {
|
|
|
2291
2304
|
* alone (`./core_commands.ts`). Moved here unchanged from `cmdBuild`.
|
|
2292
2305
|
*/
|
|
2293
2306
|
export function readBuildInvocation(flags: Record<string, string>): { label: string; opts: CompileOptions; profile: AssertionProfile; packing: boolean } {
|
|
2294
|
-
const { label, opts } = resolveCut(flags);
|
|
2307
|
+
const { label, opts } = resolveCut(flags, 'build');
|
|
2295
2308
|
const profile = readProfile(flags);
|
|
2296
2309
|
const packing = flags.pack !== undefined;
|
|
2297
2310
|
// Two combinations are refused rather than silently resolved, because in each
|
|
@@ -3001,6 +3014,10 @@ export function runCli(entry: CliEntry, argv: readonly string[]): void {
|
|
|
3001
3014
|
* (`CliEntry.refusals`). Anything else is not a refusal and is thrown on.
|
|
3002
3015
|
*/
|
|
3003
3016
|
function refuse(err: unknown, command: string | undefined, USAGE: string, entry: CliEntry): never {
|
|
3017
|
+
if (err instanceof MissingFlagError) {
|
|
3018
|
+
console.error(`rigc: ${err.message}`);
|
|
3019
|
+
process.exit(2);
|
|
3020
|
+
}
|
|
3004
3021
|
if (err instanceof UsageError) {
|
|
3005
3022
|
console.error(`rigc: ${err.message}\n\n${USAGE}`);
|
|
3006
3023
|
process.exit(2);
|
|
@@ -76,7 +76,7 @@ export function cmdValidate(flags: Record<string, string>, positional: string[])
|
|
|
76
76
|
// a directory alone cannot supply β and the report says which it had.
|
|
77
77
|
const named = flags.cut !== undefined || flags.rig !== undefined;
|
|
78
78
|
const profile = readProfile(flags);
|
|
79
|
-
const derivedOpts = named ? resolveCut(flags).opts : null;
|
|
79
|
+
const derivedOpts = named ? resolveCut(flags, 'validate').opts : null;
|
|
80
80
|
// Both files there before the first line, from the one statement of a build (issue #1046): a directory without them
|
|
81
81
|
// read the skeleton unguarded and died on an ENOENT and a stack.
|
|
82
82
|
const { skeletonPath, atlasPath } = spinePairOf(resolveBuild(derivedOpts ? derivedOpts.outDir : (positional[0] ?? '.'), flags.atlas));
|
package/src/compile.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* the two emits byte for byte (assertion A18).
|
|
19
19
|
*/
|
|
20
20
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
21
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
22
22
|
import { readPngHeader, readPngInfo } from './png.ts';
|
|
23
23
|
import {
|
|
24
24
|
DEPTH_TONE_IDENTITY,
|
|
@@ -1649,8 +1649,62 @@ function easingCurve(
|
|
|
1649
1649
|
// inputs
|
|
1650
1650
|
// ---------------------------------------------------------------------------
|
|
1651
1651
|
|
|
1652
|
-
|
|
1652
|
+
/**
|
|
1653
|
+
* Which input a JSON path was given as, for the refusal a directory gets: the
|
|
1654
|
+
* flag that named it, the file it wants, and whether a file's contents read as
|
|
1655
|
+
* one (`null` where no parser exists here β the manifest is cast, not parsed).
|
|
1656
|
+
*/
|
|
1657
|
+
interface JsonInputRole {
|
|
1658
|
+
flag: '--rig' | '--motion' | '--manifest';
|
|
1659
|
+
wants: string;
|
|
1660
|
+
reads: ((raw: unknown, where: string) => unknown) | null;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const RIG_INPUT: JsonInputRole = { flag: '--rig', wants: 'the rig spec file', reads: parseRigSpec };
|
|
1664
|
+
const MOTION_INPUT: JsonInputRole = { flag: '--motion', wants: 'the motion spec file', reads: parseMotionSpec };
|
|
1665
|
+
const MANIFEST_INPUT: JsonInputRole = { flag: '--manifest', wants: 'the cut manifest file', reads: null };
|
|
1666
|
+
|
|
1667
|
+
/** The most `.json` names a directory refusal lists before it says how many there are. */
|
|
1668
|
+
const LISTED_JSON_NAMES = 8;
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* The refusal for an input flag that names a directory (issue #1466): what the
|
|
1672
|
+
* flag wants, and what the directory holds β the one file there that reads as
|
|
1673
|
+
* that input, named by its path, or every `.json` there when that is not
|
|
1674
|
+
* exactly one. Nothing is accepted on the strength of it: the caller is told
|
|
1675
|
+
* the path, and the compile still reads only what it was given. The names are
|
|
1676
|
+
* sorted, so the sentence is the same on every machine.
|
|
1677
|
+
*/
|
|
1678
|
+
function directoryInputRefusal(dir: string, role: JsonInputRole): string {
|
|
1679
|
+
const head = `${role.flag} names a directory; it wants ${role.wants} β`;
|
|
1680
|
+
const names = readdirSync(dir)
|
|
1681
|
+
.filter((name) => name.endsWith('.json') && statSync(join(dir, name)).isFile())
|
|
1682
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1683
|
+
if (names.length === 0) return `${head} ${dir} holds no .json file`;
|
|
1684
|
+
const reading = names.filter((name) => {
|
|
1685
|
+
if (role.reads === null) return true;
|
|
1686
|
+
const file = join(dir, name);
|
|
1687
|
+
try {
|
|
1688
|
+
role.reads(parseJsonWithPosition(readFileSync(file, 'utf8')), file);
|
|
1689
|
+
return true;
|
|
1690
|
+
} catch {
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
if (reading.length === 1) return `${head} ${join(dir, reading[0])} exists there`;
|
|
1695
|
+
const listed = (list: string[]): string =>
|
|
1696
|
+
list.slice(0, LISTED_JSON_NAMES).join(', ') + (list.length > LISTED_JSON_NAMES ? `, β¦ ${list.length} in all` : '');
|
|
1697
|
+
if (reading.length === 0) {
|
|
1698
|
+
return `${head} ${dir} holds ${names.length} .json file(s) and none reads as one: ${listed(names)}`;
|
|
1699
|
+
}
|
|
1700
|
+
return `${head} ${reading.length} .json files in ${dir} read as one, so name it: ${listed(reading)}`;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function readJson<T>(path: string, role: JsonInputRole): T {
|
|
1653
1704
|
let text: string;
|
|
1705
|
+
if (existsSync(path) && statSync(path).isDirectory()) {
|
|
1706
|
+
throw new CompileError(directoryInputRefusal(path, role), 'CE_COMPILE_JSON_READ_FAILED');
|
|
1707
|
+
}
|
|
1654
1708
|
try {
|
|
1655
1709
|
text = readFileSync(path, 'utf8');
|
|
1656
1710
|
} catch (err) {
|
|
@@ -2309,11 +2363,11 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2309
2363
|
const manifestPath = opts.manifestPath === undefined ? null : resolve(opts.manifestPath);
|
|
2310
2364
|
const manifestDir = manifestPath === null ? null : dirname(manifestPath);
|
|
2311
2365
|
|
|
2312
|
-
const rig = parseRigSpec(readJson<unknown>(rigPath), rigPath);
|
|
2366
|
+
const rig = parseRigSpec(readJson<unknown>(rigPath, RIG_INPUT), rigPath);
|
|
2313
2367
|
// Parsed, not cast, since issue #307 β `src/motion.ts` states what it proves
|
|
2314
2368
|
// and which refusals it deliberately leaves in this file.
|
|
2315
|
-
const motion = parseMotionSpec(readJson<unknown>(motionPath), motionPath);
|
|
2316
|
-
const manifest = manifestPath === null ? null : readJson<FaceManifest>(manifestPath);
|
|
2369
|
+
const motion = parseMotionSpec(readJson<unknown>(motionPath, MOTION_INPUT), motionPath);
|
|
2370
|
+
const manifest = manifestPath === null ? null : readJson<FaceManifest>(manifestPath, MANIFEST_INPUT);
|
|
2317
2371
|
// The manifest is the record of what was measured, and its numbers become
|
|
2318
2372
|
// bone positions and the stage: a `crop.h` of 1e309 built as `"height": null`
|
|
2319
2373
|
// before issue #881. The rig spec's walk is `parseRigSpec`'s; this is the
|
package/src/render.ts
CHANGED
|
@@ -549,9 +549,25 @@ function spinePosed(skeleton: Skeleton): Posed {
|
|
|
549
549
|
pieces: (draw) => drawPieces(skeleton, draw),
|
|
550
550
|
bones: () => boneSnapshots(skeleton),
|
|
551
551
|
attachments: () => attachmentsOf(skeleton),
|
|
552
|
+
physics: () => steppedPhysicsOf(skeleton),
|
|
552
553
|
};
|
|
553
554
|
}
|
|
554
555
|
|
|
556
|
+
/**
|
|
557
|
+
* `Posed.physics` on spine-core (issue #1467): every physics constraint whose
|
|
558
|
+
* `lastTime` reads the skeleton's `time` after a step that moved it β which
|
|
559
|
+
* `PhysicsConstraint.update` sets under `Physics.update` and `Physics.reset`,
|
|
560
|
+
* and which an inactive constraint, a mix of 0 or `Physics.none` leaves where
|
|
561
|
+
* it was. The setup pose and frame 0 sit at time 0 and step none.
|
|
562
|
+
*/
|
|
563
|
+
function steppedPhysicsOf(skeleton: Skeleton): string[] {
|
|
564
|
+
if (!(skeleton.time > 0)) return [];
|
|
565
|
+
return skeleton.physics
|
|
566
|
+
.filter((c) => c.lastTime === skeleton.time)
|
|
567
|
+
.map((c) => c.data.name)
|
|
568
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
569
|
+
}
|
|
570
|
+
|
|
555
571
|
/**
|
|
556
572
|
* A fresh skeleton under `skin`, stepped into its setup pose β the one recipe
|
|
557
573
|
* `sampleSetupPose` and the geometry export's `rest` table both pose from, so
|
package/src/render_core.ts
CHANGED
|
@@ -102,6 +102,7 @@ import { clipThrough, type ClipShape, type ShapeClipper } from './core/clipping.
|
|
|
102
102
|
import { activeBones, CoreInputError, readModel, sourceOfDoc, underNoSkin, underSkin, type CompiledDocument, type CoreSlotRow } from './core/index.ts';
|
|
103
103
|
import { PHYSICS_CLOCK_LEAD, physicsClockStep, poseRawAnimationEach, poseRawSetup, type RawDrawn, type RawPose, type StepEnvironment } from './core/raw.ts';
|
|
104
104
|
import type { TimelinePlant } from './core/animation.ts';
|
|
105
|
+
import { stepPhysics, type PhysicsStepper } from './core/constraints_physics.ts';
|
|
105
106
|
import { CORE_DEFAULT_SKIN, lookupSkins } from './core/skins.ts';
|
|
106
107
|
import { documentPageLookup, drawnRegions, meshPageUvs, readUvSequences, regionPageUvs, type DrawnRegion, type UvRegion, type UvSource } from './core/uvs.ts';
|
|
107
108
|
import { regionCorners, worldVertices } from './core/vertices.ts';
|
|
@@ -378,10 +379,14 @@ export const CORE_RAW_WALK: CoreRawWalk = (doc, animation, steps, plant, visit,
|
|
|
378
379
|
*/
|
|
379
380
|
export { PHYSICS_CLOCK_LEAD, physicsClockStep };
|
|
380
381
|
|
|
381
|
-
/**
|
|
382
|
+
/**
|
|
383
|
+
* What a suite passes `corePoser` in place of the poser's own parts β `PieceArrays` (`RC43`), the raw walk (`RC44`), and
|
|
384
|
+
* the physics step the walk runs (issue #1467: a step that does nothing has to read 0 on the render's `physics:` line).
|
|
385
|
+
*/
|
|
382
386
|
export interface CorePoserPlants {
|
|
383
387
|
arrays?: () => PieceArrays;
|
|
384
388
|
walk?: CoreRawWalk;
|
|
389
|
+
physicsStep?: PhysicsStepper;
|
|
385
390
|
}
|
|
386
391
|
|
|
387
392
|
/**
|
|
@@ -457,7 +462,7 @@ function clipCover(pose: RawPose, active: ReadonlySet<string>): Map<string, Clip
|
|
|
457
462
|
}
|
|
458
463
|
|
|
459
464
|
/** One posed moment of the core, read through the seam's `Posed`. */
|
|
460
|
-
function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper): Posed {
|
|
465
|
+
function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper, stepped: readonly string[]): Posed {
|
|
461
466
|
const slotRows = new Map<string, CoreSlotRow>(pose.slots.map((row) => [row[0], row]));
|
|
462
467
|
let regions: Map<string, DrawnRegion> | null = null;
|
|
463
468
|
const regionsOf = (): Map<string, DrawnRegion> => {
|
|
@@ -501,6 +506,7 @@ function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper): Pose
|
|
|
501
506
|
},
|
|
502
507
|
attachments: (): AttachmentPose[] =>
|
|
503
508
|
pose.drawn.map((d) => ({ slot: d.slot, attachment: d.attachment, vertices: [...d.vertices], color: tintOf(d) })),
|
|
509
|
+
physics: (): string[] => [...stepped],
|
|
504
510
|
};
|
|
505
511
|
}
|
|
506
512
|
|
|
@@ -752,7 +758,7 @@ export function corePoser(
|
|
|
752
758
|
subset: (opts, skin) => subsetOver(roster, opts, skin),
|
|
753
759
|
setup: (skin) => {
|
|
754
760
|
const input = inputOf(skin);
|
|
755
|
-
return corePosed(input, poseRawSetup(input.doc, { through }), through);
|
|
761
|
+
return corePosed(input, poseRawSetup(input.doc, { through }), through, []);
|
|
756
762
|
},
|
|
757
763
|
animation: (name, skin, fps, count, visit, environment) => {
|
|
758
764
|
const input = inputOf(skin);
|
|
@@ -762,10 +768,22 @@ export function corePoser(
|
|
|
762
768
|
// walk that refuses a later pose still refuses the call, and a draw that throws is re-thrown once the walk has
|
|
763
769
|
// finished without refusing β the first such throw, by frame β and no frame after it is drawn.
|
|
764
770
|
const drawFailed: unknown[] = [];
|
|
765
|
-
|
|
771
|
+
// `Posed.physics` on the core (issue #1467): the constraints whose state's clock reads the skeleton's after a step
|
|
772
|
+
// that moved it β spine-core's reading (`steppedPhysicsOf` in `src/render.ts`), taken off the same state the step
|
|
773
|
+
// writes, as each constraint is stepped, and handed to the pose the step made.
|
|
774
|
+
const steppedNow = new Set<string>();
|
|
775
|
+
const inner = plants.physicsStep ?? stepPhysics;
|
|
776
|
+
const physicsStep: PhysicsStepper = (r, w, length, ctx) => {
|
|
777
|
+
const moved = inner(r, w, length, ctx);
|
|
778
|
+
if (ctx.time > 0 && ctx.states.get(r.name)?.lastTime === ctx.time) steppedNow.add(r.name);
|
|
779
|
+
return moved;
|
|
780
|
+
};
|
|
781
|
+
walk(input.doc, name, new Array<number>(count).fill(step), { through, physicsStep }, (pose, i) => {
|
|
782
|
+
const stepped = [...steppedNow].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
783
|
+
steppedNow.clear();
|
|
766
784
|
if (drawFailed.length > 0) return;
|
|
767
785
|
try {
|
|
768
|
-
visit(i, corePosed(input, pose, through));
|
|
786
|
+
visit(i, corePosed(input, pose, through, stepped));
|
|
769
787
|
} catch (thrown) {
|
|
770
788
|
drawFailed.push(thrown);
|
|
771
789
|
}
|
package/src/render_shared.ts
CHANGED
|
@@ -190,6 +190,20 @@ export interface FrameSet {
|
|
|
190
190
|
* file could only ever be the stale one.
|
|
191
191
|
*/
|
|
192
192
|
duration: number;
|
|
193
|
+
/**
|
|
194
|
+
* Whether the physics ran while these frames were posed (issue #1467): how
|
|
195
|
+
* many physics constraints the poser stepped on at least one frame of this
|
|
196
|
+
* set, and the rate it stepped them at β the set's own `fps`, one skeleton
|
|
197
|
+
* update a frame. `render` prints the same two figures on its `physics:` line.
|
|
198
|
+
*
|
|
199
|
+
* β Absent on a rig that declares no physics constraint, for `skin`'s
|
|
200
|
+
* reason in `FramesSidecar`: there is nothing for the field to count, and
|
|
201
|
+
* every frame set written before it existed says the same. Present, a 0 is a
|
|
202
|
+
* claim β the rig declares physics and no constraint took a step on any
|
|
203
|
+
* frame β and that is what tells a reading taken with physics from one
|
|
204
|
+
* taken without.
|
|
205
|
+
*/
|
|
206
|
+
physics?: { stepped: number; fps: number };
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
export interface FramesSidecar {
|
|
@@ -445,6 +459,8 @@ export interface PoseOptions {
|
|
|
445
459
|
* so `framingViewport` keeps it and the box holds what the frames draw.
|
|
446
460
|
*/
|
|
447
461
|
environment?: readonly EnvironmentStep[];
|
|
462
|
+
/** Also record the physics constraints the step that posed each frame stepped β see `Frame.physics` (issue #1467). */
|
|
463
|
+
physics?: boolean;
|
|
448
464
|
}
|
|
449
465
|
|
|
450
466
|
/**
|
|
@@ -586,6 +602,12 @@ export interface Frame {
|
|
|
586
602
|
* second derivation of it.
|
|
587
603
|
*/
|
|
588
604
|
attachments?: AttachmentPose[];
|
|
605
|
+
/**
|
|
606
|
+
* The physics constraints the step that posed this frame stepped, by name in
|
|
607
|
+
* code-unit order β present only when `PoseOptions.physics` asked for it
|
|
608
|
+
* (issue #1467). See `Posed.physics` for what counts as a step.
|
|
609
|
+
*/
|
|
610
|
+
physics?: string[];
|
|
589
611
|
}
|
|
590
612
|
|
|
591
613
|
/**
|
|
@@ -1055,6 +1077,16 @@ export interface Posed {
|
|
|
1055
1077
|
bones(): BoneSnapshot[];
|
|
1056
1078
|
/** Every slot's region or mesh attachment, whole, in the posed draw order. */
|
|
1057
1079
|
attachments(): AttachmentPose[];
|
|
1080
|
+
/**
|
|
1081
|
+
* The physics constraints the step that posed this moment stepped, by name in
|
|
1082
|
+
* code-unit order (issue #1467). A constraint is stepped when, after a step
|
|
1083
|
+
* that moved the skeleton's clock, its own clock reads the skeleton's: the
|
|
1084
|
+
* physics update sets it there, and every way of not running β inactive, a
|
|
1085
|
+
* mix of 0, the step skipped β leaves it behind. Read off the posed state on
|
|
1086
|
+
* both posers, never off which call was made, so a step removed reads 0. The
|
|
1087
|
+
* setup pose and a reset pose, where the clock has not moved, step none.
|
|
1088
|
+
*/
|
|
1089
|
+
physics(): string[];
|
|
1058
1090
|
}
|
|
1059
1091
|
|
|
1060
1092
|
/**
|
|
@@ -1126,6 +1158,7 @@ function frameOf(index: number, time: number, posed: Posed, draw: DrawOptions, o
|
|
|
1126
1158
|
pieces: posed.pieces(draw),
|
|
1127
1159
|
...(opts?.bones ? { bones: posed.bones() } : {}),
|
|
1128
1160
|
...(opts?.geometry ? { attachments: posed.attachments() } : {}),
|
|
1161
|
+
...(opts?.physics ? { physics: posed.physics() } : {}),
|
|
1129
1162
|
};
|
|
1130
1163
|
}
|
|
1131
1164
|
|