spine-rigc 1.5.3 → 1.6.2
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 +2 -1
- package/cli.ts +131 -403
- package/docs/AUTHORING.md +63 -14
- package/docs/INGEST.md +1 -0
- package/docs/MOTION.md +1 -0
- package/package.json +1 -1
- package/src/atlas.ts +25 -0
- package/src/check.ts +113 -24
- package/src/checkpics.ts +2 -0
- package/src/compile.ts +641 -506
- package/src/core/animation.ts +982 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1721 -0
- package/src/core/constraints_path.ts +942 -0
- package/src/core/constraints_physics.ts +830 -0
- package/src/core/constraints_slider.ts +505 -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 +1103 -0
- package/src/core/raw.ts +347 -0
- package/src/core/skins.ts +104 -0
- package/src/core/uvs.ts +448 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/world.ts +233 -0
- package/src/deformmeasure.ts +337 -73
- package/src/deformreport.ts +370 -0
- package/src/depth.ts +101 -2
- package/src/emit_spine.ts +860 -0
- package/src/mesh.ts +24 -15
- package/src/model.ts +958 -0
- package/src/render.ts +649 -143
- package/src/render_core.ts +582 -0
- package/src/rig.ts +34 -6
- package/src/transform.ts +26 -4
- package/src/types.ts +18 -0
- package/src/validate.ts +30 -2
|
@@ -0,0 +1,233 @@
|
|
|
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; it shares no code with `src/transform.ts`,
|
|
7
|
+
* which is the compiler's arithmetic and stays frozen so that no emitted byte
|
|
8
|
+
* moves.
|
|
9
|
+
*
|
|
10
|
+
* ## What each measured choice is
|
|
11
|
+
*
|
|
12
|
+
* - **Degrees to radians with pi written 3.1415927.** The runtime's root bone
|
|
13
|
+
* at rotation 0 reads `b = -2.3205103333142417e-8` and
|
|
14
|
+
* `d = 0.9999999999999998`: `cos` and `sin` of 90 degrees converted with
|
|
15
|
+
* pi = 3.1415927, since `(3.1415927 - pi) / 2 = 2.3205e-8`. It is that
|
|
16
|
+
* decimal in double arithmetic — not float32: pi rounded to float32 would
|
|
17
|
+
* give `4.37e-8`, and the bone fields read through `Math.fround` moved the
|
|
18
|
+
* dump further, not closer (measured on the nineteen recipes).
|
|
19
|
+
* - **The degree factor is taken once**, `pi / 180`, and every angle is
|
|
20
|
+
* multiplied by it.
|
|
21
|
+
* - **The y column is `cos(rotation + 90 + shearY)` and `sin(…)` on every
|
|
22
|
+
* bone**, never `-sin`/`cos` of the rotation. With the constant above but
|
|
23
|
+
* the `-sin` form, `2-the-12-principles` stays 61 millionths off.
|
|
24
|
+
* - **A root bone is its local matrix at its own `x`, `y`**: the skeleton is
|
|
25
|
+
* at the origin, unscaled, in every dump taken.
|
|
26
|
+
*
|
|
27
|
+
* Both halves together read IDENTICAL, worst delta exactly 0, on the 12
|
|
28
|
+
* recipes without constraints (`tools/core_gate.ts`); either half alone is
|
|
29
|
+
* exact on none of them.
|
|
30
|
+
*
|
|
31
|
+
* ## The five inherit modes
|
|
32
|
+
*
|
|
33
|
+
* What each mode takes from the parent, as the format's documentation words
|
|
34
|
+
* it, then how it is computed here. A child's ORIGIN is always the parent's
|
|
35
|
+
* matrix applied to the local `x`, `y` plus the parent's origin — the modes
|
|
36
|
+
* change only the matrix.
|
|
37
|
+
*
|
|
38
|
+
* - `normal` — everything: parent matrix times local matrix.
|
|
39
|
+
* - `onlyTranslation` — the parent's position only: the local matrix alone.
|
|
40
|
+
* - `noRotationOrReflection` — the parent's scale (and what shear leaves of
|
|
41
|
+
* it), not its rotation and not a reflection. The parent's matrix is made
|
|
42
|
+
* conformal on its x axis: that axis kept, the y axis turned to 90 degrees
|
|
43
|
+
* from it with length `|det| / |x axis|` (so the area, the scale the parent
|
|
44
|
+
* carries, is kept and its sign is not); the local matrix is built at the
|
|
45
|
+
* bone's rotation MINUS the parent's x-axis angle; the product is the world.
|
|
46
|
+
* ⚠️ **A parent x axis whose squared length is at most `1e-5` squared is
|
|
47
|
+
* collapsed** (issue #979, `COLLAPSED_X_AXIS_SQ`): the conformal frame is
|
|
48
|
+
* then the parent's y column with its x component negated, beside a zero x
|
|
49
|
+
* column, and the local matrix is built at the rotation less 90 plus the y
|
|
50
|
+
* column's angle, summed in that order. Measured on a child of such a
|
|
51
|
+
* parent: by bisection on the x axis's length, 1e-5 reads collapsed and the
|
|
52
|
+
* next double above does not, at parent rotations 0, 30 and 45 (whether the
|
|
53
|
+
* runtime compares the squared length with `1e-5 · 1e-5` or the length with
|
|
54
|
+
* `1e-5`, 200,000 parents searched separated no pair); 480 probes of x axes
|
|
55
|
+
* from 1e-4 down to 1e-20 and 0 under y axes up to 2e17, sheared, scaled and
|
|
56
|
+
* reflected, read exact, where the reading this replaced (collapsed only at
|
|
57
|
+
* length 0, the y column as it is, the angle less 90) read 71 of 96 off and
|
|
58
|
+
* the rotation-first sum `rotation − (90 − angle)` 10 of 480. The zero
|
|
59
|
+
* matrix of a bone below an inactive one is the collapsed case at length 0,
|
|
60
|
+
* and it fixes the signs of zero of every child in this mode there (`CC13`).
|
|
61
|
+
* - `noScale` — the parent's rotation (and reflection), not its scale: the
|
|
62
|
+
* bone's local rotation is carried through the parent's matrix as a
|
|
63
|
+
* direction, normalised to unit length; the y axis is that direction turned
|
|
64
|
+
* 90 degrees, turned the other way when the parent reflects (`det < 0`);
|
|
65
|
+
* the bone's own shear and scale are applied on that frame.
|
|
66
|
+
* - `noScaleOrReflection` — as `noScale`, never turned for a reflection.
|
|
67
|
+
*
|
|
68
|
+
* ## To the bit (issue #966)
|
|
69
|
+
*
|
|
70
|
+
* The rules above were measured on the oracle's six-decimal grid; three of
|
|
71
|
+
* the modes matched it and not the runtime's last bit (issue #959). Held
|
|
72
|
+
* against `pose_oracle.ts dump --raw` at tolerance 0 — a bone in the mode
|
|
73
|
+
* under a parent rotated, scaled, sheared and reflecting, two children 1e9
|
|
74
|
+
* units out (the core suite's `CR02`, 300 per mode) — three operation orders
|
|
75
|
+
* are the runtime's and the grid could not tell them:
|
|
76
|
+
*
|
|
77
|
+
* - a radian angle is turned into degrees by multiplying with `180 / pi`
|
|
78
|
+
* (`RUNTIME_DEG`), not by dividing by `pi / 180`: the division read 106
|
|
79
|
+
* of 300 `noRotationOrReflection` bones off;
|
|
80
|
+
* - `noRotationOrReflection`'s y angle is `r + shearY + 90`, the shear
|
|
81
|
+
* added before the right angle (`r + 90 + shearY` read 46 of 300 off);
|
|
82
|
+
* - `noScale`'s direction is normalised by multiplying with the reciprocal
|
|
83
|
+
* of its length (dividing read 138 of 300 off).
|
|
84
|
+
*
|
|
85
|
+
* Measured: `normal` on 12 recipes, `onlyTranslation` on 1 and
|
|
86
|
+
* `noRotationOrReflection` on 2 of the tree's corpus; all five, under rotated,
|
|
87
|
+
* scaled, sheared and reflecting parents, on the hand-written probe the core
|
|
88
|
+
* suite builds (`CO07`), because the corpus reaches `noScale` only on a rig
|
|
89
|
+
* with constraints and `noScaleOrReflection` on none.
|
|
90
|
+
*/
|
|
91
|
+
import type { ModelBone } from '../model.ts';
|
|
92
|
+
|
|
93
|
+
/** Pi as the runtime converts degrees with it — see the header. */
|
|
94
|
+
export const RUNTIME_PI = 3.1415927;
|
|
95
|
+
const RAD = RUNTIME_PI / 180;
|
|
96
|
+
/** 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. */
|
|
97
|
+
export const RUNTIME_DEG = 180 / RUNTIME_PI;
|
|
98
|
+
|
|
99
|
+
/** 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. */
|
|
100
|
+
export const COLLAPSED_X_AXIS_SQ = 0.00001 * 0.00001;
|
|
101
|
+
|
|
102
|
+
/** One bone's world transform: the matrix `[a b; c d]` and the origin, y up. */
|
|
103
|
+
export interface CoreWorld {
|
|
104
|
+
a: number;
|
|
105
|
+
b: number;
|
|
106
|
+
c: number;
|
|
107
|
+
d: number;
|
|
108
|
+
worldX: number;
|
|
109
|
+
worldY: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The five modes, after the first-letter fold the rig spec admits. */
|
|
113
|
+
export type CoreInheritMode = 'normal' | 'onlyTranslation' | 'noRotationOrReflection' | 'noScale' | 'noScaleOrReflection';
|
|
114
|
+
|
|
115
|
+
export type M2 = [number, number, number, number];
|
|
116
|
+
|
|
117
|
+
/** What computes a child's world matrix from its mode, its parent's world and its own fields. */
|
|
118
|
+
export type InheritComputation = (mode: CoreInheritMode, parent: CoreWorld, bone: ModelBone) => M2;
|
|
119
|
+
|
|
120
|
+
/** The matrix of a rotation, shears and scales, in the column form the header states. */
|
|
121
|
+
function frame(rotation: number, shearX: number, shearY: number, scaleX: number, scaleY: number): M2 {
|
|
122
|
+
const xAngle = (rotation + shearX) * RAD;
|
|
123
|
+
const yAngle = (rotation + 90 + shearY) * RAD;
|
|
124
|
+
return [Math.cos(xAngle) * scaleX, Math.cos(yAngle) * scaleY, Math.sin(xAngle) * scaleX, Math.sin(yAngle) * scaleY];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function times(p: M2, q: M2): M2 {
|
|
128
|
+
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]];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The unit direction a `noScale` / `noScaleOrReflection` bone's x axis takes:
|
|
133
|
+
* its local rotation carried through the parent's matrix and normalised. Shared
|
|
134
|
+
* by the forward frame and `localFromWorld`'s read-back (`./constraints.ts`),
|
|
135
|
+
* which builds the frame to read the bone's columns in from the rotation it
|
|
136
|
+
* has just read, through this same arithmetic (issue #966).
|
|
137
|
+
*/
|
|
138
|
+
export function noScaleDirection(p: M2, rotation: number): [number, number] {
|
|
139
|
+
const r = rotation * RAD;
|
|
140
|
+
const cos = Math.cos(r);
|
|
141
|
+
const sin = Math.sin(r);
|
|
142
|
+
const ux = p[0] * cos + p[1] * sin;
|
|
143
|
+
const uy = p[2] * cos + p[3] * sin;
|
|
144
|
+
// 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.
|
|
145
|
+
const inverse = 1 / Math.sqrt(ux * ux + uy * uy);
|
|
146
|
+
return [ux * inverse, uy * inverse];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The world matrix of a child under `parent` in `mode`. */
|
|
150
|
+
function modeMatrix(mode: CoreInheritMode, parent: CoreWorld, bone: ModelBone): M2 {
|
|
151
|
+
const rotation = bone.rotation ?? 0;
|
|
152
|
+
const shearX = bone.shearX ?? 0;
|
|
153
|
+
const shearY = bone.shearY ?? 0;
|
|
154
|
+
const scaleX = bone.scaleX ?? 1;
|
|
155
|
+
const scaleY = bone.scaleY ?? 1;
|
|
156
|
+
const p: M2 = [parent.a, parent.b, parent.c, parent.d];
|
|
157
|
+
switch (mode) {
|
|
158
|
+
case 'normal':
|
|
159
|
+
return times(p, frame(rotation, shearX, shearY, scaleX, scaleY));
|
|
160
|
+
case 'onlyTranslation':
|
|
161
|
+
return frame(rotation, shearX, shearY, scaleX, scaleY);
|
|
162
|
+
case 'noRotationOrReflection': {
|
|
163
|
+
const xLengthSq = p[0] * p[0] + p[2] * p[2];
|
|
164
|
+
let conformal: M2;
|
|
165
|
+
let r: number;
|
|
166
|
+
if (xLengthSq > COLLAPSED_X_AXIS_SQ) {
|
|
167
|
+
const k = Math.abs(p[0] * p[3] - p[1] * p[2]) / xLengthSq;
|
|
168
|
+
conformal = [p[0], -p[2] * k, p[2], p[0] * k];
|
|
169
|
+
r = rotation - Math.atan2(p[2], p[0]) * RUNTIME_DEG;
|
|
170
|
+
} else {
|
|
171
|
+
// 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.
|
|
172
|
+
conformal = [0, -p[1], 0, p[3]];
|
|
173
|
+
r = rotation - 90 + Math.atan2(p[3], p[1]) * RUNTIME_DEG;
|
|
174
|
+
}
|
|
175
|
+
// 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).
|
|
176
|
+
const xAngle = (r + shearX) * RAD;
|
|
177
|
+
const yAngle = (r + shearY + 90) * RAD;
|
|
178
|
+
return times(conformal, [Math.cos(xAngle) * scaleX, Math.cos(yAngle) * scaleY, Math.sin(xAngle) * scaleX, Math.sin(yAngle) * scaleY]);
|
|
179
|
+
}
|
|
180
|
+
case 'noScale':
|
|
181
|
+
case 'noScaleOrReflection': {
|
|
182
|
+
const [ux, uy] = noScaleDirection(p, rotation);
|
|
183
|
+
const flip = mode === 'noScale' && p[0] * p[3] - p[1] * p[2] < 0 ? -1 : 1;
|
|
184
|
+
const turned: M2 = [ux, -uy * flip, uy, ux * flip];
|
|
185
|
+
return times(turned, frame(0, shearX, shearY, scaleX, scaleY));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The fold the rig spec admits: the first letter lower-cased; anything else is not a mode. */
|
|
191
|
+
function modeOf(bone: ModelBone): CoreInheritMode {
|
|
192
|
+
const stated = bone.inheritMode;
|
|
193
|
+
if (stated === undefined) return 'normal';
|
|
194
|
+
const folded = stated.length === 0 ? stated : stated[0].toLowerCase() + stated.slice(1);
|
|
195
|
+
if (folded === 'normal' || folded === 'onlyTranslation' || folded === 'noRotationOrReflection' || folded === 'noScale' || folded === 'noScaleOrReflection') return folded;
|
|
196
|
+
throw new Error(`bone "${bone.name}": inheritMode ${JSON.stringify(stated)} is no mode (readModel refuses it first)`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Every bone's setup world transform, parents first as the model lists them.
|
|
201
|
+
*
|
|
202
|
+
* `active`, when given, is the set of bones the applied skins leave active; a
|
|
203
|
+
* bone outside it is not posed. Measured on a hand-written skeleton: the
|
|
204
|
+
* runtime's dump reads an inactive bone as all zeros — matrix and origin, its
|
|
205
|
+
* world as a skeleton is created — and a bone under it, active or not, is
|
|
206
|
+
* posed from those zeros like any child. `inherit` replaces the mode
|
|
207
|
+
* computation — the core suite's plant passes a copy with one mode's sign
|
|
208
|
+
* flipped, and nothing else does.
|
|
209
|
+
*/
|
|
210
|
+
export function worldTransforms(bones: readonly ModelBone[], active: ReadonlySet<string> | null = null, inherit: InheritComputation = modeMatrix): Map<string, CoreWorld> {
|
|
211
|
+
const out = new Map<string, CoreWorld>();
|
|
212
|
+
for (const bone of bones) {
|
|
213
|
+
if (active !== null && !active.has(bone.name)) {
|
|
214
|
+
out.set(bone.name, { a: 0, b: 0, c: 0, d: 0, worldX: 0, worldY: 0 });
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const x = bone.x ?? 0;
|
|
218
|
+
const y = bone.y ?? 0;
|
|
219
|
+
if (bone.parent === undefined) {
|
|
220
|
+
const [a, b, c, d] = frame(bone.rotation ?? 0, bone.shearX ?? 0, bone.shearY ?? 0, bone.scaleX ?? 1, bone.scaleY ?? 1);
|
|
221
|
+
out.set(bone.name, { a, b, c, d, worldX: x, worldY: y });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const parent = out.get(bone.parent);
|
|
225
|
+
if (parent === undefined) throw new Error(`bone "${bone.name}": parent "${bone.parent}" is not posed before it`);
|
|
226
|
+
const [a, b, c, d] = inherit(modeOf(bone), parent, bone);
|
|
227
|
+
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 });
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The mode computation `worldTransforms` uses, exported so a control can wrap it. */
|
|
233
|
+
export { modeMatrix };
|