@rig-c/core 2.59.0 → 2.61.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.
@@ -102,6 +102,7 @@ import { clipThrough, type ClipShape, type ShapeClipper } from './core/clipping.
102
102
  import { activeBones, CoreInputError, readModel, sourceOfDoc, underNoSkin, underSkin, type CompiledDocument, type CoreSlotRow } from './core/index.ts';
103
103
  import { PHYSICS_CLOCK_LEAD, physicsClockStep, poseRawAnimationEach, poseRawSetup, type RawDrawn, type RawPose, type StepEnvironment } from './core/raw.ts';
104
104
  import type { TimelinePlant } from './core/animation.ts';
105
+ import { stepPhysics, type PhysicsStepper } from './core/constraints_physics.ts';
105
106
  import { CORE_DEFAULT_SKIN, lookupSkins } from './core/skins.ts';
106
107
  import { documentPageLookup, drawnRegions, meshPageUvs, readUvSequences, regionPageUvs, type DrawnRegion, type UvRegion, type UvSource } from './core/uvs.ts';
107
108
  import { regionCorners, worldVertices } from './core/vertices.ts';
@@ -378,10 +379,14 @@ export const CORE_RAW_WALK: CoreRawWalk = (doc, animation, steps, plant, visit,
378
379
  */
379
380
  export { PHYSICS_CLOCK_LEAD, physicsClockStep };
380
381
 
381
- /** What a suite passes `corePoser` in place of the poser's own parts — `PieceArrays` (`RC43`) and the raw walk (`RC44`). */
382
+ /**
383
+ * What a suite passes `corePoser` in place of the poser's own parts — `PieceArrays` (`RC43`), the raw walk (`RC44`), and
384
+ * the physics step the walk runs (issue #1467: a step that does nothing has to read 0 on the render's `physics:` line).
385
+ */
382
386
  export interface CorePoserPlants {
383
387
  arrays?: () => PieceArrays;
384
388
  walk?: CoreRawWalk;
389
+ physicsStep?: PhysicsStepper;
385
390
  }
386
391
 
387
392
  /**
@@ -457,7 +462,7 @@ function clipCover(pose: RawPose, active: ReadonlySet<string>): Map<string, Clip
457
462
  }
458
463
 
459
464
  /** One posed moment of the core, read through the seam's `Posed`. */
460
- function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper): Posed {
465
+ function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper, stepped: readonly string[]): Posed {
461
466
  const slotRows = new Map<string, CoreSlotRow>(pose.slots.map((row) => [row[0], row]));
462
467
  let regions: Map<string, DrawnRegion> | null = null;
463
468
  const regionsOf = (): Map<string, DrawnRegion> => {
@@ -501,6 +506,7 @@ function corePosed(input: CoreInput, pose: RawPose, through: ShapeClipper): Pose
501
506
  },
502
507
  attachments: (): AttachmentPose[] =>
503
508
  pose.drawn.map((d) => ({ slot: d.slot, attachment: d.attachment, vertices: [...d.vertices], color: tintOf(d) })),
509
+ physics: (): string[] => [...stepped],
504
510
  };
505
511
  }
506
512
 
