spine-rigc 1.9.2 → 1.9.3
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/cli.ts +28 -4397
- package/docs/AUTHORING.md +1 -1
- package/package.json +1 -1
- package/src/assertions/bodies/a13.ts +3 -3
- package/src/assertions/bodies/a14.ts +3 -3
- package/src/assertions/bodies/a15.ts +5 -5
- package/src/assertions/bodies/a22.ts +3 -3
- package/src/assertions/facts/animated_bones.ts +4 -2
- package/src/assertions/facts/mesh_attachments.ts +33 -4
- package/src/assertions/model/animated_bones.ts +13 -5
- package/src/assertions/model/bone_timelines.ts +5 -3
- package/src/assertions/model/declared.ts +127 -0
- package/src/assertions/model/index.ts +63 -40
- package/src/assertions/model/mesh_attachments.ts +31 -2
- package/src/assertions/model/parse.ts +9 -1
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/slider_composition.ts +10 -8
- package/src/assertions/model/stepped_poses.ts +3 -2
- package/src/bonedist.ts +4 -5
- package/src/check.ts +3 -2
- package/src/checkpics.ts +1 -1
- package/src/cli/core_commands.ts +1532 -0
- package/src/cli/shared.ts +1935 -0
- package/src/cli/spine_commands.ts +1141 -0
- package/src/core/additive.ts +52 -62
- package/src/core/constraints.ts +26 -3
- package/src/core/constraints_physics.ts +11 -1
- package/src/core/constraints_slider.ts +136 -6
- package/src/core/index.ts +27 -3
- package/src/core/skins.ts +28 -3
- package/src/core/walk.ts +9 -36
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformmeasure.ts +20 -48
- package/src/deformreport.ts +2 -2
- package/src/framing.ts +1 -1
- package/src/pose.ts +1 -1
- package/src/preview.ts +1 -1
- package/src/region_joins.ts +54 -0
- package/src/render.ts +206 -2836
- package/src/render_core.ts +19 -36
- package/src/render_shared.ts +2887 -0
- package/src/slots.ts +1 -1
- package/src/spine_side.ts +123 -0
- package/src/validate.ts +11 -91
- package/src/assertions/facts/skin_meshes.ts +0 -49
- package/src/assertions/model/skin_meshes.ts +0 -90
|
@@ -16,12 +16,21 @@
|
|
|
16
16
|
* — the two lookups the core poses a linked mesh through — and its hull
|
|
17
17
|
* the source record's, read where those two read it.
|
|
18
18
|
* - **The counts the parser derives**: `worldVerticesLength` is the length
|
|
19
|
-
* of `uvs` (the length the parser hands its vertex reader for a mesh
|
|
19
|
+
* of `uvs` (the length the parser hands its vertex reader for a mesh — the
|
|
20
|
+
* reader refuses a document whose `uvs` are not one pair per vertex, so it is
|
|
21
|
+
* also two per vertex), and
|
|
20
22
|
* `hullLength` twice the record's `hull`, which the parser reads at the
|
|
21
23
|
* mesh row's default of the parser-default table when the key is absent
|
|
22
24
|
* (`PARSER_DEFAULTS` in `src/keyorder.ts`, which the selftest holds to the
|
|
23
25
|
* parser row by row).
|
|
24
26
|
*
|
|
27
|
+
* - **`width` and `height`** are the record's, off the document's JSON — a
|
|
28
|
+
* link's its source record's, the two numbers the runtime's `setSourceMesh`
|
|
29
|
+
* writes over the link's own: the core's geometry reader keeps a mesh's
|
|
30
|
+
* vertices, UVs and triangles and not its size, which no pose reads. Since
|
|
31
|
+
* issue #1054 this is the one family of the meshes; cut 4c-1's
|
|
32
|
+
* `SkinMeshFacts` stated the same entries with these two numbers added.
|
|
33
|
+
*
|
|
25
34
|
* `encoding` is empty on every entry: the document states the weighted form
|
|
26
35
|
* outright and names bones, so the clauses about the flat run have no subject
|
|
27
36
|
* here (`../facts/mesh_attachments.ts`'s 🔒).
|
|
@@ -35,6 +44,19 @@ import type { ModelVertices } from '../../model.ts';
|
|
|
35
44
|
import type { MeshBinding, MeshEntry, MeshFacts } from '../facts/mesh_attachments.ts';
|
|
36
45
|
import { fileOrderedEntries } from './skin_entries.ts';
|
|
37
46
|
import type { ReadDocument } from './parse.ts';
|
|
47
|
+
import { isObj } from '../values.ts';
|
|
48
|
+
|
|
49
|
+
/** A record's own `width` and `height` as the document states them, found by skin (the first of that name, as a link resolves its skin), slot and placeholder. */
|
|
50
|
+
function statedSize(read: ReadDocument, skin: string, slot: string, placeholder: string): { width: number; height: number } {
|
|
51
|
+
const skins = Array.isArray(read.json.skins) ? read.json.skins : [];
|
|
52
|
+
const found = skins.find((k: unknown) => isObj(k) && k.name === skin);
|
|
53
|
+
const table = isObj(found) && isObj(found.attachments) ? found.attachments[slot] : undefined;
|
|
54
|
+
const record = isObj(table) ? table[placeholder] : undefined;
|
|
55
|
+
if (!isObj(record) || typeof record.width !== 'number' || typeof record.height !== 'number') {
|
|
56
|
+
throw new Error(`internal: skin "${skin}" slot "${slot}" placeholder "${placeholder}" is a mesh record the document states no width and height for`);
|
|
57
|
+
}
|
|
58
|
+
return { width: record.width, height: record.height };
|
|
59
|
+
}
|
|
38
60
|
|
|
39
61
|
/** The parser's reading of a mesh with no `hull` key, off the parser-default table. */
|
|
40
62
|
function hullDefault(): number {
|
|
@@ -58,8 +80,10 @@ export function modelMeshFacts(read: ReadDocument): MeshFacts {
|
|
|
58
80
|
let uvs: readonly number[];
|
|
59
81
|
let triangles: readonly number[];
|
|
60
82
|
let hull: number | undefined;
|
|
83
|
+
let size: { width: number; height: number };
|
|
61
84
|
if (g.kind === 'mesh') {
|
|
62
85
|
({ vertices, uvs, triangles, hull } = g);
|
|
86
|
+
size = statedSize(read, entry.skin, entry.slot, entry.placeholder);
|
|
63
87
|
} else {
|
|
64
88
|
// `readModel` resolves every link to a mesh or refuses the document, so a miss here is this module's defect.
|
|
65
89
|
const found = sourceVertices(g.skin, g.slot, g.source);
|
|
@@ -69,19 +93,24 @@ export function modelMeshFacts(read: ReadDocument): MeshFacts {
|
|
|
69
93
|
({ uvs, triangles } = drawn);
|
|
70
94
|
const source = doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
|
|
71
95
|
hull = source?.kind === 'mesh' ? source.hull : undefined;
|
|
96
|
+
size = statedSize(read, g.skin, g.slot, g.source);
|
|
72
97
|
}
|
|
98
|
+
const bone = slotBone.get(entry.slot);
|
|
99
|
+
if (bone === undefined) throw new Error(`internal: ${where} names a slot the reader accepted and the document does not hold`);
|
|
73
100
|
const weights: MeshBinding[][] | null = vertices.weighted ? vertices.bindings.map((influences) => influences.map((b) => ({ bone: indexOf(b.bone), weight: Math.fround(b.weight) }))) : null;
|
|
74
101
|
meshes.push({
|
|
75
102
|
name: shownRow(entry).name,
|
|
76
103
|
skin: entry.skin,
|
|
77
104
|
slot: entry.slot,
|
|
78
|
-
slotBone:
|
|
105
|
+
slotBone: bone,
|
|
79
106
|
placeholder: entry.placeholder,
|
|
80
107
|
link: g.kind === 'linkedmesh' ? { source: g.source } : null,
|
|
81
108
|
triangles,
|
|
82
109
|
worldVerticesLength: uvs.length,
|
|
83
110
|
regionUVs: uvs,
|
|
84
111
|
hullLength: 2 * (hull ?? hullDefault()),
|
|
112
|
+
width: size.width,
|
|
113
|
+
height: size.height,
|
|
85
114
|
weights,
|
|
86
115
|
encoding: [],
|
|
87
116
|
});
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
import { CORE_REGION_KINDS, CoreInputError, readModel, shownRow, type CompiledDocument } from '../../core/index.ts';
|
|
41
41
|
import { documentPageLookup, frameRegionName, readUvSequences } from '../../core/uvs.ts';
|
|
42
42
|
import type { AssertionKind } from '../kinds.ts';
|
|
43
|
+
import { rigSectionProblems } from './declared.ts';
|
|
43
44
|
import type { Verdicts } from '../harness.ts';
|
|
44
45
|
|
|
45
46
|
/** The reader's refusal, as a line. */
|
|
@@ -76,7 +77,14 @@ export function modelRead({ fail }: Verdicts, modelText: string): ReadDocument |
|
|
|
76
77
|
fail(A00_MODEL_READ, err.message);
|
|
77
78
|
return null;
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
+
const json = JSON.parse(modelText) as Record<string, unknown>;
|
|
81
|
+
// Issue #1054: the rig section is a source the model side reads (`./declared.ts`), so it is read here with the rest — refused by its paths, never cast.
|
|
82
|
+
const rigProblems = rigSectionProblems(json);
|
|
83
|
+
if (rigProblems.length > 0) {
|
|
84
|
+
fail(A00_MODEL_READ, `the model document: ${rigProblems.length} problem(s) in the rig section the model side reads its declarations from: ${rigProblems.join('; ')}`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return { doc, json };
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/**
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime's words for each timeline a document's animation can hold
|
|
3
|
+
* (issue #1054): the class it loads as, the property ids it registers and what
|
|
4
|
+
* those ids carry — the values A40's sentence prints, which the document has no
|
|
5
|
+
* field for and the core has no reason to know.
|
|
6
|
+
*
|
|
7
|
+
* ⭐ **Why they live here and not beside the core's table.** Until issue #1054
|
|
8
|
+
* this table stood in `src/core/additive.ts`, beside the mode each kind's
|
|
9
|
+
* application has: 34 runtime class names and their property names as string
|
|
10
|
+
* literals in a module that otherwise speaks the document's words (#1011's
|
|
11
|
+
* second ask). The mode is the core's — it decides what the probe computes —
|
|
12
|
+
* and stays there, keyed by the document's own words (`ADDITIVE_MODE`); what
|
|
13
|
+
* the runtime calls the timeline is a fact about the validator's other
|
|
14
|
+
* supplier, and sits here with the constraint classes' names
|
|
15
|
+
* (`RUNTIME_CONSTRAINT_CLASS`, `./constraints.ts`). Both tables are keyed by
|
|
16
|
+
* the same spelling (`additiveSpelling`), and the selftest's `CO27` holds
|
|
17
|
+
* every row of both against the timeline objects spine-core loads, over a
|
|
18
|
+
* seeded population reaching every spelling.
|
|
19
|
+
*
|
|
20
|
+
* Read off the loaded timeline objects, never written from the runtime's
|
|
21
|
+
* source: the class is the object's constructor name, the properties its
|
|
22
|
+
* registered ids' names.
|
|
23
|
+
*
|
|
24
|
+
* Links nothing from the runtime.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** What a timeline's property ids are followed by: its target's index (and, for an attachment's, the attachment), or nothing. */
|
|
28
|
+
export type RuntimeTimelineAddress = 'bone' | 'slot' | 'attachment' | 'constraint' | 'none';
|
|
29
|
+
|
|
30
|
+
/** One timeline spelling, as the runtime loads it: its class, the properties it registers in order, and what its ids carry. */
|
|
31
|
+
export interface RuntimeTimelineRow {
|
|
32
|
+
runtimeClass: string;
|
|
33
|
+
properties: readonly string[];
|
|
34
|
+
address: RuntimeTimelineAddress;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const row = (runtimeClass: string, properties: readonly string[], address: RuntimeTimelineAddress): RuntimeTimelineRow => ({ runtimeClass, properties, address });
|
|
38
|
+
|
|
39
|
+
/** Every timeline spelling a document's animation can hold (`additiveSpelling`'s keys), as the runtime loads it. */
|
|
40
|
+
export const RUNTIME_TIMELINE: Readonly<Record<string, RuntimeTimelineRow>> = {
|
|
41
|
+
'bone rotate': row('RotateTimeline', ['rotate'], 'bone'),
|
|
42
|
+
'bone translate': row('TranslateTimeline', ['x', 'y'], 'bone'),
|
|
43
|
+
'bone translatex': row('TranslateXTimeline', ['x'], 'bone'),
|
|
44
|
+
'bone translatey': row('TranslateYTimeline', ['y'], 'bone'),
|
|
45
|
+
'bone scale': row('ScaleTimeline', ['scaleX', 'scaleY'], 'bone'),
|
|
46
|
+
'bone scalex': row('ScaleXTimeline', ['scaleX'], 'bone'),
|
|
47
|
+
'bone scaley': row('ScaleYTimeline', ['scaleY'], 'bone'),
|
|
48
|
+
'bone shear': row('ShearTimeline', ['shearX', 'shearY'], 'bone'),
|
|
49
|
+
'bone shearx': row('ShearXTimeline', ['shearX'], 'bone'),
|
|
50
|
+
'bone sheary': row('ShearYTimeline', ['shearY'], 'bone'),
|
|
51
|
+
'bone inherit': row('InheritTimeline', ['inherit'], 'bone'),
|
|
52
|
+
'slot attachment': row('AttachmentTimeline', ['attachment'], 'slot'),
|
|
53
|
+
'slot rgba': row('RGBATimeline', ['rgb', 'alpha'], 'slot'),
|
|
54
|
+
'slot rgb': row('RGBTimeline', ['rgb'], 'slot'),
|
|
55
|
+
'slot alpha': row('AlphaTimeline', ['alpha'], 'slot'),
|
|
56
|
+
'slot rgba2': row('RGBA2Timeline', ['rgb', 'alpha', 'rgb2'], 'slot'),
|
|
57
|
+
'slot rgb2': row('RGB2Timeline', ['rgb', 'rgb2'], 'slot'),
|
|
58
|
+
'attachment deform': row('DeformTimeline', ['deform'], 'attachment'),
|
|
59
|
+
'attachment sequence': row('SequenceTimeline', ['sequence'], 'attachment'),
|
|
60
|
+
ik: row('IkConstraintTimeline', ['ikConstraint'], 'constraint'),
|
|
61
|
+
transform: row('TransformConstraintTimeline', ['transformConstraint'], 'constraint'),
|
|
62
|
+
'path position': row('PathConstraintPositionTimeline', ['pathConstraintPosition'], 'constraint'),
|
|
63
|
+
'path spacing': row('PathConstraintSpacingTimeline', ['pathConstraintSpacing'], 'constraint'),
|
|
64
|
+
'path mix': row('PathConstraintMixTimeline', ['pathConstraintMix'], 'constraint'),
|
|
65
|
+
'physics inertia': row('PhysicsConstraintInertiaTimeline', ['physicsConstraintInertia'], 'constraint'),
|
|
66
|
+
'physics strength': row('PhysicsConstraintStrengthTimeline', ['physicsConstraintStrength'], 'constraint'),
|
|
67
|
+
'physics damping': row('PhysicsConstraintDampingTimeline', ['physicsConstraintDamping'], 'constraint'),
|
|
68
|
+
'physics mass': row('PhysicsConstraintMassTimeline', ['physicsConstraintMass'], 'constraint'),
|
|
69
|
+
'physics wind': row('PhysicsConstraintWindTimeline', ['physicsConstraintWind'], 'constraint'),
|
|
70
|
+
'physics gravity': row('PhysicsConstraintGravityTimeline', ['physicsConstraintGravity'], 'constraint'),
|
|
71
|
+
'physics mix': row('PhysicsConstraintMixTimeline', ['physicsConstraintMix'], 'constraint'),
|
|
72
|
+
// A reset registers its property with no constraint index, named or not (measured: both read `physicsConstraintReset`).
|
|
73
|
+
'physics reset': row('PhysicsConstraintResetTimeline', ['physicsConstraintReset'], 'none'),
|
|
74
|
+
'slider time': row('SliderTimeline', ['sliderTime'], 'constraint'),
|
|
75
|
+
'slider mix': row('SliderMixTimeline', ['sliderMix'], 'constraint'),
|
|
76
|
+
drawOrder: row('DrawOrderTimeline', ['drawOrder'], 'none'),
|
|
77
|
+
events: row('EventTimeline', ['event'], 'none'),
|
|
78
|
+
};
|
|
@@ -16,16 +16,17 @@
|
|
|
16
16
|
* timelines at their three levels in the order the file keys them
|
|
17
17
|
* (`keyedOrder` over the document's order, deform before sequence —
|
|
18
18
|
* `emitAttachmentTimelines`' order), then the draw order and the events.
|
|
19
|
-
* - **The words.** Each timeline's class name and properties are the
|
|
20
|
-
* table (`
|
|
21
|
-
*
|
|
19
|
+
* - **The words.** Each timeline's class name and properties are the
|
|
20
|
+
* validator's table (`RUNTIME_TIMELINE`, `./runtime_timelines.ts`; the
|
|
21
|
+
* core's until issue #1054), the one value here that is not the document's,
|
|
22
|
+
* measured off the loaded timelines; an id's index is
|
|
22
23
|
* the target's position in the document's bones, slots or constraints —
|
|
23
24
|
* which the emitter writes in that order — `-1` for a physics timeline
|
|
24
25
|
* naming none; a deform's attachment is named as the runtime names it, the
|
|
25
26
|
* record's `name`, else its placeholder.
|
|
26
27
|
* - **The behaviour** is the core's probe over the document under no skin set
|
|
27
|
-
* (`noSkinView`,
|
|
28
|
-
*
|
|
28
|
+
* (`noSkinView`, the view measured in issue #1051) and under each skin
|
|
29
|
+
* (`underSkin`): `additiveCells`, and the
|
|
29
30
|
* class `additiveBehaviour` reads off them. The core suite's `CO27` holds
|
|
30
31
|
* both to the runtime's probe; the selftest asks this supplier every
|
|
31
32
|
* question the body asks the runtime's (`VF14`).
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
* Links nothing from the runtime.
|
|
34
35
|
*/
|
|
35
36
|
import { fileSkinOrder, keyedOrder } from '../../compile.ts';
|
|
36
|
-
import {
|
|
37
|
+
import { additiveBehaviour, additiveCells, additiveSpelling, type AdditiveTimeline, type AdditiveView } from '../../core/additive.ts';
|
|
38
|
+
import { RUNTIME_TIMELINE } from './runtime_timelines.ts';
|
|
37
39
|
import { EVERY_GLOBAL_PHYSICS, type PhysicsTimelineKind } from '../../core/constraints_physics.ts';
|
|
38
40
|
import { underSkin, type CompiledDocument } from '../../core/index.ts';
|
|
39
41
|
import type { BoneTimelineKind, SlotTimelineKind } from '../../core/animation.ts';
|
|
@@ -57,8 +59,8 @@ const keyedFirsts = (names: readonly string[]): string[] => keyedOrder(names.fil
|
|
|
57
59
|
|
|
58
60
|
/** The words of one timeline: the table's class and properties, each id addressed by `index` (and `attachment`), and the sentence's naming. */
|
|
59
61
|
function factOf(doc: CompiledDocument, core: AdditiveTimeline): SliderTimelineFact {
|
|
60
|
-
const row =
|
|
61
|
-
if (row === undefined) throw new Error(`internal: "${additiveSpelling(core)}" is no row of the
|
|
62
|
+
const row = RUNTIME_TIMELINE[additiveSpelling(core)];
|
|
63
|
+
if (row === undefined) throw new Error(`internal: "${additiveSpelling(core)}" is no row of the runtime's timeline table`);
|
|
62
64
|
const property = (p: string, index: number | null, attachment: string | null, names: string) => ({
|
|
63
65
|
id: [p, ...(index === null ? [] : [String(index)]), ...(attachment === null ? [] : [attachment])].join('|'),
|
|
64
66
|
property: p,
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
* name, so on this side every bone of every pose holds one and the answer is
|
|
22
22
|
* `null` — derived from the pose rather than assumed, so a reader that
|
|
23
23
|
* started admitting such a spelling would show up here as a bone with none.
|
|
24
|
-
* - **Under no skin set**, as `validate()` poses (`noSkinView
|
|
25
|
-
*
|
|
24
|
+
* - **Under no skin set**, as `validate()` poses (`noSkinView`, the view
|
|
25
|
+
* measured in issue #1051: no skin's bones or constraints applied, a slot
|
|
26
|
+
* looked up in the default skin alone).
|
|
26
27
|
*
|
|
27
28
|
* Links nothing from the runtime.
|
|
28
29
|
*/
|
package/src/bonedist.ts
CHANGED
|
@@ -84,14 +84,13 @@ import {
|
|
|
84
84
|
type BoneSnapshot,
|
|
85
85
|
type Posable,
|
|
86
86
|
} from './render.ts';
|
|
87
|
+
import { BONEDIST_SPEC, IDENTITY_CORRESPONDENCE } from './correspondence.ts';
|
|
87
88
|
|
|
88
89
|
export class BoneDistError extends Error {}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/** What `--bones identity` is spelled as, where a file path would go. */
|
|
94
|
-
export const IDENTITY_CORRESPONDENCE = 'identity';
|
|
91
|
+
// `BONEDIST_SPEC` and `IDENTITY_CORRESPONDENCE` are `./correspondence.ts`'s since issue #1052: the CLI's help names
|
|
92
|
+
// both, and an entry that links nothing of the runtime prints that help. Re-exported here.
|
|
93
|
+
export { BONEDIST_SPEC, IDENTITY_CORRESPONDENCE };
|
|
95
94
|
|
|
96
95
|
/** How many bones the report's per-bone table shows before it says "and N more". */
|
|
97
96
|
export const BONE_TABLE_ROWS = 8;
|
package/src/check.ts
CHANGED
|
@@ -92,13 +92,14 @@ import {
|
|
|
92
92
|
type FramesSidecar,
|
|
93
93
|
type FrameSet,
|
|
94
94
|
type MakeCorePoser,
|
|
95
|
-
type Posable,
|
|
96
95
|
type PoseOptions,
|
|
97
96
|
type Poser,
|
|
98
97
|
type PoserName,
|
|
99
98
|
type TextureSubstitution,
|
|
100
99
|
type Viewport,
|
|
101
|
-
} from './
|
|
100
|
+
} from './render_shared.ts';
|
|
101
|
+
// The one name here that is the runtime's: a candidate's pages, read off the shape spine-core's loader returns.
|
|
102
|
+
import type { Posable } from './render.ts';
|
|
102
103
|
import {
|
|
103
104
|
applyFit,
|
|
104
105
|
boxHeight,
|
package/src/checkpics.ts
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
type Framing,
|
|
49
49
|
type FrameCheck,
|
|
50
50
|
} from './check.ts';
|
|
51
|
-
import { FRAMES_SIDECAR, FRAMES_SPEC } from './
|
|
51
|
+
import { FRAMES_SIDECAR, FRAMES_SPEC } from './render_shared.ts';
|
|
52
52
|
import { isContent } from './framing.ts';
|
|
53
53
|
import { isAttributable } from './slots.ts';
|
|
54
54
|
import { encodePng, Plate, type RGBA } from '../tools/plate.ts';
|