spine-rigc 1.5.1 → 1.5.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/docs/AUTHORING.md +43 -10
- package/docs/MOTION.md +125 -78
- package/package.json +1 -1
- package/src/compile.ts +36 -1
- package/src/keys.ts +286 -4
- package/src/motion.ts +137 -3
- package/src/pose.ts +53 -0
- package/src/rig.ts +270 -36
- package/src/types.ts +70 -0
- package/src/validate.ts +112 -56
package/src/validate.ts
CHANGED
|
@@ -289,16 +289,17 @@ export const SKIP_NO_ATLAS = 'the round trip did not produce an atlas to measure
|
|
|
289
289
|
* `A15_IDLE_NO_MESH_BONE_KEYS` is the pattern and already reads this way: no
|
|
290
290
|
* `idle` animation, or an `idle` with no bone timeline, is the named subject
|
|
291
291
|
* absent and SKIPs; no mesh-driving bone among the bones `idle` does key is
|
|
292
|
-
* the construct absent and passes. `A10_NO_NAN_AFTER_STEPPING`
|
|
293
|
-
* shape
|
|
294
|
-
*
|
|
292
|
+
* the construct absent and passes. `A10_NO_NAN_AFTER_STEPPING` was the same
|
|
293
|
+
* shape until issue #902, and is the next bullet's now: #882 gave it a
|
|
294
|
+
* setup-pose clause whose subject is the bones, beside the stepping clause
|
|
295
|
+
* whose subject is the animations.
|
|
295
296
|
* * An assertion with more than one clause SKIPs only when EVERY clause had
|
|
296
297
|
* nothing to measure, which is the shape `A09`, `A33` and `A38` already
|
|
297
|
-
* carry (`polygons.length === 0 && endsChecked === 0`)
|
|
298
|
-
* why the clause is stated: a declared slot budget is
|
|
299
|
-
* count of mesh slots, and zero is a count, so that half
|
|
300
|
-
* with no mesh — while a rig that declares only a TRIANGLE
|
|
301
|
-
* nothing left to measure and skips.
|
|
298
|
+
* carry (`polygons.length === 0 && endsChecked === 0`), and `A10` since #902.
|
|
299
|
+
* `A13_MESH_BUDGET` is why the clause is stated: a declared slot budget is
|
|
300
|
+
* measured against a count of mesh slots, and zero is a count, so that half
|
|
301
|
+
* passes on a rig with no mesh — while a rig that declares only a TRIANGLE
|
|
302
|
+
* budget has nothing left to measure and skips.
|
|
302
303
|
*
|
|
303
304
|
* ⚠️ What the criterion is not allowed to become is a table of assertions with
|
|
304
305
|
* their verdicts written beside them. Every row above is decided by reading the
|
|
@@ -318,6 +319,13 @@ export const SKIP_NO_ATLAS = 'the round trip did not produce an atlas to measure
|
|
|
318
319
|
export const SKIP_NO_REGION_ATTACHMENT = 'the skeleton carries no region attachment';
|
|
319
320
|
export const SKIP_NO_MESH_ATTACHMENT = 'the skeleton carries no mesh attachment';
|
|
320
321
|
export const SKIP_NO_ANIMATION = 'the skeleton carries no animation';
|
|
322
|
+
/**
|
|
323
|
+
* A10's (issue #902): its setup clause reads the bones and its stepping clause
|
|
324
|
+
* the animations, so it skips only when the skeleton carries neither — and the
|
|
325
|
+
* sentence is `SKIP_NO_ANIMATION`'s with the second subject added, so the two
|
|
326
|
+
* cannot drift into different words for the same absence.
|
|
327
|
+
*/
|
|
328
|
+
export const SKIP_NO_POSE = `${SKIP_NO_ANIMATION} and no bone, so there is no setup pose to read and nothing to step`;
|
|
321
329
|
export const SKIP_NO_TIMELINE = 'no animation here carries a timeline';
|
|
322
330
|
export const SKIP_NO_PHYSICS_CONSTRAINT = 'the skeleton declares no physics constraint';
|
|
323
331
|
export const SKIP_NO_ATLAS_PAGE = 'the atlas declares no page';
|
|
@@ -4336,13 +4344,31 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
4336
4344
|
|
|
4337
4345
|
// --- A10: step every animation and look for NaN ------------------------
|
|
4338
4346
|
check('A10_NO_NAN_AFTER_STEPPING', () => {
|
|
4339
|
-
//
|
|
4340
|
-
//
|
|
4341
|
-
//
|
|
4342
|
-
//
|
|
4343
|
-
//
|
|
4344
|
-
//
|
|
4345
|
-
|
|
4347
|
+
// Two clauses, and since issue #902 each is read on its own subject. The
|
|
4348
|
+
// SETUP POSE is posed from the bones, so it has something to read on any
|
|
4349
|
+
// skeleton that carries one; the STEPPED FRAMES are posed once per
|
|
4350
|
+
// animation, so a skeleton with none has nothing to step. This used to
|
|
4351
|
+
// skip the whole rule on the second fact alone, which was A15's pattern
|
|
4352
|
+
// (#580) for a rule whose only clause was the stepping — and #882 gave it
|
|
4353
|
+
// the setup clause without moving the skip. Measured on the static probe
|
|
4354
|
+
// with a leaf bone at `rotation: 1e309` in its emitted file: `validate`
|
|
4355
|
+
// printed this rule as SKIP and the run green under both profiles, and
|
|
4356
|
+
// `render` refused the same file by the bone. A static rig's setup pose
|
|
4357
|
+
// is the whole of what it shows, so that was the one pose nothing read.
|
|
4358
|
+
//
|
|
4359
|
+
// ⇒ The multi-clause rule of #580 now governs, the shape A09, A13, A33
|
|
4360
|
+
// and A38 carry: the rule SKIPs only when neither clause has anything to
|
|
4361
|
+
// measure, the clause that ran decides PASS or FAIL, and the clause that
|
|
4362
|
+
// had nothing is named on the stats line (`nanStepping=skipped`, beside
|
|
4363
|
+
// `animations=0`) the way A47 names a constraint it did not measure. A
|
|
4364
|
+
// PASS row carries no detail, which is why the stats line is where.
|
|
4365
|
+
//
|
|
4366
|
+
// ⚠️ A09 does not follow, and that is its own name read at its word: a
|
|
4367
|
+
// declared duration is a fact about an animation and nothing else, so a
|
|
4368
|
+
// static rig still gives it nothing at all.
|
|
4369
|
+
if (data.animations.length === 0 && data.bones.length === 0) {
|
|
4370
|
+
return skip('A10_NO_NAN_AFTER_STEPPING', SKIP_NO_POSE);
|
|
4371
|
+
}
|
|
4346
4372
|
/** Is this a mode `updateWorldTransform`'s switch has a case for? Read off the runtime's own enum. */
|
|
4347
4373
|
const isMode = (inherit: unknown): boolean => typeof inherit === 'number' && Inherit[inherit] !== undefined;
|
|
4348
4374
|
|
|
@@ -4421,6 +4447,73 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
4421
4447
|
// below is posed AFTER `state.apply`, so a bone the animation keys from
|
|
4422
4448
|
// t=0 never shows its setup value to the loop, and a runtime that shows
|
|
4423
4449
|
// the rig at rest does show it.
|
|
4450
|
+
//
|
|
4451
|
+
// 🔸 Posed once, before any animation is set, rather than once per
|
|
4452
|
+
// animation as it was until #902: `setAnimation` does not touch the
|
|
4453
|
+
// skeleton, so every animation's copy of this pose was the same pose, and
|
|
4454
|
+
// the first of them was the only one ever read.
|
|
4455
|
+
if (data.animations.length === 0) stats.nanStepping = 'skipped';
|
|
4456
|
+
const atRest = new Skeleton(data);
|
|
4457
|
+
atRest.setupPose();
|
|
4458
|
+
atRest.update(0);
|
|
4459
|
+
atRest.updateWorldTransform(Physics.reset);
|
|
4460
|
+
const atSetup = nonFiniteOfPosed('the setup pose', atRest);
|
|
4461
|
+
if (atSetup !== null) {
|
|
4462
|
+
fail('A10_NO_NAN_AFTER_STEPPING', atSetup);
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
/**
|
|
4467
|
+
* What a posed frame shows that the world-transform scan does not read: a
|
|
4468
|
+
* bone posing no inheritance mode, and a slot colour that is not finite.
|
|
4469
|
+
* `where` leads the sentence — the animation's name at a stepped frame,
|
|
4470
|
+
* as it always has, or `the setup pose` on a skeleton with no animation.
|
|
4471
|
+
*/
|
|
4472
|
+
const poseDefect = (where: string, skeleton: Skeleton): string | null => {
|
|
4473
|
+
for (const bone of skeleton.bones) {
|
|
4474
|
+
const pose = bone.appliedPose;
|
|
4475
|
+
// The setup half of the inherit-key clause above: a bone's own
|
|
4476
|
+
// `inherit` goes through the same lookup, and a miss there loads
|
|
4477
|
+
// `undefined` into the setup pose, which every frame copies. Only
|
|
4478
|
+
// reachable on a file rigc did not write — `parseRigSpec` refuses the
|
|
4479
|
+
// spelling.
|
|
4480
|
+
if (!isMode(pose.inherit)) {
|
|
4481
|
+
const rawBone = Array.isArray(raw?.bones)
|
|
4482
|
+
? (raw.bones as unknown[]).find((b) => isObj(b) && b.name === bone.data.name)
|
|
4483
|
+
: undefined;
|
|
4484
|
+
return (
|
|
4485
|
+
`${where}: bone "${bone.data.name}" poses inheritance mode ${String(pose.inherit)} — its setup ` +
|
|
4486
|
+
`spells inherit ${JSON.stringify(isObj(rawBone) ? rawBone.inherit : undefined)}, which the ` +
|
|
4487
|
+
`runtime's mode lookup does not resolve (${BONE_INHERIT_KNOWN}), so its world rotation, scale and shear ` +
|
|
4488
|
+
'are never computed — they stay 0 and everything the bone carries collapses to a point'
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
for (const slot of skeleton.slots) {
|
|
4493
|
+
const c = slot.appliedPose.color;
|
|
4494
|
+
if (![c.r, c.g, c.b, c.a].every(Number.isFinite)) return `${where}: slot "${slot.data.name}" colour is non-finite`;
|
|
4495
|
+
// The other colour a slot poses, and it was outside this loop until
|
|
4496
|
+
// issue #690 for the reason every gap here has: nothing emitted one.
|
|
4497
|
+
// `null` is the ordinary case — a slot with no `dark` allocates no
|
|
4498
|
+
// dark colour at all — and is not a reading to make, so it is
|
|
4499
|
+
// skipped rather than treated as zero.
|
|
4500
|
+
const d = slot.appliedPose.darkColor;
|
|
4501
|
+
if (d !== null && ![d.r, d.g, d.b].every(Number.isFinite)) {
|
|
4502
|
+
return `${where}: slot "${slot.data.name}" dark colour is non-finite`;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
return null;
|
|
4506
|
+
};
|
|
4507
|
+
|
|
4508
|
+
// The stepping half, and on a static rig there is nothing to step: the
|
|
4509
|
+
// setup pose is then the only frame the skeleton has, and the two readings
|
|
4510
|
+
// a stepped frame gets are made on it instead, so a colour or an
|
|
4511
|
+
// inheritance mode the rig shows at rest is not left to `render`.
|
|
4512
|
+
if (data.animations.length === 0) {
|
|
4513
|
+
const atRestDefect = poseDefect('the setup pose', atRest);
|
|
4514
|
+
if (atRestDefect !== null) fail('A10_NO_NAN_AFTER_STEPPING', atRestDefect);
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4424
4517
|
for (const anim of data.animations) {
|
|
4425
4518
|
const skeleton = new Skeleton(data);
|
|
4426
4519
|
const state = new AnimationState(new AnimationStateData(data));
|
|
@@ -4428,11 +4521,6 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
4428
4521
|
skeleton.setupPose();
|
|
4429
4522
|
skeleton.update(0);
|
|
4430
4523
|
skeleton.updateWorldTransform(Physics.reset);
|
|
4431
|
-
const atSetup = nonFiniteOfPosed('the setup pose', skeleton);
|
|
4432
|
-
if (atSetup !== null) {
|
|
4433
|
-
fail('A10_NO_NAN_AFTER_STEPPING', atSetup);
|
|
4434
|
-
return;
|
|
4435
|
-
}
|
|
4436
4524
|
const step = Math.max(anim.duration, 1) / STEP_FRAMES;
|
|
4437
4525
|
for (let i = 0; i < STEP_FRAMES; i++) {
|
|
4438
4526
|
state.update(step);
|
|
@@ -4447,42 +4535,10 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
4447
4535
|
fail('A10_NO_NAN_AFTER_STEPPING', found);
|
|
4448
4536
|
return;
|
|
4449
4537
|
}
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
// the setup pose, which every frame copies. Only reachable on a
|
|
4455
|
-
// file rigc did not write — `parseRigSpec` refuses the spelling.
|
|
4456
|
-
if (!isMode(pose.inherit)) {
|
|
4457
|
-
const rawBone = Array.isArray(raw?.bones)
|
|
4458
|
-
? (raw.bones as unknown[]).find((b) => isObj(b) && b.name === bone.data.name)
|
|
4459
|
-
: undefined;
|
|
4460
|
-
fail(
|
|
4461
|
-
'A10_NO_NAN_AFTER_STEPPING',
|
|
4462
|
-
`${anim.name}: bone "${bone.data.name}" poses inheritance mode ${String(pose.inherit)} — its setup ` +
|
|
4463
|
-
`spells inherit ${JSON.stringify(isObj(rawBone) ? rawBone.inherit : undefined)}, which the ` +
|
|
4464
|
-
`runtime's mode lookup does not resolve (${BONE_INHERIT_KNOWN}), so its world rotation, scale and shear ` +
|
|
4465
|
-
'are never computed — they stay 0 and everything the bone carries collapses to a point',
|
|
4466
|
-
);
|
|
4467
|
-
return;
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
for (const slot of skeleton.slots) {
|
|
4471
|
-
const c = slot.appliedPose.color;
|
|
4472
|
-
if (![c.r, c.g, c.b, c.a].every(Number.isFinite)) {
|
|
4473
|
-
fail('A10_NO_NAN_AFTER_STEPPING', `${anim.name}: slot "${slot.data.name}" colour is non-finite`);
|
|
4474
|
-
return;
|
|
4475
|
-
}
|
|
4476
|
-
// The other colour a slot poses, and it was outside this loop until
|
|
4477
|
-
// issue #690 for the reason every gap here has: nothing emitted one.
|
|
4478
|
-
// `null` is the ordinary case — a slot with no `dark` allocates no
|
|
4479
|
-
// dark colour at all — and is not a reading to make, so it is
|
|
4480
|
-
// skipped rather than treated as zero.
|
|
4481
|
-
const d = slot.appliedPose.darkColor;
|
|
4482
|
-
if (d !== null && ![d.r, d.g, d.b].every(Number.isFinite)) {
|
|
4483
|
-
fail('A10_NO_NAN_AFTER_STEPPING', `${anim.name}: slot "${slot.data.name}" dark colour is non-finite`);
|
|
4484
|
-
return;
|
|
4485
|
-
}
|
|
4538
|
+
const defect = poseDefect(anim.name, skeleton);
|
|
4539
|
+
if (defect !== null) {
|
|
4540
|
+
fail('A10_NO_NAN_AFTER_STEPPING', defect);
|
|
4541
|
+
return;
|
|
4486
4542
|
}
|
|
4487
4543
|
}
|
|
4488
4544
|
}
|