spine-rigc 2.9.0 → 2.10.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 +73 -16
- package/src/core/constraints.ts +37 -6
- package/src/core/index.ts +47 -5
- package/src/core/uvs.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
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": {
|
package/src/core/animation.ts
CHANGED
|
@@ -600,15 +600,33 @@ export function keyIndexAt(keys: ReadonlyArray<{ time: number }>, t: number): nu
|
|
|
600
600
|
return found;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Each Bézier segment's polylines, one per channel, kept with the key that
|
|
605
|
+
* starts the segment and the key that ends it (issue #1134): a polyline is a
|
|
606
|
+
* function of those two keys' stated numbers and handles only, and a walk
|
|
607
|
+
* evaluated the same segment's on every step. A key read is never written; a
|
|
608
|
+
* plant that changes a key passes a copy, which keeps polylines of its own.
|
|
609
|
+
*/
|
|
610
|
+
const segmentPolylines = new WeakMap<CoreKey, { end: CoreKey; channels: Array<number[] | undefined> }>();
|
|
611
|
+
|
|
603
612
|
/** Channel `channel` of the segment starting at key `index` (not the last), at `t` — the header's three curves. */
|
|
604
613
|
export function channelAt(keys: readonly CoreKey[], index: number, channel: number, t: number): number {
|
|
605
614
|
const a = keys[index];
|
|
606
615
|
const b = keys[index + 1];
|
|
607
616
|
if (b === undefined || a.curve === 'stepped') return a.values[channel];
|
|
608
617
|
if (a.curve === 'linear') return a.values[channel] + ((t - a.time) / (b.time - a.time)) * (b.values[channel] - a.values[channel]);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
618
|
+
let kept = segmentPolylines.get(a);
|
|
619
|
+
if (kept === undefined || kept.end !== b) {
|
|
620
|
+
kept = { end: b, channels: [] };
|
|
621
|
+
segmentPolylines.set(a, kept);
|
|
622
|
+
}
|
|
623
|
+
let points = kept.channels[channel];
|
|
624
|
+
if (points === undefined) {
|
|
625
|
+
const c = a.curve.slice(channel * 4, channel * 4 + 4);
|
|
626
|
+
const inner = bezierPolyline(a.stated.time, a.stated.values[channel], c[0], c[1], c[2], c[3], b.stated.time, b.stated.values[channel]);
|
|
627
|
+
points = [a.time, a.values[channel], ...inner, b.time, b.values[channel]];
|
|
628
|
+
kept.channels[channel] = points;
|
|
629
|
+
}
|
|
612
630
|
let i = 2;
|
|
613
631
|
while (i < points.length - 2 && points[i] < t) i += 2;
|
|
614
632
|
const [x0, y0, x1, y1] = [points[i - 2], points[i - 1], points[i], points[i + 1]];
|
|
@@ -635,9 +653,22 @@ function keyAt(keys: readonly CoreKey[], t: number, plant: TimelinePlant): CoreK
|
|
|
635
653
|
return index < 0 ? null : keys[index];
|
|
636
654
|
}
|
|
637
655
|
|
|
656
|
+
/** Each animation's bone timelines by bone name, kept with the timelines they index (`posedBones`). */
|
|
657
|
+
const boneTimelinesByName = new WeakMap<CoreAnimationTimelines, ReadonlyMap<string, CoreTarget<BoneTimelineKind>>>();
|
|
658
|
+
|
|
659
|
+
/** The animation's bone timelines by bone name, built once per timelines object (issue #1134: a walk built it again on every step); timelines read are never written. */
|
|
660
|
+
function boneTimelinesOf(timelines: CoreAnimationTimelines): ReadonlyMap<string, CoreTarget<BoneTimelineKind>> {
|
|
661
|
+
let byName = boneTimelinesByName.get(timelines);
|
|
662
|
+
if (byName === undefined) {
|
|
663
|
+
byName = new Map(timelines.bones.map((b) => [b.name, b]));
|
|
664
|
+
boneTimelinesByName.set(timelines, byName);
|
|
665
|
+
}
|
|
666
|
+
return byName;
|
|
667
|
+
}
|
|
668
|
+
|
|
638
669
|
/** The bones posed by the animation's bone timelines at `t`: copies of the setup bones with the posed fields — the header's rules. */
|
|
639
670
|
export function posedBones(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant = {}): ModelBone[] {
|
|
640
|
-
const byName =
|
|
671
|
+
const byName = boneTimelinesOf(timelines);
|
|
641
672
|
return doc.bones.map((setup) => {
|
|
642
673
|
const target = byName.get(setup.name);
|
|
643
674
|
if (target === undefined) return setup;
|
|
@@ -762,6 +793,38 @@ export function posedBoneRows(doc: CompiledDocument, timelines: CoreAnimationTim
|
|
|
762
793
|
|
|
763
794
|
/** `posedBoneRows`, with the world transforms the rows were read off — what the sample's attachments are posed through. */
|
|
764
795
|
export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant = {}, constraints?: CoreConstraintTimelines, sliders?: SliderApplication[], step?: { ctx: PhysicsStepContext; before: number }): { rows: CoreBoneRow[]; world: Map<string, CoreWorld> } {
|
|
796
|
+
const posed = posedBoneStep(doc, timelines, t, plant, constraints, sliders, step);
|
|
797
|
+
return { rows: stepRows(posed), world: posed.world };
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* One pose of the bones before its rows are read: the world transforms, and
|
|
802
|
+
* what the rows are rounded from. A stepped walk reads rows only off the step
|
|
803
|
+
* that lands on a sample (issue #1134: of the stepped run's poses, nine in ten
|
|
804
|
+
* are steps between samples), so `stepRows` rounds them when they are read,
|
|
805
|
+
* once; every bone's transform is required here, at every step.
|
|
806
|
+
*/
|
|
807
|
+
interface PosedStep {
|
|
808
|
+
bones: ModelBone[];
|
|
809
|
+
world: Map<string, CoreWorld>;
|
|
810
|
+
active: ReadonlySet<string>;
|
|
811
|
+
round: (v: number) => number | null;
|
|
812
|
+
rows?: CoreBoneRow[];
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** A step's bone rows, in the oracle's shape and rounding, rounded the first time they are read. */
|
|
816
|
+
function stepRows(posed: PosedStep): CoreBoneRow[] {
|
|
817
|
+
if (posed.rows !== undefined) return posed.rows;
|
|
818
|
+
const { world, active, round } = posed;
|
|
819
|
+
posed.rows = posed.bones.map((b): CoreBoneRow => {
|
|
820
|
+
const w = world.get(b.name) as CoreWorld;
|
|
821
|
+
return [b.name, round(w.worldX), round(w.worldY), round(w.a), round(w.b), round(w.c), round(w.d), active.has(b.name) ? 1 : 0, b.parent ?? null];
|
|
822
|
+
});
|
|
823
|
+
return posed.rows;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** `posedBoneWorld` up to its rows, refusing a bone the evaluator gave no transform (`PosedStep`). */
|
|
827
|
+
function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: { ctx: PhysicsStepContext; before: number } | undefined): PosedStep {
|
|
765
828
|
const active = activeBones(doc);
|
|
766
829
|
const bones = posedBones(doc, timelines, t, plant);
|
|
767
830
|
let world = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
@@ -777,14 +840,8 @@ export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTi
|
|
|
777
840
|
const previous = setupRecords.some((r) => r.kind === 'path') ? applyConstraints(doc.bones, (plant.evaluate ?? worldTransforms)(doc.bones, active), active, plant.constraints ? plant.constraints(setupRecords) : setupRecords, null, undefined, undefined, undefined, solverRules(plant.solver)) : null;
|
|
778
841
|
world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(records) : records, previous, sliders, undefined, undefined, solverRules(plant.solver));
|
|
779
842
|
}
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
const rows = bones.map((b): CoreBoneRow => {
|
|
783
|
-
const w = posed.get(b.name);
|
|
784
|
-
if (w === undefined) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
785
|
-
return [b.name, round(w.worldX), round(w.worldY), round(w.a), round(w.b), round(w.c), round(w.d), active.has(b.name) ? 1 : 0, b.parent ?? null];
|
|
786
|
-
});
|
|
787
|
-
return { rows, world: posed };
|
|
843
|
+
for (const b of bones) if (world.get(b.name) === undefined) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
844
|
+
return { bones, world, active, round: plant.round ?? gridRound };
|
|
788
845
|
}
|
|
789
846
|
|
|
790
847
|
/** Each path record with the curve its slot shows deformed by the sample's own deform timelines at `t` (`./deform.ts`), or as it was. */
|
|
@@ -904,9 +961,9 @@ export function poseAnimations(doc: CompiledDocument, phase: SamplePhase, n: num
|
|
|
904
961
|
const samples: CoreSample[] = [];
|
|
905
962
|
let last = -1;
|
|
906
963
|
// Under `--physics step` (issue #956): one walk per animation — reset at 0, then the oracle's schedule, each sample posed off its last step (`./constraints_physics.ts`).
|
|
907
|
-
const walk = dt === undefined || bonesReason !== null ? null : { ctx: freshStepContext(plant.physicsStep), now: 0, schedule: stepSchedule(phase, d, n, dt), sliders: [] as SliderApplication[], posed: null as
|
|
964
|
+
const walk = dt === undefined || bonesReason !== null ? null : { ctx: freshStepContext(plant.physicsStep), now: 0, schedule: stepSchedule(phase, d, n, dt), sliders: [] as SliderApplication[], posed: null as PosedStep | null };
|
|
908
965
|
if (walk !== null) {
|
|
909
|
-
walk.posed =
|
|
966
|
+
walk.posed = posedBoneStep(doc, anim.timelines, 0, plant, anim.constraints, walk.sliders, { ctx: walk.ctx, before: 0 });
|
|
910
967
|
walk.ctx.phase = 'update';
|
|
911
968
|
}
|
|
912
969
|
for (let i = 0; i < n; i++) {
|
|
@@ -918,11 +975,11 @@ export function poseAnimations(doc: CompiledDocument, phase: SamplePhase, n: num
|
|
|
918
975
|
const before = walk.ctx.time;
|
|
919
976
|
walk.ctx.time += s - walk.now;
|
|
920
977
|
walk.sliders = [];
|
|
921
|
-
walk.posed =
|
|
978
|
+
walk.posed = posedBoneStep(doc, anim.timelines, s, plant, anim.constraints, walk.sliders, { ctx: walk.ctx, before });
|
|
922
979
|
walk.now = s;
|
|
923
980
|
}
|
|
924
981
|
sliders = walk.sliders;
|
|
925
|
-
posed = walk.posed;
|
|
982
|
+
posed = walk.posed === null ? null : { rows: stepRows(walk.posed), world: walk.posed.world };
|
|
926
983
|
} else posed = bonesReason === null ? posedBoneWorld(doc, anim.timelines, t, plant, anim.constraints, sliders) : null;
|
|
927
984
|
const placeholders = new Map<string, string | null>();
|
|
928
985
|
const slots = posedSlots(doc, anim.timelines, t, plant, sliders, placeholders);
|
package/src/core/constraints.ts
CHANGED
|
@@ -1518,7 +1518,12 @@ export type ConstraintPlant = (records: CoreConstraintRecord[]) => CoreConstrain
|
|
|
1518
1518
|
* `./constraints_physics.ts`); without it, it applies nothing.
|
|
1519
1519
|
*/
|
|
1520
1520
|
export function applyConstraints(bones: readonly ModelBone[], world: ReadonlyMap<string, CoreWorld>, active: ReadonlySet<string>, records: readonly CoreConstraintRecord[], previous: ReadonlyMap<string, CoreWorld> | null = null, applied?: SliderApplication[], physics?: PhysicsStepContext, settled?: (state: SolverState) => void, rules: Readonly<SolverRules> = RUNTIME_SOLVER_RULES): Map<string, CoreWorld> {
|
|
1521
|
-
|
|
1521
|
+
// No record: nothing to solve, so the world as given, copied (issue #1134) — and what `sliderPhysicsTarget` leaves on the step's context with no record, an empty carry, under the plant that carries one.
|
|
1522
|
+
if (records.length === 0 && settled === undefined) {
|
|
1523
|
+
if (physics !== undefined && !rules.sliderPhysicsLastsOnePass) physics.carried = new Map();
|
|
1524
|
+
return new Map(world);
|
|
1525
|
+
}
|
|
1526
|
+
const state: SolverState = { bones: bones.map((b) => ({ ...b })), index: boneIndex(bones, active), world: new Map(world), active, rules };
|
|
1522
1527
|
const skipped = new Set<number>();
|
|
1523
1528
|
records.forEach((c, i) => {
|
|
1524
1529
|
if (inactiveWhy(state, c) !== null) skipped.add(i);
|
|
@@ -1573,19 +1578,45 @@ export function applyConstraints(bones: readonly ModelBone[], world: ReadonlyMap
|
|
|
1573
1578
|
return state.world;
|
|
1574
1579
|
}
|
|
1575
1580
|
|
|
1581
|
+
/** The last bone index each view's active set was asked for, with the names it was built over (`boneIndex`). */
|
|
1582
|
+
const indexOfView = new WeakMap<ReadonlySet<string>, { names: string[]; index: Map<string, number> }>();
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Each bone's position in `bones`, by name — the solver's index. Every pose of
|
|
1586
|
+
* a view lists the same bones in the same order (issue #1134: a walk built it
|
|
1587
|
+
* anew on each of its passes), so the index last built under the view's
|
|
1588
|
+
* active set (`activeBones`, one object per view) is reused when the names
|
|
1589
|
+
* agree position by position, and built again otherwise. Read, never written.
|
|
1590
|
+
*/
|
|
1591
|
+
function boneIndex(bones: readonly ModelBone[], active: ReadonlySet<string>): Map<string, number> {
|
|
1592
|
+
const kept = indexOfView.get(active);
|
|
1593
|
+
if (kept !== undefined && kept.names.length === bones.length && bones.every((b, i) => b.name === kept.names[i])) return kept.index;
|
|
1594
|
+
const index = new Map(bones.map((b, i) => [b.name, i]));
|
|
1595
|
+
indexOfView.set(active, { names: bones.map((b) => b.name), index });
|
|
1596
|
+
return index;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1576
1599
|
/** After a constraint: the bones it moved in world space read back into local values, and every bone below one it moved posed again (the header's update order). */
|
|
1577
1600
|
function repose(state: SolverState, changed: readonly string[], inWorld: readonly string[]): void {
|
|
1578
1601
|
for (const name of inWorld) {
|
|
1579
1602
|
const b = bone(state, name);
|
|
1580
1603
|
localFromWorld(b, parentWorld(state, b), state.world.get(name) as CoreWorld, state.rules);
|
|
1581
1604
|
}
|
|
1582
|
-
|
|
1605
|
+
// `changed` and `inWorld` are a constraint's few bones: read as they are rather than copied into sets on each of a walk's passes (issue #1134).
|
|
1606
|
+
const moved = changed;
|
|
1583
1607
|
const below = new Set(changed);
|
|
1584
|
-
const keep =
|
|
1585
|
-
|
|
1608
|
+
const keep = inWorld;
|
|
1609
|
+
// Bones are parents first (`readModel` refuses any other order) and a bone joins `below` only through its parent, so none before the first bone moved can: the walk starts there (issue #1134).
|
|
1610
|
+
let from = state.bones.length;
|
|
1611
|
+
for (const name of changed) {
|
|
1612
|
+
const at = state.index.get(name);
|
|
1613
|
+
if (at !== undefined && at < from) from = at;
|
|
1614
|
+
}
|
|
1615
|
+
for (let k = from; k < state.bones.length; k++) {
|
|
1616
|
+
const b = state.bones[k];
|
|
1586
1617
|
// Not through an inactive bone the constraint did not move itself (issue #979): the bones below it keep what the constraints wrote into them.
|
|
1587
|
-
if (b.parent !== undefined && below.has(b.parent) && (!state.rules.inactiveHoldsItsWorld || state.active.has(b.parent) || moved.
|
|
1588
|
-
if (below.has(b.name) && !keep.
|
|
1618
|
+
if (b.parent !== undefined && below.has(b.parent) && (!state.rules.inactiveHoldsItsWorld || state.active.has(b.parent) || moved.includes(b.parent))) below.add(b.name);
|
|
1619
|
+
if (below.has(b.name) && !keep.includes(b.name)) poseBone(state, b);
|
|
1589
1620
|
}
|
|
1590
1621
|
}
|
|
1591
1622
|
|
package/src/core/index.ts
CHANGED
|
@@ -1111,8 +1111,26 @@ export const NOT_ADMITTED: ReadonlyArray<readonly [string, string]> = [];
|
|
|
1111
1111
|
/** The setup blocks the core poses, in the document's order. */
|
|
1112
1112
|
const POSED_BLOCKS = ['setup.bones', 'setup.slots', 'setup.drawOrder', 'setup.attachments', 'setup.clips', 'setup.clipped'] as const;
|
|
1113
1113
|
|
|
1114
|
-
/**
|
|
1115
|
-
|
|
1114
|
+
/** Each view's active bones, kept for the view (`activeBones`). */
|
|
1115
|
+
const activeOfView = new WeakMap<CompiledDocument, ReadonlySet<string>>();
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Bones active under the skin view posed — the rule measured in the header
|
|
1119
|
+
* (`all`) and in `./skins.ts` (a named skin). Derived once per view object and
|
|
1120
|
+
* kept with it (issue #1134): the answer reads the view's bones and skins
|
|
1121
|
+
* only, a view is never written once it is posed, and every pose of a walk
|
|
1122
|
+
* asked again. Read-only to every caller; a view built anew (`underSkin`,
|
|
1123
|
+
* `underNoSkin`, a plant's) derives its own.
|
|
1124
|
+
*/
|
|
1125
|
+
export function activeBones(doc: CompiledDocument): ReadonlySet<string> {
|
|
1126
|
+
const kept = activeOfView.get(doc);
|
|
1127
|
+
if (kept !== undefined) return kept;
|
|
1128
|
+
const made = deriveActiveBones(doc);
|
|
1129
|
+
activeOfView.set(doc, made);
|
|
1130
|
+
return made;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function deriveActiveBones(doc: CompiledDocument): ReadonlySet<string> {
|
|
1116
1134
|
const named = new Set(appliedSkins(doc).flatMap((s) => s.bones));
|
|
1117
1135
|
const parentOf = new Map(doc.bones.map((b) => [b.name, b.parent]));
|
|
1118
1136
|
const reached = new Set<string>();
|
|
@@ -1150,26 +1168,50 @@ export function shownRow(shown: CoreShown): { name: string; path: string | null
|
|
|
1150
1168
|
return { name, path: CORE_REGION_KINDS.has(shown.record.kind) ? (shown.record.path ?? name) : null };
|
|
1151
1169
|
}
|
|
1152
1170
|
|
|
1171
|
+
/** Each view's resolutions, by slot and placeholder, kept for the view (`shownAttachment`). */
|
|
1172
|
+
const shownOfView = new WeakMap<CompiledDocument, Map<string, Map<string, ShownResolution>>>();
|
|
1173
|
+
|
|
1153
1174
|
/**
|
|
1154
1175
|
* What a slot shows at setup under every skin at once — the header's rule:
|
|
1155
1176
|
* nothing for a `null` placeholder or one no skin fills; the one record where
|
|
1156
1177
|
* every skin filling the placeholder gives the same row; a `conflict`, naming
|
|
1157
1178
|
* each skin in the model's order, where they differ.
|
|
1179
|
+
*
|
|
1180
|
+
* The answer reads the view's skins and the slot's name and placeholder only,
|
|
1181
|
+
* so it is resolved once per view, slot and placeholder and kept (issue
|
|
1182
|
+
* #1134: every sample of a walk resolved every slot again). Read-only to every
|
|
1183
|
+
* caller; a view built anew resolves its own.
|
|
1158
1184
|
*/
|
|
1159
1185
|
export function shownAttachment(doc: CompiledDocument, slot: ModelSlot): ShownResolution {
|
|
1160
1186
|
if (slot.setup === null) return null;
|
|
1161
|
-
|
|
1187
|
+
let bySlot = shownOfView.get(doc);
|
|
1188
|
+
if (bySlot === undefined) {
|
|
1189
|
+
bySlot = new Map();
|
|
1190
|
+
shownOfView.set(doc, bySlot);
|
|
1191
|
+
}
|
|
1192
|
+
let byPlaceholder = bySlot.get(slot.name);
|
|
1193
|
+
if (byPlaceholder === undefined) {
|
|
1194
|
+
byPlaceholder = new Map();
|
|
1195
|
+
bySlot.set(slot.name, byPlaceholder);
|
|
1196
|
+
}
|
|
1197
|
+
if (byPlaceholder.has(slot.setup)) return byPlaceholder.get(slot.setup) as ShownResolution;
|
|
1198
|
+
const resolved = resolveShown(doc, slot.name, slot.setup);
|
|
1199
|
+
byPlaceholder.set(slot.setup, resolved);
|
|
1200
|
+
return resolved;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function resolveShown(doc: CompiledDocument, slotName: string, placeholder: string): ShownResolution {
|
|
1162
1204
|
// Under a named skin: that skin's record, else the default skin's, else nothing (`./skins.ts`).
|
|
1163
1205
|
if (doc.skin !== CORE_ALL_SKINS) {
|
|
1164
1206
|
for (const skin of lookupSkins(doc)) {
|
|
1165
|
-
const record = skin.attachments[
|
|
1207
|
+
const record = skin.attachments[slotName]?.[placeholder];
|
|
1166
1208
|
if (record !== undefined) return { skin: skin.name, placeholder, record };
|
|
1167
1209
|
}
|
|
1168
1210
|
return null;
|
|
1169
1211
|
}
|
|
1170
1212
|
const filling: CoreShown[] = [];
|
|
1171
1213
|
for (const skin of doc.skins) {
|
|
1172
|
-
const record = skin.attachments[
|
|
1214
|
+
const record = skin.attachments[slotName]?.[placeholder];
|
|
1173
1215
|
if (record !== undefined) filling.push({ skin: skin.name, placeholder, record });
|
|
1174
1216
|
}
|
|
1175
1217
|
if (filling.length === 0) return null;
|
package/src/core/uvs.ts
CHANGED
|
@@ -369,7 +369,8 @@ export function shownAtSetup(doc: CompiledDocument): { shown: ShownGeometry[]; w
|
|
|
369
369
|
*/
|
|
370
370
|
export function shownAtSample(doc: CompiledDocument, anim: CoreAnimation, t: number): { shown: ShownGeometry[]; why: string[] } {
|
|
371
371
|
const sliders: SliderApplication[] = [];
|
|
372
|
-
|
|
372
|
+
// The bones are posed for what the sliders apply, and only a slider constraint applies one (`applySlider`): with none, the list stays empty and the pose would be read for nothing (issue #1134).
|
|
373
|
+
if (doc.constraints.some((c) => c.kind === 'slider')) posedBoneWorld(doc, anim.timelines, t, {}, anim.constraints, sliders);
|
|
373
374
|
const placeholders = new Map<string, string | null>();
|
|
374
375
|
posedSlots(doc, anim.timelines, t, {}, sliders, placeholders);
|
|
375
376
|
return attachmentStates(doc, shownAttachment, placeholders, { timelines: anim.timelines, t }, sliders);
|