spine-rigc 2.14.0 → 2.14.1
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/package.json +1 -1
- package/src/assertions/model/stepped_poses.ts +16 -7
- package/src/core/animation.ts +11 -0
- package/src/core/raw.ts +216 -17
- package/src/core/walk.ts +21 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.1",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
* rather than refusing it. It is held equal to spine-core's walk at
|
|
14
14
|
* tolerance 0 by `tools/core_gate.ts`'s walk block and the selftest's
|
|
15
15
|
* `CO25`, and the poses A10 asks for here are compared with spine-core's on
|
|
16
|
-
* every call with a model in hand (`VF13`).
|
|
16
|
+
* every call with a model in hand (`VF13`). Since issue #1179's second part
|
|
17
|
+
* the poses come through that walk's scan assembler (`scanWalkSetup`,
|
|
18
|
+
* `scanLoopingWalk`): the same walk, each pose carrying what A10 reads — the
|
|
19
|
+
* bones' matrix and origin, the slot rows, the drawn attachments' slot, name
|
|
20
|
+
* and vertices — and none of the bones' getter readings and oracle rows or
|
|
21
|
+
* the drawn rows' UVs, triangles and colour, which nothing it reads is
|
|
22
|
+
* computed from. The core suite's `CO43` holds the scan to the public walk's
|
|
23
|
+
* numbers bit for bit, `CO44` what it leaves out to move none of them, and
|
|
24
|
+
* `CO45` that the vertex arrays it hands on uncopied are each pose's own.
|
|
17
25
|
* - **A bone's mode**: the mode the core's bone timelines pose at the time
|
|
18
26
|
* (`posedBones`, a fresh non-looping track: the time held at the duration),
|
|
19
27
|
* folded as the runtime's lookup folds it (`foldInheritMode`). `readModel`
|
|
@@ -30,7 +38,8 @@
|
|
|
30
38
|
import { fileAnimationOrder } from '../../compile.ts';
|
|
31
39
|
import { foldInheritMode, type CompiledDocument } from '../../core/index.ts';
|
|
32
40
|
import { posedBones } from '../../core/animation.ts';
|
|
33
|
-
import {
|
|
41
|
+
import { scanLoopingWalk, scanWalkSetup } from '../../core/walk.ts';
|
|
42
|
+
import type { ScanPose } from '../../core/raw.ts';
|
|
34
43
|
import { noSkinView } from '../../render_core.ts';
|
|
35
44
|
import type { ModelBone } from '../../model.ts';
|
|
36
45
|
import type { SteppedFrame, SteppedPoseFacts } from '../facts/stepped_poses.ts';
|
|
@@ -45,11 +54,11 @@ const channel = (v: number | null): number => (v === null ? Number.NaN : v);
|
|
|
45
54
|
/** Each bone's posed mode as the facts spell it (`modeOf`), by name. */
|
|
46
55
|
const modesOf = (posed: readonly ModelBone[]): ReadonlyMap<string, string | null> => new Map(posed.map((b) => [b.name, modeOf(b)]));
|
|
47
56
|
|
|
48
|
-
/** One
|
|
49
|
-
function frameOf(pose:
|
|
57
|
+
/** One scan pose as A10 reads it, each bone's mode taken from `modes` (`modesOf` the bones the timelines posed at the pose's time); the drawn attachments are the scan's own rows, already in the shape A10 reads. */
|
|
58
|
+
function frameOf(pose: ScanPose, modes: ReadonlyMap<string, string | null>): SteppedFrame {
|
|
50
59
|
return {
|
|
51
60
|
bones: pose.bones.map((b) => ({ name: b.name, a: b.a, b: b.b, c: b.c, d: b.d, worldX: b.worldX, worldY: b.worldY, inherit: modes.get(b.name) ?? null })),
|
|
52
|
-
drawn: pose.drawn
|
|
61
|
+
drawn: pose.drawn,
|
|
53
62
|
slots: pose.slots.map((row) => ({
|
|
54
63
|
name: row[0],
|
|
55
64
|
colour: [channel(row[2]), channel(row[3]), channel(row[4]), channel(row[5])] as const,
|
|
@@ -82,10 +91,10 @@ export function modelSteppedPoses(read: ReadDocument): SteppedPoseFacts {
|
|
|
82
91
|
},
|
|
83
92
|
// The document's stated mode — asked only of a bone posing none, which the reader leaves this side no document to hold.
|
|
84
93
|
statedInherit: (name) => `${JSON.stringify(read.doc.bones.find((b) => b.name === name)?.inheritMode)}`,
|
|
85
|
-
setup: () => frameOf(
|
|
94
|
+
setup: () => frameOf(scanWalkSetup(viewOf()), modesOf(viewOf().bones)),
|
|
86
95
|
walk: (animation, step, frames) => {
|
|
87
96
|
const anim = animationOf(animation);
|
|
88
|
-
const poses =
|
|
97
|
+
const poses = scanLoopingWalk(viewOf(), animation, new Array<number>(frames).fill(step));
|
|
89
98
|
// A bone's posed mode moves only by an `inherit` timeline: every other one `posedBones` reads leaves the setup's mode on its copy. So an animation
|
|
90
99
|
// keying none poses the setup's modes at every time, and they are read once rather than posed again at each of its frames (issue #1179).
|
|
91
100
|
const keysInherit = anim.timelines.bones.some((target) => target.timelines.some((tl) => tl.kind === 'inherit'));
|
package/src/core/animation.ts
CHANGED
|
@@ -797,6 +797,17 @@ export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTi
|
|
|
797
797
|
return { rows: stepRows(posed), world: posed.world };
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
+
/**
|
|
801
|
+
* `posedBoneWorld`'s world transforms without its rows (issue #1179, the
|
|
802
|
+
* second part): the same pose by the same operations in the same order, since
|
|
803
|
+
* `stepRows` only reads the world once it is posed. A walk reads `world` and
|
|
804
|
+
* no row, so the scan's walk (`scanWalkIn` in `./raw.ts`, A10's) asks for
|
|
805
|
+
* this; the raw entry's walk still forms the rows it does not read, unchanged.
|
|
806
|
+
*/
|
|
807
|
+
export function posedBoneWorldAlone(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: { ctx: PhysicsStepContext; before: number } | undefined): Map<string, CoreWorld> {
|
|
808
|
+
return posedBoneStep(doc, timelines, t, plant, constraints, sliders, step).world;
|
|
809
|
+
}
|
|
810
|
+
|
|
800
811
|
/**
|
|
801
812
|
* One pose of the bones before its rows are read: the world transforms, and
|
|
802
813
|
* what the rows are rounded from. A stepped walk reads rows only off the step
|
package/src/core/raw.ts
CHANGED
|
@@ -114,14 +114,14 @@
|
|
|
114
114
|
* missing would draw a different picture in silence.
|
|
115
115
|
*/
|
|
116
116
|
import { activeBones, CoreInputError, drawWalkOf, poseSetup, rawNumber, readColour, shownAttachment, sourceOfDoc, underNoSkin, type CompiledDocument, type CorePlant, type CoreSlotRow } from './index.ts';
|
|
117
|
-
import { posedBoneWorld, posedSlots, type TimelinePlant } from './animation.ts';
|
|
117
|
+
import { posedBoneWorld, posedBoneWorldAlone, posedSlots, type TimelinePlant } from './animation.ts';
|
|
118
118
|
import { constraintsAbsentWhy, pathAnimationsWhy } from './constraints.ts';
|
|
119
119
|
import { freshStepContext, steppedPreviousPassWhy } from './constraints_physics.ts';
|
|
120
120
|
import { attachmentStates } from './deform.ts';
|
|
121
121
|
import { drawOrderAt } from './draw_order.ts';
|
|
122
122
|
import { eventsFired } from './events.ts';
|
|
123
|
-
import { REGION_TRIANGLES, REGION_UVS, type ShapeClipper } from './clipping.ts';
|
|
124
|
-
import { poseGeometry, type ShownGeometry } from './vertices.ts';
|
|
123
|
+
import { REGION_TRIANGLES, REGION_UVS, type CoreClippedRow, type ShapeClipper } from './clipping.ts';
|
|
124
|
+
import { poseGeometry, type CoreAttachmentRow, type CoreClipRow, type ShownGeometry } from './vertices.ts';
|
|
125
125
|
import { RUNTIME_DEG, type CoreWorld } from './world.ts';
|
|
126
126
|
import type { SliderApplication } from './constraints_slider.ts';
|
|
127
127
|
|
|
@@ -195,6 +195,43 @@ export interface RawPose {
|
|
|
195
195
|
// --- #968 render: end ---
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* One bone as the scan reads it (issue #1179, the second part): the world
|
|
200
|
+
* matrix and origin `RawBone` carries, and none of the readings it forms from
|
|
201
|
+
* them. `A10_NO_NAN_AFTER_STEPPING` reads these six numbers and the name off a
|
|
202
|
+
* bone, and nothing else (`firstNonFinite` in `src/nonfinite.ts`).
|
|
203
|
+
*/
|
|
204
|
+
export interface ScanBone {
|
|
205
|
+
name: string;
|
|
206
|
+
a: number;
|
|
207
|
+
b: number;
|
|
208
|
+
c: number;
|
|
209
|
+
d: number;
|
|
210
|
+
worldX: number;
|
|
211
|
+
worldY: number;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* One pose as the scan reads it (issue #1179, the second part) — the scan's
|
|
216
|
+
* walk (`scanWalkIn`, `scanSetupIn`): the time the animation was applied at,
|
|
217
|
+
* and the bones, slot rows and drawn attachments computed exactly as a
|
|
218
|
+
* `RawPose`'s are. Internal to A10's walk (`./walk.ts`); no exported pose type
|
|
219
|
+
* changed for it.
|
|
220
|
+
*/
|
|
221
|
+
export interface ScanPose {
|
|
222
|
+
animationTime: number;
|
|
223
|
+
bones: ScanBone[];
|
|
224
|
+
slots: CoreSlotRow[];
|
|
225
|
+
drawn: ScanDrawn[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** One region or mesh drawn, as the scan reads it: its slot, its name and its world vertices, as `RawDrawn` carries them. */
|
|
229
|
+
export interface ScanDrawn {
|
|
230
|
+
slot: string;
|
|
231
|
+
attachment: string;
|
|
232
|
+
vertices: number[];
|
|
233
|
+
}
|
|
234
|
+
|
|
198
235
|
// --- #968 render: begin ---
|
|
199
236
|
/** The shown records in `order`, the pose's draw order (a pose's `shown`). */
|
|
200
237
|
function drawOrderOf(shown: readonly ShownGeometry[], order: readonly string[]): ShownGeometry[] {
|
|
@@ -274,17 +311,96 @@ function rawBones(doc: CompiledDocument, world: ReadonlyMap<string, CoreWorld>):
|
|
|
274
311
|
});
|
|
275
312
|
}
|
|
276
313
|
|
|
277
|
-
/**
|
|
278
|
-
|
|
314
|
+
/**
|
|
315
|
+
* The bones of a scan pose (`ScanBone`): `rawBones` less the parent, the
|
|
316
|
+
* active flag and the four getter readings, which nothing the scan reads is
|
|
317
|
+
* computed from — each reading is formed from the world after it is posed,
|
|
318
|
+
* and the matrix and origin are copied as they are.
|
|
319
|
+
*/
|
|
320
|
+
function scanBones(doc: CompiledDocument, world: ReadonlyMap<string, CoreWorld>): ScanBone[] {
|
|
321
|
+
return doc.bones.map((b): ScanBone => {
|
|
322
|
+
const w = world.get(b.name);
|
|
323
|
+
if (w === undefined) throw new CoreInputError(`bone "${b.name}" has no world transform`);
|
|
324
|
+
return { name: b.name, a: w.a, b: w.b, c: w.c, d: w.d, worldX: w.worldX, worldY: w.worldY };
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* What a pose draws, posed and refused where the core leaves it out: the
|
|
330
|
+
* attachment rows, the clip rows and the drawn clipped rows — what `rawDrawn`
|
|
331
|
+
* and `scanDrawn` both assemble from.
|
|
332
|
+
*/
|
|
333
|
+
function drawnGeometry(doc: CompiledDocument, shown: readonly ShownGeometry[], world: ReadonlyMap<string, CoreWorld>, order: readonly string[], plant: CorePlant, mode: WalkMode): { rows: CoreAttachmentRow[]; clips: CoreClipRow[]; drawnClipped: CoreClippedRow[] } {
|
|
279
334
|
const draw = drawWalkOf(doc, order, plant);
|
|
280
335
|
const geometry = poseGeometry(shown, world, sourceOfDoc(doc), mode.keep ? keptNumber : rawNumber, { region: plant.region, vertices: plant.vertices }, mode.loop && plant.through === undefined ? { ...draw, through: NO_CLIPPED_ROWS } : draw);
|
|
281
336
|
if (geometry.attachments === null) throw new CoreInputError(`the raw pose leaves the attachments out: ${geometry.attachmentsWhy}`);
|
|
282
337
|
// The drawn rows (issue #964): the oracle's `clipped` rows where it is posed, and a concave or inverse clip cut through the core's own decomposition.
|
|
283
338
|
if (geometry.drawnClipped === null) throw new CoreInputError(`the raw pose leaves the clipped triangles out: ${geometry.drawnClippedWhy}`);
|
|
339
|
+
return { rows: geometry.attachments, clips: geometry.clips, drawnClipped: geometry.drawnClipped };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The drawn attachments of a scan pose (`ScanDrawn`): `rawDrawn`'s rows, each
|
|
344
|
+
* its slot, its name and its world vertices through the same `numberOf`, and
|
|
345
|
+
* none of what `rawDrawn` reads past them for a renderer — the UVs and
|
|
346
|
+
* triangles it copies, the record it looks up for the colour and the sequence
|
|
347
|
+
* frame, the hull, the clip rows' numbers. No vertex is computed from any of
|
|
348
|
+
* those. The one refusal `rawDrawn` makes past the rows, a linked mesh whose
|
|
349
|
+
* source carries no mesh, is not reachable here: `poseGeometry` resolved the
|
|
350
|
+
* same source by the same lookup (`sourceOfDoc`) and throws before it returns.
|
|
351
|
+
*
|
|
352
|
+
* 🔁 **No copy of the vertices** (issue #1179, the second part): `rawDrawn`
|
|
353
|
+
* maps each row through `numberOf` into a new array; under `keep` that map is
|
|
354
|
+
* the identity on every number and turns only a `null` into NaN. The scan
|
|
355
|
+
* hands the row's own array on wherever it holds no `null` (`allNumbers`) and
|
|
356
|
+
* maps it as before where it does, so A10 reads the same values either way.
|
|
357
|
+
* What makes the row's array safe to keep past the step — A10 collects an
|
|
358
|
+
* animation's 120 frames before it scans one — is that it is the pose's own:
|
|
359
|
+
* `poseGeometry` (`./vertices.ts`) builds every row's vertices with
|
|
360
|
+
* `.map(round)` over the corners or the skinned vertices it just computed, a
|
|
361
|
+
* new array per attachment per pose, held by nothing but the row. The core
|
|
362
|
+
* suite's `CO45` plants a poser that hands every pose the same array and reads
|
|
363
|
+
* the retained frames red.
|
|
364
|
+
*/
|
|
365
|
+
function scanDrawn(doc: CompiledDocument, shown: readonly ShownGeometry[], world: ReadonlyMap<string, CoreWorld>, order: readonly string[], plant: CorePlant, mode: WalkMode, scanPlant: ScanPlant = {}): ScanDrawn[] {
|
|
366
|
+
const { rows } = drawnGeometry(doc, shown, world, order, plant, mode);
|
|
367
|
+
const drawn: ScanDrawn[] = [];
|
|
368
|
+
let k = 0;
|
|
369
|
+
for (const s of shown) {
|
|
370
|
+
const g = s.geometry;
|
|
371
|
+
if (g.kind !== 'region' && g.kind !== 'mesh' && g.kind !== 'linkedmesh') continue;
|
|
372
|
+
const row = rows[k++];
|
|
373
|
+
if (row === undefined || row[0] !== s.slot) throw new CoreInputError(`slot "${s.slot}": the attachment rows are not in the shown records' order`);
|
|
374
|
+
const vertices = row[3];
|
|
375
|
+
const kept = mode.keep && allNumbers(vertices) ? vertices : vertices.map((v) => numberOf(v, `vertex of slot "${s.slot}"`, mode));
|
|
376
|
+
drawn.push({ slot: s.slot, attachment: row[1], vertices: scanPlant.vertices === undefined ? kept : scanPlant.vertices(kept, s.slot) });
|
|
377
|
+
}
|
|
378
|
+
return drawn;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Whether a row's vertices hold no `null` — what `numberOf` would map — so the row's own array can be handed on as numbers. */
|
|
382
|
+
function allNumbers(values: Array<number | null>): values is number[] {
|
|
383
|
+
for (const v of values) if (v === null) return false;
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* The scan's one plant (the core suite's `CO45`): what a drawn row's vertices
|
|
389
|
+
* are handed on as, given the array the scan would hand on. A poser that
|
|
390
|
+
* reuses one buffer per slot across steps is the plant `CO45` passes.
|
|
391
|
+
* Nothing but a control passes one.
|
|
392
|
+
*/
|
|
393
|
+
export interface ScanPlant {
|
|
394
|
+
vertices?: (vertices: number[], slot: string) => number[];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Every region and mesh the shown records draw, with what the renderer reads past the vertices (the header's `drawn`). */
|
|
398
|
+
function rawDrawn(doc: CompiledDocument, shown: readonly ShownGeometry[], world: ReadonlyMap<string, CoreWorld>, order: readonly string[], plant: CorePlant, mode: WalkMode = RAW_MODE): { drawn: RawDrawn[]; clips: RawClip[]; clipped: RawClipped[] } {
|
|
399
|
+
const geometry = drawnGeometry(doc, shown, world, order, plant, mode);
|
|
284
400
|
// Under a concave or inverse clip the oracle's `clipped` block is absent (the runtime's own triangle list); what the core draws is `drawnClipped`,
|
|
285
401
|
// its own decomposition, and the render samples each drawn triangle at its source triangle's affine UV, so the pixels are the decomposition's
|
|
286
402
|
// coverage alone (issue #964, `Mesh.source` in src/render.ts).
|
|
287
|
-
const rows = geometry.
|
|
403
|
+
const rows = geometry.rows;
|
|
288
404
|
const drawn: RawDrawn[] = [];
|
|
289
405
|
let k = 0;
|
|
290
406
|
for (const s of shown) {
|
|
@@ -326,14 +442,27 @@ export function poseRawSetup(doc: CompiledDocument, plant: CorePlant = {}): RawP
|
|
|
326
442
|
|
|
327
443
|
/** `poseRawSetup` in `mode` — under `keep`, a value that is not finite stays in the pose (`./walk.ts`). */
|
|
328
444
|
export function setupPoseIn(doc: CompiledDocument, plant: CorePlant, mode: WalkMode): RawPose {
|
|
445
|
+
const { world, shown, slots, drawOrder } = setupPosed(doc, plant, mode);
|
|
446
|
+
const drawn = rawDrawn(doc, shown, world, drawOrder, plant, mode);
|
|
447
|
+
return { trackTime: 0, animationTime: 0, bones: rawBones(doc, world), slots, drawOrder, ...drawn, events: [], shown: drawOrderOf(shown, drawOrder) };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** `setupPoseIn` as the scan reads it (`ScanPose`): the same setup pose, the same refusals in the same order, assembled into the scan's pose. */
|
|
451
|
+
export function scanSetupIn(doc: CompiledDocument, plant: CorePlant, mode: WalkMode, scanPlant: ScanPlant = {}): ScanPose {
|
|
452
|
+
const { world, shown, slots, drawOrder } = setupPosed(doc, plant, mode);
|
|
453
|
+
const drawn = scanDrawn(doc, shown, world, drawOrder, plant, mode, scanPlant);
|
|
454
|
+
return { animationTime: 0, bones: scanBones(doc, world), slots, drawn };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** The setup pose `setupPoseIn` and `scanSetupIn` assemble, refused by name where the core leaves a block of it out. */
|
|
458
|
+
function setupPosed(doc: CompiledDocument, plant: CorePlant, mode: WalkMode): { world: Map<string, CoreWorld>; shown: ShownGeometry[]; slots: CoreSlotRow[]; drawOrder: string[] } {
|
|
329
459
|
const posed = poseSetup(doc, { ...plant, ...roundPlant(mode) }, freshStepContext(plant.physicsStep));
|
|
330
460
|
// `setup.clipped` is the oracle's block, left out under a concave or inverse clip whose triangle list is the runtime's own; what the core draws there is `rawDrawn`'s to refuse or pose (issue #964).
|
|
331
461
|
const absent = posed.absent.filter(([block]) => block !== 'setup.clipped');
|
|
332
462
|
if (absent.length > 0) throw new CoreInputError(`the raw setup pose leaves ${absent.map(([b, why]) => `${b} out (${why})`).join('; ')}`);
|
|
333
463
|
const { setup, world, shown } = posed;
|
|
334
464
|
if (world === null || shown === null || setup.slots === null || setup.drawOrder === null) throw new CoreInputError('the raw setup pose was not posed');
|
|
335
|
-
|
|
336
|
-
return { trackTime: 0, animationTime: 0, bones: rawBones(doc, world), slots: setup.slots, drawOrder: setup.drawOrder, ...drawn, events: [], shown: drawOrderOf(shown, setup.drawOrder) };
|
|
465
|
+
return { world, shown, slots: setup.slots, drawOrder: setup.drawOrder };
|
|
337
466
|
}
|
|
338
467
|
|
|
339
468
|
/**
|
|
@@ -361,7 +490,7 @@ export function poseRawAnimation(doc: CompiledDocument, animation: string, steps
|
|
|
361
490
|
* poses before it.
|
|
362
491
|
*/
|
|
363
492
|
export function poseRawAnimationEach(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, visit: (pose: RawPose, index: number) => void): void {
|
|
364
|
-
walkEach(doc, animation, steps, plant, reset, RAW_MODE, visit);
|
|
493
|
+
walkEach(doc, animation, steps, plant, reset, RAW_MODE, RAW_SHAPE, visit);
|
|
365
494
|
}
|
|
366
495
|
|
|
367
496
|
/**
|
|
@@ -381,12 +510,81 @@ export function loopedTime(trackTime: number, duration: number): number {
|
|
|
381
510
|
*/
|
|
382
511
|
export function walkIn(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, mode: WalkMode): RawPose[] {
|
|
383
512
|
const poses: RawPose[] = [];
|
|
384
|
-
walkEach(doc, animation, steps, plant, reset, mode, (pose) => poses.push(pose));
|
|
513
|
+
walkEach(doc, animation, steps, plant, reset, mode, RAW_SHAPE, (pose) => poses.push(pose));
|
|
385
514
|
return poses;
|
|
386
515
|
}
|
|
387
516
|
|
|
388
|
-
/**
|
|
389
|
-
|
|
517
|
+
/**
|
|
518
|
+
* `walkIn` as the scan reads it (`ScanPose`, issue #1179, the second part):
|
|
519
|
+
* the same walk — every pose posed by the same operations in the same order,
|
|
520
|
+
* every refusal at the same place — each pose assembled into what A10 reads.
|
|
521
|
+
* `./walk.ts`'s `scanLoopingWalk` is its one caller.
|
|
522
|
+
*/
|
|
523
|
+
export function scanWalkIn(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, mode: WalkMode, scanPlant: ScanPlant = {}): ScanPose[] {
|
|
524
|
+
const poses: ScanPose[] = [];
|
|
525
|
+
walkEach(doc, animation, steps, plant, reset, mode, scanPlant.vertices === undefined ? SCAN_SHAPE : scanShape(scanPlant), (pose) => poses.push(pose));
|
|
526
|
+
return poses;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** What one step of a walk posed, before a pose is assembled from it (`PoseShape`). */
|
|
530
|
+
interface WalkStep {
|
|
531
|
+
trackTime: number;
|
|
532
|
+
animationTime: number;
|
|
533
|
+
world: Map<string, CoreWorld>;
|
|
534
|
+
slots: CoreSlotRow[];
|
|
535
|
+
drawOrder: string[];
|
|
536
|
+
shown: ShownGeometry[];
|
|
537
|
+
/** The events the step fired, computed when called — after the drawn rows, as the raw entry has always ordered its refusals. */
|
|
538
|
+
events: () => RawEvent[];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* What a walk assembles each pose into (issue #1179, the second part): the raw
|
|
543
|
+
* entry's whole pose (`RAW_SHAPE`), or the scan's (`SCAN_SHAPE`). The walk
|
|
544
|
+
* (`walkEach`) is one body: every number a pose carries is computed there and
|
|
545
|
+
* in the two assemblers below, by the same calls, whatever the shape. A shape
|
|
546
|
+
* chooses only what is assembled from what was posed — the scan drops the
|
|
547
|
+
* readings, rows and copies nothing it reads is computed from — and keeps the
|
|
548
|
+
* order of every call that can refuse.
|
|
549
|
+
*/
|
|
550
|
+
interface PoseShape<P> {
|
|
551
|
+
/** Whether a step forms the oracle's bone rows beside its world (`posedBoneWorld`), which no walk pose carries; `false` poses the world alone (`posedBoneWorldAlone`). */
|
|
552
|
+
rows: boolean;
|
|
553
|
+
/** The reset pose at the setup (`reset: 'setup'`). */
|
|
554
|
+
setup: (doc: CompiledDocument, world: Map<string, CoreWorld>, slots: CoreSlotRow[], drawOrder: string[], shown: ShownGeometry[], plant: CorePlant, mode: WalkMode) => P;
|
|
555
|
+
/** One step's pose. */
|
|
556
|
+
step: (doc: CompiledDocument, posed: WalkStep, plant: CorePlant, mode: WalkMode) => P;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** The raw entry's pose, assembled as it always was: at the reset the bones, then the drawn rows; at a step the drawn rows, the events, then the bones. */
|
|
560
|
+
const RAW_SHAPE: PoseShape<RawPose> = {
|
|
561
|
+
rows: true,
|
|
562
|
+
setup: (doc, world, slots, drawOrder, shown, plant, mode) => ({ trackTime: 0, animationTime: 0, bones: rawBones(doc, world), slots, drawOrder, ...rawDrawn(doc, shown, world, drawOrder, plant, mode), events: [], shown: drawOrderOf(shown, drawOrder) }),
|
|
563
|
+
step: (doc, p, plant, mode) => {
|
|
564
|
+
const drawn = rawDrawn(doc, p.shown, p.world, p.drawOrder, plant, mode);
|
|
565
|
+
const events = p.events();
|
|
566
|
+
return { trackTime: p.trackTime, animationTime: p.animationTime, bones: rawBones(doc, p.world), slots: p.slots, drawOrder: p.drawOrder, ...drawn, events, shown: p.shown };
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/** The scan's pose (`ScanPose`), in the raw shape's order: the scan's walk is a looping one, which fires no event. */
|
|
571
|
+
function scanShape(scanPlant: ScanPlant): PoseShape<ScanPose> {
|
|
572
|
+
return {
|
|
573
|
+
rows: false,
|
|
574
|
+
setup: (doc, world, slots, drawOrder, shown, plant, mode) => {
|
|
575
|
+
const bones = scanBones(doc, world);
|
|
576
|
+
return { animationTime: 0, bones, slots, drawn: scanDrawn(doc, shown, world, drawOrder, plant, mode, scanPlant) };
|
|
577
|
+
},
|
|
578
|
+
step: (doc, p, plant, mode) => {
|
|
579
|
+
const drawn = scanDrawn(doc, p.shown, p.world, p.drawOrder, plant, mode, scanPlant);
|
|
580
|
+
return { animationTime: p.animationTime, bones: scanBones(doc, p.world), slots: p.slots, drawn };
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
const SCAN_SHAPE: PoseShape<ScanPose> = scanShape({});
|
|
585
|
+
|
|
586
|
+
/** `walkIn`'s walk, each pose assembled in `shape` and handed to `visit` in order as it is posed (`poseRawAnimationEach`). */
|
|
587
|
+
function walkEach<P>(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, mode: WalkMode, shape: PoseShape<P>, visit: (pose: P, index: number) => void): void {
|
|
390
588
|
const anim = doc.animations.find((a) => a.name === animation);
|
|
391
589
|
if (anim === undefined) throw new CoreInputError(`animation "${animation}" is not one of this document's [${doc.animations.map((a) => a.name).join(', ')}]`);
|
|
392
590
|
const bad = steps.findIndex((s) => !Number.isFinite(s) || s < 0);
|
|
@@ -408,7 +606,7 @@ function walkEach(doc: CompiledDocument, animation: string, steps: readonly numb
|
|
|
408
606
|
if (absent.length > 0) throw new CoreInputError(`the raw walk's reset pose leaves ${absent.map(([b, w]) => `${b} out (${w})`).join('; ')}`);
|
|
409
607
|
if (setup.world === null || setup.shown === null || setup.setup.slots === null || setup.setup.drawOrder === null) throw new CoreInputError('the raw walk\'s reset pose was not posed');
|
|
410
608
|
ctx.phase = 'update';
|
|
411
|
-
visit(
|
|
609
|
+
visit(shape.setup(doc, setup.world, setup.setup.slots, setup.setup.drawOrder, setup.shown, plant, mode), visited++);
|
|
412
610
|
}
|
|
413
611
|
for (let i = reset === 'setup' ? 1 : 0; i <= steps.length; i++) {
|
|
414
612
|
const dt = i === 0 ? 0 : steps[i - 1];
|
|
@@ -417,7 +615,7 @@ function walkEach(doc: CompiledDocument, animation: string, steps: readonly numb
|
|
|
417
615
|
const before = ctx.time;
|
|
418
616
|
ctx.time += mode.clock === undefined ? dt : mode.clock(dt, t - (i === 0 ? 0 : Math.max(last, 0)));
|
|
419
617
|
const sliders: SliderApplication[] = [];
|
|
420
|
-
const
|
|
618
|
+
const world = shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before });
|
|
421
619
|
if (i === 0) ctx.phase = 'update';
|
|
422
620
|
const placeholders = new Map<string, string | null>();
|
|
423
621
|
const slots = posedSlots(doc, anim.timelines, t, raw, sliders, placeholders);
|
|
@@ -430,12 +628,13 @@ function walkEach(doc: CompiledDocument, animation: string, steps: readonly numb
|
|
|
430
628
|
if (states.why.length > 0) throw new CoreInputError(`the raw walk leaves the attachments out: ${states.why.join('; ')}`);
|
|
431
629
|
const rank = new Map(drawOrder.map((n, r) => [n, r]));
|
|
432
630
|
const shown = [...states.shown].sort((a, b) => (rank.get(a.slot) ?? 0) - (rank.get(b.slot) ?? 0));
|
|
433
|
-
const drawn = rawDrawn(doc, shown, posed.world, drawOrder, plant, mode);
|
|
434
631
|
// A looping walk fires no event here: what fires across the wrap was not measured, and the walk's one reader (A10) reads none. Nor does it cut its
|
|
435
632
|
// drawn attachments through a clip (`NO_CLIPPED_ROWS`), so its `clipped` rows are empty.
|
|
436
|
-
const
|
|
633
|
+
const from = last;
|
|
634
|
+
const events = (): RawEvent[] => (mode.loop ? [] : (plant.events ?? eventsFired)(anim.timelines.events, from, t, rawNumber).map((e): RawEvent => [e[0], finite(e[1], 'event time'), e[2], finite(e[3], 'event float'), e[4]]));
|
|
635
|
+
const pose = shape.step(doc, { trackTime, animationTime: t, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
|
|
437
636
|
last = t;
|
|
438
|
-
visit(
|
|
637
|
+
visit(pose, visited++);
|
|
439
638
|
}
|
|
440
639
|
}
|
|
441
640
|
|
package/src/core/walk.ts
CHANGED
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
* poses the class as the raw entry does, and refuses nothing the raw entry
|
|
77
77
|
* does not.
|
|
78
78
|
*/
|
|
79
|
-
import { setupPoseIn, walkIn, type RawBone, type RawDrawn, type WalkMode } from './raw.ts';
|
|
79
|
+
import { scanSetupIn, scanWalkIn, setupPoseIn, walkIn, type RawBone, type RawDrawn, type ScanPlant, type ScanPose, type WalkMode } from './raw.ts';
|
|
80
80
|
import { activeBones, type CompiledDocument, type CorePlant, type CoreSlotRow } from './index.ts';
|
|
81
81
|
import { historyTaint } from './constraints.ts';
|
|
82
82
|
import type { TimelinePlant } from './animation.ts';
|
|
@@ -175,3 +175,23 @@ export function poseLoopingWalk(doc: CompiledDocument, animation: string, steps:
|
|
|
175
175
|
const mode: WalkMode = { ...LOOPING, ...(walkPlant.time === undefined ? {} : { time: walkPlant.time }), ...(walkPlant.clock === undefined ? {} : { clock: walkPlant.clock }), ...(walkPlant.wrapResets === undefined ? {} : { wrapResets: walkPlant.wrapResets }) };
|
|
176
176
|
return walkIn(doc, animation, steps, plant, 'setup', mode).map((p) => narrow(p, walkPlant.number));
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The scan's walk (issue #1179, the second part): `poseLoopingWalk` and
|
|
181
|
+
* `poseWalkSetup` as A10 reads them, each pose a `ScanPose` — the bones' world
|
|
182
|
+
* matrix and origin, the slot rows and the drawn attachments, and nothing the
|
|
183
|
+
* walk forms beside them for other readers (the bones' oracle rows and getter
|
|
184
|
+
* readings). The same walk: `walkIn`'s body in `./raw.ts`, posing each step by
|
|
185
|
+
* the same operations in the same order and refusing at the same places, with
|
|
186
|
+
* another assembler (`PoseShape`). `src/assertions/model/stepped_poses.ts` is
|
|
187
|
+
* its one caller; the public entries above are unchanged, and the core suite
|
|
188
|
+
* holds the two to the same numbers (`CO43`, `CO44`, `CO45`).
|
|
189
|
+
*/
|
|
190
|
+
export function scanLoopingWalk(doc: CompiledDocument, animation: string, steps: readonly number[], scanPlant: ScanPlant = {}): ScanPose[] {
|
|
191
|
+
return scanWalkIn(doc, animation, steps, {}, 'setup', LOOPING, scanPlant);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** `poseWalkSetup` as the scan reads it (`scanLoopingWalk`). */
|
|
195
|
+
export function scanWalkSetup(doc: CompiledDocument): ScanPose {
|
|
196
|
+
return scanSetupIn(doc, {}, LOOPING);
|
|
197
|
+
}
|