rig-c 2.35.1 → 2.36.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/package.json +1 -1
- package/src/core/animation.ts +122 -3
- package/src/core/entry.ts +1 -1
- package/src/core/raw.ts +9 -7
- package/src/core/track.ts +46 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-c",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.36.0",
|
|
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/animation.ts
CHANGED
|
@@ -706,6 +706,108 @@ export function posedBones(doc: CompiledDocument, timelines: CoreAnimationTimeli
|
|
|
706
706
|
});
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
/**
|
|
710
|
+
* A player's bone-local adjustment (issue #1336): a function the live track
|
|
711
|
+
* (`openTrack`'s `adjust`, `./track.ts`) calls once per pose, after the
|
|
712
|
+
* animation's timelines have posed the bone locals at the step's time and
|
|
713
|
+
* before the world transforms, the constraints and the physics step — so a
|
|
714
|
+
* write is what the rest of the step reads: the bone's children, the
|
|
715
|
+
* attachments drawn through it, every constraint and the physics constraint
|
|
716
|
+
* stepped on top of it. It is spine-core's "application code" writing
|
|
717
|
+
* `Bone.pose` between `AnimationState.apply` and `updateWorldTransform`
|
|
718
|
+
* (`CO50` holds the two equal at tolerance 0).
|
|
719
|
+
*
|
|
720
|
+
* - **The view is the step's locals, posed from the setup pose.** Every pose
|
|
721
|
+
* of a walk is posed from the setup pose at its time (`./raw.ts`,
|
|
722
|
+
* `TrackState`), so a write lives for the pose it was written in and the
|
|
723
|
+
* next step's hook reads the timelines' value again, not the write. A
|
|
724
|
+
* channel no timeline keys reads its setup value on every step.
|
|
725
|
+
* - **A bone the hook does not write is untouched**: the timelines' bone
|
|
726
|
+
* object as it was, the setup bone itself where nothing keys it. A written
|
|
727
|
+
* bone is copied before the write, so the document is never written.
|
|
728
|
+
* - **A write that is not a finite number is refused by name**, the bone, the
|
|
729
|
+
* channel and the value — where the core refuses a non-finite value, by
|
|
730
|
+
* name, rather than posing a picture with it (`finite` in `./raw.ts`).
|
|
731
|
+
*/
|
|
732
|
+
export interface BoneLocals {
|
|
733
|
+
rotation: number;
|
|
734
|
+
x: number;
|
|
735
|
+
y: number;
|
|
736
|
+
scaleX: number;
|
|
737
|
+
scaleY: number;
|
|
738
|
+
shearX: number;
|
|
739
|
+
shearY: number;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** The locals of a step's bones, by name (`BoneLocals`). */
|
|
743
|
+
export interface AdjustableLocals {
|
|
744
|
+
/** The document's bones, in its order. */
|
|
745
|
+
readonly names: readonly string[];
|
|
746
|
+
/** One bone's locals, read and written in place; a name the document has no bone for is refused by name. */
|
|
747
|
+
bone(name: string): BoneLocals;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** What `openTrack`'s `adjust` is: called once per pose with the step's locals (`BoneLocals`). */
|
|
751
|
+
export type LocalAdjust = (locals: AdjustableLocals) => void;
|
|
752
|
+
|
|
753
|
+
/** The seven channels a `BoneLocals` reads and writes, each with the value an unstated one reads as (`worldTransforms`' defaults). */
|
|
754
|
+
const LOCAL_CHANNELS = { rotation: 0, x: 0, y: 0, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 } as const;
|
|
755
|
+
type LocalChannel = keyof typeof LOCAL_CHANNELS;
|
|
756
|
+
|
|
757
|
+
/** One bone of a step's `bones`, read through and written through, copied on its first write (`BoneLocals`' second rule). */
|
|
758
|
+
class StepLocals implements BoneLocals {
|
|
759
|
+
constructor(
|
|
760
|
+
private readonly bones: ModelBone[],
|
|
761
|
+
private readonly index: number,
|
|
762
|
+
private readonly setup: ModelBone,
|
|
763
|
+
) {}
|
|
764
|
+
|
|
765
|
+
private read(channel: LocalChannel): number {
|
|
766
|
+
return this.bones[this.index][channel] ?? LOCAL_CHANNELS[channel];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private write(channel: LocalChannel, value: number): void {
|
|
770
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) throw new CoreInputError(`the adjustment wrote bone "${this.setup.name}" ${channel} = ${String(value)}, not a finite number`);
|
|
771
|
+
if (this.bones[this.index] === this.setup) this.bones[this.index] = { ...this.setup };
|
|
772
|
+
this.bones[this.index][channel] = value;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
get rotation(): number { return this.read('rotation'); }
|
|
776
|
+
set rotation(v: number) { this.write('rotation', v); }
|
|
777
|
+
get x(): number { return this.read('x'); }
|
|
778
|
+
set x(v: number) { this.write('x', v); }
|
|
779
|
+
get y(): number { return this.read('y'); }
|
|
780
|
+
set y(v: number) { this.write('y', v); }
|
|
781
|
+
get scaleX(): number { return this.read('scaleX'); }
|
|
782
|
+
set scaleX(v: number) { this.write('scaleX', v); }
|
|
783
|
+
get scaleY(): number { return this.read('scaleY'); }
|
|
784
|
+
set scaleY(v: number) { this.write('scaleY', v); }
|
|
785
|
+
get shearX(): number { return this.read('shearX'); }
|
|
786
|
+
set shearX(v: number) { this.write('shearX', v); }
|
|
787
|
+
get shearY(): number { return this.read('shearY'); }
|
|
788
|
+
set shearY(v: number) { this.write('shearY', v); }
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** The adjustment run over a step's `bones` (`posedBones`' array, in the document's order), which it may replace bone by bone (`BoneLocals`). */
|
|
792
|
+
function adjustLocals(doc: CompiledDocument, bones: ModelBone[], adjust: LocalAdjust): void {
|
|
793
|
+
const views = new Map<string, StepLocals>();
|
|
794
|
+
const names = doc.bones.map((b) => b.name);
|
|
795
|
+
adjust({
|
|
796
|
+
names,
|
|
797
|
+
bone(name: string): BoneLocals {
|
|
798
|
+
let view = views.get(name);
|
|
799
|
+
if (view === undefined) {
|
|
800
|
+
const index = names.indexOf(name);
|
|
801
|
+
if (index < 0) throw new CoreInputError(`the adjustment asked for bone ${JSON.stringify(name)}, which is not one of this document's bones`);
|
|
802
|
+
// A copy the timelines made is this step's own; the setup bone is the document's, copied on its first write.
|
|
803
|
+
view = new StepLocals(bones, index, doc.bones[index]);
|
|
804
|
+
views.set(name, view);
|
|
805
|
+
}
|
|
806
|
+
return view;
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
709
811
|
const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
710
812
|
|
|
711
813
|
/**
|
|
@@ -778,6 +880,21 @@ export function posedSlots(
|
|
|
778
880
|
return { rows, conflicts };
|
|
779
881
|
}
|
|
780
882
|
|
|
883
|
+
/**
|
|
884
|
+
* One step of the stepped phase, as a walk hands it to the bones: the physics
|
|
885
|
+
* context and the clock before the step (`./constraints_physics.ts`), and the
|
|
886
|
+
* caller's adjustment of the locals (`LocalAdjust`). The adjustment rides here
|
|
887
|
+
* rather than on the plant because it is not a rule the core measured and a
|
|
888
|
+
* control plants back — it is the caller's input to one walk — and because
|
|
889
|
+
* the step context exists exactly where a walk is stepped: the live track's
|
|
890
|
+
* every pose but a `reset: 'setup'` pose 0, which no animation is applied to.
|
|
891
|
+
*/
|
|
892
|
+
export interface PoseStep {
|
|
893
|
+
ctx: PhysicsStepContext;
|
|
894
|
+
before: number;
|
|
895
|
+
adjust?: LocalAdjust;
|
|
896
|
+
}
|
|
897
|
+
|
|
781
898
|
/**
|
|
782
899
|
* The bone rows at `t`, in the oracle's shape and rounding. With `constraints`
|
|
783
900
|
* — the animation's constraint timelines — the document's constraints are
|
|
@@ -792,7 +909,7 @@ export function posedBoneRows(doc: CompiledDocument, timelines: CoreAnimationTim
|
|
|
792
909
|
}
|
|
793
910
|
|
|
794
911
|
/** `posedBoneRows`, with the world transforms the rows were read off — what the sample's attachments are posed through. */
|
|
795
|
-
export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant = {}, constraints?: CoreConstraintTimelines, sliders?: SliderApplication[], step?:
|
|
912
|
+
export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant = {}, constraints?: CoreConstraintTimelines, sliders?: SliderApplication[], step?: PoseStep): { rows: CoreBoneRow[]; world: Map<string, CoreWorld> } {
|
|
796
913
|
const posed = posedBoneStep(doc, timelines, t, plant, constraints, sliders, step);
|
|
797
914
|
return { rows: stepRows(posed), world: posed.world };
|
|
798
915
|
}
|
|
@@ -804,7 +921,7 @@ export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTi
|
|
|
804
921
|
* no row, so the scan's walk (`scanWalkIn` in `./raw.ts`, A10's) asks for
|
|
805
922
|
* this; the raw entry's walk still forms the rows it does not read, unchanged.
|
|
806
923
|
*/
|
|
807
|
-
export function posedBoneWorldAlone(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step:
|
|
924
|
+
export function posedBoneWorldAlone(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: PoseStep | undefined): Map<string, CoreWorld> {
|
|
808
925
|
return posedBoneStep(doc, timelines, t, plant, constraints, sliders, step).world;
|
|
809
926
|
}
|
|
810
927
|
|
|
@@ -835,9 +952,11 @@ function stepRows(posed: PosedStep): CoreBoneRow[] {
|
|
|
835
952
|
}
|
|
836
953
|
|
|
837
954
|
/** `posedBoneWorld` up to its rows, refusing a bone the evaluator gave no transform (`PosedStep`). */
|
|
838
|
-
function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step:
|
|
955
|
+
function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: PoseStep | undefined): PosedStep {
|
|
839
956
|
const active = activeBones(doc);
|
|
840
957
|
const bones = posedBones(doc, timelines, t, plant);
|
|
958
|
+
// The caller's adjustment, after the timelines and before anything reads the locals (`LocalAdjust`).
|
|
959
|
+
if (step?.adjust !== undefined) adjustLocals(doc, bones, step.adjust);
|
|
841
960
|
let world = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
842
961
|
if (constraints !== undefined && step !== undefined) {
|
|
843
962
|
// One step of the stepped phase (issue #956, `./constraints_physics.ts`): the physics records posed by their timelines and stepped under the walk's context; the deformed curve a path walks as above. No previous pass: under the step it is the previous step's, and `poseAnimations` leaves such bones out.
|
package/src/core/entry.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { poseRawSetup, poseRawAnimation, poseRawAnimationEach, loopedTime, setup
|
|
|
26
26
|
export type { RawPose, RawBone, RawDrawn, RawClip, RawClipped, RawEvent, RawReset } from './raw.ts';
|
|
27
27
|
export { poseWalkSetup, poseLoopingWalk } from './walk.ts';
|
|
28
28
|
export { openTrack } from './track.ts';
|
|
29
|
-
export type { LiveTrack, TrackOptions } from './track.ts';
|
|
29
|
+
export type { LiveTrack, TrackOptions, AdjustableLocals, BoneLocals, LocalAdjust } from './track.ts';
|
|
30
30
|
export type { WalkPose } from './walk.ts';
|
|
31
31
|
export { documentPageLookup, drawnRegions, regionPageUvs, meshPageUvs, readUvSequences } from './uvs.ts';
|
|
32
32
|
export type { DrawnRegion, UvPage, UvRegion, UvSource } from './uvs.ts';
|
package/src/core/raw.ts
CHANGED
|
@@ -114,7 +114,7 @@
|
|
|
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, posedBoneWorldAlone, posedSlots, type TimelinePlant } from './animation.ts';
|
|
117
|
+
import { posedBoneWorld, posedBoneWorldAlone, posedSlots, type LocalAdjust, 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';
|
|
@@ -612,6 +612,8 @@ export interface TrackState<P> {
|
|
|
612
612
|
readonly resolve: NonNullable<TimelinePlant['shown']>;
|
|
613
613
|
readonly shape: PoseShape<P>;
|
|
614
614
|
readonly ctx: ReturnType<typeof freshStepContext>;
|
|
615
|
+
/** The caller's adjustment of each step's locals (`LocalAdjust` in `./animation.ts`): the live track's `adjust`, absent on every batch walk. */
|
|
616
|
+
readonly adjust?: LocalAdjust;
|
|
615
617
|
/** The running sum of the steps taken. */
|
|
616
618
|
trackTime: number;
|
|
617
619
|
/** The animation time of the step before, −1 before the first. */
|
|
@@ -631,12 +633,12 @@ function animationOf(doc: CompiledDocument, animation: string): CompiledDocument
|
|
|
631
633
|
* `reset: 'setup'` pose 0 is the setup pose, reset; under `animation` it is the
|
|
632
634
|
* animation applied at 0 and reset there, a step of 0 (`stepWalk`'s `first`).
|
|
633
635
|
*/
|
|
634
|
-
function openWalk<P>(doc: CompiledDocument, anim: CompiledDocument['animations'][number], plant: TimelinePlant, reset: RawReset, mode: WalkMode, shape: PoseShape<P
|
|
636
|
+
function openWalk<P>(doc: CompiledDocument, anim: CompiledDocument['animations'][number], plant: TimelinePlant, reset: RawReset, mode: WalkMode, shape: PoseShape<P>, adjust?: LocalAdjust): { track: TrackState<P>; first: P } {
|
|
635
637
|
const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
|
|
636
638
|
if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
|
|
637
639
|
const ctx = freshStepContext(plant.physicsStep);
|
|
638
640
|
if (mode.loop && mode.wrapResets !== false) ctx.loop = true;
|
|
639
|
-
const track: TrackState<P> = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, trackTime: 0, last: -1 };
|
|
641
|
+
const track: TrackState<P> = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, adjust, trackTime: 0, last: -1 };
|
|
640
642
|
if (reset === 'setup') {
|
|
641
643
|
// The reset taken at the setup pose, before the animation is applied (`src/deformmeasure.ts`'s `poseAt`): pose 0 is the setup pose.
|
|
642
644
|
const setup = poseSetup(doc, { ...plant, ...roundPlant(mode) }, ctx);
|
|
@@ -657,7 +659,7 @@ function openWalk<P>(doc: CompiledDocument, anim: CompiledDocument['animations']
|
|
|
657
659
|
* resets the physics there — and nothing else passes it.
|
|
658
660
|
*/
|
|
659
661
|
function stepWalk<P>(track: TrackState<P>, dt: number, first = false): P {
|
|
660
|
-
const { doc, anim, duration, mode, plant, raw, resolve, shape, ctx } = track;
|
|
662
|
+
const { doc, anim, duration, mode, plant, raw, resolve, shape, ctx, adjust } = track;
|
|
661
663
|
track.trackTime += dt;
|
|
662
664
|
const trackTime = track.trackTime;
|
|
663
665
|
const last = track.last;
|
|
@@ -665,7 +667,7 @@ function stepWalk<P>(track: TrackState<P>, dt: number, first = false): P {
|
|
|
665
667
|
const before = ctx.time;
|
|
666
668
|
ctx.time += mode.clock === undefined ? dt : mode.clock(dt, t - (first ? 0 : Math.max(last, 0)));
|
|
667
669
|
const sliders: SliderApplication[] = [];
|
|
668
|
-
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 });
|
|
670
|
+
const world = shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust });
|
|
669
671
|
if (first) ctx.phase = 'update';
|
|
670
672
|
const placeholders = new Map<string, string | null>();
|
|
671
673
|
const slots = posedSlots(doc, anim.timelines, t, raw, sliders, placeholders);
|
|
@@ -706,8 +708,8 @@ function walkEach<P>(doc: CompiledDocument, animation: string, steps: readonly n
|
|
|
706
708
|
* `stepRawTrack` steps. Every pose is assembled as the raw entry's
|
|
707
709
|
* (`RAW_SHAPE`).
|
|
708
710
|
*/
|
|
709
|
-
export function openRawTrack(doc: CompiledDocument, animation: string, plant: TimelinePlant, reset: RawReset, mode: WalkMode): { track: TrackState<RawPose>; first: RawPose } {
|
|
710
|
-
return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE);
|
|
711
|
+
export function openRawTrack(doc: CompiledDocument, animation: string, plant: TimelinePlant, reset: RawReset, mode: WalkMode, adjust?: LocalAdjust): { track: TrackState<RawPose>; first: RawPose } {
|
|
712
|
+
return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE, adjust);
|
|
711
713
|
}
|
|
712
714
|
|
|
713
715
|
/** One step of an opened raw track (`openRawTrack`): `walkIn`'s step, the step itself checked by the caller. */
|
package/src/core/track.ts
CHANGED
|
@@ -95,6 +95,41 @@
|
|
|
95
95
|
* and the events (4,009 of 4,009 on the tree row, 690 of them cutting 12,420
|
|
96
96
|
* rows).
|
|
97
97
|
*
|
|
98
|
+
* ## The adjustment (issue #1336)
|
|
99
|
+
*
|
|
100
|
+
* `adjust` is a player's own bone-local value — a breath's scale, a look's
|
|
101
|
+
* rotation — written onto the bones every pose, after the animation's
|
|
102
|
+
* timelines posed the locals at the step's time and before the world
|
|
103
|
+
* transforms, the constraints and the physics step (`LocalAdjust` in
|
|
104
|
+
* `./animation.ts`, which states the view's rules). It runs on every pose the
|
|
105
|
+
* animation is applied to — every `step`, and pose 0 under `reset:
|
|
106
|
+
* 'animation'` — and not on pose 0 under `reset: 'setup'`, the setup pose
|
|
107
|
+
* with nothing applied: a player applies its adjustment wherever it applies
|
|
108
|
+
* the animation state, the first frame included, and spine-core's reset at
|
|
109
|
+
* the setup pose applies none. Absent, every pose is the one the track posed
|
|
110
|
+
* before it existed, bit for bit (`CO46`, unchanged; `CO50` holds a hook that
|
|
111
|
+
* writes nothing to the same bits).
|
|
112
|
+
*
|
|
113
|
+
* `CO50` holds it to spine-core 4.3.13 on gallery/look, whose `head` carries
|
|
114
|
+
* the deformed head mesh and every face bone, and the physics-driven
|
|
115
|
+
* `ahoge_whip` two bones below it: the same `scaleY` written on `head` by the
|
|
116
|
+
* core's hook and on `Bone.pose` between `AnimationState.apply` and
|
|
117
|
+
* `updateWorldTransform(Physics.update)`, every bone's world transform and
|
|
118
|
+
* every drawn vertex equal at tolerance 0 over looping and held walks under
|
|
119
|
+
* both resets — spine-core's skeleton brought back to the setup pose
|
|
120
|
+
* (`setupPoseBones`) before each `apply`, which is the core's own reading:
|
|
121
|
+
* every pose is posed from the setup pose. Measured, and held red there:
|
|
122
|
+
*
|
|
123
|
+
* - **The write carried from frame to frame** — spine-core without that
|
|
124
|
+
* reset, where no timeline writes an unkeyed channel back, so a `scaleY`
|
|
125
|
+
* multiplied every frame compounds (every walk off from pose 2). The core
|
|
126
|
+
* carries no local from one step to the next; a value a player wants
|
|
127
|
+
* carried it keeps and writes again.
|
|
128
|
+
* - **The write at draw time** — made after `updateWorldTransform`, the
|
|
129
|
+
* bone's own matrix alone brought up to date: the bone moves, its
|
|
130
|
+
* children, the meshes they carry and the physics do not.
|
|
131
|
+
* - **Pose 0 not adjusted** under `reset: 'animation'`.
|
|
132
|
+
*
|
|
98
133
|
* ## The oracle chain
|
|
99
134
|
*
|
|
100
135
|
* Nothing here is held to spine-core directly but the events: the live track
|
|
@@ -106,19 +141,22 @@
|
|
|
106
141
|
*
|
|
107
142
|
* ⛔ **A track refused part-way stays refused.** A step is checked before
|
|
108
143
|
* anything moves; a refusal past that — a construct the core leaves out, a
|
|
109
|
-
* non-finite vertex — comes after the track time and the physics
|
|
110
|
-
* so every later `step` refuses naming the step it was refused
|
|
111
|
-
* caller opens the track again.
|
|
144
|
+
* non-finite vertex or write — comes after the track time and the physics
|
|
145
|
+
* have moved, so every later `step` refuses naming the step it was refused
|
|
146
|
+
* at, and the caller opens the track again. So does an error the caller's own
|
|
147
|
+
* `adjust` throws: it is thrown mid-step, after the same moves. Seeking is the caller's too: a physics rig has
|
|
112
148
|
* history, so a seek is "open, then step to the time".
|
|
113
149
|
*/
|
|
114
150
|
import { CoreInputError, type CompiledDocument } from './index.ts';
|
|
115
151
|
import { openRawTrack, stepRawTrack, type RawPose, type RawReset, type TrackState, type WalkMode } from './raw.ts';
|
|
116
|
-
import type { TimelinePlant } from './animation.ts';
|
|
152
|
+
import type { LocalAdjust, TimelinePlant } from './animation.ts';
|
|
153
|
+
export type { AdjustableLocals, BoneLocals, LocalAdjust } from './animation.ts';
|
|
117
154
|
|
|
118
|
-
/** How a live track is opened: whether it loops, and where its physics is reset (`RawReset`)
|
|
155
|
+
/** How a live track is opened: whether it loops, and where its physics is reset (`RawReset`) — both the caller's, neither with a default — and the caller's adjustment of every pose's bone locals (the header's *The adjustment*). */
|
|
119
156
|
export interface TrackOptions {
|
|
120
157
|
loop: boolean;
|
|
121
158
|
reset: RawReset;
|
|
159
|
+
adjust?: LocalAdjust;
|
|
122
160
|
}
|
|
123
161
|
|
|
124
162
|
/** A live track (`openTrack`): pose 0, then one pose per `step`. */
|
|
@@ -147,7 +185,8 @@ const LOOPED: WalkMode = { loop: true, keep: false, live: true };
|
|
|
147
185
|
export function openTrack(doc: CompiledDocument, animation: string, options: TrackOptions, plant: TimelinePlant = {}): LiveTrack {
|
|
148
186
|
if (typeof options !== 'object' || options === null || typeof options.loop !== 'boolean') throw new CoreInputError(`the live track's options.loop is ${JSON.stringify((options as Partial<TrackOptions> | null)?.loop)}, not true or false`);
|
|
149
187
|
if (options.reset !== 'animation' && options.reset !== 'setup') throw new CoreInputError(`the live track's options.reset is ${JSON.stringify(options.reset)}, not "animation" or "setup"`);
|
|
150
|
-
|
|
188
|
+
if (options.adjust !== undefined && typeof options.adjust !== 'function') throw new CoreInputError(`the live track's options.adjust is ${typeof options.adjust}, not a function`);
|
|
189
|
+
const { track, first } = openRawTrack(doc, animation, plant, options.reset, options.loop ? LOOPED : HELD, options.adjust);
|
|
151
190
|
return new Track(track, first);
|
|
152
191
|
}
|
|
153
192
|
|
|
@@ -186,7 +225,7 @@ class Track implements LiveTrack {
|
|
|
186
225
|
this.latest = pose.animationTime;
|
|
187
226
|
return pose;
|
|
188
227
|
} catch (err) {
|
|
189
|
-
|
|
228
|
+
this.refused = `${n} (${err instanceof Error ? err.message : String(err)})`;
|
|
190
229
|
throw err;
|
|
191
230
|
}
|
|
192
231
|
}
|