@@ -752,7 +758,7 @@ export function corePoser(
752
758
  subset: (opts, skin) => subsetOver(roster, opts, skin),
753
759
  setup: (skin) => {
754
760
  const input = inputOf(skin);
755
- return corePosed(input, poseRawSetup(input.doc, { through }), through);
761
+ return corePosed(input, poseRawSetup(input.doc, { through }), through, []);
756
762
  },
757
763
  animation: (name, skin, fps, count, visit, environment) => {
758
764
  const input = inputOf(skin);
@@ -762,10 +768,22 @@ export function corePoser(
762
768
  // walk that refuses a later pose still refuses the call, and a draw that throws is re-thrown once the walk has
763
769
  // finished without refusing — the first such throw, by frame — and no frame after it is drawn.
764
770
  const drawFailed: unknown[] = [];
765
- walk(input.doc, name, new Array<number>(count).fill(step), { through }, (pose, i) => {
771
+ // `Posed.physics` on the core (issue #1467): the constraints whose state's clock reads the skeleton's after a step
772
+ // that moved it — spine-core's reading (`steppedPhysicsOf` in `src/render.ts`), taken off the same state the step
773
+ // writes, as each constraint is stepped, and handed to the pose the step made.
774
+ const steppedNow = new Set<string>();
775
+ const inner = plants.physicsStep ?? stepPhysics;
776
+ const physicsStep: PhysicsStepper = (r, w, length, ctx) => {
777
+ const moved = inner(r, w, length, ctx);
778
+ if (ctx.time > 0 && ctx.states.get(r.name)?.lastTime === ctx.time) steppedNow.add(r.name);
779
+ return moved;
780
+ };
781
+ walk(input.doc, name, new Array<number>(count).fill(step), { through, physicsStep }, (pose, i) => {
782
+ const stepped = [...steppedNow].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
783
+ steppedNow.clear();
766
784
  if (drawFailed.length > 0) return;
767
785
  try {
768
- visit(i, corePosed(input, pose, through));
786
+ visit(i, corePosed(input, pose, through, stepped));
769
787
  } catch (thrown) {
770
788
  drawFailed.push(thrown);
771
789
  }
@@ -190,6 +190,20 @@ export interface FrameSet {
190
190
  * file could only ever be the stale one.
191
191
  */
192
192
  duration: number;
193
+ /**
194
+ * Whether the physics ran while these frames were posed (issue #1467): how
195
+ * many physics constraints the poser stepped on at least one frame of this
196
+ * set, and the rate it stepped them at — the set's own `fps`, one skeleton
197
+ * update a frame. `render` prints the same two figures on its `physics:` line.
198
+ *
199
+ * ⭐ Absent on a rig that declares no physics constraint, for `skin`'s
200
+ * reason in `FramesSidecar`: there is nothing for the field to count, and
201
+ * every frame set written before it existed says the same. Present, a 0 is a
202
+ * claim — the rig declares physics and no constraint took a step on any
203
+ * frame — and that is what tells a reading taken with physics from one
204
+ * taken without.
205
+ */
206
+ physics?: { stepped: number; fps: number };
193
207
  }
194
208
 
195
209
  export interface FramesSidecar {
@@ -445,6 +459,8 @@ export interface PoseOptions {
445
459
  * so `framingViewport` keeps it and the box holds what the frames draw.
446
460
  */
447
461
  environment?: readonly EnvironmentStep[];
462
+ /** Also record the physics constraints the step that posed each frame stepped — see `Frame.physics` (issue #1467). */
463
+ physics?: boolean;
448
464
  }
449
465
 
450
466
  /**
@@ -586,6 +602,12 @@ export interface Frame {
586
602
  * second derivation of it.
587
603
  */
588
604
  attachments?: AttachmentPose[];
605
+ /**
606
+ * The physics constraints the step that posed this frame stepped, by name in
607
+ * code-unit order — present only when `PoseOptions.physics` asked for it
608
+ * (issue #1467). See `Posed.physics` for what counts as a step.
609
+ */
610
+ physics?: string[];
589
611
  }
590
612
 
591
613
  /**
@@ -1055,6 +1077,16 @@ export interface Posed {
1055
1077
  bones(): BoneSnapshot[];
1056
1078
  /** Every slot's region or mesh attachment, whole, in the posed draw order. */
1057
1079
  attachments(): AttachmentPose[];
1080
+ /**
1081
+ * The physics constraints the step that posed this moment stepped, by name in
1082
+ * code-unit order (issue #1467). A constraint is stepped when, after a step
1083
+ * that moved the skeleton's clock, its own clock reads the skeleton's: the
1084
+ * physics update sets it there, and every way of not running — inactive, a
1085
+ * mix of 0, the step skipped — leaves it behind. Read off the posed state on
1086
+ * both posers, never off which call was made, so a step removed reads 0. The
1087
+ * setup pose and a reset pose, where the clock has not moved, step none.
1088
+ */
1089
+ physics(): string[];
1058
1090
  }
1059
1091
 
1060
1092
  /**
@@ -1126,6 +1158,7 @@ function frameOf(index: number, time: number, posed: Posed, draw: DrawOptions, o
1126
1158
  pieces: posed.pieces(draw),
1127
1159
  ...(opts?.bones ? { bones: posed.bones() } : {}),
1128
1160
  ...(opts?.geometry ? { attachments: posed.attachments() } : {}),
1161
+ ...(opts?.physics ? { physics: posed.physics() } : {}),
1129
1162
  };
1130
1163
  }
1131
1164