rig-c 0.0.0-stage → 2.20.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.
- package/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +30 -0
- package/NOTICE.md +145 -0
- package/README.md +817 -3
- package/bin/rigc.cjs +83 -0
- package/cli.ts +61 -0
- package/cli_core.ts +46 -0
- package/docs/AUTHORING.md +9923 -0
- package/docs/FACE.md +1948 -0
- package/docs/INGEST.md +1488 -0
- package/docs/MOTION.md +1241 -0
- package/docs/PROMPTING.md +109 -0
- package/docs/RIGGING.md +1441 -0
- package/docs/SPEC_COVERAGE.md +357 -0
- package/package.json +108 -4
- package/skills/rigc/SKILL.md +133 -0
- package/skills/rigc-face/SKILL.md +60 -0
- package/skills/rigc-ingest/SKILL.md +78 -0
- package/skills/rigc-motion/SKILL.md +51 -0
- package/skills/rigc-rigging/SKILL.md +49 -0
- package/src/areaband.ts +159 -0
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a03.ts +27 -0
- package/src/assertions/bodies/a04.ts +40 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +245 -0
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a08.ts +76 -0
- package/src/assertions/bodies/a09.ts +82 -0
- package/src/assertions/bodies/a10.ts +116 -0
- package/src/assertions/bodies/a11.ts +15 -0
- package/src/assertions/bodies/a12.ts +30 -0
- package/src/assertions/bodies/a13.ts +51 -0
- package/src/assertions/bodies/a14.ts +35 -0
- package/src/assertions/bodies/a15.ts +97 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a17.ts +26 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +404 -0
- package/src/assertions/bodies/a20.ts +122 -0
- package/src/assertions/bodies/a21.ts +190 -0
- package/src/assertions/bodies/a22.ts +39 -0
- package/src/assertions/bodies/a23.ts +305 -0
- package/src/assertions/bodies/a24.ts +68 -0
- package/src/assertions/bodies/a25.ts +39 -0
- package/src/assertions/bodies/a26.ts +61 -0
- package/src/assertions/bodies/a27.ts +33 -0
- package/src/assertions/bodies/a28.ts +70 -0
- package/src/assertions/bodies/a29.ts +34 -0
- package/src/assertions/bodies/a30.ts +50 -0
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a32.ts +44 -0
- package/src/assertions/bodies/a33.ts +110 -0
- package/src/assertions/bodies/a34.ts +133 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/bodies/a36.ts +81 -0
- package/src/assertions/bodies/a37.ts +77 -0
- package/src/assertions/bodies/a38.ts +73 -0
- package/src/assertions/bodies/a39.ts +303 -0
- package/src/assertions/bodies/a40.ts +128 -0
- package/src/assertions/bodies/a42.ts +97 -0
- package/src/assertions/bodies/a43.ts +181 -0
- package/src/assertions/bodies/a44.ts +23 -0
- package/src/assertions/bodies/a45.ts +172 -0
- package/src/assertions/bodies/a46.ts +224 -0
- package/src/assertions/bodies/a47.ts +126 -0
- package/src/assertions/bodies/a48.ts +83 -0
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/constraint_words.ts +169 -0
- package/src/assertions/emitted/index.ts +148 -0
- package/src/assertions/facts/animated_bones.ts +30 -0
- package/src/assertions/facts/animation_durations.ts +37 -0
- package/src/assertions/facts/atlas_pages.ts +19 -0
- package/src/assertions/facts/atlas_regions.ts +52 -0
- package/src/assertions/facts/bone_timelines.ts +37 -0
- package/src/assertions/facts/constraint_targets.ts +56 -0
- package/src/assertions/facts/constraints.ts +155 -0
- package/src/assertions/facts/deform_survey.ts +27 -0
- package/src/assertions/facts/event_keys.ts +55 -0
- package/src/assertions/facts/linked_meshes.ts +38 -0
- package/src/assertions/facts/mesh_attachments.ts +100 -0
- package/src/assertions/facts/region_joins.ts +34 -0
- package/src/assertions/facts/sequences.ts +85 -0
- package/src/assertions/facts/skeleton_roster.ts +45 -0
- package/src/assertions/facts/skin_entries.ts +37 -0
- package/src/assertions/facts/skin_members.ts +53 -0
- package/src/assertions/facts/slider_composition.ts +78 -0
- package/src/assertions/facts/slot_colour.ts +43 -0
- package/src/assertions/facts/stage.ts +27 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/facts/stepped_poses.ts +74 -0
- package/src/assertions/facts/two_colour.ts +52 -0
- package/src/assertions/facts/vertex_polygons.ts +53 -0
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/harness.ts +109 -0
- package/src/assertions/inward_advance.ts +58 -0
- package/src/assertions/kinds.ts +105 -0
- package/src/assertions/mesh_kinds.ts +56 -0
- package/src/assertions/model/animated_bones.ts +38 -0
- package/src/assertions/model/animation_durations.ts +57 -0
- package/src/assertions/model/atlas_pages.ts +15 -0
- package/src/assertions/model/atlas_regions.ts +76 -0
- package/src/assertions/model/bone_timelines.ts +58 -0
- package/src/assertions/model/constraint_targets.ts +82 -0
- package/src/assertions/model/constraints.ts +233 -0
- package/src/assertions/model/declared.ts +125 -0
- package/src/assertions/model/deform_survey.ts +24 -0
- package/src/assertions/model/event_keys.ts +45 -0
- package/src/assertions/model/given.ts +45 -0
- package/src/assertions/model/index.ts +398 -0
- package/src/assertions/model/linked_meshes.ts +24 -0
- package/src/assertions/model/mesh_attachments.ts +119 -0
- package/src/assertions/model/parse.ts +146 -0
- package/src/assertions/model/region_joins.ts +67 -0
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/sequences.ts +157 -0
- package/src/assertions/model/skeleton_roster.ts +23 -0
- package/src/assertions/model/skin_entries.ts +69 -0
- package/src/assertions/model/skin_members.ts +64 -0
- package/src/assertions/model/slider_composition.ts +193 -0
- package/src/assertions/model/slot_colour.ts +81 -0
- package/src/assertions/model/stage.ts +28 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/assertions/model/stepped_poses.ts +105 -0
- package/src/assertions/model/two_colour.ts +61 -0
- package/src/assertions/model/vertex_polygons.ts +72 -0
- package/src/assertions/reasons.ts +129 -0
- package/src/assertions/region_lookups.ts +61 -0
- package/src/assertions/report.ts +189 -0
- package/src/assertions/values.ts +39 -0
- package/src/atlas.ts +2870 -0
- package/src/ballot.ts +866 -0
- package/src/bonedist.ts +643 -0
- package/src/chainfit.ts +2752 -0
- package/src/chains.ts +170 -0
- package/src/check.ts +4303 -0
- package/src/checkpics.ts +295 -0
- package/src/cli/core_commands.ts +1627 -0
- package/src/cli/repack.ts +414 -0
- package/src/cli/shared.ts +2776 -0
- package/src/cli/spine_commands.ts +820 -0
- package/src/compile.ts +9414 -0
- package/src/core/additive.ts +458 -0
- package/src/core/animation.ts +1050 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1876 -0
- package/src/core/constraints_path.ts +964 -0
- package/src/core/constraints_physics.ts +881 -0
- package/src/core/constraints_slider.ts +635 -0
- package/src/core/deform.ts +613 -0
- package/src/core/draw_order.ts +125 -0
- package/src/core/events.ts +135 -0
- package/src/core/hooks.ts +249 -0
- package/src/core/index.ts +1400 -0
- package/src/core/raw.ts +739 -0
- package/src/core/skins.ts +129 -0
- package/src/core/uvs.ts +469 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/walk.ts +197 -0
- package/src/core/world.ts +289 -0
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformgen.ts +630 -0
- package/src/deformmeasure.ts +732 -0
- package/src/deformreport.ts +373 -0
- package/src/deformstructure.ts +386 -0
- package/src/deformsurvey.ts +2162 -0
- package/src/depth.ts +784 -0
- package/src/diff.ts +2252 -0
- package/src/emit.ts +134 -0
- package/src/emit_spine.ts +854 -0
- package/src/errors.ts +53 -0
- package/src/framing.ts +819 -0
- package/src/generation.ts +139 -0
- package/src/ingest.ts +2293 -0
- package/src/json-position.ts +253 -0
- package/src/keyorder.ts +587 -0
- package/src/keys.ts +486 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +2382 -0
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +2042 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1425 -0
- package/src/model.ts +1245 -0
- package/src/motion.ts +809 -0
- package/src/nonfinite.ts +54 -0
- package/src/package_meta.ts +48 -0
- package/src/png.ts +297 -0
- package/src/pose.ts +2324 -0
- package/src/preview.ts +434 -0
- package/src/region_joins.ts +54 -0
- package/src/render.ts +1013 -0
- package/src/render_core.ts +871 -0
- package/src/render_shared.ts +2958 -0
- package/src/repack.ts +495 -0
- package/src/rig.ts +2941 -0
- package/src/slots.ts +892 -0
- package/src/spine_side.ts +138 -0
- package/src/timelines.ts +837 -0
- package/src/trackgen.ts +364 -0
- package/src/transform.ts +310 -0
- package/src/types.ts +1797 -0
- package/src/validate.ts +3875 -0
- package/tools/contact.ts +126 -0
- package/tools/editor_roundtrip.ts +1641 -0
- package/tools/font5x7.ts +101 -0
- package/tools/measure_contact_depth.ts +105 -0
- package/tools/plate.ts +508 -0
- package/tools/png_probe.mjs +72 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a `deform` key does to the geometry, measured once.
|
|
3
|
+
*
|
|
4
|
+
* ⭐ **One survey, two consumers.** `A39_DEFORM_KEEPS_TRIANGLE_WINDING` reads the
|
|
5
|
+
* reversals out of it and refuses a build; `explain`'s `DEFORM` block prints the
|
|
6
|
+
* whole of it and refuses nothing. That split is issue
|
|
7
|
+
* [#296](https://github.com/firejune/rigc/issues/296)'s two halves —
|
|
8
|
+
* [#314](https://github.com/firejune/rigc/pull/314) landed the assertion and
|
|
9
|
+
* [#316](https://github.com/firejune/rigc/issues/316) the report — and the reason
|
|
10
|
+
* they share this file rather than each posing the skeleton themselves is that
|
|
11
|
+
* the report's `winding 32 of 32 kept` and A39's `8 of 32 reverse` are **the same
|
|
12
|
+
* count**. Two derivations of one number drift, and the one that drifts silently
|
|
13
|
+
* is the one nobody exits non-zero on.
|
|
14
|
+
*
|
|
15
|
+
* ## The frame, and why it is the posed one
|
|
16
|
+
*
|
|
17
|
+
* Both sides of every comparison here are taken at the key's OWN time with the
|
|
18
|
+
* animation applied: the deformed mesh against **the same posed bones with the
|
|
19
|
+
* deform cleared**. Holding the bones at setup instead was tried in #314 and is
|
|
20
|
+
* wrong in principle — a weighted mesh's offsets are authored in bone space
|
|
21
|
+
* against the pose they land in, so setup bones measure a pose that never occurs.
|
|
22
|
+
* Sharing the bones between the two sides is also what makes a MIRRORED slot bone
|
|
23
|
+
* a non-event: a negative determinant flips every triangle on both sides and
|
|
24
|
+
* cancels.
|
|
25
|
+
*
|
|
26
|
+
* ⇒ So every ratio on a key is *the deform's own contribution*, and the
|
|
27
|
+
* denominator is 1.000 by construction rather than by measurement. A block that
|
|
28
|
+
* printed `(setup 1.000)` beside it would be printing the definition.
|
|
29
|
+
*
|
|
30
|
+
* ## What is deliberately NOT here
|
|
31
|
+
*
|
|
32
|
+
* **Deformed coverage.** #296 asked for it and it does not exist: `coverage` is
|
|
33
|
+
* rasterised from the attachment's **uvs** against the part's alpha
|
|
34
|
+
* (`measureAuthoredMeshFit`, called from `src/compile.ts`), and a deform moves
|
|
35
|
+
* positions and never uvs. The figure is therefore identical at every key of
|
|
36
|
+
* every timeline, so a `coverage 100.00% (setup 100.00%)` line would be a
|
|
37
|
+
* tautology dressed as a measurement. What actually moves — how much art each
|
|
38
|
+
* drawn pixel now carries — is the stretch below, and `DR04` in `selftest.ts` is
|
|
39
|
+
* the control that says the coverage figure cannot move.
|
|
40
|
+
*
|
|
41
|
+
* ## What the geometry is not enough to say (issue #401)
|
|
42
|
+
*
|
|
43
|
+
* A winding is a claim about **drawn** pixels: A39's own message says the mesh
|
|
44
|
+
* "draws its texture backwards there", and that sentence is false when the slot
|
|
45
|
+
* draws nothing at that time. So each key also carries `draw` — the attachment
|
|
46
|
+
* the slot actually shows and the alpha it shows it at, both read off the same
|
|
47
|
+
* posed skeleton the geometry came from. A key whose `draw.blank` is set is
|
|
48
|
+
* measured and then **passed over by name**: a triangle that draws no pixels
|
|
49
|
+
* cannot draw them backwards. It is per key and per time, never per slot —
|
|
50
|
+
* `invariants.deformMayFold` is the per-slot instrument and it is a declaration,
|
|
51
|
+
* not a measurement.
|
|
52
|
+
*
|
|
53
|
+
* ## And what a key is not enough to say either (issue #403)
|
|
54
|
+
*
|
|
55
|
+
* The keys are where the data is; they are not where the runtime is. Between two
|
|
56
|
+
* of them it interpolates, so a deform inside its fold angle at every key can be
|
|
57
|
+
* past it in between and no key-time measurement looks there. `scanDeformSpan`
|
|
58
|
+
* closes that, and the derivation is in its own comment: the reversal condition
|
|
59
|
+
* over a span has a **closed form** — a quadratic in the interpolation fraction —
|
|
60
|
+
* so the time is solved for rather than searched, and the measurement taken there
|
|
61
|
+
* is this file's ordinary one, at a time no key lands on.
|
|
62
|
+
*
|
|
63
|
+
* ## And WHICH frame, when the animation is never on a track (issue #407)
|
|
64
|
+
*
|
|
65
|
+
* Everything above says *at the key's own time*, and until #407 that meant one
|
|
66
|
+
* thing: the animation played on track 0. An animation a **slider** applies is
|
|
67
|
+
* never played that way — spine-core says so itself, in
|
|
68
|
+
* `SkeletonData.findSliderAnimations`: *"Slider animations are designed to be
|
|
69
|
+
* applied by slider constraints rather than on their own."* The slider picks the
|
|
70
|
+
* time out of a bone property, so **the key's time and the applied time are the
|
|
71
|
+
* same number by construction**; posing the animation on a track while its own
|
|
72
|
+
* slider applies it at the neutral is a frame no playthrough contains, and it
|
|
73
|
+
* reported a fold on a rig that is correct.
|
|
74
|
+
*
|
|
75
|
+
* ⇒ So a deform key is posed at the **reach** its animation actually has
|
|
76
|
+
* (`DeformReach`): on a track when nothing applies it, and otherwise once per
|
|
77
|
+
* slider, with that slider's own mapping inverted and its driving bone moved
|
|
78
|
+
* until the runtime selects this key's time. Inverting the constraint away
|
|
79
|
+
* instead was considered and refused for A39's own reason — a slider's animation
|
|
80
|
+
* may carry bone tracks that move the very bones the offsets are authored
|
|
81
|
+
* against, so dropping it reintroduces "setup bones measure a pose that never
|
|
82
|
+
* occurs" one level up.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ **What the artifact cannot say, and this therefore does not:** whether a
|
|
85
|
+
* slider's animation is ALSO played on a track somewhere. Nothing in skeleton
|
|
86
|
+
* data records that, so a slider-applied animation is measured in its slider
|
|
87
|
+
* frames only. Two sliders on one animation are two frames and both are measured
|
|
88
|
+
* — one frame's pass never hides another's fold — but a consumer that plays a
|
|
89
|
+
* slider animation on a track as well is outside what this can see.
|
|
90
|
+
*
|
|
91
|
+
* ## Where the halves live (issue #1025, cut 4c-3)
|
|
92
|
+
*
|
|
93
|
+
* Everything above that names no runtime class — the measurements, the
|
|
94
|
+
* survey's interface to its posers, the core's poser and the survey off a
|
|
95
|
+
* model document — is `./deformsurvey.ts`, moved there unchanged so that
|
|
96
|
+
* A39's model-side supplier can reach the survey without reaching spine-core.
|
|
97
|
+
* This file is spine-core's reader and poser and the entries that choose
|
|
98
|
+
* between the two, and re-exports every name it exported before.
|
|
99
|
+
*/
|
|
100
|
+
import {
|
|
101
|
+
AnimationState,
|
|
102
|
+
AnimationStateData,
|
|
103
|
+
type Attachment,
|
|
104
|
+
AtlasAttachmentLoader,
|
|
105
|
+
type Bone,
|
|
106
|
+
type CurveTimeline,
|
|
107
|
+
DeformTimeline,
|
|
108
|
+
FromProperty,
|
|
109
|
+
FromRotate,
|
|
110
|
+
FromScaleX,
|
|
111
|
+
FromScaleY,
|
|
112
|
+
FromShearY,
|
|
113
|
+
FromX,
|
|
114
|
+
FromY,
|
|
115
|
+
MeshAttachment,
|
|
116
|
+
Physics,
|
|
117
|
+
Skeleton,
|
|
118
|
+
type SkeletonData,
|
|
119
|
+
SkeletonJson,
|
|
120
|
+
type Skin,
|
|
121
|
+
Slider,
|
|
122
|
+
SliderData,
|
|
123
|
+
TextureAtlas,
|
|
124
|
+
type Timeline,
|
|
125
|
+
} from '@esotericsoftware/spine-core';
|
|
126
|
+
// #969: the core's side of the seam — the model document read.
|
|
127
|
+
import { readModel } from './core/index.ts';
|
|
128
|
+
// #1019: what the survey reads of the skeleton's structure, and the model document's reading of it.
|
|
129
|
+
import {
|
|
130
|
+
modelStructure,
|
|
131
|
+
type SurveyAnimation,
|
|
132
|
+
type SurveyCurve,
|
|
133
|
+
type SurveyDeformTimeline,
|
|
134
|
+
type SurveyMesh,
|
|
135
|
+
type SurveySkin,
|
|
136
|
+
type SurveySlider,
|
|
137
|
+
type SurveyStructure,
|
|
138
|
+
} from './deformstructure.ts';
|
|
139
|
+
// Issue #1025 (cut 4c-3): the half of the survey that names no runtime class.
|
|
140
|
+
import { BEZIER_POINTS, corePoser, surveyWith, type DeformSurvey, type DialField, type ShownReading, type SurveyPose, type SurveyPoser } from './deformsurvey.ts';
|
|
141
|
+
// Issue #1052: the choice of reader and poser over a build is `./deformbuild.ts`'s, where an entry that links nothing of
|
|
142
|
+
// the runtime can load it; this file's half of it — the survey through spine-core — is registered into the seam below.
|
|
143
|
+
import type { DeformSurveyInput } from './deformbuild.ts';
|
|
144
|
+
import { registerSpineSurvey, SpineRuntimeError, spineRuntimeSentence, SURVEY_RUNTIME_TAIL } from './spine_side.ts';
|
|
145
|
+
|
|
146
|
+
export { BEZIER_POINTS, DEFORM_AREA_EPSILON, float32AreaNoise, stretchSingularValues, surveyOfModel, triangleAreas, unreachableWhy } from './deformsurvey.ts';
|
|
147
|
+
export { surveyOfBuild } from './deformbuild.ts';
|
|
148
|
+
export type { DeformSurveyInput } from './deformbuild.ts';
|
|
149
|
+
export type {
|
|
150
|
+
DeformDial,
|
|
151
|
+
DeformDialDispute,
|
|
152
|
+
DeformDialTie,
|
|
153
|
+
DeformExtreme,
|
|
154
|
+
DeformFrameMeasure,
|
|
155
|
+
DeformKeyDraw,
|
|
156
|
+
DeformKeyMeasure,
|
|
157
|
+
DeformReach,
|
|
158
|
+
DeformReversal,
|
|
159
|
+
DeformSpan,
|
|
160
|
+
DeformSpanCurve,
|
|
161
|
+
DeformSurvey,
|
|
162
|
+
DeformSurveyRecord,
|
|
163
|
+
DeformSurveySource,
|
|
164
|
+
DialSession,
|
|
165
|
+
DialSpan,
|
|
166
|
+
ShownReading,
|
|
167
|
+
SurveyPose,
|
|
168
|
+
SurveyPoser,
|
|
169
|
+
} from './deformsurvey.ts';
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Load an emitted pair through the real spine-core, without touching the pages.
|
|
174
|
+
*
|
|
175
|
+
* `TextureAtlas` needs only the atlas TEXT — the page sizes and region rectangles
|
|
176
|
+
* are in it — so this runs on a build whose `--out` was never written, which is
|
|
177
|
+
* what `explain` is. Reading the PNGs is `posableFromText`'s job and it needs
|
|
178
|
+
* them because it rasterises.
|
|
179
|
+
*/
|
|
180
|
+
export function skeletonDataFromText(skeletonText: string, atlasText: string): SkeletonData {
|
|
181
|
+
const atlas = new TextureAtlas(atlasText);
|
|
182
|
+
return new SkeletonJson(new AtlasAttachmentLoader(atlas)).readSkeletonData(JSON.parse(skeletonText));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Measure every deform key of every animation.
|
|
187
|
+
*
|
|
188
|
+
* `exempt` holds slot names to pass over — A39 hands it `invariants.deformMayFold`
|
|
189
|
+
* so a declared fold is not measured at all. ⚠️ The **report** hands it an empty
|
|
190
|
+
* set on purpose: an exempted slot is the one an author most wants figures for,
|
|
191
|
+
* and a report that went quiet where the gate does would leave the only surface
|
|
192
|
+
* that can say anything about a declared fold saying nothing.
|
|
193
|
+
*
|
|
194
|
+
* ⭐ Not the same thing as `draw.blank` on a key, and the difference is the whole
|
|
195
|
+
* of issue #401: `exempt` is a **declaration** about a slot for all time and is
|
|
196
|
+
* not measured, `draw.blank` is a **measurement** of one key at one time and
|
|
197
|
+
* cannot be declared. A key that draws nothing is still surveyed and still
|
|
198
|
+
* printed; what it is not is gated.
|
|
199
|
+
*/
|
|
200
|
+
export function surveyDeformKeys(data: SkeletonData, exempt: ReadonlySet<string> = new Set()): DeformSurvey {
|
|
201
|
+
const side = runtimeSide(data);
|
|
202
|
+
return { ...surveyWith(side.structure, exempt, side.poser), source: { used: 'spine-core', why: null } };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Which `BonePose` field spine-core's own reader is named for — off the ARTIFACT,
|
|
207
|
+
* which is the second, independent answer the probe below is checked against
|
|
208
|
+
* (issue #419).
|
|
209
|
+
*
|
|
210
|
+
* ⭐ This is an identity, not a dispatch table. `SliderData.property` is one of
|
|
211
|
+
* six classes the parser built out of the rig spec's `property` field, so asking
|
|
212
|
+
* which class it is asks the file what the author wrote; it says nothing about
|
|
213
|
+
* *how* the value is computed, which is the part #407 was careful never to
|
|
214
|
+
* transcribe and which the probe still measures.
|
|
215
|
+
*
|
|
216
|
+
* `null` for a reader this file does not know — unreachable against spine-core
|
|
217
|
+
* 4.3, which has exactly these six, and deliberately not folded into a default:
|
|
218
|
+
* a seventh reader in some later runtime must be **named** in the report, not
|
|
219
|
+
* silently mapped to whatever the probe happened to find.
|
|
220
|
+
*/
|
|
221
|
+
function readerField(property: FromProperty): DialField | null {
|
|
222
|
+
if (property instanceof FromRotate) return 'rotation';
|
|
223
|
+
if (property instanceof FromX) return 'x';
|
|
224
|
+
if (property instanceof FromY) return 'y';
|
|
225
|
+
if (property instanceof FromScaleX) return 'scaleX';
|
|
226
|
+
if (property instanceof FromScaleY) return 'scaleY';
|
|
227
|
+
if (property instanceof FromShearY) return 'shearY';
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A fresh skeleton with `skin` worn, which is what every pose below starts from.
|
|
233
|
+
*
|
|
234
|
+
* ## Why a skin, and why this one
|
|
235
|
+
*
|
|
236
|
+
* A deform timeline is keyed on a `skin / slot / attachment` triple, so the mesh
|
|
237
|
+
* it deforms belongs to exactly one skin — and a skeleton nobody dressed shows
|
|
238
|
+
* only what `SkeletonData.defaultSkin` holds. Posing with no skin therefore read
|
|
239
|
+
* a slot that showed **nothing** for every mesh an author had moved into a named
|
|
240
|
+
* skin, and the whole survey then reported the rig as undrawn: `A39` went from
|
|
241
|
+
* PASS to SKIP with a sentence that blamed the rig for what the measurement was
|
|
242
|
+
* doing (issue #583). `placementOf` already recovers which skin holds a
|
|
243
|
+
* timeline's attachment, so the pose can wear it.
|
|
244
|
+
*
|
|
245
|
+
* ## What `setSkin` changes, off the runtime rather than from memory
|
|
246
|
+
*
|
|
247
|
+
* `Skeleton.setSkinBySkin` (spine-core 4.3.13 `Skeleton.js:292-313`) puts the
|
|
248
|
+
* skin's art into each slot's pose and calls `updateCache`, and `updateCache`
|
|
249
|
+
* (`Skeleton.js:142-187`) is where the other two thirds live: a `skinRequired`
|
|
250
|
+
* bone is `active` only if the worn skin lists it, and a `skinRequired`
|
|
251
|
+
* constraint only if `skin.constraints` includes it. Both were measured on this
|
|
252
|
+
* fixture before the repair and both were silent in their own way —
|
|
253
|
+
*
|
|
254
|
+
* - a slider whose driving bone is skin-required reads a world property that
|
|
255
|
+
* **nothing moves** while the bone is inactive, so `planDial` found no
|
|
256
|
+
* responding field, returned `null`, and the animation was reported as
|
|
257
|
+
* *"played on a track"* — an animation only a slider ever applies;
|
|
258
|
+
* - a skin-required slider **constraint** is left out of the update cache, so
|
|
259
|
+
* `SliderPose.time` never leaves its setup value and every key came back as
|
|
260
|
+
* *"at a time no dial selects"*.
|
|
261
|
+
*
|
|
262
|
+
* Each pose below then calls `setupPose()`, which re-resolves every slot's setup
|
|
263
|
+
* attachment through `Skeleton.getAttachment` — the worn skin first, then
|
|
264
|
+
* `defaultSkin` (`Skeleton.js:335-346`) — so wearing the skin before the pose is
|
|
265
|
+
* the whole of what is needed and nothing has to be re-attached afterwards.
|
|
266
|
+
*
|
|
267
|
+
* ⭐ It takes the `Skin` **object**, not its name. `placementOf` found it by
|
|
268
|
+
* identity, and `findSkin` resolves a name to the FIRST skin that carries it —
|
|
269
|
+
* so a round trip through the name would hand the runtime a different skin on a
|
|
270
|
+
* skeleton that declares two of one name, and there would be nothing in the
|
|
271
|
+
* output to say so. `src/render.ts`'s `skeletonUnderSkin` takes a name because
|
|
272
|
+
* its name came from `--skin` on the command line and refusing an unknown one
|
|
273
|
+
* **by name, with the names that would have worked** is the whole of its job;
|
|
274
|
+
* here there is no name to refuse and no lookup that can fail.
|
|
275
|
+
*/
|
|
276
|
+
function skeletonUnderSkin(data: SkeletonData, skin: Skin | null): Skeleton {
|
|
277
|
+
const skeleton = new Skeleton(data);
|
|
278
|
+
if (skin !== null) skeleton.setSkin(skin);
|
|
279
|
+
return skeleton;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The `Slider` on `skeleton` that `data` describes, or `null`. */
|
|
283
|
+
function sliderOn(skeleton: Skeleton, data: SliderData): Slider | null {
|
|
284
|
+
for (const constraint of skeleton.constraints) {
|
|
285
|
+
if (constraint instanceof Slider && constraint.data === data) return constraint;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The `offsets` argument every `FromProperty.value` takes.
|
|
292
|
+
*
|
|
293
|
+
* `Slider.offsets` is a private all-zero array — a slider has no per-property
|
|
294
|
+
* offset the way a transform constraint does — so this is that constant, spelled
|
|
295
|
+
* out because it cannot be imported.
|
|
296
|
+
*/
|
|
297
|
+
const DIAL_ZERO_OFFSETS = [0, 0, 0, 0, 0, 0];
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The skeleton of `data`, posed by `animation` at `time`.
|
|
301
|
+
*
|
|
302
|
+
* By the same route A10 steps an animation, and with a fresh state every call:
|
|
303
|
+
* that is what lands the sample exactly ON `time` rather than one update short of
|
|
304
|
+
* it, and it is why a probe between two keys is as trustworthy as a key.
|
|
305
|
+
*/
|
|
306
|
+
function poseAt(data: SkeletonData, skin: Skin | null, animation: string, time: number): Skeleton {
|
|
307
|
+
const posed = skeletonUnderSkin(data, skin);
|
|
308
|
+
const state = new AnimationState(new AnimationStateData(data));
|
|
309
|
+
state.setAnimation(0, animation, false);
|
|
310
|
+
posed.setupPose();
|
|
311
|
+
posed.update(0);
|
|
312
|
+
posed.updateWorldTransform(Physics.reset);
|
|
313
|
+
state.update(time);
|
|
314
|
+
state.apply(posed);
|
|
315
|
+
posed.update(time);
|
|
316
|
+
posed.updateWorldTransform(Physics.update);
|
|
317
|
+
return posed;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* `CurveTimeline.curves`, which is `protected` and is read anyway.
|
|
322
|
+
*
|
|
323
|
+
* ⚠️ A deliberate reach into the runtime's own storage, in the one file whose
|
|
324
|
+
* whole job is reading what the runtime will do. The public surface is
|
|
325
|
+
* `getCurvePercent(time, frame)` — a fraction at a time — and the scan needs the
|
|
326
|
+
* inverse and the reachable range, which no sequence of forward evaluations
|
|
327
|
+
* gives exactly: the breakpoints of the polyline it interpolates over are
|
|
328
|
+
* precisely what this array holds, and any other route to them would be sampling
|
|
329
|
+
* with a spacing to defend. The alternative considered and rejected was
|
|
330
|
+
* re-deriving the sampling from the four bezier handles in the emitted JSON,
|
|
331
|
+
* which would be a **second** derivation of the runtime's own arithmetic — the
|
|
332
|
+
* thing this file's opening paragraph forbids — and would then be checking
|
|
333
|
+
* rigc's copy of spine-core's maths rather than spine-core's.
|
|
334
|
+
*
|
|
335
|
+
* ⚠️ That alternative is what the model document's reader does since issue
|
|
336
|
+
* #1019, and the objection does not reach it, because it is not a copy written
|
|
337
|
+
* here: it is the core's own deform curve (`bezierPolyline` with
|
|
338
|
+
* `DEFORM_CURVE_END`, `./core/deform.ts`), the one the core POSES with, measured
|
|
339
|
+
* against the runtime there. On a build the core poses, the pose and the span
|
|
340
|
+
* scan read one derivation rather than two, and `DM12` (the gallery) and
|
|
341
|
+
* `tools/survey_hashes.ts structure` (every corpus row) hold its points to this
|
|
342
|
+
* array's. What spine-core's reader of the survey reads
|
|
343
|
+
* is still this array.
|
|
344
|
+
*
|
|
345
|
+
* `A05` already gates the emitted curve arrays, and `DW18` is the control that
|
|
346
|
+
* the reading here matches what the runtime does with them.
|
|
347
|
+
*
|
|
348
|
+
* Exported for `validate.ts`'s `curveChannelValues` (issue #752), which asks the
|
|
349
|
+
* same array what values a constraint's `mix` timeline poses between its keys —
|
|
350
|
+
* so the reach into protected storage stays one reach, in this file.
|
|
351
|
+
*/
|
|
352
|
+
export function curveStorage(timeline: CurveTimeline): ArrayLike<number> {
|
|
353
|
+
return (timeline as unknown as { curves: ArrayLike<number> }).curves;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The mesh's world vertices with one deform array written into the slot.
|
|
358
|
+
*
|
|
359
|
+
* The array is copied rather than aliased: for an unweighted attachment with no
|
|
360
|
+
* `vertices` on its key, `SkeletonJson` stores the ATTACHMENT'S OWN setup array
|
|
361
|
+
* as that key's deform, and handing the runtime a live reference to it would let
|
|
362
|
+
* a later write edit the mesh itself.
|
|
363
|
+
*/
|
|
364
|
+
function worldWithDeform(
|
|
365
|
+
posed: Skeleton,
|
|
366
|
+
slotIndex: number,
|
|
367
|
+
attachment: MeshAttachment,
|
|
368
|
+
deform: ArrayLike<number>,
|
|
369
|
+
): Float32Array {
|
|
370
|
+
const count = attachment.worldVerticesLength;
|
|
371
|
+
const slot = posed.slots[slotIndex];
|
|
372
|
+
const array = slot.appliedPose.deform;
|
|
373
|
+
array.length = deform.length;
|
|
374
|
+
for (let i = 0; i < deform.length; i++) array[i] = deform[i];
|
|
375
|
+
const world = new Float32Array(count);
|
|
376
|
+
attachment.computeWorldVertices(posed, slot, 0, count, world, 0, 2);
|
|
377
|
+
array.length = 0;
|
|
378
|
+
return world;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* What one slot shows of one mesh at the pose it is currently in, and at what
|
|
383
|
+
* alpha — with no opinion about whether that is a reason for anything.
|
|
384
|
+
*
|
|
385
|
+
* `alpha` is `slot.color.a × attachment.color.a`, which is the product
|
|
386
|
+
* `src/render.ts` builds a piece's tint from; it is 0 when the slot shows some
|
|
387
|
+
* other attachment, because then none of this mesh is on screen. The skeleton's
|
|
388
|
+
* own colour is deliberately not a factor: it is runtime state a consumer sets,
|
|
389
|
+
* not something the skeleton data can say, and `src/render.ts` does not read it
|
|
390
|
+
* either.
|
|
391
|
+
*/
|
|
392
|
+
function shownAt(posed: Skeleton, slotIndex: number, attachment: MeshAttachment): ShownReading {
|
|
393
|
+
const pose = posed.slots[slotIndex]?.appliedPose;
|
|
394
|
+
const shown: Attachment | null = pose?.attachment ?? null;
|
|
395
|
+
// The same comparison `DeformTimeline.applyToSlot` makes before it writes
|
|
396
|
+
// anything, so "shown" here means exactly "the runtime deforms it here".
|
|
397
|
+
const showsThisMesh = shown !== null && shown.timelineAttachment === attachment;
|
|
398
|
+
const slotAlpha = pose?.color.a ?? 0;
|
|
399
|
+
const attachmentAlpha = shown instanceof MeshAttachment ? shown.color.a : 1;
|
|
400
|
+
return {
|
|
401
|
+
shown: shown?.name ?? null,
|
|
402
|
+
showsThisMesh,
|
|
403
|
+
slotAlpha,
|
|
404
|
+
attachmentAlpha,
|
|
405
|
+
alpha: showsThisMesh ? slotAlpha * attachmentAlpha : 0,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Which skin holds this attachment, and under which placeholder — the other two
|
|
411
|
+
* thirds of the `skin/slot/attachment` triple the format keys a deform timeline
|
|
412
|
+
* on, and the triple `explain` already prints for the timeline itself.
|
|
413
|
+
*
|
|
414
|
+
* ⚠️ Recovered by identity rather than by name. A `DeformTimeline` carries the
|
|
415
|
+
* attachment it RESOLVED to and neither the skin it came out of nor the
|
|
416
|
+
* placeholder it was written as, and those two are not the same string in
|
|
417
|
+
* general: a skin puts its own attachment behind a shared placeholder, which is
|
|
418
|
+
* the whole point of skins. So the scan compares the attachment object, and the
|
|
419
|
+
* placeholder printed is the one the spec wrote.
|
|
420
|
+
*
|
|
421
|
+
* ⭐ The `Skin` it found comes back with the name, because that object is what
|
|
422
|
+
* the pose wears (`skeletonUnderSkin`, issue #583). Handing the pose the name
|
|
423
|
+
* instead would resolve it again through `SkeletonData.findSkin`, which returns
|
|
424
|
+
* the FIRST skin of that name — a second resolution that can land somewhere else
|
|
425
|
+
* on a skeleton declaring two, and land there silently.
|
|
426
|
+
*
|
|
427
|
+
* `holder` is `null` only when no skin carries this attachment at all. Nothing
|
|
428
|
+
* reaches that through `SkeletonJson`, which resolves a deform timeline's
|
|
429
|
+
* attachment out of a skin before it can build the timeline, so it is the
|
|
430
|
+
* defensive branch and not a case: the pose then wears nothing and behaves
|
|
431
|
+
* exactly as every pose here did before #583.
|
|
432
|
+
*/
|
|
433
|
+
function placementOf(
|
|
434
|
+
data: SkeletonData,
|
|
435
|
+
slotIndex: number,
|
|
436
|
+
attachment: Attachment,
|
|
437
|
+
): { skin: string; holder: Skin | null; placeholder: string } {
|
|
438
|
+
for (const skin of [data.defaultSkin, ...data.skins]) {
|
|
439
|
+
if (!skin) continue;
|
|
440
|
+
const entries: Array<{ placeholder: string; attachment: unknown }> = [];
|
|
441
|
+
skin.getAttachmentsForSlot(slotIndex, entries as Parameters<typeof skin.getAttachmentsForSlot>[1]);
|
|
442
|
+
for (const entry of entries) {
|
|
443
|
+
if (entry.attachment === attachment) return { skin: skin.name, holder: skin, placeholder: entry.placeholder };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return { skin: 'default', holder: null, placeholder: attachment.name };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// --- #1019 the runtime's reading of the structure: begin ---
|
|
450
|
+
//
|
|
451
|
+
// `src/deformstructure.ts` states what the survey reads of a skeleton's
|
|
452
|
+
// structure and gives the model document's reading of it. This is the other
|
|
453
|
+
// one — the survey's reads as they always were, off spine-core's parsed
|
|
454
|
+
// `SkeletonData`, moved behind the interface unchanged — and the spine-core
|
|
455
|
+
// poser over the same objects. The two are made together because the poser
|
|
456
|
+
// needs the parsed objects the handles stand for: a handle the model
|
|
457
|
+
// document's reader made carries none, and is refused here by name.
|
|
458
|
+
|
|
459
|
+
/** The parsed objects behind the runtime reader's handles — what spine-core's poser is handed. */
|
|
460
|
+
interface RuntimeLinks {
|
|
461
|
+
skin(handle: SurveySkin | null): Skin | null;
|
|
462
|
+
mesh(handle: SurveyMesh): MeshAttachment;
|
|
463
|
+
slider(handle: SurveySlider): SliderData;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* The slot timelines' key times on `slots` — every timeline carrying a
|
|
468
|
+
* `slotIndex`, duck-typed (see `scanDeformSpan`'s visibility split).
|
|
469
|
+
*
|
|
470
|
+
* ⭐ Duck-typed rather than matched against a list of classes, because a list
|
|
471
|
+
* is a thing that goes stale when the format grows a timeline and the failure
|
|
472
|
+
* would be silent.
|
|
473
|
+
*/
|
|
474
|
+
function visibilityKeyTimes(timelines: readonly Timeline[], slots: ReadonlySet<number>): number[] {
|
|
475
|
+
const times: number[] = [];
|
|
476
|
+
for (const timeline of timelines) {
|
|
477
|
+
const carrier = timeline as unknown as { slotIndex?: unknown };
|
|
478
|
+
if (typeof carrier.slotIndex !== 'number' || !slots.has(carrier.slotIndex)) continue;
|
|
479
|
+
const entries = timeline.getFrameEntries();
|
|
480
|
+
for (let i = 0; i < timeline.getFrameCount(); i++) times.push(timeline.frames[i * entries]);
|
|
481
|
+
}
|
|
482
|
+
return times;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* spine-core's parsed skeleton read as the survey's structure, and posed by
|
|
487
|
+
* spine-core — the survey as it always was (the section's note).
|
|
488
|
+
*/
|
|
489
|
+
function runtimeSide(data: SkeletonData): { structure: SurveyStructure; poser: SurveyPoser; links: RuntimeLinks } {
|
|
490
|
+
const skins = new Map<Skin, SurveySkin>();
|
|
491
|
+
const skinBack = new Map<SurveySkin, Skin>();
|
|
492
|
+
const skinOf = (skin: Skin | null): SurveySkin | null => {
|
|
493
|
+
if (skin === null) return null;
|
|
494
|
+
let handle = skins.get(skin);
|
|
495
|
+
if (handle === undefined) {
|
|
496
|
+
handle = { name: skin.name };
|
|
497
|
+
skins.set(skin, handle);
|
|
498
|
+
skinBack.set(handle, skin);
|
|
499
|
+
}
|
|
500
|
+
return handle;
|
|
501
|
+
};
|
|
502
|
+
const meshes = new Map<MeshAttachment, SurveyMesh>();
|
|
503
|
+
const meshBack = new Map<SurveyMesh, MeshAttachment>();
|
|
504
|
+
const meshOf = (attachment: MeshAttachment, slotIndex: number): SurveyMesh => {
|
|
505
|
+
const already = meshes.get(attachment);
|
|
506
|
+
if (already !== undefined) return already;
|
|
507
|
+
const placed = placementOf(data, slotIndex, attachment);
|
|
508
|
+
const handle: SurveyMesh = {
|
|
509
|
+
name: attachment.name,
|
|
510
|
+
triangles: attachment.triangles,
|
|
511
|
+
worldVerticesLength: attachment.worldVerticesLength,
|
|
512
|
+
weighted: attachment.bones !== null,
|
|
513
|
+
timelineSlots: attachment.timelineSlots,
|
|
514
|
+
record: placed.holder === null ? null : { skin: placed.skin, slot: data.slots[slotIndex]?.name ?? `#${slotIndex}`, placeholder: placed.placeholder },
|
|
515
|
+
};
|
|
516
|
+
meshes.set(attachment, handle);
|
|
517
|
+
meshBack.set(handle, attachment);
|
|
518
|
+
return handle;
|
|
519
|
+
};
|
|
520
|
+
const deformOf = (timeline: DeformTimeline): SurveyDeformTimeline => {
|
|
521
|
+
const attachment = timeline.attachment;
|
|
522
|
+
const mesh = attachment instanceof MeshAttachment ? meshOf(attachment, timeline.slotIndex) : null;
|
|
523
|
+
return {
|
|
524
|
+
slotIndex: timeline.slotIndex,
|
|
525
|
+
mesh,
|
|
526
|
+
frames: timeline.frames,
|
|
527
|
+
vertices: (frame) => timeline.vertices[frame],
|
|
528
|
+
curve: (frame): SurveyCurve => {
|
|
529
|
+
const curves = curveStorage(timeline);
|
|
530
|
+
const code = curves[frame];
|
|
531
|
+
// 1 is STEPPED and 0 LINEAR; 2 + i is BEZIER, its sampled points starting at `i`.
|
|
532
|
+
if (code === 1) return { kind: 'stepped' };
|
|
533
|
+
if (code === 0) return { kind: 'linear' };
|
|
534
|
+
const points: number[] = [];
|
|
535
|
+
for (let i = code - 2, n = code - 2 + BEZIER_POINTS * 2; i < n; i++) points.push(curves[i]);
|
|
536
|
+
return { kind: 'bezier', points };
|
|
537
|
+
},
|
|
538
|
+
placement: () => {
|
|
539
|
+
const placed = placementOf(data, timeline.slotIndex, attachment);
|
|
540
|
+
return { skin: placed.skin, holder: skinOf(placed.holder), placeholder: placed.placeholder };
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
const animations: SurveyAnimation[] = data.animations.map((anim) => ({
|
|
545
|
+
name: anim.name,
|
|
546
|
+
deforms: anim.timelines.flatMap((timeline) => (timeline instanceof DeformTimeline ? [deformOf(timeline)] : [])),
|
|
547
|
+
slotKeyTimes: (slots) => visibilityKeyTimes(anim.timelines, slots),
|
|
548
|
+
}));
|
|
549
|
+
const sliderBack = new Map<SurveySlider, SliderData>();
|
|
550
|
+
const sliders: SurveySlider[] = [];
|
|
551
|
+
for (const constraint of data.constraints) {
|
|
552
|
+
if (!(constraint instanceof SliderData)) continue;
|
|
553
|
+
// The animation and the property are read when they are used, as they always were: only a slider with an
|
|
554
|
+
// animation is planned, and only one with a bone has a property — a bone-less one's is not set at all.
|
|
555
|
+
const handle: SurveySlider = {
|
|
556
|
+
name: constraint.name,
|
|
557
|
+
mix: constraint.setupPose.mix,
|
|
558
|
+
animation: constraint.animation?.name ?? '',
|
|
559
|
+
get duration() {
|
|
560
|
+
return constraint.animation.duration;
|
|
561
|
+
},
|
|
562
|
+
bone: constraint.bone?.name ?? null,
|
|
563
|
+
local: constraint.local,
|
|
564
|
+
get stated() {
|
|
565
|
+
return readerField(constraint.property);
|
|
566
|
+
},
|
|
567
|
+
get reader() {
|
|
568
|
+
return constraint.property.constructor.name;
|
|
569
|
+
},
|
|
570
|
+
get from() {
|
|
571
|
+
return constraint.property.offset;
|
|
572
|
+
},
|
|
573
|
+
to: constraint.offset,
|
|
574
|
+
scale: constraint.scale,
|
|
575
|
+
loop: constraint.loop,
|
|
576
|
+
};
|
|
577
|
+
sliders.push(handle);
|
|
578
|
+
sliderBack.set(handle, constraint);
|
|
579
|
+
}
|
|
580
|
+
const structure: SurveyStructure = {
|
|
581
|
+
skins: [data.defaultSkin, ...data.skins].flatMap((k, i, all) => (k !== null && all.indexOf(k) === i ? [skinOf(k) as SurveySkin] : [])),
|
|
582
|
+
animations,
|
|
583
|
+
sliders,
|
|
584
|
+
slotName: (index) => data.slots[index]?.name ?? `#${index}`,
|
|
585
|
+
skinsNamed: (name) => data.skins.filter((k) => k.name === name).length,
|
|
586
|
+
};
|
|
587
|
+
const foreign = (what: string): Error => new Error(`internal: spine-core's poser was handed a ${what} another reader made — it poses the parsed skeleton's own objects`);
|
|
588
|
+
const links: RuntimeLinks = {
|
|
589
|
+
skin: (handle) => {
|
|
590
|
+
if (handle === null) return null;
|
|
591
|
+
const skin = skinBack.get(handle);
|
|
592
|
+
if (skin === undefined) throw foreign(`skin "${handle.name}"`);
|
|
593
|
+
return skin;
|
|
594
|
+
},
|
|
595
|
+
mesh: (handle) => {
|
|
596
|
+
const mesh = meshBack.get(handle);
|
|
597
|
+
if (mesh === undefined) throw foreign(`mesh "${handle.name}"`);
|
|
598
|
+
return mesh;
|
|
599
|
+
},
|
|
600
|
+
slider: (handle) => {
|
|
601
|
+
const slider = sliderBack.get(handle);
|
|
602
|
+
if (slider === undefined) throw foreign(`slider "${handle.name}"`);
|
|
603
|
+
return slider;
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
return { structure, poser: spinePoser(data, links), links };
|
|
607
|
+
}
|
|
608
|
+
// --- #1019 the runtime's reading of the structure: end ---
|
|
609
|
+
|
|
610
|
+
/** A live spine-core skeleton, read the way the survey always read it. */
|
|
611
|
+
function spinePose(skeleton: Skeleton, links: RuntimeLinks): SurveyPose {
|
|
612
|
+
return {
|
|
613
|
+
under: () => skeleton.skin?.name ?? null,
|
|
614
|
+
shownAt: (slotIndex, handle) => shownAt(skeleton, slotIndex, links.mesh(handle)),
|
|
615
|
+
deformed: (slotIndex, handle) => {
|
|
616
|
+
const attachment = links.mesh(handle);
|
|
617
|
+
const count = attachment.worldVerticesLength;
|
|
618
|
+
const world = new Float32Array(count);
|
|
619
|
+
attachment.computeWorldVertices(skeleton, skeleton.slots[slotIndex], 0, count, world, 0, 2);
|
|
620
|
+
return world;
|
|
621
|
+
},
|
|
622
|
+
plain: (slotIndex, handle) => {
|
|
623
|
+
const attachment = links.mesh(handle);
|
|
624
|
+
const count = attachment.worldVerticesLength;
|
|
625
|
+
const slot = skeleton.slots[slotIndex];
|
|
626
|
+
slot.appliedPose.deform.length = 0;
|
|
627
|
+
const world = new Float32Array(count);
|
|
628
|
+
attachment.computeWorldVertices(skeleton, slot, 0, count, world, 0, 2);
|
|
629
|
+
return world;
|
|
630
|
+
},
|
|
631
|
+
withDeform: (slotIndex, handle, deform) => worldWithDeform(skeleton, slotIndex, links.mesh(handle), deform),
|
|
632
|
+
rows: (slotIndex, handle, deform) => {
|
|
633
|
+
const attachment = links.mesh(handle);
|
|
634
|
+
const slot = skeleton.slots[slotIndex];
|
|
635
|
+
const array = slot.appliedPose.deform;
|
|
636
|
+
const kept = [...array];
|
|
637
|
+
if (deform === 'cleared') array.length = 0;
|
|
638
|
+
else if (deform !== 'posed') {
|
|
639
|
+
array.length = deform.length;
|
|
640
|
+
for (let i = 0; i < deform.length; i++) array[i] = deform[i];
|
|
641
|
+
}
|
|
642
|
+
const out = new Array<number>(attachment.worldVerticesLength);
|
|
643
|
+
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, out, 0, 2);
|
|
644
|
+
array.length = kept.length;
|
|
645
|
+
for (let i = 0; i < kept.length; i++) array[i] = kept[i];
|
|
646
|
+
return out;
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** The survey's poses through spine-core — the recipes the survey has always taken (`poseAt`; the dial below). */
|
|
652
|
+
function spinePoser(data: SkeletonData, links: RuntimeLinks): SurveyPoser {
|
|
653
|
+
return {
|
|
654
|
+
track: (skin, animation, time) => spinePose(poseAt(data, links.skin(skin), animation, time), links),
|
|
655
|
+
dial: (skin, handle) => {
|
|
656
|
+
const slider = links.slider(handle);
|
|
657
|
+
const skeleton = skeletonUnderSkin(data, links.skin(skin));
|
|
658
|
+
const instance = sliderOn(skeleton, slider);
|
|
659
|
+
if (instance === null) return null;
|
|
660
|
+
const bone = instance.bone;
|
|
661
|
+
return {
|
|
662
|
+
hasBone: bone !== null,
|
|
663
|
+
base: (field) => {
|
|
664
|
+
skeleton.setupPose();
|
|
665
|
+
return (bone as Bone).pose[field];
|
|
666
|
+
},
|
|
667
|
+
// `Slider.update`'s own reading, at its own point in the update: the
|
|
668
|
+
// setup pose, `update(0)`, the field (or the time) written, `Physics.reset`.
|
|
669
|
+
at: (field, candidate) => {
|
|
670
|
+
skeleton.setupPose();
|
|
671
|
+
skeleton.update(0);
|
|
672
|
+
if (field !== null && bone !== null) bone.pose[field] = candidate;
|
|
673
|
+
else instance.pose.time = candidate;
|
|
674
|
+
skeleton.updateWorldTransform(Physics.reset);
|
|
675
|
+
if (field === null || bone === null) return { read: candidate, applied: instance.appliedPose.time };
|
|
676
|
+
if (slider.local) bone.appliedPose.validateLocalTransform(skeleton);
|
|
677
|
+
return { read: slider.property.value(skeleton, bone.appliedPose, slider.local, DIAL_ZERO_OFFSETS), applied: instance.appliedPose.time };
|
|
678
|
+
},
|
|
679
|
+
pose: () => spinePose(skeleton, links),
|
|
680
|
+
};
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Both readers and both posers over one build, for measurement:
|
|
687
|
+
* `tools/survey_hashes.ts` holds every hook of the core's poser to
|
|
688
|
+
* spine-core's at tolerance 0 over the runtime's structure (`hooks`), and the
|
|
689
|
+
* model document's structure to the runtime's (`structure`); the selftest's
|
|
690
|
+
* `DM` controls hold the same. `spine` and `core` both pose `structure`'s
|
|
691
|
+
* handles; `model` is the model document's reading, which only `core` and the
|
|
692
|
+
* model's own poser can pose.
|
|
693
|
+
*/
|
|
694
|
+
export function deformPosers(input: DeformSurveyInput & { modelText: string }): { data: SkeletonData; structure: SurveyStructure; model: SurveyStructure; spine: SurveyPoser; core: SurveyPoser } {
|
|
695
|
+
const data = skeletonDataFromText(input.skeletonText, input.atlasText);
|
|
696
|
+
const side = runtimeSide(data);
|
|
697
|
+
const doc = readModel(input.modelText);
|
|
698
|
+
return { data, structure: side.structure, model: modelStructure(doc), spine: side.poser, core: corePoser(side.structure, doc) };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Touch the runtime once, before the skeleton is parsed through it, and refuse
|
|
703
|
+
* by name when it cannot be used (issue #1019) — the sentence `render` and
|
|
704
|
+
* `check` give a run that needs the runtime and cannot use it
|
|
705
|
+
* (`SpineRuntimeError`, issue #1014). A survey whose input the core poses
|
|
706
|
+
* never comes here.
|
|
707
|
+
*/
|
|
708
|
+
function requireSpineRuntime(label: string, why: string): void {
|
|
709
|
+
try {
|
|
710
|
+
// A property read on the class the parse starts from: no runtime code runs, and a runtime that cannot be used throws here.
|
|
711
|
+
void TextureAtlas.prototype;
|
|
712
|
+
} catch (err) {
|
|
713
|
+
// The sentence an entry that registered no Spine side is refused in too (`./spine_side.ts`), with the runtime's own words as the reason.
|
|
714
|
+
throw new SpineRuntimeError(spineRuntimeSentence(label, why, (err as Error).message, SURVEY_RUNTIME_TAIL));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* The survey through spine-core — the half of `surveyOfBuild`
|
|
720
|
+
* (`./deformbuild.ts`, moved there unchanged in issue #1052) that names the
|
|
721
|
+
* runtime, registered into the seam when this file is loaded: it touches the
|
|
722
|
+
* runtime once (`requireSpineRuntime`), then reads and poses the Spine
|
|
723
|
+
* skeleton and names `why` as the survey's source. Every program that imports
|
|
724
|
+
* this file surveys as before; one that does not refuses this half by name.
|
|
725
|
+
*/
|
|
726
|
+
registerSpineSurvey({
|
|
727
|
+
throughSpine: (label, why, input, exempt) => {
|
|
728
|
+
requireSpineRuntime(label, why ?? '--poser spine');
|
|
729
|
+
const side = runtimeSide(skeletonDataFromText(input.skeletonText, input.atlasText));
|
|
730
|
+
return { ...surveyWith(side.structure, exempt, side.poser), source: { used: 'spine-core', why } };
|
|
731
|
+
},
|
|
732
|
+
});
|