spine-rigc 1.6.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.ts +82 -43
- package/docs/AUTHORING.md +9 -6
- package/package.json +1 -1
- package/src/atlas.ts +26 -16
- package/src/check.ts +37 -39
- package/src/compile.ts +59 -74
- package/src/core/constraints_path.ts +60 -38
- package/src/core/constraints_physics.ts +22 -4
- package/src/core/index.ts +95 -10
- package/src/core/uvs.ts +25 -5
- package/src/core/world.ts +79 -24
- package/src/deformmeasure.ts +6 -3
- package/src/emit.ts +17 -6
- package/src/ingest.ts +18 -13
- package/src/keyorder.ts +2 -2
- package/src/model.ts +109 -11
- package/src/render.ts +347 -34
- package/src/render_core.ts +115 -18
- package/src/transform.ts +62 -126
- package/src/types.ts +6 -5
- package/src/validate.ts +51 -29
package/src/compile.ts
CHANGED
|
@@ -128,6 +128,8 @@ import {
|
|
|
128
128
|
type BoneTransform,
|
|
129
129
|
} from './transform.ts';
|
|
130
130
|
import { emitSkeleton, PHYSICS_PARAMS, UNSTATED_REFERENCE_SCALE, type SkeletonHeader } from './emit_spine.ts';
|
|
131
|
+
import { curveLengthTable } from './core/constraints_path.ts';
|
|
132
|
+
import { frameRegionName } from './core/uvs.ts';
|
|
131
133
|
import {
|
|
132
134
|
isModelVertexAttachment,
|
|
133
135
|
type CarriedFromCompileResult,
|
|
@@ -1979,8 +1981,9 @@ function resolveFromAtlas(
|
|
|
1979
1981
|
* ⭐ *Either way* was not true for a pack that turns a region until issue #570:
|
|
1980
1982
|
* `extractRegion` refused a `rotate: 90`, so how the art was delivered decided
|
|
1981
1983
|
* whether a measurement could be taken at all — on a foreign pack, where turning
|
|
1982
|
-
* is the norm rather than the exception. It now
|
|
1983
|
-
*
|
|
1984
|
+
* is the norm rather than the exception. It now lifts every rotation, measured
|
|
1985
|
+
* as `extractRegion` states (`PKR02` against the runtime's own sampling, `PK29`
|
|
1986
|
+
* against the packer's fixture), which is what makes the sentence above a
|
|
1984
1987
|
* description rather than an aspiration.
|
|
1985
1988
|
*/
|
|
1986
1989
|
function partPlate(img: CompiledImage): Plate {
|
|
@@ -3802,10 +3805,12 @@ interface AttachmentContext {
|
|
|
3802
3805
|
* Linked meshes whose `source` is still unresolved — `resolveLinkedMeshes`
|
|
3803
3806
|
* empties it once every skin has been built.
|
|
3804
3807
|
*
|
|
3805
|
-
* ⭐ Deferred
|
|
3806
|
-
*
|
|
3807
|
-
*
|
|
3808
|
-
*
|
|
3808
|
+
* ⭐ Deferred because the format resolves a link only once every skin is
|
|
3809
|
+
* read — the reader queues each one (`this.linkedMeshes`, `SkeletonJson.js:56`
|
|
3810
|
+
* filled at `:581`) and resolves the queue after the skins (`:427-449`) — so
|
|
3811
|
+
* a link may name a source in a skin or a slot this loop has not reached yet,
|
|
3812
|
+
* and resolving it in place would refuse a correct rig on nothing but
|
|
3813
|
+
* declaration order.
|
|
3809
3814
|
*/
|
|
3810
3815
|
links: PendingLink[];
|
|
3811
3816
|
}
|
|
@@ -4195,8 +4200,11 @@ export function pathChain(points: Array<[number, number]>, closed: boolean): Arr
|
|
|
4195
4200
|
}
|
|
4196
4201
|
|
|
4197
4202
|
/**
|
|
4198
|
-
* Cumulative
|
|
4199
|
-
* **the
|
|
4203
|
+
* Cumulative length at the end of each curve of the chain, in world units —
|
|
4204
|
+
* **the core's curve table** (`curveLengthTable` in
|
|
4205
|
+
* [`src/core/constraints_path.ts`](core/constraints_path.ts), issue #1015), the
|
|
4206
|
+
* one the core's path constraint walks at constant speed. This function only
|
|
4207
|
+
* flattens the chain and counts its curves.
|
|
4200
4208
|
*
|
|
4201
4209
|
* One entry per curve, which is what `lengths[curve]` indexes: the parser walks
|
|
4202
4210
|
* curves with `if (p > lengths[curve]) continue`, and reads `lengths[curveCount]`
|
|
@@ -4206,12 +4214,16 @@ export function pathChain(points: Array<[number, number]>, closed: boolean): Arr
|
|
|
4206
4214
|
* fact about the Bezier, it is the number the consumer of the field computes for
|
|
4207
4215
|
* itself when it is not given one. `PathConstraint.computeWorldPositions`
|
|
4208
4216
|
* (`PathConstraint.js:289-324` in `@esotericsoftware/spine-core` 4.3.13) measures
|
|
4209
|
-
* a `constantSpeed` path
|
|
4210
|
-
*
|
|
4211
|
-
*
|
|
4212
|
-
* `
|
|
4213
|
-
*
|
|
4214
|
-
*
|
|
4217
|
+
* a `constantSpeed` path for itself, and the Spine editor's exported `lengths`
|
|
4218
|
+
* are the same number. What that number is was measured, not read: the core's
|
|
4219
|
+
* table — four forward-difference steps per curve at `t = 1/4` (`0.1875`,
|
|
4220
|
+
* `0.09375`, `0.75`, `0.16666667`), four `Math.sqrt` terms accumulated into a
|
|
4221
|
+
* running total carried across curves — is the rule its header states with the
|
|
4222
|
+
* readings that missed beside it, and `PS67`, `PS68` and `PS186` in
|
|
4223
|
+
* `selftest.ts` hold this function to `PathConstraint`'s own `curves` array read
|
|
4224
|
+
* off a posed skeleton, **bit for bit**. Since issue #1015 the core suite also
|
|
4225
|
+
* holds the swap: 100,000 seeded chains through this function and through the
|
|
4226
|
+
* arithmetic it replaced, 0 differing.
|
|
4215
4227
|
*
|
|
4216
4228
|
* 🚨 **What that computation is fed decides the number, and this comment once
|
|
4217
4229
|
* claimed more than was measured** (issue #804). It said the loop matched the
|
|
@@ -4223,8 +4235,8 @@ export function pathChain(points: Array<[number, number]>, closed: boolean): Arr
|
|
|
4223
4235
|
* vertices. rigc blends weighted vertices through its own setup transforms,
|
|
4224
4236
|
* which until #804 ignored bone scale, shear and `inherit` — 2.35× the
|
|
4225
4237
|
* runtime's own `curves` on a production rig's weighted path, 0.752× on a
|
|
4226
|
-
* 50/50 probe over a bone at scale 2. `computeWorldTransforms`
|
|
4227
|
-
*
|
|
4238
|
+
* 50/50 probe over a bone at scale 2. `computeWorldTransforms` honours all
|
|
4239
|
+
* three now, and the gap is gone on both.
|
|
4228
4240
|
* - **Constraints.** The runtime measures the pose its update order hands the
|
|
4229
4241
|
* path constraint at the first `updateWorldTransform`: a transform
|
|
4230
4242
|
* constraint on the path's slot bone ordered BEFORE the path constraint
|
|
@@ -4232,11 +4244,11 @@ export function pathChain(points: Array<[number, number]>, closed: boolean): Arr
|
|
|
4232
4244
|
* not. A production 4.2 export's four numbers are the constrained pose's to
|
|
4233
4245
|
* the digit, and its unconstrained setup measures 76.08 against 76.65 on the
|
|
4234
4246
|
* first curve. Reproducing that means solving every constraint type in
|
|
4235
|
-
* update order, which is posing — and `src/compile.ts` does not
|
|
4236
|
-
*
|
|
4237
|
-
*
|
|
4238
|
-
*
|
|
4239
|
-
*
|
|
4247
|
+
* update order, which is posing — and `src/compile.ts` does not pose. ⇒
|
|
4248
|
+
* **An omitted `lengths` is measured on the unconstrained setup pose**, and
|
|
4249
|
+
* a path whose bones a constraint moves at rest gets that figure. What
|
|
4250
|
+
* closes the gap for an editor export is not measuring at all: a stated
|
|
4251
|
+
* `lengths` is carried (`buildRigPath`).
|
|
4240
4252
|
*
|
|
4241
4253
|
* ⚠️ rigc measured this with a 64-chord sum until issue #560, and the comment
|
|
4242
4254
|
* that stood here argued the difference was inside anything's tolerance. It was
|
|
@@ -4247,46 +4259,17 @@ export function pathChain(points: Array<[number, number]>, closed: boolean): Arr
|
|
|
4247
4259
|
* two apart and since issue #716 rigc's own file can only where the two land on
|
|
4248
4260
|
* either side of a float's boundary. Under the six-decimal rounding that stood
|
|
4249
4261
|
* until then it could: on both measured rigs the two spellings differed on the
|
|
4250
|
-
* LAST curve, where the accumulated difference is largest.
|
|
4251
|
-
*
|
|
4252
|
-
*
|
|
4253
|
-
*
|
|
4254
|
-
*
|
|
4255
|
-
*
|
|
4256
|
-
* running total carried across curves rather than restarted. Each of those is a
|
|
4257
|
-
* place where a more accurate line would emit a different file.
|
|
4262
|
+
* LAST curve, where the accumulated difference is largest.
|
|
4263
|
+
*
|
|
4264
|
+
* 🔒 The spelling is load-bearing: `Math.sqrt(dx * dx + dy * dy)` rather than
|
|
4265
|
+
* `Math.hypot`, `0.16666667` rather than `1 / 6`, and the running total carried
|
|
4266
|
+
* across curves rather than restarted. Each of those is a place where a more
|
|
4267
|
+
* accurate line would emit a different file.
|
|
4258
4268
|
*/
|
|
4259
4269
|
export function pathCurveLengths(chain: Array<[number, number]>): number[] {
|
|
4260
|
-
const
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
const [x1, y1] = chain[c];
|
|
4264
|
-
const [cx1, cy1] = chain[c + 1];
|
|
4265
|
-
const [cx2, cy2] = chain[c + 2];
|
|
4266
|
-
const [x2, y2] = chain[c + 3];
|
|
4267
|
-
const tmpx = (x1 - cx1 * 2 + cx2) * 0.1875;
|
|
4268
|
-
const tmpy = (y1 - cy1 * 2 + cy2) * 0.1875;
|
|
4269
|
-
const dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
|
|
4270
|
-
const dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
|
|
4271
|
-
let ddfx = tmpx * 2 + dddfx;
|
|
4272
|
-
let ddfy = tmpy * 2 + dddfy;
|
|
4273
|
-
let dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667;
|
|
4274
|
-
let dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667;
|
|
4275
|
-
total += Math.sqrt(dfx * dfx + dfy * dfy);
|
|
4276
|
-
dfx += ddfx;
|
|
4277
|
-
dfy += ddfy;
|
|
4278
|
-
ddfx += dddfx;
|
|
4279
|
-
ddfy += dddfy;
|
|
4280
|
-
total += Math.sqrt(dfx * dfx + dfy * dfy);
|
|
4281
|
-
dfx += ddfx;
|
|
4282
|
-
dfy += ddfy;
|
|
4283
|
-
total += Math.sqrt(dfx * dfx + dfy * dfy);
|
|
4284
|
-
dfx += ddfx + dddfx;
|
|
4285
|
-
dfy += ddfy + dddfy;
|
|
4286
|
-
total += Math.sqrt(dfx * dfx + dfy * dfy);
|
|
4287
|
-
out.push(total);
|
|
4288
|
-
}
|
|
4289
|
-
return out;
|
|
4270
|
+
const flat: number[] = [];
|
|
4271
|
+
for (const [x, y] of chain) flat.push(x, y);
|
|
4272
|
+
return curveLengthTable(flat, Math.max(0, Math.floor((chain.length - 1) / 3)));
|
|
4290
4273
|
}
|
|
4291
4274
|
|
|
4292
4275
|
/**
|
|
@@ -4516,20 +4499,22 @@ function meshTextureKeys(att: { name?: string; path?: string; image?: string; co
|
|
|
4516
4499
|
}
|
|
4517
4500
|
|
|
4518
4501
|
/**
|
|
4519
|
-
* The atlas region frame `i` of a sequence resolves to —
|
|
4520
|
-
* (`
|
|
4521
|
-
* with zeros to `digits`. `start` and `digits` take the parser's
|
|
4522
|
-
* (`readSequence`: 1 and 0)
|
|
4523
|
-
*
|
|
4524
|
-
* ⚠️
|
|
4525
|
-
*
|
|
4526
|
-
*
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4502
|
+
* The atlas region frame `i` of a sequence resolves to — the core's
|
|
4503
|
+
* `frameRegionName` (`src/core/uvs.ts`, issue #1015): the stem, then `start + i`
|
|
4504
|
+
* left-padded with zeros to `digits`. `start` and `digits` take the parser's
|
|
4505
|
+
* defaults when the spec leaves them out (`readSequence`: 1 and 0).
|
|
4506
|
+
*
|
|
4507
|
+
* ⚠️ The same name is derived in `validate.ts` (`attachmentRegionLookups`, the
|
|
4508
|
+
* walk `A08` joins the atlas with) through the same function. What holds it to
|
|
4509
|
+
* the loader is a measurement, not this sentence: the core suite reads
|
|
4510
|
+
* `Sequence.getPath` and the parser's defaults off spine-core over 7,680 cases
|
|
4511
|
+
* and compares this function, the core's and A08's walk with them, and `A00`'s
|
|
4512
|
+
* round trip asks the runtime's own `getPath` for every frame a build emits.
|
|
4513
|
+
*
|
|
4514
|
+
* Exported for the selftest alone.
|
|
4529
4515
|
*/
|
|
4530
|
-
function sequenceFrameRegion(stem: string, seq: RigSequence, i: number): string {
|
|
4531
|
-
|
|
4532
|
-
return `${stem}${'0'.repeat(Math.max(0, (seq.digits ?? 0) - frame.length))}${frame}`;
|
|
4516
|
+
export function sequenceFrameRegion(stem: string, seq: RigSequence, i: number): string {
|
|
4517
|
+
return frameRegionName(stem, { count: seq.count, start: seq.start ?? 1, digits: seq.digits ?? 0, setup: seq.setup ?? 0 }, i);
|
|
4533
4518
|
}
|
|
4534
4519
|
|
|
4535
4520
|
/** The model's `sequence` record: the four fields exactly as the spec stated them. */
|
|
@@ -4796,8 +4781,8 @@ function bindNamedWeights(weights: RigMeshBinding[][], where: string, ctx: Attac
|
|
|
4796
4781
|
* an `image` under `--atlas-in` ENDED the build, by a refusal raised inside a
|
|
4797
4782
|
* measurement, on 36 of 42 atlases of the pack the card was filed from. The
|
|
4798
4783
|
* repair was to make the refusal unnecessary rather than to catch it: reading a
|
|
4799
|
-
* turned region is
|
|
4800
|
-
*
|
|
4784
|
+
* turned region is now what `extractRegion` does, measured against
|
|
4785
|
+
* `MeshAttachment.computeUVs` (`PKR02`), so there is no unmeasurable case left for this
|
|
4801
4786
|
* function to report and no catch here to keep reachable.
|
|
4802
4787
|
*/
|
|
4803
4788
|
function measureAuthoredFit(
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
*
|
|
45
45
|
* 1. **A table of the curves' lengths**, each curve four forward-difference
|
|
46
46
|
* steps (`0.1875`, `0.09375`, `0.75` and `0.16666667`, the running total
|
|
47
|
-
* carried across curves) — `
|
|
48
|
-
*
|
|
47
|
+
* carried across curves) — `curveLengthTable`, which `src/compile.ts`'s
|
|
48
|
+
* `pathCurveLengths` also calls (issue #1015). A ten-step table missed
|
|
49
|
+
* 460 of the grid's 976. The
|
|
49
50
|
* last entry is the path's length; `percent` multiplies the position by
|
|
50
51
|
* it. The stated `lengths` are not read: the stated total in their place
|
|
51
52
|
* missed 680 of 1,000.
|
|
@@ -217,6 +218,60 @@ import type { CoreCurve, CoreKey } from './animation.ts';
|
|
|
217
218
|
import type { CoreConstraintRecord } from './constraints.ts';
|
|
218
219
|
|
|
219
220
|
const RAD = RUNTIME_PI / 180;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The constant-speed walk's table of the curves' lengths — the header's *The
|
|
224
|
+
* walk*, step 1: each curve four forward-difference steps (`0.1875`,
|
|
225
|
+
* `0.09375`, `0.75`, `0.16666667`), the running total carried across curves,
|
|
226
|
+
* one cumulative entry per curve. `chain` is flat, `x, y` per point, knot
|
|
227
|
+
* handle handle knot …; `curveCount` curves are read off it.
|
|
228
|
+
*
|
|
229
|
+
* Exported because it has a second caller: an omitted `lengths` on a path
|
|
230
|
+
* attachment is measured by `src/compile.ts` through this function (issue
|
|
231
|
+
* #1015), so the number rigc writes into a file and the number the core walks
|
|
232
|
+
* are one computation. Measured as the header states it on the grid (a
|
|
233
|
+
* ten-step table missed 460 of 976) and held there by the core suite's `CP`
|
|
234
|
+
* controls.
|
|
235
|
+
*/
|
|
236
|
+
export function curveLengthTable(chain: readonly number[], curveCount: number): number[] {
|
|
237
|
+
const curveEnds: number[] = [];
|
|
238
|
+
let total = 0;
|
|
239
|
+
let x1 = chain[0];
|
|
240
|
+
let y1 = chain[1];
|
|
241
|
+
for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
|
|
242
|
+
const cx1 = chain[w];
|
|
243
|
+
const cy1 = chain[w + 1];
|
|
244
|
+
const cx2 = chain[w + 2];
|
|
245
|
+
const cy2 = chain[w + 3];
|
|
246
|
+
const x2 = chain[w + 4];
|
|
247
|
+
const y2 = chain[w + 5];
|
|
248
|
+
const h2x = (x1 - cx1 * 2 + cx2) * 0.1875;
|
|
249
|
+
const h2y = (y1 - cy1 * 2 + cy2) * 0.1875;
|
|
250
|
+
const d3x = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
|
|
251
|
+
const d3y = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
|
|
252
|
+
let d2x = h2x * 2 + d3x;
|
|
253
|
+
let d2y = h2y * 2 + d3y;
|
|
254
|
+
let d1x = (cx1 - x1) * 0.75 + h2x + d3x * 0.16666667;
|
|
255
|
+
let d1y = (cy1 - y1) * 0.75 + h2y + d3y * 0.16666667;
|
|
256
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
257
|
+
d1x += d2x;
|
|
258
|
+
d1y += d2y;
|
|
259
|
+
d2x += d3x;
|
|
260
|
+
d2y += d3y;
|
|
261
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
262
|
+
d1x += d2x;
|
|
263
|
+
d1y += d2y;
|
|
264
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
265
|
+
d1x += d2x + d3x;
|
|
266
|
+
d1y += d2y + d3y;
|
|
267
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
268
|
+
curveEnds.push(total);
|
|
269
|
+
x1 = x2;
|
|
270
|
+
y1 = y2;
|
|
271
|
+
}
|
|
272
|
+
return curveEnds;
|
|
273
|
+
}
|
|
274
|
+
|
|
220
275
|
const EPSILON = 0.00001;
|
|
221
276
|
|
|
222
277
|
/** The three modes a path constraint names, each as the model spells it once its first letter is lower-cased. */
|
|
@@ -556,47 +611,14 @@ function positionsOf(state: PathSolverState, c: CorePathRecord, g: CorePathGeome
|
|
|
556
611
|
chain.push(...P(0), ...P(1));
|
|
557
612
|
} else for (let k = 1; k < n - 1; k++) chain.push(...P(k));
|
|
558
613
|
const curveCount = closed ? n / 3 : n / 3 - 1;
|
|
559
|
-
const curveEnds
|
|
560
|
-
|
|
561
|
-
let x1 = chain[0];
|
|
562
|
-
let y1 = chain[1];
|
|
563
|
-
for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
|
|
564
|
-
const cx1 = chain[w];
|
|
565
|
-
const cy1 = chain[w + 1];
|
|
566
|
-
const cx2 = chain[w + 2];
|
|
567
|
-
const cy2 = chain[w + 3];
|
|
568
|
-
const x2 = chain[w + 4];
|
|
569
|
-
const y2 = chain[w + 5];
|
|
570
|
-
const h2x = (x1 - cx1 * 2 + cx2) * 0.1875;
|
|
571
|
-
const h2y = (y1 - cy1 * 2 + cy2) * 0.1875;
|
|
572
|
-
const d3x = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
|
|
573
|
-
const d3y = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
|
|
574
|
-
let d2x = h2x * 2 + d3x;
|
|
575
|
-
let d2y = h2y * 2 + d3y;
|
|
576
|
-
let d1x = (cx1 - x1) * 0.75 + h2x + d3x * 0.16666667;
|
|
577
|
-
let d1y = (cy1 - y1) * 0.75 + h2y + d3y * 0.16666667;
|
|
578
|
-
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
579
|
-
d1x += d2x;
|
|
580
|
-
d1y += d2y;
|
|
581
|
-
d2x += d3x;
|
|
582
|
-
d2y += d3y;
|
|
583
|
-
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
584
|
-
d1x += d2x;
|
|
585
|
-
d1y += d2y;
|
|
586
|
-
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
587
|
-
d1x += d2x + d3x;
|
|
588
|
-
d1y += d2y + d3y;
|
|
589
|
-
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
590
|
-
curveEnds.push(total);
|
|
591
|
-
x1 = x2;
|
|
592
|
-
y1 = y2;
|
|
593
|
-
}
|
|
614
|
+
const curveEnds = curveLengthTable(chain, curveCount);
|
|
615
|
+
const total = curveEnds.length > 0 ? curveEnds[curveEnds.length - 1] : 0;
|
|
594
616
|
if (c.positionMode === 'percent') position *= total;
|
|
595
617
|
const spaceScale = c.spacingMode === 'percent' ? total : c.spacingMode === 'proportional' ? total / spaceCount : 1;
|
|
596
618
|
const segmentEnds: number[] = new Array(10).fill(0);
|
|
597
619
|
let curveTotal = 0;
|
|
598
620
|
let lastCurve = -1;
|
|
599
|
-
let cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
|
|
621
|
+
let x1 = 0, y1 = 0, cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
|
|
600
622
|
for (let i = 0, o = 0, curve = 0, segment = 0; i < spaceCount; i++, o += 3) {
|
|
601
623
|
const space = spaces[i] * spaceScale;
|
|
602
624
|
position += space;
|
|
@@ -561,6 +561,27 @@ export function physicsState(ctx: PhysicsStepContext, name: string): PhysicsStat
|
|
|
561
561
|
const PI2 = RUNTIME_PI * 2;
|
|
562
562
|
const INV_PI2 = 1 / PI2;
|
|
563
563
|
|
|
564
|
+
/** What a physics constraint's step moves, by its five component fields. */
|
|
565
|
+
export interface PhysicsDrives {
|
|
566
|
+
x: boolean;
|
|
567
|
+
y: boolean;
|
|
568
|
+
/** `rotate` or `shearX` above 0: the two share the angular part of the step. */
|
|
569
|
+
rotateOrShearX: boolean;
|
|
570
|
+
scaleX: boolean;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Which parts of the step a constraint drives: a component above 0 drives its
|
|
575
|
+
* part and nothing else does — the test `stepPhysics` takes before each part
|
|
576
|
+
* of *The step* above, posed against the runtime with the rest of it. Exported
|
|
577
|
+
* because `src/ingest.ts` asks the same question of a raw file (a
|
|
578
|
+
* constraint that drives none of the four moves no bone, issue #731) and
|
|
579
|
+
* answers it through this function (issue #1015).
|
|
580
|
+
*/
|
|
581
|
+
export function physicsDrives(r: { x: number; y: number; rotate: number; shearX: number; scaleX: number }): PhysicsDrives {
|
|
582
|
+
return { x: r.x > 0, y: r.y > 0, rotateOrShearX: r.rotate > 0 || r.shearX > 0, scaleX: r.scaleX > 0 };
|
|
583
|
+
}
|
|
584
|
+
|
|
564
585
|
/**
|
|
565
586
|
* One physics constraint updated on its bone's world transform `w`
|
|
566
587
|
* (mutated), `length` the bone's length — the header's *The step*, every
|
|
@@ -572,10 +593,7 @@ export function stepPhysics(r: CorePhysicsRecord, w: CoreWorld, length: number,
|
|
|
572
593
|
const mix = r.mix;
|
|
573
594
|
if (mix === 0) return false;
|
|
574
595
|
const s = physicsState(ctx, r.name);
|
|
575
|
-
const x = r
|
|
576
|
-
const y = r.y > 0;
|
|
577
|
-
const rotateOrShearX = r.rotate > 0 || r.shearX > 0;
|
|
578
|
-
const scaleX = r.scaleX > 0;
|
|
596
|
+
const { x, y, rotateOrShearX, scaleX } = physicsDrives(r);
|
|
579
597
|
const l = length;
|
|
580
598
|
if (ctx.phase === 'reset') resetPhysicsState(s, ctx.time);
|
|
581
599
|
const delta = Math.max(ctx.time - s.lastTime, 0);
|
package/src/core/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rigc's own core: it reads the compiled model as `build` writes it
|
|
3
|
-
* (`rigc-compiled/
|
|
3
|
+
* (`rigc-compiled/2`, and `/1` before issue #1016; `skeleton.model.json`)
|
|
4
|
+
* and poses it (issue #925, step 2a
|
|
4
5
|
* of issue #380). It is the second dumper `tools/pose_oracle.ts` was shaped
|
|
5
6
|
* for: what it poses is written into the same `pose-oracle/2` document the
|
|
6
7
|
* runtime's dump is, and `compare` holds the two to each other.
|
|
@@ -168,7 +169,7 @@
|
|
|
168
169
|
* child process, no file system — `readModel` takes the document's TEXT — and
|
|
169
170
|
* nothing from the Spine runtime package, as a value or as a type.
|
|
170
171
|
*/
|
|
171
|
-
import type { ModelAtlasRect, ModelBone, ModelSlot, ModelVertices, SkinTableEntry } from '../model.ts';
|
|
172
|
+
import type { ModelAtlasRect, ModelBone, ModelPage, ModelPageRegion, ModelSlot, ModelVertices, SkinTableEntry } from '../model.ts';
|
|
172
173
|
import { worldTransforms, type CoreWorld } from './world.ts';
|
|
173
174
|
import { readAnimationTimelines, type CoreAnimationTimelines } from './animation.ts';
|
|
174
175
|
import { readEventDefs, type CoreEventDef } from './events.ts';
|
|
@@ -183,8 +184,19 @@ import { attachmentStates, type DeformBlender, type DeformEvaluator, type Sequen
|
|
|
183
184
|
import { drawOrderAt, type DrawOrderEvaluator } from './draw_order.ts';
|
|
184
185
|
import type { EventsFired } from './events.ts';
|
|
185
186
|
|
|
186
|
-
/**
|
|
187
|
-
|
|
187
|
+
/**
|
|
188
|
+
* The document spec `build` writes today, and the one this reader takes with
|
|
189
|
+
* the `pages` section (issue #1016).
|
|
190
|
+
*/
|
|
191
|
+
export const CORE_DOCUMENT_SPEC = 'rigc-compiled/2';
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The spec before issue #1016: the same sections without `pages`. Read as
|
|
195
|
+
* before — the draw then takes where each region sits from the atlas beside
|
|
196
|
+
* the document (`corePoser` in `src/render_core.ts` says so by name), because
|
|
197
|
+
* a `/1` document does not state it.
|
|
198
|
+
*/
|
|
199
|
+
export const CORE_DOCUMENT_SPEC_1 = 'rigc-compiled/1';
|
|
188
200
|
|
|
189
201
|
/** Who posed a dump the core wrote — the oracle document's `dumper`. */
|
|
190
202
|
export const CORE_DUMPER = 'rigc-core';
|
|
@@ -196,10 +208,19 @@ export class CoreInputError extends Error {}
|
|
|
196
208
|
export const CORE_SECTIONS = [
|
|
197
209
|
'referenceScale', 'bones', 'slots', 'skins', 'constraints', 'events', 'animations',
|
|
198
210
|
'images', 'pageGrids', 'droppedStates', 'absentParts', 'meshBones', 'meshes', 'physics', 'deformTransforms', 'trackDerivations', 'rig',
|
|
211
|
+
// Issue #1016: where each region sits on its page, in the atlas written beside the document (`pagesOfAtlas` in `src/model.ts`). `/2` only.
|
|
212
|
+
'pages',
|
|
199
213
|
// Issue #968: the digest of the `skeleton.json` written beside the document (`spineFileSha256` in `src/model.ts`).
|
|
200
214
|
'spine',
|
|
201
215
|
] as const;
|
|
202
216
|
|
|
217
|
+
/** The sections of a `rigc-compiled/1` document: `CORE_SECTIONS` without `pages`. */
|
|
218
|
+
export const CORE_SECTIONS_1: readonly string[] = CORE_SECTIONS.filter((key) => key !== 'pages');
|
|
219
|
+
|
|
220
|
+
/** The fields of a page and of a region in the `pages` section, as the writer lists them (`MODEL_PAGE_FIELDS`, `MODEL_PAGE_REGION_FIELDS` in `src/model.ts`, mirrored). */
|
|
221
|
+
export const CORE_PAGE_FIELDS = ['name', 'width', 'height', 'regions'] as const;
|
|
222
|
+
export const CORE_PAGE_REGION_FIELDS = ['name', 'x', 'y', 'width', 'height', 'offsetX', 'offsetY', 'originalWidth', 'originalHeight', 'degrees', 'index'] as const;
|
|
223
|
+
|
|
203
224
|
/** The fields a bone record may carry, as the writer lists them. A field outside this list is refused. */
|
|
204
225
|
export const CORE_BONE_FIELDS = ['name', 'parent', 'length', 'x', 'y', 'rotation', 'scaleX', 'scaleY', 'shearX', 'shearY', 'inheritMode', 'skinRequired', 'editor'] as const;
|
|
205
226
|
const BONE_NUMBERS = ['length', 'x', 'y', 'rotation', 'scaleX', 'scaleY', 'shearX', 'shearY'] as const;
|
|
@@ -297,7 +318,7 @@ export interface CoreAnimation {
|
|
|
297
318
|
}
|
|
298
319
|
|
|
299
320
|
/**
|
|
300
|
-
* A `rigc-compiled/1` document, read. `bones` and `slots` are checked field by
|
|
321
|
+
* A `rigc-compiled/2` (or `/1`) document, read. `bones` and `slots` are checked field by
|
|
301
322
|
* field against the writer's own records; skins and constraints are read as
|
|
302
323
|
* far as their names and memberships, and every other section is only
|
|
303
324
|
* required to be present — no construct this card admits reads it.
|
|
@@ -319,6 +340,66 @@ export interface CompiledDocument {
|
|
|
319
340
|
animations: CoreAnimation[];
|
|
320
341
|
/** The digest of the `skeleton.json` `build` wrote beside the document (issue #968) — what a render holds the file beside it to before posing it here. */
|
|
321
342
|
spine: { sha256: string };
|
|
343
|
+
/**
|
|
344
|
+
* Every page of the atlas `build` wrote beside the document and every region
|
|
345
|
+
* on it, in file order (issue #1016) — where each drawing sits, which the
|
|
346
|
+
* draw's page UVs read. `null` for a `rigc-compiled/1` document, which does
|
|
347
|
+
* not state it.
|
|
348
|
+
*/
|
|
349
|
+
pages: ModelPage[] | null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The `pages` section, checked: a list of pages, each holding exactly
|
|
354
|
+
* `CORE_PAGE_FIELDS` — a non-empty name, a size of two positive finite
|
|
355
|
+
* numbers, a list of regions — and each region exactly
|
|
356
|
+
* `CORE_PAGE_REGION_FIELDS`, a non-empty name and nine finite numbers. Every
|
|
357
|
+
* fault is named by its path. Nothing is defaulted: a field the writer always
|
|
358
|
+
* writes is required here.
|
|
359
|
+
*/
|
|
360
|
+
function readPages(value: unknown, problems: string[]): ModelPage[] {
|
|
361
|
+
if (!Array.isArray(value)) {
|
|
362
|
+
problems.push(`pages is ${JSON.stringify(value) ?? 'absent'}, not a list of pages`);
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const out: ModelPage[] = [];
|
|
366
|
+
value.forEach((raw, i) => {
|
|
367
|
+
const where = `pages[${i}]`;
|
|
368
|
+
if (!isRecord(raw)) {
|
|
369
|
+
problems.push(`${where} is not an object`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const before = problems.length;
|
|
373
|
+
const label = typeof raw.name === 'string' ? `${where} "${raw.name}"` : where;
|
|
374
|
+
unknownFields(raw, CORE_PAGE_FIELDS, label, problems);
|
|
375
|
+
if (typeof raw.name !== 'string' || raw.name === '') problems.push(`${where}: name is ${JSON.stringify(raw.name) ?? 'absent'}, not a non-empty string`);
|
|
376
|
+
for (const key of ['width', 'height'] as const) {
|
|
377
|
+
const v = raw[key];
|
|
378
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) problems.push(`${label}: ${key} is ${JSON.stringify(v) ?? 'absent'}, not a positive finite number — a page UV divides by it`);
|
|
379
|
+
}
|
|
380
|
+
const regions: ModelPageRegion[] = [];
|
|
381
|
+
if (!Array.isArray(raw.regions)) problems.push(`${label}: regions is not a list`);
|
|
382
|
+
else {
|
|
383
|
+
raw.regions.forEach((region, j) => {
|
|
384
|
+
const at = `${label}.regions[${j}]`;
|
|
385
|
+
if (!isRecord(region)) {
|
|
386
|
+
problems.push(`${at} is not an object`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const named = typeof region.name === 'string' ? `${at} "${region.name}"` : at;
|
|
390
|
+
unknownFields(region, CORE_PAGE_REGION_FIELDS, named, problems);
|
|
391
|
+
if (typeof region.name !== 'string' || region.name === '') problems.push(`${at}: name is ${JSON.stringify(region.name) ?? 'absent'}, not a non-empty string`);
|
|
392
|
+
for (const key of CORE_PAGE_REGION_FIELDS) {
|
|
393
|
+
if (key === 'name') continue;
|
|
394
|
+
const v = region[key];
|
|
395
|
+
if (typeof v !== 'number' || !Number.isFinite(v)) problems.push(`${named}: ${key} is ${JSON.stringify(v) ?? 'absent'}, not a finite number`);
|
|
396
|
+
}
|
|
397
|
+
regions.push(region as unknown as ModelPageRegion);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if (problems.length === before) out.push({ name: raw.name as string, width: raw.width as number, height: raw.height as number, regions });
|
|
401
|
+
});
|
|
402
|
+
return out;
|
|
322
403
|
}
|
|
323
404
|
|
|
324
405
|
/** The `spine` section's value, checked: `{ "sha256": <64 lowercase hex> }` and nothing else, each fault named by path (issue #968). */
|
|
@@ -615,7 +696,8 @@ function readConstraints(value: unknown, animations: readonly CoreAnimation[], b
|
|
|
615
696
|
}
|
|
616
697
|
|
|
617
698
|
/**
|
|
618
|
-
* Read a `rigc-compiled/
|
|
699
|
+
* Read a `rigc-compiled/2` document — or a `rigc-compiled/1` one, which has
|
|
700
|
+
* no `pages` and is read with `pages: null` — from its text, refusing by name a text
|
|
619
701
|
* that is not JSON, a wrong `spec`, a missing section (`referenceScale`
|
|
620
702
|
* among them, issue #958), a section the document does not have, a
|
|
621
703
|
* `referenceScale` that is not a finite number, and — in the records these constructs read (bones, slots,
|
|
@@ -633,12 +715,15 @@ export function readModel(text: string, where = 'the model document'): CompiledD
|
|
|
633
715
|
throw new CoreInputError(`${where}: not JSON — ${(err as Error).message}`);
|
|
634
716
|
}
|
|
635
717
|
if (!isRecord(value)) throw new CoreInputError(`${where}: not a JSON object`);
|
|
636
|
-
if (value.spec !== CORE_DOCUMENT_SPEC) throw new CoreInputError(`${where}: spec is ${JSON.stringify(value.spec)}, not "${CORE_DOCUMENT_SPEC}"`);
|
|
718
|
+
if (value.spec !== CORE_DOCUMENT_SPEC && value.spec !== CORE_DOCUMENT_SPEC_1) throw new CoreInputError(`${where}: spec is ${JSON.stringify(value.spec)}, not "${CORE_DOCUMENT_SPEC}" (or "${CORE_DOCUMENT_SPEC_1}", read without its pages)`);
|
|
719
|
+
const spec = value.spec;
|
|
720
|
+
const sections: readonly string[] = spec === CORE_DOCUMENT_SPEC ? CORE_SECTIONS : CORE_SECTIONS_1;
|
|
637
721
|
const problems: string[] = [];
|
|
638
|
-
for (const key of
|
|
722
|
+
for (const key of sections) if (!(key in value)) problems.push(`section "${key}" is missing`);
|
|
639
723
|
for (const key of Object.keys(value)) {
|
|
640
|
-
if (key !== 'spec' && !
|
|
724
|
+
if (key !== 'spec' && !sections.includes(key)) problems.push(`section "${key}" is not one a ${spec} document has`);
|
|
641
725
|
}
|
|
726
|
+
const pages = spec === CORE_DOCUMENT_SPEC && 'pages' in value ? readPages(value.pages, problems) : null;
|
|
642
727
|
// The skeleton's reference scale (issue #958): wind and gravity act over it, so a missing one is the section refusal above, and a value the runtime could not read as a number is refused here by name.
|
|
643
728
|
const referenceScale = typeof value.referenceScale === 'number' && Number.isFinite(value.referenceScale) ? value.referenceScale : NaN;
|
|
644
729
|
if ('referenceScale' in value && Number.isNaN(referenceScale)) problems.push(`referenceScale is ${JSON.stringify(value.referenceScale)}, not a finite number — wind and gravity act over it`);
|
|
@@ -666,7 +751,7 @@ export function readModel(text: string, where = 'the model document'): CompiledD
|
|
|
666
751
|
});
|
|
667
752
|
}
|
|
668
753
|
if (problems.length > 0) throw new CoreInputError(`${where}: ${problems.length} problem(s): ${problems.join('; ')}`);
|
|
669
|
-
const doc: CompiledDocument = { spec
|
|
754
|
+
const doc: CompiledDocument = { spec, skin: CORE_ALL_SKINS, referenceScale, bones, slots, skins, constraints, animations, spine: { sha256: spine }, pages };
|
|
670
755
|
resolveSkinView(doc);
|
|
671
756
|
return doc;
|
|
672
757
|
}
|
package/src/core/uvs.ts
CHANGED
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* and `sequence.getUVs(index)` — and `MeshAttachment.computeUVs`'s job, the
|
|
6
6
|
* mapping texture substitution calls, as pure functions.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
8
|
+
* The page layout is not a model record (`ModelAtlasRect`'s 🔸 in
|
|
9
9
|
* `src/model.ts`: the page, `x`, `y` and `rotate` are the packer's
|
|
10
|
-
* arrangement, not the drawing)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* arrangement, not the drawing). It reaches these rules as a lookup
|
|
11
|
+
* (`UvLookup`), so nothing here links the runtime or opens a file: since
|
|
12
|
+
* issue #1016 from the document's own `pages` section, which `build` spells
|
|
13
|
+
* from the atlas it writes (`documentPageLookup` below); for a
|
|
14
|
+
* `rigc-compiled/1` document, which has none, from the atlas beside it, read
|
|
15
|
+
* by rigc's own reader (`parseAtlasText` and `atlasRegionLookup` in
|
|
16
|
+
* `src/atlas.ts`).
|
|
14
17
|
*
|
|
15
18
|
* ## How every rule below was fixed
|
|
16
19
|
*
|
|
@@ -143,6 +146,7 @@ import { applyConstraints } from './constraints.ts';
|
|
|
143
146
|
import type { SliderApplication } from './constraints_slider.ts';
|
|
144
147
|
import { attachmentStates } from './deform.ts';
|
|
145
148
|
import type { ShownGeometry } from './vertices.ts';
|
|
149
|
+
import type { ModelPage, ModelPageRegion } from '../model.ts';
|
|
146
150
|
import { worldTransforms } from './world.ts';
|
|
147
151
|
|
|
148
152
|
/** A region as these rules read it — `AtlasRegion` in `src/atlas.ts` is one, and so is spine-core's `TextureAtlasRegion`. */
|
|
@@ -168,6 +172,22 @@ export interface UvPage {
|
|
|
168
172
|
/** The region an atlas draws under a name, and its page — the first of that name, as the header states; `null` when there is none. */
|
|
169
173
|
export type UvLookup = (name: string) => { page: UvPage; region: UvRegion } | null;
|
|
170
174
|
|
|
175
|
+
/**
|
|
176
|
+
* The lookup over a document's `pages` section (issue #1016): the first region
|
|
177
|
+
* of a name in file order, pages in order and regions in each page's order —
|
|
178
|
+
* the order `pagesOfAtlas` in `src/model.ts` copies from the atlas, so this is
|
|
179
|
+
* `atlasRegionLookup` over the same text, with no atlas read. The name is
|
|
180
|
+
* compared as stored, untrimmed, as the header's *Which region* states.
|
|
181
|
+
*/
|
|
182
|
+
export function documentPageLookup(pages: readonly ModelPage[]): (name: string) => { page: UvPage; region: ModelPageRegion } | null {
|
|
183
|
+
const first = new Map<string, { page: UvPage; region: ModelPageRegion }>();
|
|
184
|
+
for (const page of pages) {
|
|
185
|
+
const at: UvPage = { name: page.name, width: page.width, height: page.height };
|
|
186
|
+
for (const region of page.regions) if (!first.has(region.name)) first.set(region.name, { page: at, region });
|
|
187
|
+
}
|
|
188
|
+
return (name) => first.get(name) ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
/**
|
|
172
192
|
* The readings the header's tables rejected, each a switch — what the core
|
|
173
193
|
* suite's `CU` plants pass to show the gate names it. The functions below with
|