spine-rigc 1.9.2 → 1.9.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.
Files changed (49) hide show
  1. package/cli.ts +28 -4397
  2. package/docs/AUTHORING.md +4 -4
  3. package/package.json +1 -1
  4. package/src/assertions/bodies/a06.ts +53 -3
  5. package/src/assertions/bodies/a13.ts +3 -3
  6. package/src/assertions/bodies/a14.ts +3 -3
  7. package/src/assertions/bodies/a15.ts +5 -5
  8. package/src/assertions/bodies/a19.ts +44 -2
  9. package/src/assertions/bodies/a22.ts +3 -3
  10. package/src/assertions/facts/animated_bones.ts +4 -2
  11. package/src/assertions/facts/mesh_attachments.ts +33 -4
  12. package/src/assertions/model/animated_bones.ts +13 -5
  13. package/src/assertions/model/bone_timelines.ts +5 -3
  14. package/src/assertions/model/declared.ts +127 -0
  15. package/src/assertions/model/index.ts +63 -40
  16. package/src/assertions/model/mesh_attachments.ts +31 -2
  17. package/src/assertions/model/parse.ts +9 -1
  18. package/src/assertions/model/runtime_timelines.ts +78 -0
  19. package/src/assertions/model/slider_composition.ts +10 -8
  20. package/src/assertions/model/stepped_poses.ts +3 -2
  21. package/src/bonedist.ts +4 -5
  22. package/src/check.ts +3 -2
  23. package/src/checkpics.ts +1 -1
  24. package/src/cli/core_commands.ts +1532 -0
  25. package/src/cli/shared.ts +1935 -0
  26. package/src/cli/spine_commands.ts +1141 -0
  27. package/src/core/additive.ts +52 -62
  28. package/src/core/constraints.ts +26 -3
  29. package/src/core/constraints_physics.ts +11 -1
  30. package/src/core/constraints_slider.ts +136 -6
  31. package/src/core/index.ts +27 -3
  32. package/src/core/skins.ts +28 -3
  33. package/src/core/walk.ts +9 -36
  34. package/src/correspondence.ts +15 -0
  35. package/src/deformbuild.ts +60 -0
  36. package/src/deformmeasure.ts +20 -48
  37. package/src/deformreport.ts +2 -2
  38. package/src/framing.ts +1 -1
  39. package/src/pose.ts +1 -1
  40. package/src/preview.ts +1 -1
  41. package/src/region_joins.ts +54 -0
  42. package/src/render.ts +206 -2836
  43. package/src/render_core.ts +19 -36
  44. package/src/render_shared.ts +2887 -0
  45. package/src/slots.ts +1 -1
  46. package/src/spine_side.ts +123 -0
  47. package/src/validate.ts +11 -91
  48. package/src/assertions/facts/skin_meshes.ts +0 -49
  49. package/src/assertions/model/skin_meshes.ts +0 -90
package/src/slots.ts CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  type Frame,
55
55
  type Footprint,
56
56
  type Viewport,
57
- } from './render.ts';
57
+ } from './render_shared.ts';
58
58
 
59
59
  /** Components smaller than this are antialiasing crumbs, not parts. */
