@rig-c/core 2.77.0 → 2.78.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/docs/AUTHORING.md +10 -10
- package/package.json +1 -1
- package/src/runtime/curves.ts +16 -7
- package/src/runtime/index.ts +5 -4
- package/src/runtime/model.ts +44 -14
- package/src/runtime/pose.ts +60 -14
package/docs/AUTHORING.md
CHANGED
|
@@ -12647,7 +12647,7 @@ verdict on those figures.
|
|
|
12647
12647
|
| <a id="CE_COMPILE_ALPHA_CLIP_CONFLICT"></a>`CE_COMPILE_ALPHA_CLIP_CONFLICT` | Names authored fields or convex/inverse flags conflicting with generated inside-clipping geometry. Use one authored/generated form.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦: alphaClip conflicts with [⟧<br>⟦vertexCount⟧<br>⟦vertices⟧<br>⟦weights⟧<br>⟦boneIndexing⟧<br>⟦, ⟧<br>⟦], convex=⟧<br>⟦, inverse=⟧<br>⟦; required ordinary inside clipping with generated geometry only. Remove authored geometry/flags.⟧ |
|
|
12648
12648
|
| <a id="CE_COMPILE_ALPHA_CLIP_DEFORM"></a>`CE_COMPILE_ALPHA_CLIP_DEFORM` | Names animation and source/clip key/frame counts that cannot share the existing projection contract. Give matching explicit supported transform keys in compatible actual frames, with constant world shares where needed, or author the corresponding clip geometry and motion; weighted binding does not copy source deform.<br>Exact fixed diagnostic chunks (named values between chunks vary): ⟦ alphaClip from "⟧<br>⟦"⟧<br>⟦: animation "⟧<br>⟦" has ⟧<br>⟦ source and ⟧<br>⟦ clip keys; source frame=⟧<br>⟦no vertex geometry⟧<br>⟦clip frame=⟧<br>⟦, constant world influence shares=⟧<br>⟦Required matching explicit affine/depth-yaw/pitch transforms in compatible DeformGeometry frames, ⟧<br>⟦with constant shares for world-space transfer. Fix the named frame/keys or author corresponding clip motion.⟧ |
|
|
12649
12649
|
|
|
12650
|
-
## 15. Posing in place for a player — the runtime poser (stage
|
|
12650
|
+
## 15. Posing in place for a player — the runtime poser (stage 5)
|
|
12651
12651
|
|
|
12652
12652
|
rigc poses a model document twice over, on purpose (issue #1591). The **oracle**
|
|
12653
12653
|
(`src/core/`) is the reference: each pose is a pure function of the document,
|
|
@@ -12673,8 +12673,8 @@ pose.setAnimation('walk'); // a refused animat
|
|
|
12673
12673
|
pose.step(1 / 60); // or pose.apply(t) at an animation time
|
|
12674
12674
|
pose.computeVertices();
|
|
12675
12675
|
// pose.world: 6 numbers per bone (a, b, c, d, worldX, worldY — W_A … W_Y), y up
|
|
12676
|
-
// pose.light: 4 per slot; pose.shown: per slot an index into model.attachments, or −1
|
|
12677
|
-
// pose.vertices: model.attachments[i].count numbers at model.attachments[i].offset
|
|
12676
|
+
// pose.light: 4 per slot; pose.dark: 3 per slot; pose.shown: per slot an index into model.attachments, or −1
|
|
12677
|
+
// pose.drawOrder: slot indices in the live frame's draw order; pose.vertices: model.attachments[i].count numbers at model.attachments[i].offset
|
|
12678
12678
|
```
|
|
12679
12679
|
|
|
12680
12680
|
Each `model.attachments[i]` also carries readonly `skin` and `placeholder`: the
|
|
@@ -12682,11 +12682,11 @@ exact resolved table identity `(skin, model.slots[attachment.slot].name, placeho
|
|
|
12682
12682
|
The display `name` may be shared by different placeholders; it is never a table
|
|
12683
12683
|
key to guess the source from. These load-time strings do not change the pose path.
|
|
12684
12684
|
|
|
12685
|
-
| | what stage
|
|
12685
|
+
| | what stage 5 poses |
|
|
12686
12686
|
| --- | --- |
|
|
12687
12687
|
| bones | local and world transforms, all five inherit modes, skin-required bones under the view's skin (an inactive bone is zeros, as the oracle poses it) |
|
|
12688
|
-
| timelines | `rotate`, `translate(x/y)`, `scale(x/y)`, `shear(x/y)`; a slot's `attachment`, `rgba`, `rgb` and `
|
|
12689
|
-
| constraints | physics with its exact clock, reset and parameter timelines, and optional constraint-scoped external translate/rotate; value-mapped sliders with bone/slot/deform/physics composition; path constraints across all position, spacing and rotate modes on open or closed, constant-speed or arc-length, weighted or unweighted, deformed curves; one- and two-bone ik; world-space, absolute transform constraints; the document's order, each followed by the bones below what it moved posed again |
|
|
12688
|
+
| timelines | `rotate`, `translate(x/y)`, `scale(x/y)`, `shear(x/y)`; a slot's `attachment`, light colour (`rgba`, `rgb`, `alpha`) and dark colour (`rgba2`, `rgb2`); `drawOrder`; ik, transform and path timelines; linear, stepped and Bézier keys, each read from a per-pose key cursor |
|
|
12689
|
+
| constraints | physics with its exact clock, reset and parameter timelines, and optional constraint-scoped external translate/rotate; value-mapped sliders with bone/slot/draw-order/deform/physics composition; path constraints across all position, spacing and rotate modes on open or closed, constant-speed or arc-length, weighted or unweighted, deformed curves; one- and two-bone ik; world-space, absolute transform constraints; the document's order, each followed by the bones below what it moved posed again |
|
|
12690
12690
|
| vertices | regions, meshes, weighted meshes and linked meshes, written into the pose's buffer |
|
|
12691
12691
|
| deform | weighted and unweighted mesh and path timelines, linear/stepped/Bézier, per-pose key cursors and in-place buffers; linked meshes inherit their source's timelines only with `timelines: true` |
|
|
12692
12692
|
| skins and events | named/no-skin views with their skin-required bones and constraints; event delivery in key order over `(last, time]`, tail then head across a wrap |
|
|
@@ -12761,15 +12761,15 @@ values and blends from the current pose. Ordinary tracks still read only the
|
|
|
12761
12761
|
last target for each bone. An earlier unsupported inherit target is refused
|
|
12762
12762
|
when a slider applies it, even if the track's last target would hide it.
|
|
12763
12763
|
Absolute/additive bone and
|
|
12764
|
-
deform blends and slot switches follow the oracle, including direct scale and
|
|
12765
|
-
colour targets at mix one. Slider physics writes last one pass and affect only
|
|
12764
|
+
deform blends, draw-order keys and slot switches follow the oracle, including direct scale and
|
|
12765
|
+
light/dark colour targets at mix one. Slider physics writes last one pass and affect only
|
|
12766
12766
|
constraints later in update order. An unsupported timeline in a slider's
|
|
12767
12767
|
animation refuses the whole model under that timeline's existing feature name,
|
|
12768
12768
|
so an unselected track cannot hide a construct the slider always applies.
|
|
12769
12769
|
|
|
12770
12770
|
**Everything else is refused at load, by name.** `RuntimeRefusal.missing` lists
|
|
12771
12771
|
every construct met, each `{ feature, detail, animation }`, and the message
|
|
12772
|
-
reads `not posed by the runtime poser (stage
|
|
12772
|
+
reads `not posed by the runtime poser (stage 5), N construct(s) missing:
|
|
12773
12773
|
<feature>: <detail>; …`. A model-level feature refuses the whole model; an
|
|
12774
12774
|
animation-level one refuses that animation alone (`model.refused`;
|
|
12775
12775
|
`model.animation(name)` and `setAnimation(name)` throw its refusal) and every
|
|
@@ -12784,7 +12784,7 @@ refused: nothing is posed approximately.
|
|
|
12784
12784
|
| `clipping`, `null-atlas-region` | the model when shown at setup, else the animation that keys it | a clipping attachment; a region whose atlas rectangle the model states as `null` |
|
|
12785
12785
|
| `skin-conflict` | as above | a placeholder the skins fill differently |
|
|
12786
12786
|
| `oracle-absent` | the model | a pose the oracle itself leaves out (`constraintsAbsentWhy`, `pathAnimationsWhy`, `steppedPreviousPassWhy`, `sliderDeformsWalkedPathWhy`) |
|
|
12787
|
-
| `inherit-timeline`, `sequence-timeline
|
|
12787
|
+
| `inherit-timeline`, `sequence-timeline` | the animation | a timeline of that kind |
|
|
12788
12788
|
|
|
12789
12789
|
A call it cannot answer throws `RuntimeInputError`, naming the call:
|
|
12790
12790
|
`apply: no animation is set (setAnimation first)`, `animation "x" is not one of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.78.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/runtime/curves.ts
CHANGED
|
@@ -46,8 +46,13 @@ export interface RuntimeCurve {
|
|
|
46
46
|
readonly polylines: Float64Array;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** A timeline's keys compiled; `channels` is how many values each key carries (0 for an attachment timeline). */
|
|
50
|
-
export function compileCurve(keys: readonly
|
|
49
|
+
/** A timeline's keys compiled; `channels` is how many values each key carries (0 for an attachment or draw-order timeline). */
|
|
50
|
+
export function compileCurve(keys: readonly { readonly time: number }[], channels: 0): RuntimeCurve;
|
|
51
|
+
export function compileCurve(keys: readonly CoreKey[], channels: number): RuntimeCurve;
|
|
52
|
+
export function compileCurve(
|
|
53
|
+
keys: readonly { readonly time: number; readonly values?: readonly number[]; readonly stated?: CoreKey['stated']; readonly curve?: CoreKey['curve'] }[],
|
|
54
|
+
channels: number,
|
|
55
|
+
): RuntimeCurve {
|
|
51
56
|
const n = keys.length;
|
|
52
57
|
const times = new Float64Array(n);
|
|
53
58
|
const values = new Float64Array(n * channels);
|
|
@@ -56,20 +61,24 @@ export function compileCurve(keys: readonly CoreKey[], channels: number): Runtim
|
|
|
56
61
|
for (let i = 0; i < n; i++) {
|
|
57
62
|
const a = keys[i];
|
|
58
63
|
times[i] = a.time;
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const av = a.values;
|
|
65
|
+
if (av !== undefined) for (let c = 0; c < channels; c++) values[i * channels + c] = av[c];
|
|
66
|
+
const curve = a.curve ?? 'stepped';
|
|
61
67
|
kinds[i] = curve === 'linear' ? CURVE_LINEAR : curve === 'stepped' ? CURVE_STEPPED : CURVE_BEZIER;
|
|
62
68
|
if (typeof curve === 'string' || i + 1 >= n) continue;
|
|
63
69
|
const b = keys[i + 1];
|
|
70
|
+
const aStated = a.stated!;
|
|
71
|
+
const bStated = b.stated!;
|
|
72
|
+
const bValues = b.values!;
|
|
64
73
|
for (let c = 0; c < channels; c++) {
|
|
65
74
|
// The oracle's polyline (`channelAt`): the segment's float32 ends around the recurrence over the numbers as the document states them.
|
|
66
|
-
const inner = bezierPolyline(
|
|
75
|
+
const inner = bezierPolyline(aStated.time, aStated.values[c], curve[c * 4], curve[c * 4 + 1], curve[c * 4 + 2], curve[c * 4 + 3], bStated.time, bStated.values[c]);
|
|
67
76
|
const o = (i * channels + c) * POLYLINE;
|
|
68
77
|
polylines[o] = a.time;
|
|
69
|
-
polylines[o + 1] =
|
|
78
|
+
polylines[o + 1] = av![c];
|
|
70
79
|
for (let q = 0; q < inner.length; q++) polylines[o + 2 + q] = inner[q];
|
|
71
80
|
polylines[o + POLYLINE - 2] = b.time;
|
|
72
|
-
polylines[o + POLYLINE - 1] =
|
|
81
|
+
polylines[o + POLYLINE - 1] = bValues[c];
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
return { keys: n, channels, times, values, kinds, polylines };
|
package/src/runtime/index.ts
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
* corpus through both at the oracle's samples and compares full doubles at
|
|
22
22
|
* tolerance 0 (*The runtime poser* there). The oracle is not changed for it.
|
|
23
23
|
*
|
|
24
|
-
* ## Stage
|
|
24
|
+
* ## Stage 5: what it poses, and what it refuses
|
|
25
25
|
*
|
|
26
26
|
* Bones and their five inherit modes; the rotate, translate, scale and shear
|
|
27
|
-
* timelines; a slot's attachment
|
|
28
|
-
*
|
|
27
|
+
* timelines; a slot's attachment, light colour (`rgba`, `rgb`, `alpha`) and
|
|
28
|
+
* dark colour (`rgba2`, `rgb2`) timelines; draw-order timelines (including
|
|
29
|
+
* slider draw-order application); weighted/unweighted deform timelines; region, mesh, weighted mesh and linked mesh vertices; one- and
|
|
29
30
|
* two-bone ik, world-space absolute transforms, path constraints, physics (with optional movement scope) and value-mapped sliders with their
|
|
30
31
|
* timelines; skin-required bones and constraints under the view's skin. One
|
|
31
32
|
* animation at full weight from the setup pose, at a time or stepped as a
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
* const pose = new RuntimePose(model);
|
|
44
45
|
* pose.setAnimation('walk');
|
|
45
46
|
* pose.step(1 / 60); // or pose.apply(t)
|
|
46
|
-
* pose.computeVertices(); // pose.world, pose.light, pose.shown, pose.vertices
|
|
47
|
+
* pose.computeVertices(); // pose.world, pose.light, pose.dark, pose.shown, pose.drawOrder, pose.vertices
|
|
47
48
|
* ```
|
|
48
49
|
*
|
|
49
50
|
* This module is the `rig-c/runtime` entry (`exports` in package.json), which
|
package/src/runtime/model.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The runtime poser's load-time half (issue #1591, stage
|
|
2
|
+
* The runtime poser's load-time half (issue #1591, stage 5): a skin view of
|
|
3
3
|
* the compiled model (`readModel`, then `underSkin` / `underNoSkin` — the
|
|
4
4
|
* oracle's reader and its views, read and never written) compiled once into
|
|
5
5
|
* index-resolved bones and slots, a constraint plan, compiled timelines and
|
|
@@ -21,12 +21,11 @@
|
|
|
21
21
|
* differently, or a block the oracle itself leaves out;
|
|
22
22
|
* - **refuses an animation** (`RuntimeModel.refused`; `animation(name)`
|
|
23
23
|
* throws the same `RuntimeRefusal`) when the construct is the animation's
|
|
24
|
-
* — an `inherit` timeline, a sequence timeline,
|
|
25
|
-
* timeline, a dark-colour (`rgba2`, `rgb2`) timeline, or an
|
|
24
|
+
* — an `inherit` timeline, a sequence timeline, or an
|
|
26
25
|
* attachment key showing a clipping attachment or a region with no atlas
|
|
27
26
|
* rectangle. Every other animation of the model is posed.
|
|
28
27
|
*
|
|
29
|
-
* `RUNTIME_FEATURES` is the closed list of what can be named. Stage
|
|
28
|
+
* `RUNTIME_FEATURES` is the closed list of what can be named. Stage 5 adds draw-order timelines (including slider draw-order application) and dark-colour (`rgba2`, `rgb2`) timelines on top of Stage 4's path constraints and scoped physics movement. Mesh and path deforms, named/no-skin views
|
|
30
29
|
* and immutable event deliveries remain covered. `ignore: ['events']` remains an explicit diagnostic opt-out; it is
|
|
31
30
|
* never the default. A merged view with conflicting attachments still refuses
|
|
32
31
|
* by name, as the oracle itself cannot resolve that view without file order.
|
|
@@ -55,7 +54,7 @@ import type { CoreSliderRecord } from '../core/constraints_slider.ts';
|
|
|
55
54
|
import { compileCurve, compileDeformCurve, type RuntimeCurve } from './curves.ts';
|
|
56
55
|
|
|
57
56
|
/** The stage of issue #1591 this poser implements. */
|
|
58
|
-
export const RUNTIME_STAGE =
|
|
57
|
+
export const RUNTIME_STAGE = 5;
|
|
59
58
|
|
|
60
59
|
/** Every construct the runtime poser can refuse, by the name its refusals carry. */
|
|
61
60
|
export const RUNTIME_FEATURES = [
|
|
@@ -69,8 +68,6 @@ export const RUNTIME_FEATURES = [
|
|
|
69
68
|
'oracle-absent',
|
|
70
69
|
'inherit-timeline',
|
|
71
70
|
'sequence-timeline',
|
|
72
|
-
'draw-order-timeline',
|
|
73
|
-
'dark-colour-timeline',
|
|
74
71
|
] as const;
|
|
75
72
|
export type RuntimeFeature = (typeof RUNTIME_FEATURES)[number];
|
|
76
73
|
|
|
@@ -131,6 +128,8 @@ export interface RuntimePlant {
|
|
|
131
128
|
readonly deformAfterWeights?: boolean;
|
|
132
129
|
readonly omitPath?: boolean;
|
|
133
130
|
readonly ignorePhysicsScope?: boolean;
|
|
131
|
+
readonly omitDrawOrder?: boolean;
|
|
132
|
+
readonly omitDarkColour?: boolean;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
// A bone's local values, seven per bone.
|
|
@@ -354,7 +353,7 @@ export interface RuntimeBoneTimeline {
|
|
|
354
353
|
readonly multiply: Uint8Array;
|
|
355
354
|
}
|
|
356
355
|
|
|
357
|
-
/** A slot timeline: an attachment timeline (each key's attachment index, −1 nothing) or a colour timeline (each channel's index into the light colour). */
|
|
356
|
+
/** A slot timeline: an attachment timeline (each key's attachment index, −1 nothing) or a colour timeline (each channel's index into the light or dark colour). */
|
|
358
357
|
export interface RuntimeSlotTimeline {
|
|
359
358
|
readonly slot: number;
|
|
360
359
|
readonly curve: RuntimeCurve;
|
|
@@ -391,6 +390,13 @@ export interface RuntimeDeformTimeline {
|
|
|
391
390
|
readonly count: number;
|
|
392
391
|
}
|
|
393
392
|
|
|
393
|
+
/** An animation's draw-order timeline: each key's slot-index permutation of `slots.length` (`orderOf` in `src/core/draw_order.ts`), expanded once at load. */
|
|
394
|
+
export interface RuntimeDrawOrderTimeline {
|
|
395
|
+
readonly curve: RuntimeCurve;
|
|
396
|
+
readonly cursor: number;
|
|
397
|
+
readonly orders: Int32Array;
|
|
398
|
+
}
|
|
399
|
+
|
|
394
400
|
/** One covered animation, compiled. */
|
|
395
401
|
export interface RuntimeAnimation {
|
|
396
402
|
readonly name: string;
|
|
@@ -403,6 +409,7 @@ export interface RuntimeAnimation {
|
|
|
403
409
|
readonly sliders: ReadonlyArray<RuntimeSliderTimeline | null>;
|
|
404
410
|
readonly paths: ReadonlyArray<RuntimePathTimeline | null>;
|
|
405
411
|
readonly slots: readonly RuntimeSlotTimeline[];
|
|
412
|
+
readonly drawOrder: RuntimeDrawOrderTimeline | null;
|
|
406
413
|
/** Per constraint of the plan, its timeline in this animation or `null`. */
|
|
407
414
|
readonly constraints: ReadonlyArray<RuntimeConstraintTimeline | null>;
|
|
408
415
|
/** How many key cursors a pose keeps for it. */
|
|
@@ -421,6 +428,8 @@ export interface RuntimeModel {
|
|
|
421
428
|
readonly sliderAsTrack: boolean;
|
|
422
429
|
readonly omitPath: boolean;
|
|
423
430
|
readonly ignorePhysicsScope: boolean;
|
|
431
|
+
readonly omitDrawOrder: boolean;
|
|
432
|
+
readonly omitDarkColour: boolean;
|
|
424
433
|
readonly hasPath: boolean;
|
|
425
434
|
readonly maxPathSpaces: number;
|
|
426
435
|
readonly maxPathVertices: number;
|
|
@@ -472,8 +481,8 @@ const BONE_KINDS: Record<string, ReadonlyArray<readonly [number, 0 | 1]>> = {
|
|
|
472
481
|
sheary: [[L_SHEAR_Y, 0]],
|
|
473
482
|
};
|
|
474
483
|
|
|
475
|
-
/** The light colour
|
|
476
|
-
const COLOUR_CHANNELS: Record<string, readonly number[]> = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3] };
|
|
484
|
+
/** The light (`0 .. 3`) and dark (`4 .. 6`) colour channels each colour timeline sets, in its curve's channel order (`COLOUR_CHANNELS` in `src/core/animation.ts` and `src/core/constraints_slider.ts`). */
|
|
485
|
+
const COLOUR_CHANNELS: Record<string, readonly number[]> = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3], rgba2: [0, 1, 2, 3, 4, 5, 6], rgb2: [0, 1, 2, 4, 5, 6] };
|
|
477
486
|
|
|
478
487
|
/** Where refusals are gathered while a model compiles. */
|
|
479
488
|
class Refusals {
|
|
@@ -721,6 +730,8 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
721
730
|
deformAfterWeights: plant.deformAfterWeights === true,
|
|
722
731
|
omitPath: plant.omitPath === true,
|
|
723
732
|
ignorePhysicsScope: plant.ignorePhysicsScope === true,
|
|
733
|
+
omitDrawOrder: plant.omitDrawOrder === true,
|
|
734
|
+
omitDarkColour: plant.omitDarkColour === true,
|
|
724
735
|
hasPath,
|
|
725
736
|
maxPathSpaces,
|
|
726
737
|
maxPathVertices,
|
|
@@ -899,7 +910,28 @@ function compileAnimation(
|
|
|
899
910
|
const sequence = tl.attachments.filter((a) => a.sequence !== null);
|
|
900
911
|
|
|
901
912
|
if (sequence.length > 0) refusals.note('sequence-timeline', `${sequence.length} sequence timeline(s): ${sequence.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(', ')}${sequence.length > 3 ? ', …' : ''}`, name);
|
|
902
|
-
|
|
913
|
+
|
|
914
|
+
let drawOrder: RuntimeDrawOrderTimeline | null = null;
|
|
915
|
+
if (tl.drawOrder.length > 0) {
|
|
916
|
+
const ns = slots.length;
|
|
917
|
+
const orders = new Int32Array(tl.drawOrder.length * ns).fill(-1);
|
|
918
|
+
const moved = new Uint8Array(ns);
|
|
919
|
+
for (let i = 0; i < tl.drawOrder.length; i++) {
|
|
920
|
+
const base = i * ns;
|
|
921
|
+
moved.fill(0);
|
|
922
|
+
for (const m of tl.drawOrder[i].moves) {
|
|
923
|
+
orders[base + m.slot + m.offset] = m.slot;
|
|
924
|
+
moved[m.slot] = 1;
|
|
925
|
+
}
|
|
926
|
+
for (let s = 0, d = 0; s < ns; s++) {
|
|
927
|
+
if (moved[s] !== 0) continue;
|
|
928
|
+
while (orders[base + d] !== -1) d++;
|
|
929
|
+
orders[base + d] = s;
|
|
930
|
+
d++;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
drawOrder = { curve: compileCurve(tl.drawOrder, 0), cursor: cursors++, orders };
|
|
934
|
+
}
|
|
903
935
|
|
|
904
936
|
const lastBy = <T extends { name: string }>(targets: readonly T[]): Map<string, T> => new Map(targets.map((t) => [t.name, t]));
|
|
905
937
|
const bones: RuntimeBoneTimeline[] = [];
|
|
@@ -941,8 +973,6 @@ function compileAnimation(
|
|
|
941
973
|
if (t.kind === 'attachment') {
|
|
942
974
|
const ids = Int32Array.from(t.keys.map((k) => resolve(si, k.name ?? null)));
|
|
943
975
|
slotTimelines.push({ slot: si, curve: compileCurve(t.keys, 0), cursor: cursors++, attachments: ids, channels: null });
|
|
944
|
-
} else if (t.kind === 'rgba2' || t.kind === 'rgb2') {
|
|
945
|
-
refusals.note('dark-colour-timeline', `slot "${target.name}" keys an ${t.kind} timeline`, name);
|
|
946
976
|
} else {
|
|
947
977
|
const channels = COLOUR_CHANNELS[t.kind];
|
|
948
978
|
slotTimelines.push({ slot: si, curve: compileCurve(t.keys, channels.length), cursor: cursors++, attachments: null, channels: Int32Array.from(channels) });
|
|
@@ -991,5 +1021,5 @@ function compileAnimation(
|
|
|
991
1021
|
const arrays = keys.map(k => heldArray(g.vertices, k));
|
|
992
1022
|
return { identity: `${a.skin}/${a.slot}/${a.attachment}`, cursor: cursors++, curve: compileDeformCurve(keys), count: arrays[0].length, held: Float64Array.from(arrays.flat()) };
|
|
993
1023
|
});
|
|
994
|
-
return { name, duration: tl.duration, physics, sliders, paths, deforms, bones, sliderBones, slots: slotTimelines, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map(key => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
|
|
1024
|
+
return { name, duration: tl.duration, physics, sliders, paths, deforms, bones, sliderBones, slots: slotTimelines, drawOrder, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map(key => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
|
|
995
1025
|
}
|
package/src/runtime/pose.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The runtime poser's per-instance half (issue #1591, stage
|
|
2
|
+
* The runtime poser's per-instance half (issue #1591, stage 5): one
|
|
3
3
|
* `RuntimePose` per skeleton on screen, every buffer allocated when it is
|
|
4
4
|
* made and overwritten in place on every pose, so that posing a frame
|
|
5
5
|
* allocates nothing.
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
* The oracle's sample, as `src/core/animation.ts` poses it (`poseAnimations`):
|
|
10
10
|
* every pose is posed from the setup pose — the bones' setup local values,
|
|
11
11
|
* then the animation's bone timelines at `t` at full weight, then the world
|
|
12
|
-
* transforms parents first, then each slot's
|
|
13
|
-
* timelines, then the ik, transform, path,
|
|
14
|
-
* document's order with their timelines'
|
|
15
|
-
* bones below what it moved posed again,
|
|
16
|
-
* timelines; then, when asked,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* transforms parents first, then the draw order and each slot's light and dark
|
|
13
|
+
* colour, attachment and own deform timelines, then the ik, transform, path,
|
|
14
|
+
* physics and slider constraints in the document's order with their timelines'
|
|
15
|
+
* values at `t`, each followed by the bones below what it moved posed again,
|
|
16
|
+
* then the sliders' draw-order, slot and deform timelines; then, when asked,
|
|
17
|
+
* every shown attachment's world vertices. Nothing of one unstepped pose
|
|
18
|
+
* survives but key cursors. A stepped pose also keeps its physics clock,
|
|
19
|
+
* offsets, velocities and lags; sliders select values within this pose rather
|
|
20
|
+
* than advancing an independent track.
|
|
20
21
|
*
|
|
21
22
|
* ## The arithmetic is the oracle's
|
|
22
23
|
*
|
|
@@ -118,7 +119,7 @@ const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
|
118
119
|
export interface RuntimePoseOptions { readonly clock?: (dt: number) => number }
|
|
119
120
|
export type RuntimePhysicsPhase = 'none' | 'reset' | 'update';
|
|
120
121
|
|
|
121
|
-
/** One skeleton's pose, every value in place. Read `world`, `light`, `shown` and `vertices` after a pose. */
|
|
122
|
+
/** One skeleton's pose, every value in place. Read `world`, `light`, `dark`, `shown`, `drawOrder` and `vertices` after a pose. */
|
|
122
123
|
export class RuntimePose {
|
|
123
124
|
readonly model: RuntimeModel;
|
|
124
125
|
/** Every bone's local values, `LOCALS` per bone (`L_*`). */
|
|
@@ -127,8 +128,12 @@ export class RuntimePose {
|
|
|
127
128
|
readonly world: Float64Array;
|
|
128
129
|
/** Every slot's light colour, four per slot. */
|
|
129
130
|
readonly light: Float64Array;
|
|
131
|
+
/** Every slot's dark colour, three per slot (`r2`, `g2`, `b2`), or zeros when `model.slots[s].dark === null`. */
|
|
132
|
+
readonly dark: Float64Array;
|
|
130
133
|
/** What every slot shows: an index into `model.attachments`, or −1 for nothing. */
|
|
131
134
|
readonly shown: Int32Array;
|
|
135
|
+
/** The slot indices in the live frame's draw order (`0 .. model.slots.length - 1`), updated in place. */
|
|
136
|
+
readonly drawOrder: Int32Array;
|
|
132
137
|
/** Every attachment's world vertices at its `offset`, written by `computeVertices` for the attachments shown. */
|
|
133
138
|
readonly vertices: Float64Array;
|
|
134
139
|
/** Held/interpolated deform values, one pair per binding when weighted. */
|
|
@@ -199,7 +204,10 @@ export class RuntimePose {
|
|
|
199
204
|
this.locals = new Float64Array(nb * LOCALS);
|
|
200
205
|
this.world = new Float64Array(nb * WORLDS);
|
|
201
206
|
this.light = new Float64Array(ns * 4);
|
|
207
|
+
this.dark = new Float64Array(ns * 3);
|
|
202
208
|
this.shown = new Int32Array(ns);
|
|
209
|
+
this.drawOrder = new Int32Array(ns);
|
|
210
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = s;
|
|
203
211
|
this.vertices = new Float64Array(model.vertexCount);
|
|
204
212
|
this.deform = new Float64Array(model.deformCount);
|
|
205
213
|
this.deformed = new Uint8Array(model.attachments.length);
|
|
@@ -1179,6 +1187,13 @@ export class RuntimePose {
|
|
|
1179
1187
|
const k = index, c = cs[k];
|
|
1180
1188
|
if (c.kind !== 'slider' || !c.applied || this.sliderMix[k] === 0) return;
|
|
1181
1189
|
const a = c.animation, t = this.sliderAt[k], alpha = this.sliderMix[k];
|
|
1190
|
+
if (!this.model.omitDrawOrder && a.drawOrder !== null) {
|
|
1191
|
+
const di = keyAt(a.drawOrder.curve, this.cursors, a.drawOrder.cursor, t);
|
|
1192
|
+
if (di >= 0) {
|
|
1193
|
+
const ns = this.drawOrder.length, orders = a.drawOrder.orders, base = di * ns;
|
|
1194
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1182
1197
|
for (let q = 0; q < a.slots.length; q++) {
|
|
1183
1198
|
const tl = a.slots[q], i = keyAt(tl.curve, this.cursors, tl.cursor, t);
|
|
1184
1199
|
if (i < 0) continue;
|
|
@@ -1189,9 +1204,17 @@ export class RuntimePose {
|
|
|
1189
1204
|
}
|
|
1190
1205
|
else {
|
|
1191
1206
|
const channels = tl.channels!;
|
|
1207
|
+
const slot = this.model.slots[tl.slot];
|
|
1208
|
+
const o = tl.slot * 4, d = tl.slot * 3;
|
|
1192
1209
|
for (let f = 0; f < channels.length; f++) {
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1210
|
+
const at = channels[f], v = channelValue(tl.curve, i, f, t);
|
|
1211
|
+
if (at < 4) {
|
|
1212
|
+
const n = o + at;
|
|
1213
|
+
this.light[n] = clamp01(alpha === 1 ? v : this.light[n] + (v - this.light[n]) * alpha);
|
|
1214
|
+
} else if (slot.dark !== null && !this.model.omitDarkColour) {
|
|
1215
|
+
const n = d + (at - 4);
|
|
1216
|
+
this.dark[n] = clamp01(alpha === 1 ? v : this.dark[n] + (v - this.dark[n]) * alpha);
|
|
1217
|
+
}
|
|
1195
1218
|
}
|
|
1196
1219
|
}
|
|
1197
1220
|
}
|
|
@@ -1715,16 +1738,32 @@ export class RuntimePose {
|
|
|
1715
1738
|
private slotValues(anim: RuntimeAnimation | null, t: number): void {
|
|
1716
1739
|
const slots = this.model.slots;
|
|
1717
1740
|
const light = this.light;
|
|
1741
|
+
const dark = this.dark;
|
|
1718
1742
|
for (let s = 0; s < slots.length; s++) {
|
|
1719
1743
|
const slot = slots[s];
|
|
1720
1744
|
this.shown[s] = slot.setupAttachment;
|
|
1745
|
+
this.drawOrder[s] = s;
|
|
1721
1746
|
const o = s * 4;
|
|
1722
1747
|
light[o] = slot.light[0];
|
|
1723
1748
|
light[o + 1] = slot.light[1];
|
|
1724
1749
|
light[o + 2] = slot.light[2];
|
|
1725
1750
|
light[o + 3] = slot.light[3];
|
|
1751
|
+
const setupDark = slot.dark;
|
|
1752
|
+
if (setupDark !== null) {
|
|
1753
|
+
const d = s * 3;
|
|
1754
|
+
dark[d] = setupDark[0];
|
|
1755
|
+
dark[d + 1] = setupDark[1];
|
|
1756
|
+
dark[d + 2] = setupDark[2];
|
|
1757
|
+
}
|
|
1726
1758
|
}
|
|
1727
1759
|
if (anim === null) return;
|
|
1760
|
+
if (!this.model.omitDrawOrder && anim.drawOrder !== null) {
|
|
1761
|
+
const di = keyAt(anim.drawOrder.curve, this.cursors, anim.drawOrder.cursor, t);
|
|
1762
|
+
if (di >= 0) {
|
|
1763
|
+
const ns = slots.length, orders = anim.drawOrder.orders, base = di * ns;
|
|
1764
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1728
1767
|
const timelines = anim.slots;
|
|
1729
1768
|
for (let k = 0; k < timelines.length; k++) {
|
|
1730
1769
|
const tl = timelines[k];
|
|
@@ -1735,11 +1774,18 @@ export class RuntimePose {
|
|
|
1735
1774
|
continue;
|
|
1736
1775
|
}
|
|
1737
1776
|
const channels = tl.channels as Int32Array;
|
|
1738
|
-
const
|
|
1777
|
+
const slot = slots[tl.slot];
|
|
1778
|
+
const setup = slot.light;
|
|
1779
|
+
const setupDark = slot.dark;
|
|
1739
1780
|
const o = tl.slot * 4;
|
|
1781
|
+
const d = tl.slot * 3;
|
|
1740
1782
|
for (let c = 0; c < channels.length; c++) {
|
|
1741
1783
|
const at = channels[c];
|
|
1742
|
-
|
|
1784
|
+
if (at < 4) {
|
|
1785
|
+
light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
|
|
1786
|
+
} else if (setupDark !== null && !this.model.omitDarkColour) {
|
|
1787
|
+
dark[d + (at - 4)] = i < 0 ? setupDark[at - 4] : clamp01(channelValue(tl.curve, i, c, t));
|
|
1788
|
+
}
|
|
1743
1789
|
}
|
|
1744
1790
|
}
|
|
1745
1791
|
}
|