spine-rigc 2.55.0 → 2.57.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 +4 -2
- package/docs/AUTHORING.md +549 -8
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +4 -1
- package/src/anglerange.ts +190 -0
- package/src/cli/core_commands.ts +218 -13
- package/src/cli/shared.ts +160 -9
- package/src/compile.ts +69 -8
- package/src/core/entry.ts +6 -1
- package/src/deformgen.ts +333 -2
- package/src/meshsplit.ts +683 -0
- package/src/motion.ts +20 -8
- package/src/render_shared.ts +30 -0
- package/src/rig.ts +95 -3
- package/src/stop.ts +472 -0
- package/src/transform.ts +20 -0
- package/src/viewportpin.ts +168 -0
- package/src/whirl.ts +580 -0
package/src/motion.ts
CHANGED
|
@@ -147,10 +147,11 @@ export const MOTION_KEYS = {
|
|
|
147
147
|
MotionDrawOrderOffset: ['slot', 'offset'],
|
|
148
148
|
MotionEventKey: ['t', 'name', 'int', 'float', 'string', 'volume', 'balance'],
|
|
149
149
|
TrackDeriveTurn: ['kind', 'degrees', 'depth', 'carried', 'about'],
|
|
150
|
-
DeformTurn: ['kind', 'radius', 'depth', 'degrees', 'about'],
|
|
151
|
-
DeformAffine: ['kind', 'scale', 'about'],
|
|
152
|
-
DeformWave: ['kind', 'amplitude', 'wavelength', 'phase', 'along', 'axis'],
|
|
153
|
-
DeformBend: ['kind', 'amount', 'from', 'to', 'power', 'along', 'axis'],
|
|
150
|
+
DeformTurn: ['kind', 'radius', 'depth', 'degrees', 'about', 'envelope'],
|
|
151
|
+
DeformAffine: ['kind', 'scale', 'about', 'envelope'],
|
|
152
|
+
DeformWave: ['kind', 'amplitude', 'wavelength', 'phase', 'along', 'axis', 'envelope'],
|
|
153
|
+
DeformBend: ['kind', 'amount', 'from', 'to', 'power', 'along', 'axis', 'envelope'],
|
|
154
|
+
DeformEnvelope: ['axis', 'from', 'to', 'shape', 'mask'],
|
|
154
155
|
} as const satisfies Record<string, readonly string[]>;
|
|
155
156
|
|
|
156
157
|
/**
|
|
@@ -220,10 +221,16 @@ export const MOTION_TYPES = {
|
|
|
220
221
|
t: 'number', name: 'string', int: 'number', float: 'number', string: 'string', volume: 'number', balance: 'number',
|
|
221
222
|
},
|
|
222
223
|
TrackDeriveTurn: { kind: 'enum', degrees: 'number', depth: 'mixed', carried: 'number', about: 'number' },
|
|
223
|
-
DeformTurn: { kind: 'enum', radius: 'number', depth: 'boolean', degrees: 'number', about: 'number' },
|
|
224
|
-
DeformAffine: { kind: 'enum', scale: 'number[]', about: 'number[]' },
|
|
225
|
-
DeformWave: {
|
|
226
|
-
|
|
224
|
+
DeformTurn: { kind: 'enum', radius: 'number', depth: 'boolean', degrees: 'number', about: 'number', envelope: 'object' },
|
|
225
|
+
DeformAffine: { kind: 'enum', scale: 'number[]', about: 'number[]', envelope: 'object' },
|
|
226
|
+
DeformWave: {
|
|
227
|
+
kind: 'enum', amplitude: 'number', wavelength: 'number', phase: 'number', along: 'enum', axis: 'enum', envelope: 'object',
|
|
228
|
+
},
|
|
229
|
+
DeformBend: {
|
|
230
|
+
kind: 'enum', amount: 'number', from: 'number', to: 'number', power: 'number', along: 'enum', axis: 'enum',
|
|
231
|
+
envelope: 'object',
|
|
232
|
+
},
|
|
233
|
+
DeformEnvelope: { axis: 'number[]', from: 'number', to: 'number', shape: 'enum', mask: 'string' },
|
|
227
234
|
} as const satisfies MotionTypeTable;
|
|
228
235
|
|
|
229
236
|
/**
|
|
@@ -242,6 +249,7 @@ export const MOTION_ENUMS = {
|
|
|
242
249
|
DeformAffine: { kind: { owner: 'evaluateDeformTransform' } },
|
|
243
250
|
DeformWave: { kind: { owner: 'evaluateDeformTransform' }, along: { owner: 'evaluateDeformTransform' }, axis: { owner: 'evaluateDeformTransform' } },
|
|
244
251
|
DeformBend: { kind: { owner: 'evaluateDeformTransform' }, along: { owner: 'evaluateDeformTransform' }, axis: { owner: 'evaluateDeformTransform' } },
|
|
252
|
+
DeformEnvelope: { shape: { owner: 'evaluateDeformEnvelope' } },
|
|
245
253
|
} as const satisfies SpecEnumTable<typeof MOTION_TYPES>;
|
|
246
254
|
|
|
247
255
|
/** A deform key's `transform` kinds, and the shape each one's keys come from. */
|
|
@@ -587,6 +595,10 @@ function parseDeform(raw: unknown, where: string, at: string): void {
|
|
|
587
595
|
if (isObj(parsed.transform)) {
|
|
588
596
|
const shape = DEFORM_TRANSFORM_SHAPE[String(parsed.transform.kind)];
|
|
589
597
|
if (shape !== undefined) known(parsed.transform, shape, where, `${at}.transform`);
|
|
598
|
+
// The envelope is one shape under all five kinds (issue #1413); which
|
|
599
|
+
// of its two forms it states, and whether the fields agree, is
|
|
600
|
+
// `evaluateDeformEnvelope`'s to say.
|
|
601
|
+
known(parsed.transform.envelope, 'DeformEnvelope', where, `${at}.transform.envelope`);
|
|
590
602
|
}
|
|
591
603
|
}
|
|
592
604
|
}
|
package/src/render_shared.ts
CHANGED
|
@@ -115,6 +115,17 @@ export const SETUP_POSE_DIR = 'setup';
|
|
|
115
115
|
* rate writes `<animation>/`, and any other rate writes `<animation>@<fps>fps/`.
|
|
116
116
|
*/
|
|
117
117
|
export const PROTOCOL_FPS = 12;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The directory a frame set of `animation` sampled at `fps` is written to under
|
|
121
|
+
* the render root: the animation's own name at the protocol rate, and
|
|
122
|
+
* `<animation>@<fps>fps` at any other — so two rates of one animation sit side by
|
|
123
|
+
* side. `render` and `bench/render_reference.ts` both name their sets through
|
|
124
|
+
* it, and `render --help` and AUTHORING §0 say what it does (issue #1414).
|
|
125
|
+
*/
|
|
126
|
+
export function frameSetDir(animation: string, fps: number): string {
|
|
127
|
+
return fps === PROTOCOL_FPS ? animation : `${animation}@${fps}fps`;
|
|
128
|
+
}
|
|
118
129
|
/** The rate the framing box is measured at, whatever `--fps` writes frames at. */
|
|
119
130
|
export const FRAMING_FPS = 60;
|
|
120
131
|
|
|
@@ -222,6 +233,18 @@ export interface FramesSidecar {
|
|
|
222
233
|
* it as a reference by name.
|
|
223
234
|
*/
|
|
224
235
|
environment?: EnvironmentStep[];
|
|
236
|
+
/**
|
|
237
|
+
* The box was given rather than fitted, when `render --viewport` or
|
|
238
|
+
* `render --viewport-from` gave it (issue #1414): which flag, and every slot
|
|
239
|
+
* that reaches past the box, by set and frame — what lies past it is not in
|
|
240
|
+
* the frames, since each frame is the box.
|
|
241
|
+
*
|
|
242
|
+
* ⭐ Absent on a fitted render, for `skin`'s reason: every frame set written
|
|
243
|
+
* before the field existed says the same, and a fitted box is measured over
|
|
244
|
+
* the rig's own poses (every animation at `FRAMING_FPS`), padded, rather than
|
|
245
|
+
* stated by anyone.
|
|
246
|
+
*/
|
|
247
|
+
pinned?: PinnedViewport;
|
|
225
248
|
/** The colour the frames were cleared to, straight RGBA 0..255. */
|
|
226
249
|
background: RGBA;
|
|
227
250
|
viewport: {
|
|
@@ -238,6 +261,13 @@ export interface FramesSidecar {
|
|
|
238
261
|
sets: FrameSet[];
|
|
239
262
|
}
|
|
240
263
|
|
|
264
|
+
/** How a given box was given, and what it leaves out — `FramesSidecar.pinned`. */
|
|
265
|
+
export interface PinnedViewport {
|
|
266
|
+
by: '--viewport' | '--viewport-from';
|
|
267
|
+
/** Every slot whose drawn geometry reaches past the box, per set, in the order the sets were drawn and the slots first reached past it. */
|
|
268
|
+
outside: Array<{ set: string; slot: string; frames: number[] }>;
|
|
269
|
+
}
|
|
270
|
+
|
|
241
271
|
// ---------------------------------------------------------------------------
|
|
242
272
|
// posing
|
|
243
273
|
// ---------------------------------------------------------------------------
|
package/src/rig.ts
CHANGED
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
* - `invariants` — the structural facts skeleton JSON cannot state about itself,
|
|
33
33
|
* which the validator's archetype assertions read. Nothing in the file says
|
|
34
34
|
* "this bone carries the cut's axis" or "this parentage is forbidden".
|
|
35
|
+
* - `stops` — a swinging chain held off a leg, one declaration for the network
|
|
36
|
+
* of bones and constraints it stands for (`RigStop`), expanded into plain
|
|
37
|
+
* bones and constraints before anything else reads the rig.
|
|
35
38
|
*
|
|
36
39
|
* ## What a field's PRESENCE means
|
|
37
40
|
*
|
|
@@ -61,6 +64,7 @@
|
|
|
61
64
|
*/
|
|
62
65
|
import { ikShapeFault } from './assertions/bodies/a47.ts';
|
|
63
66
|
import { CompileError, NotImplementedError } from './errors.ts';
|
|
67
|
+
import { expandStops } from './stop.ts';
|
|
64
68
|
import { dottedPath, refuseNumbersTheFileCannotCarry, refuseUnknownKeys, refuseValuesOfTheWrongType, refuseValuesOutsideTheirSet } from './keys.ts';
|
|
65
69
|
import type { ShapeVisit, SpecEnumTable, SpecValueType } from './keys.ts';
|
|
66
70
|
|
|
@@ -1887,6 +1891,69 @@ export interface RigDeformFoldExemption {
|
|
|
1887
1891
|
why: string;
|
|
1888
1892
|
}
|
|
1889
1893
|
|
|
1894
|
+
// ---------------------------------------------------------------------------
|
|
1895
|
+
// stops — a swinging chain held off a leg (issue #1413)
|
|
1896
|
+
// ---------------------------------------------------------------------------
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* One chain held off a leg: the network [`src/stop.ts`](stop.ts) expands it to,
|
|
1900
|
+
* written once instead of by hand. Spine has no collider and no angle limit, so
|
|
1901
|
+
* a physics-driven skirt chain swings its hem through the thigh it hangs in
|
|
1902
|
+
* front of; this holds the chain's DRAWN links — the bones its mesh is
|
|
1903
|
+
* weighted to — so they may swing away from the leg and not toward it past
|
|
1904
|
+
* their rest angle.
|
|
1905
|
+
*
|
|
1906
|
+
* It expands, before anything else reads the rig, to exactly:
|
|
1907
|
+
*
|
|
1908
|
+
* - `<chain>_touch` — a point bone, child of `against`, at `at`;
|
|
1909
|
+
* - `<chain>_stop` — a frame hung where the first link hangs (its parent,
|
|
1910
|
+
* `x`, `y` and `length`), aimed at the point by the ik constraint
|
|
1911
|
+
* `<chain>_stop_aim`;
|
|
1912
|
+
* - `<link>_rel` — a child of the frame that the transform constraint
|
|
1913
|
+
* `<link>_rel_copy` gives the first link's world rotation;
|
|
1914
|
+
* - `<link>_d` — the first link's drawn link, a child of the frame, which the
|
|
1915
|
+
* transform constraint `<link>_clamp` gives `_rel`'s turn in the frame,
|
|
1916
|
+
* clamped to the range;
|
|
1917
|
+
* - for each link below the first, `<link>_d` — its drawn link, a sibling
|
|
1918
|
+
* with its stated transform — and `<link>_clamp`, which gives it the link's
|
|
1919
|
+
* own turn, clamped to `lower`.
|
|
1920
|
+
*
|
|
1921
|
+
* So a chain's next link hangs from the drawn link above it: author the chain
|
|
1922
|
+
* with each link's parent below `<previous link>_d`, and weight the mesh to the
|
|
1923
|
+
* `_d` bones. What the expansion derives, it derives from the setup pose this
|
|
1924
|
+
* file states (`src/stop.ts` lists it); nothing is measured off the art.
|
|
1925
|
+
*/
|
|
1926
|
+
export interface RigStop {
|
|
1927
|
+
/** The name the point, the frame and the aim are called after. */
|
|
1928
|
+
chain: string;
|
|
1929
|
+
/**
|
|
1930
|
+
* The chain's swinging links, root first: each one written by a constraint
|
|
1931
|
+
* in `constraints` (its swing), each below the first hanging from the drawn
|
|
1932
|
+
* link of the one above. Resolved by name.
|
|
1933
|
+
*/
|
|
1934
|
+
links: string[];
|
|
1935
|
+
/** The bone the point rides on — the leg. Resolved by name. */
|
|
1936
|
+
against: string;
|
|
1937
|
+
/** The point, `[x, y]` in `against`'s local space. Required: the compiler places no point. */
|
|
1938
|
+
at: [number, number];
|
|
1939
|
+
/**
|
|
1940
|
+
* The first drawn link's range, as its two ends: degrees from its rest angle
|
|
1941
|
+
* to the aim line, counted positive AWAY from the point. `inward: 0,
|
|
1942
|
+
* outward: 60` — never past rest toward the leg, up to 60° away from it.
|
|
1943
|
+
* Both required, `inward` not above `outward`.
|
|
1944
|
+
*/
|
|
1945
|
+
inward: number;
|
|
1946
|
+
outward: number;
|
|
1947
|
+
/** Each lower drawn link's range off its own rest bend, the same way round. Required when `links` has more than one. */
|
|
1948
|
+
lower?: RigStopLower;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/** A lower link's range — `RigStop.lower`. */
|
|
1952
|
+
export interface RigStopLower {
|
|
1953
|
+
inward: number;
|
|
1954
|
+
outward: number;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1890
1957
|
// ---------------------------------------------------------------------------
|
|
1891
1958
|
// the file
|
|
1892
1959
|
// ---------------------------------------------------------------------------
|
|
@@ -1932,6 +1999,12 @@ export interface RigSpec {
|
|
|
1932
1999
|
*/
|
|
1933
2000
|
events?: Record<string, RigEvent>;
|
|
1934
2001
|
invariants?: RigInvariants;
|
|
2002
|
+
/**
|
|
2003
|
+
* Chains held off a leg — see `RigStop`. Expanded by `parseRigSpec` into
|
|
2004
|
+
* bones and constraints before anything else reads them; kept as written,
|
|
2005
|
+
* and emitted nowhere.
|
|
2006
|
+
*/
|
|
2007
|
+
stops?: RigStop[];
|
|
1935
2008
|
}
|
|
1936
2009
|
|
|
1937
2010
|
// ---------------------------------------------------------------------------
|
|
@@ -1960,7 +2033,9 @@ function isObj(v: unknown): v is Record<string, unknown> {
|
|
|
1960
2033
|
* fault.
|
|
1961
2034
|
*/
|
|
1962
2035
|
export const RIG_KEYS = {
|
|
1963
|
-
RigSpec: ['spec', 'name', 'note', 'skeleton', 'images', 'bones', 'slots', 'skins', 'constraints', 'events', 'invariants'],
|
|
2036
|
+
RigSpec: ['spec', 'name', 'note', 'skeleton', 'images', 'bones', 'slots', 'skins', 'constraints', 'events', 'invariants', 'stops'],
|
|
2037
|
+
RigStop: ['chain', 'links', 'against', 'at', 'inward', 'outward', 'lower'],
|
|
2038
|
+
RigStopLower: ['inward', 'outward'],
|
|
1964
2039
|
RigSkeletonHeader: ['x', 'y', 'width', 'height', 'fps', 'referenceScale', 'images', 'audio', 'stageBox'],
|
|
1965
2040
|
RigStageBox: ['slot', 'attachment'],
|
|
1966
2041
|
RigBone: ['name', 'parent', 'length', 'x', 'y', 'rotation', 'scaleX', 'scaleY', 'shearX', 'shearY', 'inherit', 'skin', 'color', 'icon', 'from'],
|
|
@@ -2060,8 +2135,10 @@ type RigTypeTable = {
|
|
|
2060
2135
|
export const RIG_TYPES = {
|
|
2061
2136
|
RigSpec: {
|
|
2062
2137
|
spec: 'enum', name: 'string', note: 'string', skeleton: 'object', images: 'string', bones: 'object[]', slots: 'object[]',
|
|
2063
|
-
skins: 'map of object', constraints: 'object[]', events: 'map of object', invariants: 'object',
|
|
2138
|
+
skins: 'map of object', constraints: 'object[]', events: 'map of object', invariants: 'object', stops: 'object[]',
|
|
2064
2139
|
},
|
|
2140
|
+
RigStop: { chain: 'string', links: 'string[]', against: 'string', at: 'number[]', inward: 'number', outward: 'number', lower: 'object' },
|
|
2141
|
+
RigStopLower: { inward: 'number', outward: 'number' },
|
|
2065
2142
|
RigSkeletonHeader: {
|
|
2066
2143
|
x: 'number', y: 'number', width: 'number | null', height: 'number | null', fps: 'number', referenceScale: 'number',
|
|
2067
2144
|
images: 'string', audio: 'string | null', stageBox: 'object',
|
|
@@ -2405,6 +2482,12 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): Array<{
|
|
|
2405
2482
|
at(event, 'RigEvent', `event "${name}"`, ['events', name]);
|
|
2406
2483
|
}
|
|
2407
2484
|
|
|
2485
|
+
for (const [i, stop] of (Array.isArray(raw.stops) ? raw.stops : []).entries()) {
|
|
2486
|
+
const who = isObj(stop) && typeof stop.chain === 'string' ? `stop "${stop.chain}"` : `stops[${i}]`;
|
|
2487
|
+
at(stop, 'RigStop', who, ['stops', i]);
|
|
2488
|
+
if (isObj(stop)) at(stop.lower, 'RigStopLower', `${who}'s "lower"`, ['stops', i, 'lower']);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2408
2491
|
if (isObj(raw.invariants)) {
|
|
2409
2492
|
at(raw.invariants, 'RigInvariants', '"invariants"', ['invariants']);
|
|
2410
2493
|
for (const [i, rule] of (Array.isArray(raw.invariants.detached) ? raw.invariants.detached : []).entries()) {
|
|
@@ -2528,6 +2611,11 @@ function rigPlace(raw: Record<string, unknown>): (path: ReadonlyArray<string | n
|
|
|
2528
2611
|
if (head === 'bones') return `${named(raw.bones, second, 'bone', 'bones')}${rest(path.slice(2))}`;
|
|
2529
2612
|
if (head === 'slots') return `${named(raw.slots, second, 'slot', 'slots')}${rest(path.slice(2))}`;
|
|
2530
2613
|
if (head === 'constraints') return `${named(raw.constraints, second, 'constraint', 'constraints')}${rest(path.slice(2))}`;
|
|
2614
|
+
if (head === 'stops') {
|
|
2615
|
+
const node = Array.isArray(raw.stops) ? raw.stops[second] : undefined;
|
|
2616
|
+
const who = isObj(node) && typeof node.chain === 'string' ? `stop ${JSON.stringify(node.chain)}` : `stops[${second}]`;
|
|
2617
|
+
return `${who}${rest(path.slice(2))}`;
|
|
2618
|
+
}
|
|
2531
2619
|
}
|
|
2532
2620
|
if (head === 'events' && typeof second === 'string') return `event ${JSON.stringify(second)}${rest(path.slice(2))}`;
|
|
2533
2621
|
if (head === 'skins' && typeof second === 'string') {
|
|
@@ -2609,7 +2697,11 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2609
2697
|
throw new CompileError(`${where}: a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)`);
|
|
2610
2698
|
}
|
|
2611
2699
|
|
|
2612
|
-
|
|
2700
|
+
// `stops` first, ahead of every name this function resolves: a chain's next
|
|
2701
|
+
// link hangs from the drawn link a stop generates, so the bones below are
|
|
2702
|
+
// read as the expansion lays them out — and every reader after this one sees
|
|
2703
|
+
// ordinary bones and constraints (`src/stop.ts`).
|
|
2704
|
+
const spec = expandStops(raw as unknown as RigSpec, where);
|
|
2613
2705
|
|
|
2614
2706
|
// The stage, stated or stated absent. Half a statement is refused here rather
|
|
2615
2707
|
// than resolved in `compile`, because which half was meant is not derivable
|