60
60
  const MIN_COMPONENT_PIXELS = 4;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The seam the Spine side registers into (issue #1052, step 4e of #380).
3
+ *
4
+ * ⭐ Everything rigc does without spine-core lives in modules that link
5
+ * nothing of it — the posing seam and its samplers, the candidate and its
6
+ * poser choice, the framing and the rasteriser (`./render_shared.ts`), the
7
+ * deform survey's choice of reader (`./deformbuild.ts`). Where one of those
8
+ * reaches an input only the runtime can read — a Spine export, `--poser
9
+ * spine`, a fallback the poser line names, a skeleton spine-core already
10
+ * parsed — it asks this registry for the Spine side instead of importing it.
11
+ * The modules that link the runtime fill it when they are loaded:
12
+ * `./render.ts` registers the posing side (spine-core's poser, the export's
13
+ * loader, its atlas reader), `./deformmeasure.ts` the survey's spine-core
14
+ * reader and poser. So a program that imports either of them — `cli.ts`, the
15
+ * tools, the selftest — behaves exactly as it did before the registry
16
+ * existed, and an entry that imports neither links nothing of the runtime and
17
+ * refuses such an input by name (`SpineRuntimeError`, the sentence below).
18
+ *
19
+ * 🔒 Nothing here is a fallback or a default. With nothing registered, the
20
+ * question "what would spine-core say about this input" has no answer, and
21
+ * the refusal says which input asked it and why it needed the runtime.
22
+ */
23
+ import type { DeformSurvey } from './deformsurvey.ts';
24
+ import type { DeformSurveyInput } from './deformbuild.ts';
25
+ import type { Poser, SkeletonFacts, SkinRoster, SubstituteRegion } from './render_shared.ts';
26
+
27
+ /**
28
+ * A skeleton spine-core parsed, as the side of the seam that does not link
29
+ * the runtime holds it: opaque. Only the module that registered the posing
30
+ * side reads it (`SkeletonData` in `./render.ts`), so nothing here names the
31
+ * runtime's type.
32
+ */
33
+ export type SpineSkeletonData = object;
34
+
35
+ /**
36
+ * Posing an input through spine-core when the runtime cannot be reached —
37
+ * refused naming the input and why it needs the runtime (issue #1014).
38
+ *
39
+ * A Spine export, `--poser spine` and a fallback the poser line names are what
40
+ * spine-core is loaded for; a rigc build the core poses reads nothing through
41
+ * it. So the one run that can meet a runtime it cannot use is one of those
42
+ * three, and it says so rather than surfacing whatever the first access threw.
43
+ *
44
+ * Since issue #1052 it is also the refusal of an entry that registered no
45
+ * Spine side (`SPINE_SIDE_ABSENT`): the same three inputs, the same sentence,
46
+ * and the reason is that the runtime is not linked at all.
47
+ */
48
+ export class SpineRuntimeError extends Error {}
49
+
50
+ /** What `render` and `check` say after the reason, naming what the runtime is for. */
51
+ export const POSING_RUNTIME_TAIL =
52
+ 'spine-core is what poses a Spine export, --poser spine and a fallback the poser line names; ' +
53
+ 'a rigc build the core poses reads nothing through it';
54
+
55
+ /** What `explain`'s deform survey says after the reason. */
56
+ export const SURVEY_RUNTIME_TAIL =
57
+ 'spine-core is what reads and poses a Spine export, --poser spine and a fallback the survey names; ' +
58
+ 'a rigc build the core poses reads nothing through it';
59
+
60
+ /**
61
+ * The reason an entry that registered no Spine side gives — in the place the
62
+ * runtime's own error stands when it is linked and cannot be used.
63
+ */
64
+ export const SPINE_SIDE_ABSENT = 'the runtime side is not installed in this entry, which links nothing of spine-core';
65
+
66
+ /** The one sentence a run that needs the runtime and cannot use it is refused in — `tail` says what the runtime is for. */
67
+ export function spineRuntimeSentence(label: string, why: string, reason: string, tail: string): string {
68
+ return `${label} is posed through spine-core (${why}), and the runtime could not be used: ${reason}. ${tail}`;
69
+ }
70
+
71
+ /** What `./render.ts` registers: spine-core's poser, the export's loader and its atlas reader. */
72
+ export interface SpinePosingSide {
73
+ /** Touch the runtime once and refuse by name when it cannot be used (`SpineRuntimeError`). */
74
+ requireRuntime(label: string, why: string): void;
75
+ /** A skeleton parsed against its atlas; a pair the runtime cannot load is `refuse`'s refusal, given the runtime's message. */
76
+ skeletonData(skeletonText: string, atlasText: string, refuse: (runtime: string) => Error): SpineSkeletonData;
77
+ /** What `render` and `check` read off a skeleton spine-core parsed, `atlasText` the atlas it was parsed through. */
78
+ facts(data: SpineSkeletonData, atlasText: string | null): SkeletonFacts;
79
+ /** spine-core's implementation of the posing seam over a parsed skeleton. */
80
+ poser(data: SpineSkeletonData): Poser;
81
+ /** The skin roster of a parsed skeleton, as the runtime flags its bones under each skin. */
82
+ skinRoster(data: SpineSkeletonData): SkinRoster;
83
+ /** The page names an atlas declares, in its order, as the runtime's atlas reader reads them. */
84
+ atlasPageNames(atlasText: string): string[];
85
+ /** An atlas's pages and regions as the runtime reads them, for a texture substitution. */
86
+ substitution(atlasText: string): { pages: string[]; regions: Map<string, SubstituteRegion> };
87
+ }
88
+
89
+ /** What `./deformmeasure.ts` registers: the deform survey read and posed through spine-core. */
90
+ export interface SpineSurveySide {
91
+ /** The survey of `input`'s Spine skeleton through the runtime; `why` the reason the survey names (`null`: `--poser spine`). */
92
+ throughSpine(label: string, why: string | null, input: DeformSurveyInput, exempt: ReadonlySet<string>): DeformSurvey;
93
+ }
94
+
95
+ let posing: SpinePosingSide | null = null;
96
+ let survey: SpineSurveySide | null = null;
97
+
98
+ /** Called by `./render.ts` when it is loaded. */
99
+ export function registerSpinePosing(side: SpinePosingSide): void {
100
+ posing = side;
101
+ }
102
+
103
+ /** Called by `./deformmeasure.ts` when it is loaded. */
104
+ export function registerSpineSurvey(side: SpineSurveySide): void {
105
+ survey = side;
106
+ }
107
+
108
+ /** The posing side, or a refusal of `label` (needed for `why`) naming the runtime as absent. */
109
+ export function spinePosingFor(label: string, why: string): SpinePosingSide {
110
+ if (posing === null) throw new SpineRuntimeError(spineRuntimeSentence(label, why, SPINE_SIDE_ABSENT, POSING_RUNTIME_TAIL));
111
+ return posing;
112
+ }
113
+
114
+ /** The survey side, or a refusal of `label` (needed for `why`) naming the runtime as absent. */
115
+ export function spineSurveyFor(label: string, why: string): SpineSurveySide {
116
+ if (survey === null) throw new SpineRuntimeError(spineRuntimeSentence(label, why, SPINE_SIDE_ABSENT, SURVEY_RUNTIME_TAIL));
117
+ return survey;
118
+ }
119
+
120
+ /** Which sides are registered in this process — what an entry's own report of itself reads. */
121
+ export function registeredSpineSides(): { posing: boolean; survey: boolean } {
122
+ return { posing: posing !== null, survey: survey !== null };
123
+ }
package/src/validate.ts CHANGED
@@ -87,7 +87,7 @@ import { a11NoClippingAttachments } from './assertions/bodies/a11.ts';
87
87
  import { a17AtlasPageFilesExist } from './assertions/bodies/a17.ts';
88
88
  import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from './assertions/bodies/a45.ts';
89
89
  import { attachmentRegionLookups, type AttachmentRegionJoin } from './assertions/region_lookups.ts';
90
- import type { MeshEntry, SkinMeshFacts } from './assertions/facts/skin_meshes.ts';
90
+ import { attachmentRegionJoins } from './region_joins.ts';
91
91
  import type { AnimatedBoneFacts } from './assertions/facts/animated_bones.ts';
92
92
  import type { RegionJoinFacts } from './assertions/facts/region_joins.ts';
93
93
  import type { StageFacts } from './assertions/facts/stage.ts';
@@ -458,46 +458,9 @@ const SPINE_4_3: SpineGeneration = '4.3';
458
458
  // sides, and the model side links nothing from the runtime. Re-exported here.
459
459
  export { attachmentRegionLookups, type AttachmentRegionJoin };
460
460
 
461
- /**
462
- * Every atlas-region lookup `AtlasAttachmentLoader` will perform, read off the
463
- * RAW skeleton JSON — before the loader is asked, which is the whole point.
464
- *
465
- * 🚨 This is a SECOND implementation of a join `spine-core` already performs,
466
- * and the tree's standing judgment about a second opinion on somebody else's
467
- * format is that it is measured rather than asserted: `PS127` runs the loader
468
- * with its `findRegion` recording what it asked for, and compares. A wrong walk
469
- * here would refuse correct foreign data by name, which is the one failure that
470
- * would be worse than the silence #589 removed.
471
- *
472
- * Which entries resolve a region is the parser's list, not a guess:
473
- * `SkeletonJson.readAttachment` (`dist/SkeletonJson.js:524-575`) calls the
474
- * loader with a path for `region`, `mesh` and `linkedmesh` — a linked mesh
475
- * resolves its own region before the `source` branch — and for nothing else.
476
- * `type` defaults to `region` (`:527`), `name` to the placeholder (`:526`) and
477
- * `path` to the name (`:529`, `:560`): three names that default into one
478
- * another, which is why a report printing only the last of them cannot say
479
- * what to change.
480
- */
481
- export function attachmentRegionJoins(raw: unknown): AttachmentRegionJoin[] {
482
- const joins: AttachmentRegionJoin[] = [];
483
- if (!isObj(raw) || !Array.isArray(raw.skins)) return joins;
484
- for (const skin of raw.skins as unknown[]) {
485
- if (!isObj(skin) || !isObj(skin.attachments)) continue;
486
- const skinName = typeof skin.name === 'string' ? skin.name : '(unnamed)';
487
- for (const [slot, entries] of Object.entries(skin.attachments)) {
488
- if (!isObj(entries)) continue;
489
- for (const [placeholder, entry] of Object.entries(entries)) {
490
- if (!isObj(entry)) continue;
491
- const type = entry.type === undefined ? 'region' : entry.type;
492
- if (type !== 'region' && type !== 'mesh' && type !== 'linkedmesh') continue;
493
- const name = typeof entry.name === 'string' ? entry.name : placeholder;
494
- const path = typeof entry.path === 'string' ? entry.path : name;
495
- joins.push({ skin: skinName, slot, placeholder, name, lookups: attachmentRegionLookups(entry.sequence, path) });
496
- }
497
- }
498
- }
499
- return joins;
500
- }
461
+ // `attachmentRegionJoins` is `./region_joins.ts`'s since issue #1052: `explain` reads it, and an entry that links
462
+ // nothing of the runtime has to be able to load what `explain` reads. Re-exported here.
463
+ export { attachmentRegionJoins };
501
464
 
502
465
  /**
503
466
  * The mesh keys the `source` branch never reaches — the geometry a linked mesh
@@ -923,7 +886,6 @@ export function runtimeFacts(skeletonText: string, atlasText: string): {
923
886
  skinEntries: SkinEntryFacts;
924
887
  atlasPages: AtlasPageFacts;
925
888
  slotColour: SlotColourFacts;
926
- skinMeshes: SkinMeshFacts;
927
889
  animatedBones: AnimatedBoneFacts;
928
890
  skinMembers: SkinMemberFacts;
929
891
  regionJoins: RegionJoinFacts;
@@ -946,7 +908,6 @@ export function runtimeFacts(skeletonText: string, atlasText: string): {
946
908
  skinEntries: spineSkinEntries(data),
947
909
  atlasPages: { atlas },
948
910
  slotColour: spineSlotColourFacts(raw, data),
949
- skinMeshes: spineSkinMeshes(data),
950
911
  animatedBones: spineAnimatedBones(raw),
951
912
  skinMembers: data,
952
913
  regionJoins: spineRegionJoins(atlasText, raw),
@@ -1274,48 +1235,6 @@ export function spineSequenceFacts(raw: Json | null, data: ReturnType<SkeletonJs
1274
1235
  };
1275
1236
  }
1276
1237
 
1277
- /**
1278
- * The runtime's supply of `SkinMeshFacts` (issue #1025, cut 4c-1): every
1279
- * loaded `MeshAttachment` of every skin, linked meshes included, in the loaded
1280
- * skins' order — the walk this file's prelude makes for `meshAttachments` —
1281
- * with its slot and that slot's bone, and the bones its weights name decoded
1282
- * off the loaded index run, a bone index the skeleton lacks left out as A15
1283
- * always left it out. The model side's supply is
1284
- * `./assertions/model/skin_meshes.ts`.
1285
- */
1286
- export function spineSkinMeshes(data: ReturnType<SkeletonJson['readSkeletonData']>): SkinMeshFacts {
1287
- const meshes: MeshEntry[] = [];
1288
- for (const skin of data.skins) {
1289
- for (const entry of skin.getAttachments()) {
1290
- const mesh = entry.attachment;
1291
- if (!(mesh instanceof MeshAttachment)) continue;
1292
- const weightBones: string[] = [];
1293
- if (mesh.bones) {
1294
- for (let i = 0; i < mesh.bones.length; ) {
1295
- const boneCount = mesh.bones[i++];
1296
- for (let n = 0; n < boneCount; n++, i++) {
1297
- const bone = data.bones[mesh.bones[i]];
1298
- if (bone) weightBones.push(bone.name);
1299
- }
1300
- }
1301
- }
1302
- const slot = data.slots[entry.slotIndex];
1303
- meshes.push({
1304
- name: mesh.name,
1305
- slot: slot.name,
1306
- slotBone: slot.boneData.name,
1307
- triangles: mesh.triangles,
1308
- width: mesh.width,
1309
- height: mesh.height,
1310
- regionUVs: mesh.regionUVs,
1311
- worldVerticesLength: mesh.worldVerticesLength,
1312
- weightBones,
1313
- });
1314
- }
1315
- }
1316
- return { meshes };
1317
- }
1318
-
1319
1238
  /**
1320
1239
  * The runtime's supply of `AnimatedBoneFacts` (issue #1025, cut 4c-1): read
1321
1240
  * off the skeleton JSON, as A15 always read the bones `idle` keys — an
@@ -1410,6 +1329,8 @@ export function spineMeshFacts(data: ReturnType<SkeletonJson['readSkeletonData']
1410
1329
  worldVerticesLength: att.worldVerticesLength,
1411
1330
  regionUVs: Array.from(att.regionUVs ?? []),
1412
1331
  hullLength: att.hullLength,
1332
+ width: att.width,
1333
+ height: att.height,
1413
1334
  weights,
1414
1335
  encoding,
1415
1336
  });
@@ -2634,8 +2555,7 @@ export function validate(input: ValidateInput): ValidateReport {
2634
2555
  // the runtime's supply of `SkinEntryFacts`, in the loaded skins' own order
2635
2556
  // (issue #1025).
2636
2557
  const skinEntries = spineSkinEntries(data);
2637
- // What the mesh rules that moved read of the skins (issue #1025, cut 4c-1).
2638
- const skinMeshes = spineSkinMeshes(data);
2558
+ // What the mesh rules that moved read of the skins (issue #1025, cut 4c-1) is the one family of the meshes since issue #1054, `loadedMeshFacts`.
2639
2559
 
2640
2560
  // --- A03: every region has finite width/height (case 6c) ---------------
2641
2561
  check('A03_REGION_WIDTH_HEIGHT_FINITE', () => a03RegionWidthHeightFinite(verdicts, skinEntries));
@@ -2657,8 +2577,8 @@ export function validate(input: ValidateInput): ValidateReport {
2657
2577
  // constant in the validator would fail correct foreign data in the name of
2658
2578
  // somebody else's canvas. A rig that declares no budget has nothing to be
2659
2579
  // measured against, and the assertion says so instead of inventing a wall.
2660
- check('A13_MESH_BUDGET', () => a13MeshBudget(verdicts, skinMeshes, input));
2661
- check('A14_NO_FULL_FRAME_MESH', () => a14NoFullFrameMesh(verdicts, skinMeshes, spineStage(data)));
2580
+ check('A13_MESH_BUDGET', () => a13MeshBudget(verdicts, loadedMeshFacts(), input));
2581
+ check('A14_NO_FULL_FRAME_MESH', () => a14NoFullFrameMesh(verdicts, loadedMeshFacts(), spineStage(data)));
2662
2582
 
2663
2583
  // --- A15: idle must not key a mesh-driving bone (dirty-skip lever) -----
2664
2584
  //
@@ -2669,7 +2589,7 @@ export function validate(input: ValidateInput): ValidateReport {
2669
2589
  // of them weighted meshes, and an `idle` whose job is to move them — so the
2670
2590
  // rig can say so in `invariants.idleDrivesMeshes`, and the rule then reports
2671
2591
  // what the declaration costs instead of refusing each bone.
2672
- check('A15_IDLE_NO_MESH_BONE_KEYS', () => a15IdleNoMeshBoneKeys(verdicts, skinMeshes, spineAnimatedBones(raw), input));
2592
+ check('A15_IDLE_NO_MESH_BONE_KEYS', () => a15IdleNoMeshBoneKeys(verdicts, loadedMeshFacts(), spineAnimatedBones(raw), input));
2673
2593
 
2674
2594
  // --- A20/A21/A22: the mesh checks the parser will never make ------------
2675
2595
  //
@@ -2684,7 +2604,7 @@ export function validate(input: ValidateInput): ValidateReport {
2684
2604
 
2685
2605
  check('A21_MESH_RIM_PINNED', () => a21MeshRimPinned(verdicts, loadedMeshFacts(), input.rig));
2686
2606
 
2687
- check('A22_MESH_UVS_IN_UNIT_RANGE', () => a22MeshUvsInUnitRange(verdicts, skinMeshes));
2607
+ check('A22_MESH_UVS_IN_UNIT_RANGE', () => a22MeshUvsInUnitRange(verdicts, loadedMeshFacts()));
2688
2608
 
2689
2609
  // --- A39: a deform key that turns a triangle inside out ------------------
2690
2610
  //
@@ -1,49 +0,0 @@
1
- /**
2
- * The skins' mesh entries, as the budget and canvas rules read them (issue
3
- * #1025, step 4c of #380) — the census's F03, F05 and F08 for A13, A14, A15 and
4
- * A22: every mesh a skin holds, linked meshes included, with where it sits
5
- * (its slot and that slot's bone), how big it is (its size, its triangle list,
6
- * its authored UVs and how many coordinates its vertices make) and which bones
7
- * its weights name.
8
- *
9
- * ⭐ **A linked mesh is the geometry it draws.** The runtime loads a link as a
10
- * mesh whose triangles, UVs, vertices, weights, width and height are its
11
- * source's (`setSourceMesh` overwrites the link's own `width` and `height`),
12
- * so an entry for a link carries its source's — the model side reads them
13
- * through the core's own join, `sourceOfDoc`, never a copy of the rule.
14
- *
15
- * ⭐ **The order is the file's**, as `./skin_entries.ts` states it: skins in
16
- * the emitter's order, a skin's entries slot by slot in the skeleton's slot
17
- * order, each slot's in its table's order. A13 and A22 print one line per
18
- * offending mesh in this order.
19
- *
20
- * Links nothing from the runtime: `validate()` reads each entry off
21
- * spine-core's loaded `MeshAttachment`, and the model side off the document
22
- * (`../model/skin_meshes.ts`).
23
- */
24
-
25
- /** One mesh entry of one skin. */
26
- export interface MeshEntry {
27
- /** The name it loaded under: its `name`, else its placeholder. */
28
- readonly name: string;
29
- /** The slot whose table holds it. */
30
- readonly slot: string;
31
- /** That slot's bone. */
32
- readonly slotBone: string;
33
- /** Its triangle list, three vertex indices per triangle. */
34
- readonly triangles: readonly number[];
35
- readonly width: number;
36
- readonly height: number;
37
- /** The UVs the file authored, two per vertex — the runtime's `regionUVs`, not the page-space `uvs` a renderer computes. */
38
- readonly regionUVs: ArrayLike<number>;
39
- /** Two per vertex, whatever the vertex encoding — the count the runtime recomputes. */
40
- readonly worldVerticesLength: number;
41
- /** Every bone its weights name, in the weight run's order (repeats kept); empty for an unweighted mesh. */
42
- readonly weightBones: readonly string[];
43
- }
44
-
45
- /** What A13, A14, A15 and A22 read of the skins. */
46
- export interface SkinMeshFacts {
47
- /** Every mesh entry of every skin, in the file's walk order. */
48
- readonly meshes: readonly MeshEntry[];
49
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * The model side's supply of `SkinMeshFacts` (issue #1025, step 4c of #380):
3
- * every mesh and linked-mesh record of every skin, read off the document in the
4
- * file's walk order.
5
- *
6
- * - **The order** is `fileOrderedEntries`'s (`./skin_entries.ts`): the skins by
7
- * the file's order (`fileSkinOrder`, issue #1034), the slots in the document's slot order, a
8
- * slot's records in its table's order — the walk the selftest measures equal
9
- * to spine-core's `getAttachments()`.
10
- * - **A linked mesh's geometry is its source's**, read through the core's own
11
- * joins rather than a copy of them: the vertices through `sourceOfDoc` and
12
- * the UVs and triangles through the draw walk's `meshOf` (`drawWalkOf`), both
13
- * in `src/core/index.ts`; and its `width` and `height` are the source
14
- * record's too, the two numbers `setSourceMesh` overwrites the link's own
15
- * with. `readModel` refuses a link whose source does not resolve, so a miss
16
- * here is this module's defect.
17
- * - **The bones a mesh's weights name** are its bindings' bone names, in the
18
- * bindings' order — the document states a weighted vertex by bone NAME, and
19
- * the Spine file's index run is the emitter's encoding of exactly that list.
20
- * - **`worldVerticesLength`** is two per vertex: `2 · bindings.length` for a
21
- * weighted mesh, `xy.length` for a plain one — the derivation
22
- * `src/deformstructure.ts` already makes for the deform survey.
23
- * - **`width` and `height`** are the record's, off the document's JSON: the
24
- * core's geometry reader keeps a mesh's vertices, UVs and triangles and not
25
- * its size, which no pose reads.
26
- *
27
- * Links nothing from the runtime.
28
- */
29
- import { drawWalkOf, shownRow, sourceOfDoc } from '../../core/index.ts';
30
- import type { ModelVertices } from '../../model.ts';
31
- import type { MeshEntry, SkinMeshFacts } from '../facts/skin_meshes.ts';
32
- import { isObj } from '../values.ts';
33
- import type { ReadDocument } from './parse.ts';
34
- import { fileOrderedEntries } from './skin_entries.ts';
35
-
36
- /** 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. */
37
- function statedSize(read: ReadDocument, skin: string, slot: string, placeholder: string): { width: number; height: number } {
38
- const skins = Array.isArray(read.json.skins) ? read.json.skins : [];
39
- const found = skins.find((k: unknown) => isObj(k) && k.name === skin);
40
- const table = isObj(found) && isObj(found.attachments) ? found.attachments[slot] : undefined;
41
- const record = isObj(table) ? table[placeholder] : undefined;
42
- if (!isObj(record) || typeof record.width !== 'number' || typeof record.height !== 'number') {
43
- throw new Error(`internal: skin "${skin}" slot "${slot}" placeholder "${placeholder}" is a mesh record the document states no width and height for`);
44
- }
45
- return { width: record.width, height: record.height };
46
- }
47
-
48
- export function modelSkinMeshes(read: ReadDocument): SkinMeshFacts {
49
- const doc = read.doc;
50
- const sourceVertices = sourceOfDoc(doc);
51
- const walk = drawWalkOf(doc, []);
52
- const slotBone = new Map(doc.slots.map((slot) => [slot.name, slot.bone] as const));
53
- const meshes: MeshEntry[] = [];
54
- for (const entry of fileOrderedEntries(read)) {
55
- if (entry.record.kind !== 'mesh' && entry.record.kind !== 'linkedmesh') continue;
56
- const g = entry.record.geometry;
57
- let vertices: ModelVertices;
58
- let uvs: number[];
59
- let triangles: number[];
60
- let size: { width: number; height: number };
61
- if (g?.kind === 'mesh') {
62
- ({ vertices, uvs, triangles } = g);
63
- size = statedSize(read, entry.skin, entry.slot, entry.placeholder);
64
- } else if (g?.kind === 'linkedmesh') {
65
- const source = sourceVertices(g.skin, g.slot, g.source);
66
- const drawn = walk.meshOf(g.skin, g.slot, g.source);
67
- if (typeof source === 'string' || drawn === undefined) throw new Error(`internal: skin "${entry.skin}" slot "${entry.slot}" placeholder "${entry.placeholder}": ${typeof source === 'string' ? source : 'its source draws no mesh'}, and the reader accepted it`);
68
- vertices = source;
69
- ({ uvs, triangles } = drawn);
70
- size = statedSize(read, g.skin, g.slot, g.source);
71
- } else {
72
- // `readModel` reads every mesh and link record's geometry or refuses the document, so a record without one is this module's defect.
73
- throw new Error(`internal: skin "${entry.skin}" slot "${entry.slot}" placeholder "${entry.placeholder}" is a ${entry.record.kind} record the reader returned without its geometry`);
74
- }
75
- const bone = slotBone.get(entry.slot);
76
- if (bone === undefined) throw new Error(`internal: skin "${entry.skin}" names slot "${entry.slot}", which the reader accepted and the document does not hold`);
77
- meshes.push({
78
- name: shownRow(entry).name,
79
- slot: entry.slot,
80
- slotBone: bone,
81
- triangles,
82
- width: size.width,
83
- height: size.height,
84
- regionUVs: uvs,
85
- worldVerticesLength: vertices.weighted ? 2 * vertices.bindings.length : vertices.xy.length,
86
- weightBones: vertices.weighted ? vertices.bindings.flatMap((vertex) => vertex.map((binding) => binding.bone)) : [],
87
- });
88
- }
89
- return { meshes };
90
- }