rig-c 0.0.0-stage → 2.20.4
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/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +30 -0
- package/NOTICE.md +145 -0
- package/README.md +817 -3
- package/bin/rigc.cjs +83 -0
- package/cli.ts +61 -0
- package/cli_core.ts +46 -0
- package/docs/AUTHORING.md +9923 -0
- package/docs/FACE.md +1948 -0
- package/docs/INGEST.md +1488 -0
- package/docs/MOTION.md +1241 -0
- package/docs/PROMPTING.md +109 -0
- package/docs/RIGGING.md +1441 -0
- package/docs/SPEC_COVERAGE.md +357 -0
- package/package.json +108 -4
- package/skills/rigc/SKILL.md +133 -0
- package/skills/rigc-face/SKILL.md +60 -0
- package/skills/rigc-ingest/SKILL.md +78 -0
- package/skills/rigc-motion/SKILL.md +51 -0
- package/skills/rigc-rigging/SKILL.md +49 -0
- package/src/areaband.ts +159 -0
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a03.ts +27 -0
- package/src/assertions/bodies/a04.ts +40 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +245 -0
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a08.ts +76 -0
- package/src/assertions/bodies/a09.ts +82 -0
- package/src/assertions/bodies/a10.ts +116 -0
- package/src/assertions/bodies/a11.ts +15 -0
- package/src/assertions/bodies/a12.ts +30 -0
- package/src/assertions/bodies/a13.ts +51 -0
- package/src/assertions/bodies/a14.ts +35 -0
- package/src/assertions/bodies/a15.ts +97 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a17.ts +26 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +404 -0
- package/src/assertions/bodies/a20.ts +122 -0
- package/src/assertions/bodies/a21.ts +190 -0
- package/src/assertions/bodies/a22.ts +39 -0
- package/src/assertions/bodies/a23.ts +305 -0
- package/src/assertions/bodies/a24.ts +68 -0
- package/src/assertions/bodies/a25.ts +39 -0
- package/src/assertions/bodies/a26.ts +61 -0
- package/src/assertions/bodies/a27.ts +33 -0
- package/src/assertions/bodies/a28.ts +70 -0
- package/src/assertions/bodies/a29.ts +34 -0
- package/src/assertions/bodies/a30.ts +50 -0
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a32.ts +44 -0
- package/src/assertions/bodies/a33.ts +110 -0
- package/src/assertions/bodies/a34.ts +133 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/bodies/a36.ts +81 -0
- package/src/assertions/bodies/a37.ts +77 -0
- package/src/assertions/bodies/a38.ts +73 -0
- package/src/assertions/bodies/a39.ts +303 -0
- package/src/assertions/bodies/a40.ts +128 -0
- package/src/assertions/bodies/a42.ts +97 -0
- package/src/assertions/bodies/a43.ts +181 -0
- package/src/assertions/bodies/a44.ts +23 -0
- package/src/assertions/bodies/a45.ts +172 -0
- package/src/assertions/bodies/a46.ts +224 -0
- package/src/assertions/bodies/a47.ts +126 -0
- package/src/assertions/bodies/a48.ts +83 -0
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/constraint_words.ts +169 -0
- package/src/assertions/emitted/index.ts +148 -0
- package/src/assertions/facts/animated_bones.ts +30 -0
- package/src/assertions/facts/animation_durations.ts +37 -0
- package/src/assertions/facts/atlas_pages.ts +19 -0
- package/src/assertions/facts/atlas_regions.ts +52 -0
- package/src/assertions/facts/bone_timelines.ts +37 -0
- package/src/assertions/facts/constraint_targets.ts +56 -0
- package/src/assertions/facts/constraints.ts +155 -0
- package/src/assertions/facts/deform_survey.ts +27 -0
- package/src/assertions/facts/event_keys.ts +55 -0
- package/src/assertions/facts/linked_meshes.ts +38 -0
- package/src/assertions/facts/mesh_attachments.ts +100 -0
- package/src/assertions/facts/region_joins.ts +34 -0
- package/src/assertions/facts/sequences.ts +85 -0
- package/src/assertions/facts/skeleton_roster.ts +45 -0
- package/src/assertions/facts/skin_entries.ts +37 -0
- package/src/assertions/facts/skin_members.ts +53 -0
- package/src/assertions/facts/slider_composition.ts +78 -0
- package/src/assertions/facts/slot_colour.ts +43 -0
- package/src/assertions/facts/stage.ts +27 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/facts/stepped_poses.ts +74 -0
- package/src/assertions/facts/two_colour.ts +52 -0
- package/src/assertions/facts/vertex_polygons.ts +53 -0
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/harness.ts +109 -0
- package/src/assertions/inward_advance.ts +58 -0
- package/src/assertions/kinds.ts +105 -0
- package/src/assertions/mesh_kinds.ts +56 -0
- package/src/assertions/model/animated_bones.ts +38 -0
- package/src/assertions/model/animation_durations.ts +57 -0
- package/src/assertions/model/atlas_pages.ts +15 -0
- package/src/assertions/model/atlas_regions.ts +76 -0
- package/src/assertions/model/bone_timelines.ts +58 -0
- package/src/assertions/model/constraint_targets.ts +82 -0
- package/src/assertions/model/constraints.ts +233 -0
- package/src/assertions/model/declared.ts +125 -0
- package/src/assertions/model/deform_survey.ts +24 -0
- package/src/assertions/model/event_keys.ts +45 -0
- package/src/assertions/model/given.ts +45 -0
- package/src/assertions/model/index.ts +398 -0
- package/src/assertions/model/linked_meshes.ts +24 -0
- package/src/assertions/model/mesh_attachments.ts +119 -0
- package/src/assertions/model/parse.ts +146 -0
- package/src/assertions/model/region_joins.ts +67 -0
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/sequences.ts +157 -0
- package/src/assertions/model/skeleton_roster.ts +23 -0
- package/src/assertions/model/skin_entries.ts +69 -0
- package/src/assertions/model/skin_members.ts +64 -0
- package/src/assertions/model/slider_composition.ts +193 -0
- package/src/assertions/model/slot_colour.ts +81 -0
- package/src/assertions/model/stage.ts +28 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/assertions/model/stepped_poses.ts +105 -0
- package/src/assertions/model/two_colour.ts +61 -0
- package/src/assertions/model/vertex_polygons.ts +72 -0
- package/src/assertions/reasons.ts +129 -0
- package/src/assertions/region_lookups.ts +61 -0
- package/src/assertions/report.ts +189 -0
- package/src/assertions/values.ts +39 -0
- package/src/atlas.ts +2870 -0
- package/src/ballot.ts +866 -0
- package/src/bonedist.ts +643 -0
- package/src/chainfit.ts +2752 -0
- package/src/chains.ts +170 -0
- package/src/check.ts +4303 -0
- package/src/checkpics.ts +295 -0
- package/src/cli/core_commands.ts +1627 -0
- package/src/cli/repack.ts +414 -0
- package/src/cli/shared.ts +2776 -0
- package/src/cli/spine_commands.ts +820 -0
- package/src/compile.ts +9414 -0
- package/src/core/additive.ts +458 -0
- package/src/core/animation.ts +1050 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1876 -0
- package/src/core/constraints_path.ts +964 -0
- package/src/core/constraints_physics.ts +881 -0
- package/src/core/constraints_slider.ts +635 -0
- package/src/core/deform.ts +613 -0
- package/src/core/draw_order.ts +125 -0
- package/src/core/events.ts +135 -0
- package/src/core/hooks.ts +249 -0
- package/src/core/index.ts +1400 -0
- package/src/core/raw.ts +739 -0
- package/src/core/skins.ts +129 -0
- package/src/core/uvs.ts +469 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/walk.ts +197 -0
- package/src/core/world.ts +289 -0
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformgen.ts +630 -0
- package/src/deformmeasure.ts +732 -0
- package/src/deformreport.ts +373 -0
- package/src/deformstructure.ts +386 -0
- package/src/deformsurvey.ts +2162 -0
- package/src/depth.ts +784 -0
- package/src/diff.ts +2252 -0
- package/src/emit.ts +134 -0
- package/src/emit_spine.ts +854 -0
- package/src/errors.ts +53 -0
- package/src/framing.ts +819 -0
- package/src/generation.ts +139 -0
- package/src/ingest.ts +2293 -0
- package/src/json-position.ts +253 -0
- package/src/keyorder.ts +587 -0
- package/src/keys.ts +486 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +2382 -0
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +2042 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1425 -0
- package/src/model.ts +1245 -0
- package/src/motion.ts +809 -0
- package/src/nonfinite.ts +54 -0
- package/src/package_meta.ts +48 -0
- package/src/png.ts +297 -0
- package/src/pose.ts +2324 -0
- package/src/preview.ts +434 -0
- package/src/region_joins.ts +54 -0
- package/src/render.ts +1013 -0
- package/src/render_core.ts +871 -0
- package/src/render_shared.ts +2958 -0
- package/src/repack.ts +495 -0
- package/src/rig.ts +2941 -0
- package/src/slots.ts +892 -0
- package/src/spine_side.ts +138 -0
- package/src/timelines.ts +837 -0
- package/src/trackgen.ts +364 -0
- package/src/transform.ts +310 -0
- package/src/types.ts +1797 -0
- package/src/validate.ts +3875 -0
- package/tools/contact.ts +126 -0
- package/tools/editor_roundtrip.ts +1641 -0
- package/tools/font5x7.ts +101 -0
- package/tools/measure_contact_depth.ts +105 -0
- package/tools/plate.ts +508 -0
- package/tools/png_probe.mjs +72 -0
package/src/core/walk.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core's looping walk (issue #1025, cut 4c-5a of step 4c of #380): one
|
|
3
|
+
* animation on a LOOPING track, reset at the setup pose and then stepped at
|
|
4
|
+
* the caller's steps, every pose returned as the doubles the core computed —
|
|
5
|
+
* a value that is not finite included, in place, rather than refused.
|
|
6
|
+
*
|
|
7
|
+
* This is the walk `A10_NO_NAN_AFTER_STEPPING` takes, and the reason it is
|
|
8
|
+
* its own entry rather than the raw entry (`./raw.ts`) under another name is
|
|
9
|
+
* the two things the raw entry's contract excludes on purpose: its track
|
|
10
|
+
* holds at the duration, and it refuses a non-finite value by name
|
|
11
|
+
* (`CoreInputError`), because a renderer handed one would draw a different
|
|
12
|
+
* picture in silence. A10's subject is exactly that value, so here it is the
|
|
13
|
+
* answer, not a refusal. Everything else is the raw entry's walk, called
|
|
14
|
+
* rather than restated (`walkIn` in `./raw.ts`, which branches on the two).
|
|
15
|
+
*
|
|
16
|
+
* ## The walk, measured
|
|
17
|
+
*
|
|
18
|
+
* Written from spine-core 4.3.13 running A10's own recipe — `Skeleton`,
|
|
19
|
+
* `AnimationState` with `setAnimation(0, name, true)`; `setupPose()`,
|
|
20
|
+
* `update(0)`, `updateWorldTransform(Physics.reset)`; then per step
|
|
21
|
+
* `AnimationState.update(dt)`, `apply`, `Skeleton.update(dt)`,
|
|
22
|
+
* `updateWorldTransform(Physics.update)` — and read off `TrackEntry.trackTime`,
|
|
23
|
+
* `TrackEntry.getAnimationTime()`, every bone's `appliedPose`, every shown
|
|
24
|
+
* region's and mesh's world vertices, every slot's colours and the calls the
|
|
25
|
+
* runtime makes to `PhysicsConstraint.reset`. The PR of issue #1025's cut
|
|
26
|
+
* 4c-5a carries the tables; each rule names the count that held it.
|
|
27
|
+
*
|
|
28
|
+
* - **Pose 0 is the setup pose, reset** — the animation is set and not yet
|
|
29
|
+
* applied, so the reset is taken at the setup pose (`RawReset` `setup`).
|
|
30
|
+
* - **The track time is the running sum of the steps**, as on the raw
|
|
31
|
+
* entry's walk: `TrackEntry.trackTime` equalled it on every step of the
|
|
32
|
+
* nineteen tree rows' walks (6,480 of 6,480).
|
|
33
|
+
* - **The time** — the animation is applied at the track time wrapped by the
|
|
34
|
+
* duration (`loopedTime` in `./raw.ts`): `trackTime % duration` in double,
|
|
35
|
+
* so a track time of exactly the duration is 0, the first pose again, and
|
|
36
|
+
* over a duration of 0 the time is 0 whatever the track time (an animation
|
|
37
|
+
* keying nothing, or keying only at 0) — `getAnimationTime()` read so on
|
|
38
|
+
* 6,480 of 6,480 steps, 1,087 of them at or past the duration and 4 exactly
|
|
39
|
+
* on it; before the zero-duration reading it read 6,000.
|
|
40
|
+
* - **The physics clock moves by the step itself** (`Skeleton.update(dt)`),
|
|
41
|
+
* whatever the wrap does to the animation time: moved by the animation
|
|
42
|
+
* time's own change instead, 32 of 82 seeded and forged walks read off.
|
|
43
|
+
* - **A `reset` key across the wrap.** A key is crossed when it lies in
|
|
44
|
+
* `(previous animation time, animation time]`, as on the raw entry; on a
|
|
45
|
+
* step whose animation time went DOWN — the wrap — it is crossed when it
|
|
46
|
+
* lies after the previous time or at or before the new one, `(last, ∞)`
|
|
47
|
+
* and `(−1, t]` (`resetCrossed` in `./constraints_physics.ts`). A step
|
|
48
|
+
* that passes the duration once or more while its animation time still
|
|
49
|
+
* rises is not a wrap there: a key at 0.25 s of a 0.3 s animation stepped
|
|
50
|
+
* by 0.7 s fired on neither of the two steps that carried the track past
|
|
51
|
+
* it, and an equal time is not a wrap either (a 0.25 s animation stepped by
|
|
52
|
+
* 0.25 s fired its key at 0 once). With the wrap firing nothing, 957 of
|
|
53
|
+
* 14,460 poses of a seeded population read off.
|
|
54
|
+
* - **A value that is not finite** is the IEEE result of the same
|
|
55
|
+
* arithmetic, kept: a world matrix term, a world position, a vertex, a slot
|
|
56
|
+
* colour channel. Where the raw entry writes such a value `null` and
|
|
57
|
+
* refuses, this entry writes the double; on 4,333 poses holding one, every
|
|
58
|
+
* NaN and Infinity stood where spine-core's did.
|
|
59
|
+
*
|
|
60
|
+
* Events are not carried: A10 reads none, and what a looping track fires
|
|
61
|
+
* across the wrap was not measured. Nor are the clipped rows, and the walk
|
|
62
|
+
* does not cut them (issue #1179): every clip it starts is still planned, so
|
|
63
|
+
* a clip the core does not draw refuses the walk by name, as below.
|
|
64
|
+
*
|
|
65
|
+
* ⛔ **What is still refused** is the raw entry's other refusal: a construct
|
|
66
|
+
* the core would leave out (a block the oracle's core dump names absent) is a
|
|
67
|
+
* `CoreInputError` naming why, as there — a pose with a block missing is not
|
|
68
|
+
* a pose whose values can be judged finite.
|
|
69
|
+
*
|
|
70
|
+
* 🔁 **A slider whose animation keys a physics timeline** was refused here
|
|
71
|
+
* by name until issue #1049 (`sliderPhysicsWhy`): six of the path-slider
|
|
72
|
+
* suite's distinct builds walked off spine-core from the third step, and the
|
|
73
|
+
* raw entry read the same gap. The core had not applied a slider's physics keys at all;
|
|
74
|
+
* it now does, under the step, by the rule `./constraints_slider.ts` *Its
|
|
75
|
+
* physics timelines* states and the core suite's `CO31` holds — so this walk
|
|
76
|
+
* poses the class as the raw entry does, and refuses nothing the raw entry
|
|
77
|
+
* does not.
|
|
78
|
+
*/
|
|
79
|
+
import { scanSetupIn, scanWalkIn, setupPoseIn, walkIn, type RawBone, type RawDrawn, type ScanPlant, type ScanPose, type WalkMode } from './raw.ts';
|
|
80
|
+
import { activeBones, type CompiledDocument, type CorePlant, type CoreSlotRow } from './index.ts';
|
|
81
|
+
import { historyTaint } from './constraints.ts';
|
|
82
|
+
import type { TimelinePlant } from './animation.ts';
|
|
83
|
+
|
|
84
|
+
/** One pose of the looping walk: the raw entry's pose less what this walk does not carry (events, the clip rows, the shown records). */
|
|
85
|
+
export interface WalkPose {
|
|
86
|
+
/** The running sum of the steps, 0 for pose 0. */
|
|
87
|
+
trackTime: number;
|
|
88
|
+
/** The time the animation was applied at: the track time wrapped by the duration (`loopedTime`). */
|
|
89
|
+
animationTime: number;
|
|
90
|
+
/** Every bone in document order, its world matrix and origin as computed, finite or not. */
|
|
91
|
+
bones: RawBone[];
|
|
92
|
+
/** Every slot row (`CoreSlotRow`) with its colour channels as computed, finite or not. */
|
|
93
|
+
slots: CoreSlotRow[];
|
|
94
|
+
/** The slot names in the posed draw order. */
|
|
95
|
+
drawOrder: string[];
|
|
96
|
+
/** Every region and mesh a slot shows, in draw order, its world vertices as computed, finite or not. */
|
|
97
|
+
drawn: RawDrawn[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The bones and slots whose numbers the walk does not vouch for, because the
|
|
102
|
+
* runtime's own value there is HISTORY (issue #979): a bone the view leaves
|
|
103
|
+
* unposed — inactive, or below an inactive bone — that a constraint writing
|
|
104
|
+
* into an inactive bone reaches (`historyTaint` in `./constraints.ts`, the
|
|
105
|
+
* walk the core's leak refusal reads), each with that writer; and every slot
|
|
106
|
+
* on such a bone. spine-core never updates an inactive bone, so what a
|
|
107
|
+
* constraint wrote into it on the step before is what the constraint reads
|
|
108
|
+
* on the next one; the core poses every step from the setup pose. Measured on
|
|
109
|
+
* a public probe (a transform constraint writing four skin-required bones no
|
|
110
|
+
* skin activates): at mix 1 the two differ only in the sign of zero of the
|
|
111
|
+
* matrix cells (`atan2` of a zero matrix is 0° or 180° by those signs), and at
|
|
112
|
+
* a translate mix of 0.5 by value (worldX 30.000000348 in spine-core against
|
|
113
|
+
* 20.000000232 in the core at step 1), on the bones and on an active child of
|
|
114
|
+
* one. Where such a value reaches a posed bone the core already refuses the
|
|
115
|
+
* document (`unposedLeakWhy`); here nothing posed reads it, so a walk
|
|
116
|
+
* comparison counts these numbers apart by name and compares them with
|
|
117
|
+
* nothing, as `tools/pose_oracle.ts unposed` does under the step.
|
|
118
|
+
*/
|
|
119
|
+
export function walkHistory(view: CompiledDocument): { bones: Map<string, string>; slots: Map<string, string> } {
|
|
120
|
+
const active = activeBones(view);
|
|
121
|
+
const unposed = new Set<string>();
|
|
122
|
+
for (const b of view.bones) if (!active.has(b.name) || (b.parent !== undefined && unposed.has(b.parent))) unposed.add(b.name);
|
|
123
|
+
const bones = new Map<string, string>();
|
|
124
|
+
for (const [bone, w] of historyTaint(view).tainted) if (unposed.has(bone)) bones.set(bone, `${w.kind}/${w.name} on inactive ${w.inactive}`);
|
|
125
|
+
return { bones, slots: new Map(view.slots.filter((s) => bones.has(s.bone)).map((s) => [s.name, s.bone])) };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The looping walk's mode: the track loops, and a non-finite value stays in the pose. */
|
|
129
|
+
const LOOPING: WalkMode = { loop: true, keep: true };
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The looping walk's plants (the core suite's `CO26`), each the reading the
|
|
133
|
+
* walk was measured against and rejected, in a copy: the time a step applies
|
|
134
|
+
* at, what the physics clock moves by, and what a number is written as.
|
|
135
|
+
* Nothing but a control passes one.
|
|
136
|
+
*/
|
|
137
|
+
export interface WalkPlant {
|
|
138
|
+
time?: WalkMode['time'];
|
|
139
|
+
clock?: WalkMode['clock'];
|
|
140
|
+
wrapResets?: WalkMode['wrapResets'];
|
|
141
|
+
/** Every number of a pose rewritten — a swallowed non-finite value is `(v) => (Number.isFinite(v) ? v : 0)`. */
|
|
142
|
+
number?: (v: number) => number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** A raw pose narrowed to what the looping walk carries, every number through `number`. */
|
|
146
|
+
function narrow(p: { trackTime: number; animationTime: number; bones: RawBone[]; slots: CoreSlotRow[]; drawOrder: string[]; drawn: RawDrawn[] }, number?: (v: number) => number): WalkPose {
|
|
147
|
+
if (number === undefined) return { trackTime: p.trackTime, animationTime: p.animationTime, bones: p.bones, slots: p.slots, drawOrder: p.drawOrder, drawn: p.drawn };
|
|
148
|
+
const n = (v: number | null): number | null => (v === null ? null : number(v));
|
|
149
|
+
return {
|
|
150
|
+
trackTime: p.trackTime,
|
|
151
|
+
animationTime: p.animationTime,
|
|
152
|
+
bones: p.bones.map((b) => ({ ...b, a: number(b.a), b: number(b.b), c: number(b.c), d: number(b.d), worldX: number(b.worldX), worldY: number(b.worldY) })),
|
|
153
|
+
slots: p.slots.map((r): CoreSlotRow => [r[0], r[1], n(r[2]), n(r[3]), n(r[4]), n(r[5]), r[6] === null ? null : [n(r[6][0]), n(r[6][1]), n(r[6][2])], r[7], r[8]]),
|
|
154
|
+
drawOrder: p.drawOrder,
|
|
155
|
+
drawn: p.drawn.map((d) => ({ ...d, vertices: d.vertices.map(number) })),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The setup pose, every physics state reset, every value kept whatever it is —
|
|
161
|
+
* what A10 reads before any animation is set.
|
|
162
|
+
*/
|
|
163
|
+
export function poseWalkSetup(doc: CompiledDocument, plant: CorePlant = {}, walkPlant: WalkPlant = {}): WalkPose {
|
|
164
|
+
return narrow(setupPoseIn(doc, plant, LOOPING), walkPlant.number);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* One animation on a looping track (the header's *The walk*): pose 0 the setup
|
|
169
|
+
* pose reset, then one pose per step of `steps` — `steps.length + 1` poses in
|
|
170
|
+
* all. A step must be a finite time at or above 0 and the animation the
|
|
171
|
+
* document's, as on the raw entry; a construct the core would leave out
|
|
172
|
+
* refuses the call by name; a value that is not finite does not.
|
|
173
|
+
*/
|
|
174
|
+
export function poseLoopingWalk(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant = {}, walkPlant: WalkPlant = {}): WalkPose[] {
|
|
175
|
+
const mode: WalkMode = { ...LOOPING, ...(walkPlant.time === undefined ? {} : { time: walkPlant.time }), ...(walkPlant.clock === undefined ? {} : { clock: walkPlant.clock }), ...(walkPlant.wrapResets === undefined ? {} : { wrapResets: walkPlant.wrapResets }) };
|
|
176
|
+
return walkIn(doc, animation, steps, plant, 'setup', mode).map((p) => narrow(p, walkPlant.number));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The scan's walk (issue #1179, the second part): `poseLoopingWalk` and
|
|
181
|
+
* `poseWalkSetup` as A10 reads them, each pose a `ScanPose` — the bones' world
|
|
182
|
+
* matrix and origin, the slot rows and the drawn attachments, and nothing the
|
|
183
|
+
* walk forms beside them for other readers (the bones' oracle rows and getter
|
|
184
|
+
* readings). The same walk: `walkIn`'s body in `./raw.ts`, posing each step by
|
|
185
|
+
* the same operations in the same order and refusing at the same places, with
|
|
186
|
+
* another assembler (`PoseShape`). `src/assertions/model/stepped_poses.ts` is
|
|
187
|
+
* its one caller; the public entries above are unchanged, and the core suite
|
|
188
|
+
* holds the two to the same numbers (`CO43`, `CO44`, `CO45`).
|
|
189
|
+
*/
|
|
190
|
+
export function scanLoopingWalk(doc: CompiledDocument, animation: string, steps: readonly number[], scanPlant: ScanPlant = {}): ScanPose[] {
|
|
191
|
+
return scanWalkIn(doc, animation, steps, {}, 'setup', LOOPING, scanPlant);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** `poseWalkSetup` as the scan reads it (`scanLoopingWalk`). */
|
|
195
|
+
export function scanWalkSetup(doc: CompiledDocument): ScanPose {
|
|
196
|
+
return scanSetupIn(doc, {}, LOOPING);
|
|
197
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core's own setup evaluator: every bone's world matrix and origin from
|
|
3
|
+
* the compiled model's bones (issue #925). Written from what a bone's fields
|
|
4
|
+
* mean and from measurement against the runtime's pose dump
|
|
5
|
+
* (`tools/pose_oracle.ts dump`, spine-core 4.3.13), and held to it by the core
|
|
6
|
+
* suite's equivalence controls.
|
|
7
|
+
*
|
|
8
|
+
* ⭐ **The compiler's setup transforms are this evaluator too** (issue #1015),
|
|
9
|
+
* under the same arithmetic (issue #1021). `src/transform.ts` held a second
|
|
10
|
+
* evaluator of its own until #1015, and the two differed in exactly three
|
|
11
|
+
* places, none of them a different matrix: the degree factor (`Math.PI / 180`
|
|
12
|
+
* there, the runtime's 3.1415927 here), the frame of a bone with scale 1 and
|
|
13
|
+
* no shear (`[cos, −sin, sin, cos]` of the rotation there, the y column at
|
|
14
|
+
* `rotation + 90` here), and how a radian angle is turned into degrees
|
|
15
|
+
* (divided by the degree factor there, multiplied by `180 / pi` here). #1015
|
|
16
|
+
* made them a parameter (`WorldArithmetic`) so that no byte moved; #1021
|
|
17
|
+
* measured which binds closer — every point the compiler binds from a world
|
|
18
|
+
* position, posed by spine-core from the emitted build, against that position
|
|
19
|
+
* — and the runtime's three put every one at the float32 floor where the
|
|
20
|
+
* compiler's left `gallery/look`'s up to 8.6e-5 off. So the compiler passes
|
|
21
|
+
* none now, and `RUNTIME_ARITHMETIC`, the measured choices below, is the only
|
|
22
|
+
* arithmetic anything here runs under; the parameter stays for the plants
|
|
23
|
+
* that swap one choice back in to show a control fire.
|
|
24
|
+
*
|
|
25
|
+
* ## What each measured choice is
|
|
26
|
+
*
|
|
27
|
+
* - **Degrees to radians with pi written 3.1415927.** The runtime's root bone
|
|
28
|
+
* at rotation 0 reads `b = -2.3205103333142417e-8` and
|
|
29
|
+
* `d = 0.9999999999999998`: `cos` and `sin` of 90 degrees converted with
|
|
30
|
+
* pi = 3.1415927, since `(3.1415927 - pi) / 2 = 2.3205e-8`. It is that
|
|
31
|
+
* decimal in double arithmetic — not float32: pi rounded to float32 would
|
|
32
|
+
* give `4.37e-8`, and the bone fields read through `Math.fround` moved the
|
|
33
|
+
* dump further, not closer (measured on the nineteen recipes).
|
|
34
|
+
* - **The degree factor is taken once**, `pi / 180`, and every angle is
|
|
35
|
+
* multiplied by it.
|
|
36
|
+
* - **The y column is `cos(rotation + 90 + shearY)` and `sin(…)` on every
|
|
37
|
+
* bone**, never `-sin`/`cos` of the rotation. With the constant above but
|
|
38
|
+
* the `-sin` form, `2-the-12-principles` stays 61 millionths off.
|
|
39
|
+
* - **A root bone is its local matrix at its own `x`, `y`**: the skeleton is
|
|
40
|
+
* at the origin, unscaled, in every dump taken.
|
|
41
|
+
*
|
|
42
|
+
* Both halves together read IDENTICAL, worst delta exactly 0, on the 12
|
|
43
|
+
* recipes without constraints (`tools/core_gate.ts`); either half alone is
|
|
44
|
+
* exact on none of them.
|
|
45
|
+
*
|
|
46
|
+
* ## The five inherit modes
|
|
47
|
+
*
|
|
48
|
+
* What each mode takes from the parent, as the format's documentation words
|
|
49
|
+
* it, then how it is computed here. A child's ORIGIN is always the parent's
|
|
50
|
+
* matrix applied to the local `x`, `y` plus the parent's origin — the modes
|
|
51
|
+
* change only the matrix.
|
|
52
|
+
*
|
|
53
|
+
* - `normal` — everything: parent matrix times local matrix.
|
|
54
|
+
* - `onlyTranslation` — the parent's position only: the local matrix alone.
|
|
55
|
+
* - `noRotationOrReflection` — the parent's scale (and what shear leaves of
|
|
56
|
+
* it), not its rotation and not a reflection. The parent's matrix is made
|
|
57
|
+
* conformal on its x axis: that axis kept, the y axis turned to 90 degrees
|
|
58
|
+
* from it with length `|det| / |x axis|` (so the area, the scale the parent
|
|
59
|
+
* carries, is kept and its sign is not); the local matrix is built at the
|
|
60
|
+
* bone's rotation MINUS the parent's x-axis angle; the product is the world.
|
|
61
|
+
* ⚠️ **A parent x axis whose squared length is at most `1e-5` squared is
|
|
62
|
+
* collapsed** (issue #979, `COLLAPSED_X_AXIS_SQ`): the conformal frame is
|
|
63
|
+
* then the parent's y column with its x component negated, beside a zero x
|
|
64
|
+
* column, and the local matrix is built at the rotation less 90 plus the y
|
|
65
|
+
* column's angle, summed in that order. Measured on a child of such a
|
|
66
|
+
* parent: by bisection on the x axis's length, 1e-5 reads collapsed and the
|
|
67
|
+
* next double above does not, at parent rotations 0, 30 and 45 (whether the
|
|
68
|
+
* runtime compares the squared length with `1e-5 · 1e-5` or the length with
|
|
69
|
+
* `1e-5`, 200,000 parents searched separated no pair); 480 probes of x axes
|
|
70
|
+
* from 1e-4 down to 1e-20 and 0 under y axes up to 2e17, sheared, scaled and
|
|
71
|
+
* reflected, read exact, where the reading this replaced (collapsed only at
|
|
72
|
+
* length 0, the y column as it is, the angle less 90) read 71 of 96 off and
|
|
73
|
+
* the rotation-first sum `rotation − (90 − angle)` 10 of 480. The zero
|
|
74
|
+
* matrix of a bone below an inactive one is the collapsed case at length 0,
|
|
75
|
+
* and it fixes the signs of zero of every child in this mode there (`CC13`).
|
|
76
|
+
* - `noScale` — the parent's rotation (and reflection), not its scale: the
|
|
77
|
+
* bone's local rotation is carried through the parent's matrix as a
|
|
78
|
+
* direction, normalised to unit length; the y axis is that direction turned
|
|
79
|
+
* 90 degrees, turned the other way when the parent reflects (`det < 0`);
|
|
80
|
+
* the bone's own shear and scale are applied on that frame.
|
|
81
|
+
* - `noScaleOrReflection` — as `noScale`, never turned for a reflection.
|
|
82
|
+
*
|
|
83
|
+
* ## To the bit (issue #966)
|
|
84
|
+
*
|
|
85
|
+
* The rules above were measured on the oracle's six-decimal grid; three of
|
|
86
|
+
* the modes matched it and not the runtime's last bit (issue #959). Held
|
|
87
|
+
* against `pose_oracle.ts dump --raw` at tolerance 0 — a bone in the mode
|
|
88
|
+
* under a parent rotated, scaled, sheared and reflecting, two children 1e9
|
|
89
|
+
* units out (the core suite's `CR02`, 300 per mode) — three operation orders
|
|
90
|
+
* are the runtime's and the grid could not tell them:
|
|
91
|
+
*
|
|
92
|
+
* - a radian angle is turned into degrees by multiplying with `180 / pi`
|
|
93
|
+
* (`RUNTIME_DEG`), not by dividing by `pi / 180`: the division read 106
|
|
94
|
+
* of 300 `noRotationOrReflection` bones off;
|
|
95
|
+
* - `noRotationOrReflection`'s y angle is `r + shearY + 90`, the shear
|
|
96
|
+
* added before the right angle (`r + 90 + shearY` read 46 of 300 off);
|
|
97
|
+
* - `noScale`'s direction is normalised by multiplying with the reciprocal
|
|
98
|
+
* of its length (dividing read 138 of 300 off).
|
|
99
|
+
*
|
|
100
|
+
* Measured: `normal` on 12 recipes, `onlyTranslation` on 1 and
|
|
101
|
+
* `noRotationOrReflection` on 2 of the tree's corpus; all five, under rotated,
|
|
102
|
+
* scaled, sheared and reflecting parents, on the hand-written probe the core
|
|
103
|
+
* suite builds (`CO07`), because the corpus reaches `noScale` only on a rig
|
|
104
|
+
* with constraints and `noScaleOrReflection` on none.
|
|
105
|
+
*/
|
|
106
|
+
import type { ModelBone } from '../model.ts';
|
|
107
|
+
|
|
108
|
+
/** Pi as the runtime converts degrees with it — see the header. */
|
|
109
|
+
export const RUNTIME_PI = 3.1415927;
|
|
110
|
+
const RAD = RUNTIME_PI / 180;
|
|
111
|
+
/** Radians to degrees as the runtime converts them (issue #966): multiplied by `180 / pi`, not divided by `pi / 180` — the two differ in the last bit. */
|
|
112
|
+
export const RUNTIME_DEG = 180 / RUNTIME_PI;
|
|
113
|
+
|
|
114
|
+
/** A parent x axis whose squared length is at most this is collapsed for `noRotationOrReflection` (issue #979): `1e-5` squared, as measured — see the header. */
|
|
115
|
+
export const COLLAPSED_X_AXIS_SQ = 0.00001 * 0.00001;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The three places an evaluator's arithmetic may differ in the last bit while
|
|
119
|
+
* computing the same matrix (issue #1015) — see the header. The core poses and
|
|
120
|
+
* the compiler binds with `RUNTIME_ARITHMETIC` (issue #1021); another value is
|
|
121
|
+
* a plant's.
|
|
122
|
+
*/
|
|
123
|
+
export interface WorldArithmetic {
|
|
124
|
+
/** Degrees to radians: every angle is multiplied by it. */
|
|
125
|
+
radiansPerDegree: number;
|
|
126
|
+
/** Radians to degrees — the angle `noRotationOrReflection` takes off a parent's axis. */
|
|
127
|
+
degreesOf: (radians: number) => number;
|
|
128
|
+
/** A bone with scale 1 and no shear takes `[cos r, −sin r, sin r, cos r]` instead of the y column at `r + 90`; the general frame everywhere else. */
|
|
129
|
+
rotationOnlyFrame: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The runtime's arithmetic, as measured (the header's three choices): what the core poses with. */
|
|
133
|
+
export const RUNTIME_ARITHMETIC: WorldArithmetic = {
|
|
134
|
+
radiansPerDegree: RAD,
|
|
135
|
+
degreesOf: (radians) => radians * RUNTIME_DEG,
|
|
136
|
+
rotationOnlyFrame: false,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** One bone's world transform: the matrix `[a b; c d]` and the origin, y up. */
|
|
140
|
+
export interface CoreWorld {
|
|
141
|
+
a: number;
|
|
142
|
+
b: number;
|
|
143
|
+
c: number;
|
|
144
|
+
d: number;
|
|
145
|
+
worldX: number;
|
|
146
|
+
worldY: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The five modes, after the first-letter fold the rig spec admits. */
|
|
150
|
+
export type CoreInheritMode = 'normal' | 'onlyTranslation' | 'noRotationOrReflection' | 'noScale' | 'noScaleOrReflection';
|
|
151
|
+
|
|
152
|
+
export type M2 = [number, number, number, number];
|
|
153
|
+
|
|
154
|
+
/** What computes a child's world matrix from its mode, its parent's world, its own fields and the arithmetic in force. */
|
|
155
|
+
export type InheritComputation = (mode: CoreInheritMode, parent: CoreWorld, bone: ModelBone, arithmetic: WorldArithmetic) => M2;
|
|
156
|
+
|
|
157
|
+
/** The matrix of a rotation, shears and scales, in the column form the header states. */
|
|
158
|
+
function frame(rotation: number, shearX: number, shearY: number, scaleX: number, scaleY: number, rad: number = RAD): M2 {
|
|
159
|
+
const xAngle = (rotation + shearX) * rad;
|
|
160
|
+
const yAngle = (rotation + 90 + shearY) * rad;
|
|
161
|
+
return [Math.cos(xAngle) * scaleX, Math.cos(yAngle) * scaleY, Math.sin(xAngle) * scaleX, Math.sin(yAngle) * scaleY];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** A bone's own frame — a root's matrix, and the local factor of `normal` and `onlyTranslation` — under `arithmetic`. */
|
|
165
|
+
function localFrame(arithmetic: WorldArithmetic, rotation: number, shearX: number, shearY: number, scaleX: number, scaleY: number): M2 {
|
|
166
|
+
const rad = arithmetic.radiansPerDegree;
|
|
167
|
+
if (arithmetic.rotationOnlyFrame && scaleX === 1 && scaleY === 1 && shearX === 0 && shearY === 0) {
|
|
168
|
+
const cos = Math.cos(rotation * rad);
|
|
169
|
+
const sin = Math.sin(rotation * rad);
|
|
170
|
+
return [cos, -sin, sin, cos];
|
|
171
|
+
}
|
|
172
|
+
return frame(rotation, shearX, shearY, scaleX, scaleY, rad);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function times(p: M2, q: M2): M2 {
|
|
176
|
+
return [p[0] * q[0] + p[1] * q[2], p[0] * q[1] + p[1] * q[3], p[2] * q[0] + p[3] * q[2], p[2] * q[1] + p[3] * q[3]];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The unit direction a `noScale` / `noScaleOrReflection` bone's x axis takes:
|
|
181
|
+
* its local rotation carried through the parent's matrix and normalised. Shared
|
|
182
|
+
* by the forward frame and `localFromWorld`'s read-back (`./constraints.ts`),
|
|
183
|
+
* which builds the frame to read the bone's columns in from the rotation it
|
|
184
|
+
* has just read, through this same arithmetic (issue #966).
|
|
185
|
+
*/
|
|
186
|
+
export function noScaleDirection(p: M2, rotation: number, rad: number = RAD): [number, number] {
|
|
187
|
+
const r = rotation * rad;
|
|
188
|
+
const cos = Math.cos(r);
|
|
189
|
+
const sin = Math.sin(r);
|
|
190
|
+
const ux = p[0] * cos + p[1] * sin;
|
|
191
|
+
const uy = p[2] * cos + p[3] * sin;
|
|
192
|
+
// Normalised by multiplying with the reciprocal of the length, not by dividing by it (issue #966): the division reads 1 ulp off on 375 bone-samples of the corpus's `noScale` row.
|
|
193
|
+
const inverse = 1 / Math.sqrt(ux * ux + uy * uy);
|
|
194
|
+
return [ux * inverse, uy * inverse];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The world matrix of a child under `parent` in `mode`, under `arithmetic` (the runtime's unless a caller passes its own). */
|
|
198
|
+
function modeMatrix(mode: CoreInheritMode, parent: CoreWorld, bone: ModelBone, arithmetic: WorldArithmetic = RUNTIME_ARITHMETIC): M2 {
|
|
199
|
+
const rotation = bone.rotation ?? 0;
|
|
200
|
+
const shearX = bone.shearX ?? 0;
|
|
201
|
+
const shearY = bone.shearY ?? 0;
|
|
202
|
+
const scaleX = bone.scaleX ?? 1;
|
|
203
|
+
const scaleY = bone.scaleY ?? 1;
|
|
204
|
+
const rad = arithmetic.radiansPerDegree;
|
|
205
|
+
const p: M2 = [parent.a, parent.b, parent.c, parent.d];
|
|
206
|
+
switch (mode) {
|
|
207
|
+
case 'normal':
|
|
208
|
+
return times(p, localFrame(arithmetic, rotation, shearX, shearY, scaleX, scaleY));
|
|
209
|
+
case 'onlyTranslation':
|
|
210
|
+
return localFrame(arithmetic, rotation, shearX, shearY, scaleX, scaleY);
|
|
211
|
+
case 'noRotationOrReflection': {
|
|
212
|
+
const xLengthSq = p[0] * p[0] + p[2] * p[2];
|
|
213
|
+
let conformal: M2;
|
|
214
|
+
let r: number;
|
|
215
|
+
if (xLengthSq > COLLAPSED_X_AXIS_SQ) {
|
|
216
|
+
const k = Math.abs(p[0] * p[3] - p[1] * p[2]) / xLengthSq;
|
|
217
|
+
conformal = [p[0], -p[2] * k, p[2], p[0] * k];
|
|
218
|
+
r = rotation - arithmetic.degreesOf(Math.atan2(p[2], p[0]));
|
|
219
|
+
} else {
|
|
220
|
+
// A collapsed x axis (issue #979): the parent's y column with its x component negated, and the rotation less 90 plus the y column's angle — see the header.
|
|
221
|
+
conformal = [0, -p[1], 0, p[3]];
|
|
222
|
+
r = rotation - 90 + arithmetic.degreesOf(Math.atan2(p[3], p[1]));
|
|
223
|
+
}
|
|
224
|
+
// The y angle adds the shear before the right angle here, unlike `frame` (issue #966): `(r + 90 + shearY)` reads last-bit off on 81 of 205 sheared or scaled bones at a 1e9 amplifier, `(r + shearY + 90)` on none; `r = rotation − parentAngle` taken first (adding the shear before the parent's angle is taken off reads 102 of 205 off).
|
|
225
|
+
const xAngle = (r + shearX) * rad;
|
|
226
|
+
const yAngle = (r + shearY + 90) * rad;
|
|
227
|
+
return times(conformal, [Math.cos(xAngle) * scaleX, Math.cos(yAngle) * scaleY, Math.sin(xAngle) * scaleX, Math.sin(yAngle) * scaleY]);
|
|
228
|
+
}
|
|
229
|
+
case 'noScale':
|
|
230
|
+
case 'noScaleOrReflection': {
|
|
231
|
+
const [ux, uy] = noScaleDirection(p, rotation, rad);
|
|
232
|
+
const flip = mode === 'noScale' && p[0] * p[3] - p[1] * p[2] < 0 ? -1 : 1;
|
|
233
|
+
const turned: M2 = [ux, -uy * flip, uy, ux * flip];
|
|
234
|
+
return times(turned, frame(0, shearX, shearY, scaleX, scaleY, rad));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The fold the rig spec admits: the first letter lower-cased; anything else is not a mode. */
|
|
240
|
+
function modeOf(bone: ModelBone): CoreInheritMode {
|
|
241
|
+
const stated = bone.inheritMode;
|
|
242
|
+
if (stated === undefined) return 'normal';
|
|
243
|
+
const folded = stated.length === 0 ? stated : stated[0].toLowerCase() + stated.slice(1);
|
|
244
|
+
if (folded === 'normal' || folded === 'onlyTranslation' || folded === 'noRotationOrReflection' || folded === 'noScale' || folded === 'noScaleOrReflection') return folded;
|
|
245
|
+
throw new Error(`bone "${bone.name}": inheritMode ${JSON.stringify(stated)} is no mode (readModel refuses it first)`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Every bone's setup world transform, parents first as the model lists them.
|
|
250
|
+
*
|
|
251
|
+
* `active`, when given, is the set of bones the applied skins leave active; a
|
|
252
|
+
* bone outside it is not posed. Measured on a hand-written skeleton: the
|
|
253
|
+
* runtime's dump reads an inactive bone as all zeros — matrix and origin, its
|
|
254
|
+
* world as a skeleton is created — and a bone under it, active or not, is
|
|
255
|
+
* posed from those zeros like any child. `inherit` replaces the mode
|
|
256
|
+
* computation — the core suite's plant passes a copy with one mode's sign
|
|
257
|
+
* flipped, and nothing else does. `arithmetic` is the runtime's unless a
|
|
258
|
+
* caller states its own, and only a plant does: `src/transform.ts` takes the
|
|
259
|
+
* default since issue #1021, as every posing path does.
|
|
260
|
+
*/
|
|
261
|
+
export function worldTransforms(
|
|
262
|
+
bones: readonly ModelBone[],
|
|
263
|
+
active: ReadonlySet<string> | null = null,
|
|
264
|
+
inherit: InheritComputation = modeMatrix,
|
|
265
|
+
arithmetic: WorldArithmetic = RUNTIME_ARITHMETIC,
|
|
266
|
+
): Map<string, CoreWorld> {
|
|
267
|
+
const out = new Map<string, CoreWorld>();
|
|
268
|
+
for (const bone of bones) {
|
|
269
|
+
if (active !== null && !active.has(bone.name)) {
|
|
270
|
+
out.set(bone.name, { a: 0, b: 0, c: 0, d: 0, worldX: 0, worldY: 0 });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const x = bone.x ?? 0;
|
|
274
|
+
const y = bone.y ?? 0;
|
|
275
|
+
if (bone.parent === undefined) {
|
|
276
|
+
const [a, b, c, d] = localFrame(arithmetic, bone.rotation ?? 0, bone.shearX ?? 0, bone.shearY ?? 0, bone.scaleX ?? 1, bone.scaleY ?? 1);
|
|
277
|
+
out.set(bone.name, { a, b, c, d, worldX: x, worldY: y });
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const parent = out.get(bone.parent);
|
|
281
|
+
if (parent === undefined) throw new Error(`bone "${bone.name}": parent "${bone.parent}" is not posed before it`);
|
|
282
|
+
const [a, b, c, d] = inherit(modeOf(bone), parent, bone, arithmetic);
|
|
283
|
+
out.set(bone.name, { a, b, c, d, worldX: parent.a * x + parent.b * y + parent.worldX, worldY: parent.c * x + parent.d * y + parent.worldY });
|
|
284
|
+
}
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The mode computation `worldTransforms` uses, exported so a control can wrap it. */
|
|
289
|
+
export { modeMatrix };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two spellings of a bone correspondence — what `rigc bonedist --bones`
|
|
3
|
+
* and `rigc bench --bones` take — moved here unchanged from `./bonedist.ts`
|
|
4
|
+
* (issue #1052), which re-exports both.
|
|
5
|
+
*
|
|
6
|
+
* Their own module because the CLI's help states them (`--bones`'s flag row)
|
|
7
|
+
* and an entry that links nothing of the runtime prints that help, while
|
|
8
|
+
* `./bonedist.ts` poses both skeletons through spine-core by design.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The sidecar spec a correspondence file declares, and the report's own. */
|
|
12
|
+
export const BONEDIST_SPEC = 'rigc-bonedist/1';
|
|
13
|
+
|
|
14
|
+
/** What `--bones identity` is spelled as, where a file path would go. */
|
|
15
|
+
export const IDENTITY_CORRESPONDENCE = 'identity';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deform survey of a build, through the reader and poser asked for —
|
|
3
|
+
* `explain`'s `DEFORM` block and `tools/survey_hashes.ts` (issues #969,
|
|
4
|
+
* #1019).
|
|
5
|
+
*
|
|
6
|
+
* Moved here unchanged from `./deformmeasure.ts` (issue #1052, step 4e of
|
|
7
|
+
* #380), which re-exports both names: the choice between the model document
|
|
8
|
+
* posed by the core and the Spine skeleton posed by spine-core names no
|
|
9
|
+
* runtime class, and `explain` makes it on a rigc build without touching the
|
|
10
|
+
* runtime — so an entry that links nothing of spine-core has to be able to
|
|
11
|
+
* load it. The spine-core half (`throughSpine`) is `./deformmeasure.ts`'s,
|
|
12
|
+
* reached through the seam (`./spine_side.ts`); where nothing registered it,
|
|
13
|
+
* a survey that needs it is refused by name (`SpineRuntimeError`).
|
|
14
|
+
*/
|
|
15
|
+
import { CoreInputError, readModel } from './core/index.ts';
|
|
16
|
+
import { surveyOfModel, type DeformSurvey, type DeformSurveySource } from './deformsurvey.ts';
|
|
17
|
+
import { spineFileSha256 } from './model.ts';
|
|
18
|
+
import { spineSurveyFor } from './spine_side.ts';
|
|
19
|
+
|
|
20
|
+
/** A build's three texts, as `explain` and `tools/survey_hashes.ts` hold them. */
|
|
21
|
+
export interface DeformSurveyInput {
|
|
22
|
+
skeletonText: string;
|
|
23
|
+
atlasText: string;
|
|
24
|
+
/** The model document (`skeleton.model.json`), or `null` when the input carries none (a Spine export). */
|
|
25
|
+
modelText: string | null;
|
|
26
|
+
/** What a refusal names the survey's input as; `the deform survey` unstated. */
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The survey of a build, through the reader and poser asked for (issues #969,
|
|
32
|
+
* #1019): `model` — the model document's structure posed by the core,
|
|
33
|
+
* refused when there is none or when the Spine file beside it is not the one
|
|
34
|
+
* it records; `spine-core` — the Spine skeleton read and posed by the runtime;
|
|
35
|
+
* `auto` — the model document when the input carries one and the core poses
|
|
36
|
+
* it, spine-core otherwise, the reason named in `source.why`. On the model
|
|
37
|
+
* path spine-core is not touched: the Spine text is hashed, never parsed.
|
|
38
|
+
*/
|
|
39
|
+
export function surveyOfBuild(input: DeformSurveyInput, exempt: ReadonlySet<string>, asked: 'auto' | DeformSurveySource): DeformSurvey {
|
|
40
|
+
const label = input.label ?? 'the deform survey';
|
|
41
|
+
// `./deformmeasure.ts`'s reader and poser, through the seam: it touches the runtime first and refuses by name where it cannot be used.
|
|
42
|
+
const throughSpine = (why: string | null): DeformSurvey => spineSurveyFor(label, why ?? '--poser spine').throughSpine(label, why, input, exempt);
|
|
43
|
+
if (asked === 'spine-core') return throughSpine(null);
|
|
44
|
+
if (input.modelText === null) {
|
|
45
|
+
if (asked === 'model') throw new CoreInputError('the survey was asked to pose the model document, and the input carries none');
|
|
46
|
+
return throughSpine('the input carries no model document (skeleton.model.json), so the survey posed the Spine skeleton through spine-core');
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const doc = readModel(input.modelText);
|
|
50
|
+
// The document reads the rig it was built with; the Spine file beside it must be that build's (issue #968's rule).
|
|
51
|
+
const found = spineFileSha256(input.skeletonText);
|
|
52
|
+
if (found !== doc.spine.sha256) {
|
|
53
|
+
throw new CoreInputError(`the skeleton is not the one the model document was written beside: its sha256 is ${found}, the document records ${doc.spine.sha256 || 'none'}`);
|
|
54
|
+
}
|
|
55
|
+
return surveyOfModel(doc, exempt);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (!(err instanceof CoreInputError) || asked === 'model') throw err;
|
|
58
|
+
return throughSpine(`the core refused to pose the model document — ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|