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
package/src/slots.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
463
|
-
|
|
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,
|
|
2661
|
-
check('A14_NO_FULL_FRAME_MESH', () => a14NoFullFrameMesh(verdicts,
|
|
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,
|
|
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,
|
|
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
|
-
}
|