spine-rigc 2.47.0 → 2.47.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/core/raw.ts +59 -1
- package/src/folds.ts +11 -6
- package/src/meshcompare.ts +58 -13
- package/src/render_core.ts +5 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.47.
|
|
3
|
+
"version": "2.47.1",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/core/raw.ts
CHANGED
|
@@ -496,13 +496,71 @@ export function poseRawAnimation(doc: CompiledDocument, animation: string, steps
|
|
|
496
496
|
* A refusal is thrown from the pose it is met at, after `visit` has seen the
|
|
497
497
|
* poses before it. `clock`, when given, is what each step moves the physics
|
|
498
498
|
* clock by in place of the step itself — the render's offline schedule
|
|
499
|
-
* (`physicsClockStep`
|
|
499
|
+
* (`physicsClockStep` below, issue #1380); the track time and
|
|
500
500
|
* the animation time are the steps' running sum either way.
|
|
501
501
|
*/
|
|
502
502
|
export function poseRawAnimationEach(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, visit: (pose: RawPose, index: number) => void, clock?: (dt: number) => number): void {
|
|
503
503
|
walkEach(doc, animation, steps, plant, reset, clock === undefined ? RAW_MODE : { ...RAW_MODE, clock: (dt) => clock(dt) }, RAW_SHAPE, visit);
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/**
|
|
507
|
+
* How much faster than the frames a render's physics clock runs — and, since issue
|
|
508
|
+
* #1389, a mesh comparison's (`src/meshcompare.ts`, `comparisonWalk`): each frame
|
|
509
|
+
* moves the skeleton's clock — the time a physics constraint integrates — by
|
|
510
|
+
* `physicsClockStep(1 / fps)`, the frame's step and `2⁻³⁰` of it more, while
|
|
511
|
+
* the animation is applied at the running sum of the frame steps as before
|
|
512
|
+
* (issue #1380).
|
|
513
|
+
*
|
|
514
|
+
* ⭐ **Why a render's physics clock is not the frame clock.** A physics
|
|
515
|
+
* constraint keeps the time it has not yet integrated (`remaining`), takes
|
|
516
|
+
* whole steps of `1 / fps` (its own `fps`, 60 by default) while at least one
|
|
517
|
+
* step remains, and shows `offset − lag·(1 − remaining / step)`, `lag` being
|
|
518
|
+
* everything this update's steps moved (`src/core/constraints_physics.ts`,
|
|
519
|
+
* *The step*). Whenever a frame lasts a whole number of physics steps — 12,
|
|
520
|
+
* 20, 30 and 60 fps against 60 Hz, and every second frame at 24 — the time
|
|
521
|
+
* left at the frame's end is zero in exact arithmetic, and in doubles it is
|
|
522
|
+
* either a few ulps above zero or a few ulps short of one whole step. Which of
|
|
523
|
+
* the two is decided by rounding, not by the motion: the shown weight is 1 or
|
|
524
|
+
* 0, so the spring is drawn one frame late or on time, and it changes regime
|
|
525
|
+
* on a single frame. Measured on the public trailer (`show`, 20 fps, 321
|
|
526
|
+
* frames): every spring jumps at frames 5, 12, 45 and 188 — the four frames
|
|
527
|
+
* that arithmetic alone predicts — and the loop's start (frame 80, on time)
|
|
528
|
+
* and end (frame 320, a frame late, which is what exact arithmetic draws)
|
|
529
|
+
* differ by 31.70 units of vertex and 10.37 of bone
|
|
530
|
+
* while every keyed bone agrees to 1.3e-12. spine-core 4.3.13 stepped the
|
|
531
|
+
* same way draws the same frames to the bit: this is the runtime under a
|
|
532
|
+
* fixed offline step, not a defect of the core's port, which is why the fix
|
|
533
|
+
* is the render's schedule and the physics is untouched.
|
|
534
|
+
*
|
|
535
|
+
* Running the clock `2⁻³⁰` fast puts the time left a little ABOVE every
|
|
536
|
+
* whole number of steps instead of on it, by `2⁻³⁰ · t` after `t` seconds:
|
|
537
|
+
* 1.5e-8 s at 16 s, against a rounding error of at most a few ulps of the
|
|
538
|
+
* clock (3.6e-15 s at 16 s) — so every tie resolves the way exact arithmetic
|
|
539
|
+
* resolves it, a step taken, and the shown weight sits at 1 − 9e-7 rather
|
|
540
|
+
* than flipping. It stays below the smallest gap a step boundary can fall
|
|
541
|
+
* into for 7.8e5 s even at 23 fps against 60 Hz (a gap of 1/1380 s), and a
|
|
542
|
+
* physics `reset` key, which restarts the time left from zero, is resolved
|
|
543
|
+
* the same way on every frame after it, because the lead is taken on every
|
|
544
|
+
* frame rather than once (read off the clock arithmetic with a reset at frame
|
|
545
|
+
* 100, 16 s at the five rates: no tie; no tree probe keys a reset under the
|
|
546
|
+
* render's clock). A pose stays a function of its frame
|
|
547
|
+
* index alone: the lead does not depend on how many frames are asked for.
|
|
548
|
+
*
|
|
549
|
+
* Rejected, measured over the same arithmetic (16 s at 12, 20, 24, 25, 30 and
|
|
550
|
+
* 60 fps): the clock as `i / fps` rather than a running sum (90 frames still
|
|
551
|
+
* flipped at 20 fps); a one-time lead of half a step (114 flipped at 24 fps,
|
|
552
|
+
* where half a step lands on the other tie); a one-time lead of any size,
|
|
553
|
+
* which a `reset` key cancels; and snapping `remaining` inside the physics,
|
|
554
|
+
* which changes the runtime's physics rather than the render's schedule and
|
|
555
|
+
* which spine-core's poser could not follow.
|
|
556
|
+
*/
|
|
557
|
+
export const PHYSICS_CLOCK_LEAD = 2 ** -30;
|
|
558
|
+
|
|
559
|
+
/** What one frame of `step` seconds moves a render's physics clock by — `PHYSICS_CLOCK_LEAD`; both render posers and the mesh comparison step with it. */
|
|
560
|
+
export function physicsClockStep(step: number): number {
|
|
561
|
+
return step + step * PHYSICS_CLOCK_LEAD;
|
|
562
|
+
}
|
|
563
|
+
|
|
506
564
|
/**
|
|
507
565
|
* The animation time a looping track applies its animation at (`./walk.ts`,
|
|
508
566
|
* *The time*): the track time wrapped by the duration, and 0 over a duration
|
package/src/folds.ts
CHANGED
|
@@ -170,6 +170,8 @@ export interface FoldPlant {
|
|
|
170
170
|
boxWholeMesh?: boolean;
|
|
171
171
|
/** Size the folded triangles by their unsigned area as posed at the frame, rather than at the setup pose. */
|
|
172
172
|
areaPosed?: boolean;
|
|
173
|
+
/** Read degeneracy against this share of the largest coordinate rather than `DEGENERATE_SHARE` — 0 drops the line (issue #1390). */
|
|
174
|
+
degenerateShare?: number;
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
/** Twice the signed area of triangle `t` in `xy` (`x, y` per vertex). */
|
|
@@ -201,12 +203,13 @@ function magnitude(xy: readonly number[]): number {
|
|
|
201
203
|
|
|
202
204
|
/**
|
|
203
205
|
* The sign of triangle `t`'s area in `xy`: +1, −1, or 0 when it is degenerate
|
|
204
|
-
* — its height over its longest edge at most `
|
|
206
|
+
* — its height over its longest edge at most `share` (`DEGENERATE_SHARE`
|
|
207
|
+
* unplanted) of `scale`.
|
|
205
208
|
*/
|
|
206
|
-
function orientation(xy: readonly number[], triangles: readonly number[], t: number, scale: number): -1 | 0 | 1 {
|
|
209
|
+
function orientation(xy: readonly number[], triangles: readonly number[], t: number, scale: number, share: number): -1 | 0 | 1 {
|
|
207
210
|
const twice = cross2(xy, triangles, t);
|
|
208
211
|
const edge = longestEdge(xy, triangles, t);
|
|
209
|
-
if (edge === 0 || Math.abs(twice) / edge <=
|
|
212
|
+
if (edge === 0 || Math.abs(twice) / edge <= share * scale) return 0;
|
|
210
213
|
return twice > 0 ? 1 : -1;
|
|
211
214
|
}
|
|
212
215
|
|
|
@@ -220,6 +223,7 @@ export interface SetupReading {
|
|
|
220
223
|
/** The setup reading of one mesh — see the module comment for what degenerate and inverted mean. */
|
|
221
224
|
export function setupReading(rest: FoldRest, plant: FoldPlant = {}): SetupReading {
|
|
222
225
|
const count = Math.floor(rest.triangles.length / 3);
|
|
226
|
+
const share = plant.degenerateShare ?? DEGENERATE_SHARE;
|
|
223
227
|
const signs = new Int8Array(count);
|
|
224
228
|
const world: number[] = [];
|
|
225
229
|
const uvSigns: number[] = [];
|
|
@@ -229,8 +233,8 @@ export function setupReading(rest: FoldRest, plant: FoldPlant = {}): SetupReadin
|
|
|
229
233
|
let sense = 0;
|
|
230
234
|
const degenerate: number[] = [];
|
|
231
235
|
for (let t = 0; t < count; t++) {
|
|
232
|
-
const w = orientation(rest.vertices, rest.triangles, t, worldScale);
|
|
233
|
-
const u = uvs.length === rest.vertices.length ? orientation(uvs, rest.triangles, t, uvScale) : 1;
|
|
236
|
+
const w = orientation(rest.vertices, rest.triangles, t, worldScale, share);
|
|
237
|
+
const u = uvs.length === rest.vertices.length ? orientation(uvs, rest.triangles, t, uvScale, share) : 1;
|
|
234
238
|
world.push(w);
|
|
235
239
|
uvSigns.push(u);
|
|
236
240
|
if (w === 0 || u === 0) {
|
|
@@ -263,11 +267,12 @@ export function foldedAt(rest: FoldRest, setup: SetupReading, vertices: readonly
|
|
|
263
267
|
);
|
|
264
268
|
}
|
|
265
269
|
const scale = magnitude(vertices);
|
|
270
|
+
const share = plant.degenerateShare ?? DEGENERATE_SHARE;
|
|
266
271
|
const out: number[] = [];
|
|
267
272
|
for (let t = 0; t < setup.signs.length; t++) {
|
|
268
273
|
const before = setup.signs[t];
|
|
269
274
|
if (before === 0) continue;
|
|
270
|
-
const now = orientation(vertices, rest.triangles, t, scale);
|
|
275
|
+
const now = orientation(vertices, rest.triangles, t, scale, share);
|
|
271
276
|
if (now === 0) continue;
|
|
272
277
|
if (plant.signFlipped ? now === before : now === -before) out.push(t);
|
|
273
278
|
}
|
package/src/meshcompare.ts
CHANGED
|
@@ -66,7 +66,11 @@
|
|
|
66
66
|
* on every walk and stepped at the declared `dt` to each frame (the oracle's
|
|
67
67
|
* `stepSchedule` rule: from the last frame, steps of `dt` while before the
|
|
68
68
|
* next, then to it), the same steps for the reference and every candidate. A
|
|
69
|
-
* nonzero `warmupSteps` is refused, never run as zero.
|
|
69
|
+
* nonzero `warmupSteps` is refused, never run as zero. The physics clock
|
|
70
|
+
* moves by `physicsClockStep` of each step, the render's clock
|
|
71
|
+
* (`PHYSICS_CLOCK_LEAD` in `src/core/raw.ts`, issue #1380), so a step
|
|
72
|
+
* lasting a whole number of a constraint's steps takes them as exact
|
|
73
|
+
* arithmetic does (`comparisonWalk`, issue #1389).
|
|
70
74
|
*
|
|
71
75
|
* ## What it never does
|
|
72
76
|
*
|
|
@@ -106,8 +110,8 @@ import {
|
|
|
106
110
|
import { areaBand, stretchSingularValues, triangleAreas } from './areaband.ts';
|
|
107
111
|
import { uvCarriers, uvCarriersWith, type Carrier, type CarrierPlant } from './meshcarriers.ts';
|
|
108
112
|
import { CoreInputError, readModel, underNoSkin, underSkin, type CompiledDocument } from './core/index.ts';
|
|
109
|
-
import { poseRawAnimationEach, poseRawSetup, type RawPose } from './core/raw.ts';
|
|
110
|
-
import { sampleTime } from './core/animation.ts';
|
|
113
|
+
import { physicsClockStep, poseRawAnimationEach, poseRawSetup, type RawPose } from './core/raw.ts';
|
|
114
|
+
import { sampleTime, type TimelinePlant } from './core/animation.ts';
|
|
111
115
|
import { CORE_DEFAULT_SKIN } from './core/skins.ts';
|
|
112
116
|
import { readVersion } from './package_meta.ts';
|
|
113
117
|
import { hypot2 } from './numeric.ts';
|
|
@@ -187,7 +191,9 @@ export interface MotionComparisonInput {
|
|
|
187
191
|
* Issue #1291's faults in carrying `motionAmplitude` into the setup measurement, for the `mesh-compare` suite's
|
|
188
192
|
* negative controls: the amplitude not handed over; `null` handed over as the field left out; the field not validated
|
|
189
193
|
* before the builds are read; `null` echoed for a field left out; and the load counted as a required row, so the
|
|
190
|
-
* amplitude moves a verdict. `compareMeshesInMotion` plants none. Issue #
|
|
194
|
+
* amplitude moves a verdict. `compareMeshesInMotion` plants none. Issue #1389 adds `'frame-physics-clock'`: every
|
|
195
|
+
* build posed with the physics clock moved by the schedule's step itself, the walk before that issue
|
|
196
|
+
* (`comparisonWalk`). Issue #1323 adds `CarrierPlant`
|
|
191
197
|
* (`src/meshcarriers.ts`): a fault in resolving a sample's several carriers, planted in every `uvCarriers` the
|
|
192
198
|
* comparison reads.
|
|
193
199
|
*/
|
|
@@ -197,6 +203,7 @@ export type ComparePlant =
|
|
|
197
203
|
| 'amplitude-unvalidated'
|
|
198
204
|
| 'echo-when-unset'
|
|
199
205
|
| 'amplitude-gates-acceptance'
|
|
206
|
+
| 'frame-physics-clock'
|
|
200
207
|
| OverBoundPlant
|
|
201
208
|
| CarrierPlant;
|
|
202
209
|
|
|
@@ -594,7 +601,37 @@ function drawnOf(pose: RawPose, ref: AttachmentRef): number[] | null {
|
|
|
594
601
|
return row === undefined ? null : row.vertices;
|
|
595
602
|
}
|
|
596
603
|
|
|
597
|
-
|
|
604
|
+
/**
|
|
605
|
+
* The walk every build of a comparison is posed on (issue #1389): `poseRawAnimationEach`, reset at the animation, the
|
|
606
|
+
* physics clock moved by `physicsClockStep` of each step — the render's clock (`src/core/raw.ts`). A walk time that
|
|
607
|
+
* ends on a whole number of a physics constraint's steps leaves zero time over in exact arithmetic and a few ulps
|
|
608
|
+
* either side of zero in doubles, so with the step itself as the clock the constraint took one step too few or one
|
|
609
|
+
* too many by rounding. Measured over the clock arithmetic on 300 durations as the core reads them (float32, as Spine
|
|
610
|
+
* does: n/12 s to 20 s and tenths to 8 s): at 12 fps and `dt` 1/12, the rate `@rig-c/parts` walks, 11,248 of 44,486
|
|
611
|
+
* grid updates sit on a tie against a 60 Hz constraint and the frame clock happens to resolve every one as exact
|
|
612
|
+
* arithmetic does, while against 30, 24 and 50 Hz it mis-steps 3,274, 3,350 and 3,160 of them; at 4 fps and `dt` 1/60
|
|
613
|
+
* it mis-steps 19,876 grid and 594 irr updates against 60 Hz. The render's clock mis-steps none of any, so no time
|
|
614
|
+
* that is not a tie came within its lead of a whole step. `clock: 'frame'` is that walk, the step itself — `ComparePlant`
|
|
615
|
+
* `'frame-physics-clock'` and `MQ178`'s planted red. With no physics constraint the clock is read by nothing, so every
|
|
616
|
+
* pose is the same either way (`MQ179`); with one, the lead moves the shown lag weight by about `2⁻³⁰` of the time
|
|
617
|
+
* walked even where no step moves: on `MQ179`'s spring at parts' rate and 60 Hz, one unit of a row's sixth decimal,
|
|
618
|
+
* which can move the worst sample to a neighbour that nearly ties it.
|
|
619
|
+
*/
|
|
620
|
+
export function comparisonWalk(doc: CompiledDocument, animation: string, deltas: readonly number[], plant: TimelinePlant, visit: (pose: RawPose, index: number) => void, clock: 'render' | 'frame' = 'render'): void {
|
|
621
|
+
poseRawAnimationEach(doc, animation, deltas, plant, 'animation', visit, clock === 'render' ? physicsClockStep : undefined);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The walks `schedule` plans over the model document `model`, in walk order, each with the steps every build is posed
|
|
626
|
+
* on — `planSchedule`'s, read for the `mesh-compare` suite's `MQ178`. Internal: merely exported, so not promised; it
|
|
627
|
+
* reads the schedule without the input checks `compareMeshesInMotion` runs first.
|
|
628
|
+
*/
|
|
629
|
+
export function comparisonWalks(schedule: MotionSchedule, model: string): Array<{ animation: string; phase: 'grid' | 'irr' | null; deltas: number[] }> {
|
|
630
|
+
const doc = readModel(model, 'the model document');
|
|
631
|
+
return planSchedule(schedule, doc, doc.constraints.filter((c) => c.kind === 'physics').length).walks.map((w) => ({ animation: w.animation, phase: w.phase, deltas: [...w.deltas] }));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function poseBuild(doc: CompiledDocument, refs: readonly AttachmentRef[], frames: readonly Planned[], walks: readonly Walk[], clock: 'render' | 'frame'): Posed | { refused: string } {
|
|
598
635
|
try {
|
|
599
636
|
const view = viewOf(doc, refs[0].skin);
|
|
600
637
|
const setupPose = poseRawSetup(view);
|
|
@@ -612,12 +649,19 @@ function poseBuild(doc: CompiledDocument, refs: readonly AttachmentRef[], frames
|
|
|
612
649
|
list.push(i);
|
|
613
650
|
wanted.set(f.pose, list);
|
|
614
651
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
652
|
+
comparisonWalk(
|
|
653
|
+
view,
|
|
654
|
+
w.animation,
|
|
655
|
+
w.deltas,
|
|
656
|
+
{},
|
|
657
|
+
(pose, index) => {
|
|
658
|
+
const at = wanted.get(index);
|
|
659
|
+
if (at === undefined) return;
|
|
660
|
+
const drawn = refs.map((r) => drawnOf(pose, r));
|
|
661
|
+
for (const i of at) out[i] = drawn;
|
|
662
|
+
},
|
|
663
|
+
clock,
|
|
664
|
+
);
|
|
621
665
|
});
|
|
622
666
|
return { setup, frames: out, setupBones };
|
|
623
667
|
} catch (err) {
|
|
@@ -1003,11 +1047,12 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1003
1047
|
const carriersOf = (uvs: readonly number[], triangles: readonly number[], samples: ReadonlyArray<{ uv: [number, number] }>, who: string): Array<Carrier | null> =>
|
|
1004
1048
|
uvCarriersWith(uvs, triangles, samples, who, carrierPlant);
|
|
1005
1049
|
const refCarriers = order.map((i, k) => carriersOf(reference.meshes[i].uvs, reference.meshes[i].triangles, samplesOf[k].samples, `reference "${reference.id}", attachment ${nameOf(refs[i])}`));
|
|
1006
|
-
const
|
|
1050
|
+
const clock = plant === 'frame-physics-clock' ? 'frame' : 'render';
|
|
1051
|
+
const posedRef = poseBuild(reference.doc, order.map((i) => refs[i]), frames, walks, clock);
|
|
1007
1052
|
|
|
1008
1053
|
motionOf = (b: Build, isReference: boolean) => {
|
|
1009
1054
|
const carriers = isReference ? refCarriers : order.map((i, k) => carriersOf(b.meshes[i].uvs, b.meshes[i].triangles, samplesOf[k].samples, `candidate "${b.id}", attachment ${nameOf(refs[i])}`));
|
|
1010
|
-
const posed = isReference ? posedRef : poseBuild(b.doc, order.map((i) => refs[i]), frames, walks);
|
|
1055
|
+
const posed = isReference ? posedRef : poseBuild(b.doc, order.map((i) => refs[i]), frames, walks, clock);
|
|
1011
1056
|
const built: Built[] = [];
|
|
1012
1057
|
const perFrame: NonNullable<CandidateReport['perFrame']> = [];
|
|
1013
1058
|
// Issue #1315: kept for a candidate only, and only when asked — a call without the field does none of this.
|
package/src/render_core.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the same walk handing each pose over as it is posed (`CORE_RAW_WALK`), so
|
|
31
31
|
* one pose is held at a time. Since issue #1380 the physics clock — and
|
|
32
32
|
* nothing else — moves by `physicsClockStep(1/fps)` rather than by the step
|
|
33
|
-
* (`PHYSICS_CLOCK_LEAD`
|
|
33
|
+
* (`PHYSICS_CLOCK_LEAD`, `./core/raw.ts`), in both posers: `CR03` still holds the walk
|
|
34
34
|
* under the step itself, and the loop-seam suite's `LS06` holds the two
|
|
35
35
|
* posers equal under the render's clock.
|
|
36
36
|
* - **Vertices, triangles, bones, slot colours, draw order** are the raw
|
|
@@ -100,7 +100,7 @@ import { atlasRegionLookup, parseAtlasText } from './atlas.ts';
|
|
|
100
100
|
import { pagesOfAtlas, spineFileSha256, type ModelPage } from './model.ts';
|
|
101
101
|
import { clipThrough, type ClipShape, type ShapeClipper } from './core/clipping.ts';
|
|
102
102
|
import { activeBones, CoreInputError, readModel, sourceOfDoc, underNoSkin, underSkin, type CompiledDocument, type CoreSlotRow } from './core/index.ts';
|
|
103
|
-
import { poseRawAnimationEach, poseRawSetup, type RawDrawn, type RawPose } from './core/raw.ts';
|
|
103
|
+
import { PHYSICS_CLOCK_LEAD, physicsClockStep, poseRawAnimationEach, poseRawSetup, type RawDrawn, type RawPose } from './core/raw.ts';
|
|
104
104
|
import type { TimelinePlant } from './core/animation.ts';
|
|
105
105
|
import { CORE_DEFAULT_SKIN, lookupSkins } from './core/skins.ts';
|
|
106
106
|
import { documentPageLookup, drawnRegions, meshPageUvs, readUvSequences, regionPageUvs, type DrawnRegion, type UvRegion, type UvSource } from './core/uvs.ts';
|
|
@@ -369,61 +369,10 @@ export type CoreRawWalk = (doc: CompiledDocument, animation: string, steps: read
|
|
|
369
369
|
export const CORE_RAW_WALK: CoreRawWalk = (doc, animation, steps, plant, visit) => poseRawAnimationEach(doc, animation, steps, plant, 'animation', visit, physicsClockStep);
|
|
370
370
|
|
|
371
371
|
/**
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* `physicsClockStep(1 / fps)`, the frame's step and `2⁻³⁰` of it more, while
|
|
375
|
-
* the animation is applied at the running sum of the frame steps as before
|
|
376
|
-
* (issue #1380).
|
|
377
|
-
*
|
|
378
|
-
* ⭐ **Why a render's physics clock is not the frame clock.** A physics
|
|
379
|
-
* constraint keeps the time it has not yet integrated (`remaining`), takes
|
|
380
|
-
* whole steps of `1 / fps` (its own `fps`, 60 by default) while at least one
|
|
381
|
-
* step remains, and shows `offset − lag·(1 − remaining / step)`, `lag` being
|
|
382
|
-
* everything this update's steps moved (`src/core/constraints_physics.ts`,
|
|
383
|
-
* *The step*). Whenever a frame lasts a whole number of physics steps — 12,
|
|
384
|
-
* 20, 30 and 60 fps against 60 Hz, and every second frame at 24 — the time
|
|
385
|
-
* left at the frame's end is zero in exact arithmetic, and in doubles it is
|
|
386
|
-
* either a few ulps above zero or a few ulps short of one whole step. Which of
|
|
387
|
-
* the two is decided by rounding, not by the motion: the shown weight is 1 or
|
|
388
|
-
* 0, so the spring is drawn one frame late or on time, and it changes regime
|
|
389
|
-
* on a single frame. Measured on the public trailer (`show`, 20 fps, 321
|
|
390
|
-
* frames): every spring jumps at frames 5, 12, 45 and 188 — the four frames
|
|
391
|
-
* that arithmetic alone predicts — and the loop's start (frame 80, on time)
|
|
392
|
-
* and end (frame 320, a frame late, which is what exact arithmetic draws)
|
|
393
|
-
* differ by 31.70 units of vertex and 10.37 of bone
|
|
394
|
-
* while every keyed bone agrees to 1.3e-12. spine-core 4.3.13 stepped the
|
|
395
|
-
* same way draws the same frames to the bit: this is the runtime under a
|
|
396
|
-
* fixed offline step, not a defect of the core's port, which is why the fix
|
|
397
|
-
* is the render's schedule and the physics is untouched.
|
|
398
|
-
*
|
|
399
|
-
* Running the clock `2⁻³⁰` fast puts the time left a little ABOVE every
|
|
400
|
-
* whole number of steps instead of on it, by `2⁻³⁰ · t` after `t` seconds:
|
|
401
|
-
* 1.5e-8 s at 16 s, against a rounding error of at most a few ulps of the
|
|
402
|
-
* clock (3.6e-15 s at 16 s) — so every tie resolves the way exact arithmetic
|
|
403
|
-
* resolves it, a step taken, and the shown weight sits at 1 − 9e-7 rather
|
|
404
|
-
* than flipping. It stays below the smallest gap a step boundary can fall
|
|
405
|
-
* into for 7.8e5 s even at 23 fps against 60 Hz (a gap of 1/1380 s), and a
|
|
406
|
-
* physics `reset` key, which restarts the time left from zero, is resolved
|
|
407
|
-
* the same way on every frame after it, because the lead is taken on every
|
|
408
|
-
* frame rather than once (read off the clock arithmetic with a reset at frame
|
|
409
|
-
* 100, 16 s at the five rates: no tie; no tree probe keys a reset under the
|
|
410
|
-
* render's clock). A pose stays a function of its frame
|
|
411
|
-
* index alone: the lead does not depend on how many frames are asked for.
|
|
412
|
-
*
|
|
413
|
-
* Rejected, measured over the same arithmetic (16 s at 12, 20, 24, 25, 30 and
|
|
414
|
-
* 60 fps): the clock as `i / fps` rather than a running sum (90 frames still
|
|
415
|
-
* flipped at 20 fps); a one-time lead of half a step (114 flipped at 24 fps,
|
|
416
|
-
* where half a step lands on the other tie); a one-time lead of any size,
|
|
417
|
-
* which a `reset` key cancels; and snapping `remaining` inside the physics,
|
|
418
|
-
* which changes the runtime's physics rather than the render's schedule and
|
|
419
|
-
* which spine-core's poser could not follow.
|
|
372
|
+
* The render's physics clock (issue #1380): defined beside the walk that takes it (`./core/raw.ts`), where the mesh
|
|
373
|
+
* comparison (`./meshcompare.ts`, issue #1389) reads the same clock without this module's closure.
|
|
420
374
|
*/
|
|
421
|
-
export
|
|
422
|
-
|
|
423
|
-
/** What one frame of `step` seconds moves a render's physics clock by — `PHYSICS_CLOCK_LEAD`; both posers step with it. */
|
|
424
|
-
export function physicsClockStep(step: number): number {
|
|
425
|
-
return step + step * PHYSICS_CLOCK_LEAD;
|
|
426
|
-
}
|
|
375
|
+
export { PHYSICS_CLOCK_LEAD, physicsClockStep };
|
|
427
376
|
|
|
428
377
|
/** What a suite passes `corePoser` in place of the poser's own parts — `PieceArrays` (`RC43`) and the raw walk (`RC44`). */
|
|
429
378
|
export interface CorePoserPlants {
|