@rig-c/core 2.75.0 → 2.77.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 +47 -10
- package/package.json +1 -1
- package/src/runtime/curves.ts +3 -1
- package/src/runtime/index.ts +3 -2
- package/src/runtime/model.ts +255 -29
- package/src/runtime/physics.ts +235 -0
- package/src/runtime/pose.ts +861 -24
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 4)
|
|
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,
|
|
@@ -12682,13 +12682,13 @@ 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 4 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 `alpha`; ik and
|
|
12689
|
-
| constraints | 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`, `rgba`, `rgb` and `alpha`; 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/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
|
-
| deform | weighted and unweighted mesh timelines, linear/stepped/Bézier, per-pose key cursors and in-place buffers; linked meshes inherit their source's timelines only with `timelines: true` |
|
|
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 |
|
|
12693
12693
|
| time | one animation at full weight from the setup pose, at a time (`apply`) or stepped as a looping track (`step`, the track time wrapped by the duration as `openTrack` wraps it) |
|
|
12694
12694
|
|
|
@@ -12729,9 +12729,47 @@ back to the definition's **volume**, matching the measured runtime rather than
|
|
|
12729
12729
|
inventing a correction. An explicit `ignore: ['events']` diagnostic still mutes
|
|
12730
12730
|
the list; normal compilation never does.
|
|
12731
12731
|
|
|
12732
|
+
Physics is explicit on a direct pose: `apply(time, 'none')` leaves physics inert;
|
|
12733
|
+
`apply(time, 'reset', delta)` or `apply(time, 'update', delta)` first applies
|
|
12734
|
+
reset keys over `(last, time]` against the old clock, then adds `delta` to the
|
|
12735
|
+
physics clock and solves. A backwards animation time crosses the tail then head
|
|
12736
|
+
of the interval, as a looping track does. `step(dt)` updates physics and the
|
|
12737
|
+
looping track; it takes the initial reset at animation zero if needed.
|
|
12738
|
+
`resetPhysics('animation')` starts at that animation's pose zero;
|
|
12739
|
+
`resetPhysics('setup')` starts from setup before applying the animation.
|
|
12740
|
+
The physics clock is a running sum, never the animation time; constructor option
|
|
12741
|
+
`{ clock: physicsClockStep }` can select the render clock without changing track
|
|
12742
|
+
time. `physicsTime` exposes that sum. A non-finite or negative delta/clock result
|
|
12743
|
+
throws `RuntimeInputError` naming the call. `physicsTranslate(x, y, scope?)` and
|
|
12744
|
+
`physicsRotate(x, y, degrees, scope?)` shift the previous origins across every
|
|
12745
|
+
physics constraint when `scope` is omitted, or only across the named physics
|
|
12746
|
+
constraints (`scope: []` moves none; duplicate names apply once; an unknown or
|
|
12747
|
+
non-physics constraint name throws `RuntimeInputError` before mutating any
|
|
12748
|
+
state); `setEnvironment({
|
|
12749
|
+
wind: [x, y], gravity: [x, y] })` supplies the skeleton directions (positive y
|
|
12750
|
+
points down), all numbers finite. Offsets, velocities, lags and buffers belong
|
|
12751
|
+
to each pose; a zero physics mix does not consume its constraint's clock.
|
|
12752
|
+
|
|
12753
|
+
A slider reads its dial where earlier constraints left it: local or world
|
|
12754
|
+
property mapped as `to + (value - from) * scale`, clamped at zero or looped by
|
|
12755
|
+
its animation duration. A boneless slider uses its keyed `time`. The selected
|
|
12756
|
+
animation is applied at that value, never advanced as a track, and fires no
|
|
12757
|
+
events or physics reset keys. Mix zero does not re-pose bones; nonzero mix
|
|
12758
|
+
re-poses every keyed bone even before its first key. Its bone targets compose
|
|
12759
|
+
in authored order, including repeated names; each entry uses the same setup
|
|
12760
|
+
values and blends from the current pose. Ordinary tracks still read only the
|
|
12761
|
+
last target for each bone. An earlier unsupported inherit target is refused
|
|
12762
|
+
when a slider applies it, even if the track's last target would hide it.
|
|
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
|
|
12766
|
+
constraints later in update order. An unsupported timeline in a slider's
|
|
12767
|
+
animation refuses the whole model under that timeline's existing feature name,
|
|
12768
|
+
so an unselected track cannot hide a construct the slider always applies.
|
|
12769
|
+
|
|
12732
12770
|
**Everything else is refused at load, by name.** `RuntimeRefusal.missing` lists
|
|
12733
12771
|
every construct met, each `{ feature, detail, animation }`, and the message
|
|
12734
|
-
reads `not posed by the runtime poser (stage
|
|
12772
|
+
reads `not posed by the runtime poser (stage 4), N construct(s) missing:
|
|
12735
12773
|
<feature>: <detail>; …`. A model-level feature refuses the whole model; an
|
|
12736
12774
|
animation-level one refuses that animation alone (`model.refused`;
|
|
12737
12775
|
`model.animation(name)` and `setAnimation(name)` throw its refusal) and every
|
|
@@ -12740,13 +12778,12 @@ refused: nothing is posed approximately.
|
|
|
12740
12778
|
|
|
12741
12779
|
| feature | refuses | what it is |
|
|
12742
12780
|
| --- | --- | --- |
|
|
12743
|
-
| `path-constraint`, `physics-constraint`, `slider-constraint` | the model | a constraint of that kind |
|
|
12744
12781
|
| `local-transform`, `additive-transform` | the model | a transform constraint with `localSource` or `localTarget`, or `additive` |
|
|
12745
|
-
| `inactive-constrained-bone` | the model | an applied ik or
|
|
12782
|
+
| `inactive-constrained-bone` | the model | an applied ik, transform or path constraint naming a bone the view leaves inactive (what the oracle writes there depends on the pass before) |
|
|
12746
12783
|
| `noscale-world-target` | the model | a world-space transform onto a `noScale` or `noScaleOrReflection` bone, whose local values would be read back in that mode |
|
|
12747
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` |
|
|
12748
12785
|
| `skin-conflict` | as above | a placeholder the skins fill differently |
|
|
12749
|
-
| `oracle-absent` | the model | a pose the oracle itself leaves out (`constraintsAbsentWhy`) |
|
|
12786
|
+
| `oracle-absent` | the model | a pose the oracle itself leaves out (`constraintsAbsentWhy`, `pathAnimationsWhy`, `steppedPreviousPassWhy`, `sliderDeformsWalkedPathWhy`) |
|
|
12750
12787
|
| `inherit-timeline`, `sequence-timeline`, `draw-order-timeline`, `dark-colour-timeline` | the animation | a timeline of that kind (`dark-colour` is `rgba2` / `rgb2`) |
|
|
12751
12788
|
|
|
12752
12789
|
A call it cannot answer throws `RuntimeInputError`, naming the call:
|
|
@@ -12755,7 +12792,7 @@ this document's [...]`, `apply: the time NaN is not finite`.
|
|
|
12755
12792
|
|
|
12756
12793
|
**The gate.** `tools/core_gate.ts` runs a third dumper (`runtimeDump` in
|
|
12757
12794
|
`tools/pose_oracle.ts`) over every row it builds, at the oracle's samples, and
|
|
12758
|
-
compares
|
|
12795
|
+
compares both unstepped and stepped documents with the oracle's in full doubles at tolerance 0 twice:
|
|
12759
12796
|
through `compare` and cell by cell (which reads the bones `compare`'s
|
|
12760
12797
|
ill-conditioned rule leaves out). One `RUNTIME` line per row reads `IDENTICAL`
|
|
12761
12798
|
(covered whole and equal), `SKIP` (refused at load, or animations or events left
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.77.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
|
@@ -78,7 +78,9 @@ export function compileCurve(keys: readonly CoreKey[], channels: number): Runtim
|
|
|
78
78
|
/** The last key at or before `t` (−1 before the first), found from `cursors[slot]` and left there — the header's first read. */
|
|
79
79
|
export function keyAt(curve: RuntimeCurve, cursors: Int32Array, slot: number, t: number): number {
|
|
80
80
|
const times = curve.times;
|
|
81
|
-
|
|
81
|
+
// A slider can evaluate another animation between two reads of this one.
|
|
82
|
+
// Shared cursor slots are only search hints: keep the hint inside THIS curve.
|
|
83
|
+
let i = Math.min(cursors[slot], curve.keys - 1);
|
|
82
84
|
while (i >= 0 && times[i] > t) i--;
|
|
83
85
|
while (i + 1 < curve.keys && times[i + 1] <= t) i++;
|
|
84
86
|
cursors[slot] = i;
|
package/src/runtime/index.ts
CHANGED
|
@@ -21,12 +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 4: what it poses, and what it refuses
|
|
25
25
|
*
|
|
26
26
|
* Bones and their five inherit modes; the rotate, translate, scale and shear
|
|
27
27
|
* timelines; a slot's attachment and colour (`rgba`, `rgb`, `alpha`)
|
|
28
28
|
* timelines; weighted/unweighted deform timelines; region, mesh, weighted mesh and linked mesh vertices; one- and
|
|
29
|
-
* two-bone ik
|
|
29
|
+
* two-bone ik, world-space absolute transforms, path constraints, physics (with optional movement scope) and value-mapped sliders with their
|
|
30
30
|
* timelines; skin-required bones and constraints under the view's skin. One
|
|
31
31
|
* animation at full weight from the setup pose, at a time or stepped as a
|
|
32
32
|
* looping track. Everything else is refused at load by name with the list of
|
|
@@ -81,4 +81,5 @@ export {
|
|
|
81
81
|
L_SHEAR_Y,
|
|
82
82
|
} from './model.ts';
|
|
83
83
|
export type { RuntimeAnimation, RuntimeEvent, RuntimeAttachment, RuntimeFeature, RuntimeMissing, RuntimeModel, RuntimeOptions, RuntimePlant, RuntimeSlot } from './model.ts';
|
|
84
|
+
export type { RuntimePoseOptions, RuntimePhysicsPhase } from './pose.ts';
|
|
84
85
|
export { RuntimePose } from './pose.ts';
|
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 4): 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
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* (`RuntimeMissing`), and:
|
|
13
13
|
*
|
|
14
14
|
* - **refuses the model** (`RuntimeRefusal`, every missing feature listed)
|
|
15
|
-
* when one is a property of the whole model — a
|
|
15
|
+
* when one is a property of the whole model — a transform constraint reading or
|
|
16
16
|
* writing local values or adding (`localSource`, `localTarget`,
|
|
17
|
-
* `additive`), an ik or
|
|
17
|
+
* `additive`), an ik, transform or path constraint over a bone the view leaves
|
|
18
18
|
* inactive, a world-space transform onto a `noScale` or
|
|
19
19
|
* `noScaleOrReflection` bone, a clipping attachment or a region with no
|
|
20
20
|
* atlas rectangle shown at setup, a placeholder the skins fill
|
|
@@ -26,9 +26,8 @@
|
|
|
26
26
|
* attachment key showing a clipping attachment or a region with no atlas
|
|
27
27
|
* rectangle. Every other animation of the model is posed.
|
|
28
28
|
*
|
|
29
|
-
* `RUNTIME_FEATURES` is the closed list of what can be named. Stage
|
|
30
|
-
*
|
|
31
|
-
* at load. `ignore: ['events']` remains an explicit diagnostic opt-out; it is
|
|
29
|
+
* `RUNTIME_FEATURES` is the closed list of what can be named. Stage 4 adds path constraints and scoped physics movement (`physicsTranslate`/`physicsRotate` with an optional constraint scope) on top of Stage 3's indexed physics and value-mapped sliders. Mesh and path deforms, named/no-skin views
|
|
30
|
+
* and immutable event deliveries remain covered. `ignore: ['events']` remains an explicit diagnostic opt-out; it is
|
|
32
31
|
* never the default. A merged view with conflicting attachments still refuses
|
|
33
32
|
* by name, as the oracle itself cannot resolve that view without file order.
|
|
34
33
|
*
|
|
@@ -47,19 +46,19 @@
|
|
|
47
46
|
*/
|
|
48
47
|
import type { ModelVertices } from '../model.ts';
|
|
49
48
|
import { activeBones, constraintRecords, CORE_REGION_KINDS, foldInheritMode, readBlend, readColour, shownAttachment, shownRow, sourceOfDoc, underSkin, type CompiledDocument, type CoreAnimation, type CoreBlendMode } from '../core/index.ts';
|
|
50
|
-
import { constraintInactiveWhy, constraintsAbsentWhy, TRANSFORM_PROPERTIES, type CoreIkRecord, type CoreTransformRecord } from '../core/constraints.ts';
|
|
49
|
+
import { constraintInactiveWhy, constraintsAbsentWhy, pathAnimationsWhy, TRANSFORM_PROPERTIES, type CoreIkRecord, type CoreTransformRecord } from '../core/constraints.ts';
|
|
50
|
+
import { slotBonePlan, type CorePathRecord, type SlotBoneEvent } from '../core/constraints_path.ts';
|
|
51
51
|
import { RUNTIME_PI } from '../core/world.ts';
|
|
52
52
|
import { heldArray, timelineIdentity } from '../core/deform.ts';
|
|
53
|
+
import { steppedPreviousPassWhy, type CorePhysicsRecord, type PhysicsTimelineKind } from '../core/constraints_physics.ts';
|
|
54
|
+
import type { CoreSliderRecord } from '../core/constraints_slider.ts';
|
|
53
55
|
import { compileCurve, compileDeformCurve, type RuntimeCurve } from './curves.ts';
|
|
54
56
|
|
|
55
57
|
/** The stage of issue #1591 this poser implements. */
|
|
56
|
-
export const RUNTIME_STAGE =
|
|
58
|
+
export const RUNTIME_STAGE = 4;
|
|
57
59
|
|
|
58
60
|
/** Every construct the runtime poser can refuse, by the name its refusals carry. */
|
|
59
61
|
export const RUNTIME_FEATURES = [
|
|
60
|
-
'path-constraint',
|
|
61
|
-
'physics-constraint',
|
|
62
|
-
'slider-constraint',
|
|
63
62
|
'local-transform',
|
|
64
63
|
'additive-transform',
|
|
65
64
|
'inactive-constrained-bone',
|
|
@@ -125,9 +124,13 @@ export interface RuntimePlant {
|
|
|
125
124
|
readonly eventWrapOffByOne?: boolean;
|
|
126
125
|
/** The old restore behavior, which repeats keys up to the restored time. */
|
|
127
126
|
readonly restoreEventsFromStart?: boolean;
|
|
128
|
-
/** Omit deform / multiply offsets by weights too early: negative controls only. */
|
|
127
|
+
/** Omit deform / multiply offsets by weights too early / omit path / ignore physics scope: negative controls only. */
|
|
128
|
+
readonly omitPhysics?: boolean;
|
|
129
|
+
readonly sliderAsTrack?: boolean;
|
|
129
130
|
readonly omitDeform?: boolean;
|
|
130
131
|
readonly deformAfterWeights?: boolean;
|
|
132
|
+
readonly omitPath?: boolean;
|
|
133
|
+
readonly ignorePhysicsScope?: boolean;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
// A bone's local values, seven per bone.
|
|
@@ -175,6 +178,17 @@ export const IK_BEND_POSITIVE = 1;
|
|
|
175
178
|
export const IK_COMPRESS = 2;
|
|
176
179
|
export const IK_STRETCH = 4;
|
|
177
180
|
|
|
181
|
+
// A path constraint's position, spacing and rotate modes.
|
|
182
|
+
export const PATH_POSITION_FIXED = 0;
|
|
183
|
+
export const PATH_POSITION_PERCENT = 1;
|
|
184
|
+
export const PATH_SPACING_LENGTH = 0;
|
|
185
|
+
export const PATH_SPACING_FIXED = 1;
|
|
186
|
+
export const PATH_SPACING_PERCENT = 2;
|
|
187
|
+
export const PATH_SPACING_PROPORTIONAL = 3;
|
|
188
|
+
export const PATH_ROTATE_TANGENT = 0;
|
|
189
|
+
export const PATH_ROTATE_CHAIN = 1;
|
|
190
|
+
export const PATH_ROTATE_CHAIN_SCALE = 2;
|
|
191
|
+
|
|
178
192
|
/** Degrees to radians, with the runtime's pi (`src/core/world.ts`). */
|
|
179
193
|
export const RAD = RUNTIME_PI / 180;
|
|
180
194
|
|
|
@@ -257,7 +271,79 @@ export interface RuntimeTransform {
|
|
|
257
271
|
readonly clamp: boolean;
|
|
258
272
|
}
|
|
259
273
|
|
|
260
|
-
export
|
|
274
|
+
export interface RuntimePhysics {
|
|
275
|
+
readonly kind: 'physics';
|
|
276
|
+
readonly name: string;
|
|
277
|
+
readonly index: number;
|
|
278
|
+
readonly applied: boolean;
|
|
279
|
+
readonly bones: Int32Array;
|
|
280
|
+
readonly setup: CorePhysicsRecord;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface RuntimeSlider {
|
|
284
|
+
readonly kind: 'slider';
|
|
285
|
+
readonly name: string;
|
|
286
|
+
readonly index: number;
|
|
287
|
+
readonly applied: boolean;
|
|
288
|
+
readonly bones: Int32Array;
|
|
289
|
+
readonly bone: number;
|
|
290
|
+
readonly property: number;
|
|
291
|
+
readonly setup: CoreSliderRecord;
|
|
292
|
+
/** Resolved after every animation is compiled. Never a track. */
|
|
293
|
+
animation: RuntimeAnimation;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface RuntimePath {
|
|
297
|
+
readonly kind: 'path';
|
|
298
|
+
readonly name: string;
|
|
299
|
+
readonly index: number;
|
|
300
|
+
readonly applied: boolean;
|
|
301
|
+
readonly bones: Int32Array;
|
|
302
|
+
readonly slot: number;
|
|
303
|
+
readonly slotBone: number;
|
|
304
|
+
/** Setup path attachment index into `model.attachments`, or −1 when the slot shows no path. */
|
|
305
|
+
attachment: number;
|
|
306
|
+
/** Number of vertex coordinates (`2 * vertexCount`) in the setup path attachment. */
|
|
307
|
+
pathVertices: number;
|
|
308
|
+
readonly closed: boolean;
|
|
309
|
+
readonly constantSpeed: boolean;
|
|
310
|
+
readonly lengths: Float64Array;
|
|
311
|
+
readonly positionMode: number;
|
|
312
|
+
readonly spacingMode: number;
|
|
313
|
+
readonly rotateMode: number;
|
|
314
|
+
readonly offsetRotation: number;
|
|
315
|
+
readonly position: number;
|
|
316
|
+
readonly spacing: number;
|
|
317
|
+
readonly mixRotate: number;
|
|
318
|
+
readonly mixX: number;
|
|
319
|
+
readonly mixY: number;
|
|
320
|
+
/** Constraint index whose `before` or `after` state sets the slot bone's determinant sign (`slotBonePlan`), or −1. */
|
|
321
|
+
readonly slotEventAt: number;
|
|
322
|
+
readonly slotEventAfter: boolean;
|
|
323
|
+
readonly setup: CorePathRecord;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface RuntimePhysicsTimeline {
|
|
327
|
+
readonly kind: PhysicsTimelineKind;
|
|
328
|
+
readonly targets: Int32Array;
|
|
329
|
+
readonly curve: RuntimeCurve;
|
|
330
|
+
readonly cursor: number;
|
|
331
|
+
}
|
|
332
|
+
export interface RuntimeSliderTimeline {
|
|
333
|
+
readonly time: RuntimeCurve | null;
|
|
334
|
+
readonly mix: RuntimeCurve | null;
|
|
335
|
+
readonly timeCursor: number;
|
|
336
|
+
readonly mixCursor: number;
|
|
337
|
+
}
|
|
338
|
+
export interface RuntimePathTimeline {
|
|
339
|
+
readonly position: RuntimeCurve | null;
|
|
340
|
+
readonly spacing: RuntimeCurve | null;
|
|
341
|
+
readonly mix: RuntimeCurve | null;
|
|
342
|
+
readonly positionCursor: number;
|
|
343
|
+
readonly spacingCursor: number;
|
|
344
|
+
readonly mixCursor: number;
|
|
345
|
+
}
|
|
346
|
+
export type RuntimeConstraint = RuntimeIk | RuntimeTransform | RuntimePhysics | RuntimeSlider | RuntimePath;
|
|
261
347
|
|
|
262
348
|
/** A bone timeline: the bone, its curve, and per channel the local field it writes and whether it multiplies the setup value (scale) or adds to it. */
|
|
263
349
|
export interface RuntimeBoneTimeline {
|
|
@@ -310,7 +396,12 @@ export interface RuntimeAnimation {
|
|
|
310
396
|
readonly name: string;
|
|
311
397
|
readonly duration: number;
|
|
312
398
|
readonly bones: readonly RuntimeBoneTimeline[];
|
|
399
|
+
/** Every authored target in order; sliders compose duplicates that tracks discard. */
|
|
400
|
+
readonly sliderBones: readonly RuntimeBoneTimeline[];
|
|
313
401
|
readonly deforms: readonly RuntimeDeformTimeline[];
|
|
402
|
+
readonly physics: readonly RuntimePhysicsTimeline[];
|
|
403
|
+
readonly sliders: ReadonlyArray<RuntimeSliderTimeline | null>;
|
|
404
|
+
readonly paths: ReadonlyArray<RuntimePathTimeline | null>;
|
|
314
405
|
readonly slots: readonly RuntimeSlotTimeline[];
|
|
315
406
|
/** Per constraint of the plan, its timeline in this animation or `null`. */
|
|
316
407
|
readonly constraints: ReadonlyArray<RuntimeConstraintTimeline | null>;
|
|
@@ -326,6 +417,13 @@ export interface RuntimeModel {
|
|
|
326
417
|
/** Control-only boundary defect; fixed at load. */
|
|
327
418
|
readonly omitDeform: boolean;
|
|
328
419
|
readonly deformAfterWeights: boolean;
|
|
420
|
+
readonly omitPhysics: boolean;
|
|
421
|
+
readonly sliderAsTrack: boolean;
|
|
422
|
+
readonly omitPath: boolean;
|
|
423
|
+
readonly ignorePhysicsScope: boolean;
|
|
424
|
+
readonly hasPath: boolean;
|
|
425
|
+
readonly maxPathSpaces: number;
|
|
426
|
+
readonly maxPathVertices: number;
|
|
329
427
|
readonly deformCount: number;
|
|
330
428
|
readonly eventWrapOffByOne: boolean;
|
|
331
429
|
readonly restoreEventsFromStart: boolean;
|
|
@@ -401,7 +499,7 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
401
499
|
const refusals = new Refusals(new Set(plant.admit ?? []));
|
|
402
500
|
const ignoreEvents = (options.ignore ?? []).includes('events');
|
|
403
501
|
|
|
404
|
-
const absentWhy = constraintsAbsentWhy(doc);
|
|
502
|
+
const absentWhy = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
|
|
405
503
|
if (absentWhy !== null) refusals.note('oracle-absent', `the oracle leaves the bones out: ${absentWhy}`);
|
|
406
504
|
|
|
407
505
|
// --- bones ---
|
|
@@ -412,6 +510,12 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
412
510
|
if (i === undefined) throw new RuntimeInputError(`bone "${name}" is not a bone of this document (readModel refuses it first)`);
|
|
413
511
|
return i;
|
|
414
512
|
};
|
|
513
|
+
const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
|
|
514
|
+
const slotIndexOf = (name: string): number => {
|
|
515
|
+
const i = slotIndex.get(name);
|
|
516
|
+
if (i === undefined) throw new RuntimeInputError(`slot "${name}" is not a slot of this document (readModel refuses it first)`);
|
|
517
|
+
return i;
|
|
518
|
+
};
|
|
415
519
|
const active = activeBones(doc);
|
|
416
520
|
const parents = new Int32Array(nb);
|
|
417
521
|
const modes = new Uint8Array(nb);
|
|
@@ -449,15 +553,29 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
449
553
|
|
|
450
554
|
// --- constraints, in the document's order ---
|
|
451
555
|
const constraints: RuntimeConstraint[] = [];
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
556
|
+
const records = constraintRecords(doc);
|
|
557
|
+
const skipped = new Set<number>();
|
|
558
|
+
records.forEach((r, i) => {
|
|
559
|
+
if (constraintInactiveWhy(r, active) !== null) skipped.add(i);
|
|
560
|
+
});
|
|
561
|
+
const pathPlan = records.some((r) => r.kind === 'path') ? slotBonePlan(doc.bones, active, records, skipped) : new Map<number, SlotBoneEvent | null>();
|
|
562
|
+
for (const r of records) {
|
|
455
563
|
const applied = constraintInactiveWhy(r, active) === null;
|
|
456
|
-
const bones = Int32Array.from(r.bones.map(indexOf));
|
|
457
|
-
if (applied && r.bones.some((b) => !active.has(b))) refusals.note('inactive-constrained-bone', `${r.kind} constraint "${r.name}" names bone(s) ${r.bones.filter((b) => !active.has(b)).map((b) => `"${b}"`).join(', ')}, which the view leaves inactive — what the oracle writes there depends on the pass before`);
|
|
564
|
+
const bones = Int32Array.from((r.kind === 'physics' ? [r.bone] : r.kind === 'slider' ? r.timelines.bones.filter(t => t.timelines.length > 0).map(t => t.name) : r.bones).map(indexOf));
|
|
458
565
|
const index = constraints.length;
|
|
566
|
+
if (r.kind === 'physics') {
|
|
567
|
+
constraints.push({ kind: 'physics', name: r.name, index, applied, bones, setup: r });
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (r.kind === 'slider') {
|
|
571
|
+
constraints.push({ kind: 'slider', name: r.name, index, applied, bones, setup: r, bone: r.bone === null ? -1 : indexOf(r.bone), property: TRANSFORM_PROPERTIES.indexOf(r.property), animation: null as unknown as RuntimeAnimation });
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (applied && r.bones.some((b) => !active.has(b))) refusals.note('inactive-constrained-bone', `${r.kind} constraint "${r.name}" names bone(s) ${r.bones.filter((b) => !active.has(b)).map((b) => `"${b}"`).join(', ')}, which the view leaves inactive — what the oracle writes there depends on the pass before`);
|
|
459
575
|
if (r.kind === 'ik') constraints.push(compileIk(r, index, applied, bones, indexOf));
|
|
460
|
-
else {
|
|
576
|
+
else if (r.kind === 'path') {
|
|
577
|
+
constraints.push(compilePath(r, index, applied, bones, indexOf, slotIndexOf, pathPlan.get(index) ?? null));
|
|
578
|
+
} else {
|
|
461
579
|
if (r.localSource || r.localTarget) refusals.note('local-transform', `transform constraint "${r.name}" ${[r.localSource ? 'reads local values (localSource)' : '', r.localTarget ? 'writes local values (localTarget)' : ''].filter((x) => x !== '').join(' and ')}`);
|
|
462
580
|
if (r.additive) refusals.note('additive-transform', `transform constraint "${r.name}" is additive`);
|
|
463
581
|
const t = compileTransform(r, index, applied, bones, indexOf);
|
|
@@ -535,8 +653,10 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
535
653
|
const source = g.kind === 'mesh' ? g.vertices : sourceOf(g.skin, g.slot, g.source);
|
|
536
654
|
if (typeof source === 'string') throw new RuntimeInputError(`slot "${slot.name}": ${source} (readModel refuses it first)`);
|
|
537
655
|
made = meshAttachment(at, source, indexOf, plant.bindingIndex, nb);
|
|
656
|
+
} else if (g?.kind === 'path') {
|
|
657
|
+
made = pathAttachment(at, g.vertices, indexOf, plant.bindingIndex, nb);
|
|
538
658
|
}
|
|
539
|
-
// A bounding box, a
|
|
659
|
+
// A bounding box, a refused record admitted by a plant: the row's name and path, and nothing drawn.
|
|
540
660
|
if (made === null) made = vertexAttachment(at, DRAW_NONE, new Float64Array(0), 0);
|
|
541
661
|
attachments.push(made);
|
|
542
662
|
vertexCount += made.count;
|
|
@@ -557,7 +677,21 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
557
677
|
blend: readBlend(slot),
|
|
558
678
|
};
|
|
559
679
|
});
|
|
560
|
-
|
|
680
|
+
let hasPath = false;
|
|
681
|
+
let maxPathSpaces = 0;
|
|
682
|
+
let maxPathVertices = 0;
|
|
683
|
+
for (const c of constraints) {
|
|
684
|
+
if (c.kind !== 'path') continue;
|
|
685
|
+
hasPath = true;
|
|
686
|
+
c.attachment = c.setup.path === null ? -1 : slots[c.slot].setupAttachment;
|
|
687
|
+
const spaceCount = c.rotateMode === PATH_ROTATE_TANGENT ? c.bones.length : c.bones.length + 1;
|
|
688
|
+
if (spaceCount > maxPathSpaces) maxPathSpaces = spaceCount;
|
|
689
|
+
if (c.attachment >= 0) {
|
|
690
|
+
const at = attachments[c.attachment];
|
|
691
|
+
c.pathVertices = at.bindingCounts.length > 0 ? at.bindingCounts.length * 2 : at.local.length;
|
|
692
|
+
if (c.pathVertices > maxPathVertices) maxPathVertices = c.pathVertices;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
561
695
|
|
|
562
696
|
// --- animations ---
|
|
563
697
|
const animations = new Map<string, RuntimeAnimation>();
|
|
@@ -573,11 +707,23 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
573
707
|
}
|
|
574
708
|
}
|
|
575
709
|
|
|
710
|
+
for (const c of constraints) if (c.kind === 'slider') {
|
|
711
|
+
const a = animations.get(c.setup.animation);
|
|
712
|
+
if (a !== undefined) c.animation = a;
|
|
713
|
+
else for (const m of refusals.byAnimation.get(c.setup.animation) ?? []) refusals.note(m.feature, `slider "${c.name}" applies refused animation "${c.setup.animation}": ${m.detail}`);
|
|
714
|
+
}
|
|
576
715
|
if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
|
|
577
716
|
const names = doc.animations.map((a) => a.name);
|
|
578
717
|
return {
|
|
718
|
+
omitPhysics: plant.omitPhysics === true,
|
|
719
|
+
sliderAsTrack: plant.sliderAsTrack === true,
|
|
579
720
|
omitDeform: plant.omitDeform === true,
|
|
580
721
|
deformAfterWeights: plant.deformAfterWeights === true,
|
|
722
|
+
omitPath: plant.omitPath === true,
|
|
723
|
+
ignorePhysicsScope: plant.ignorePhysicsScope === true,
|
|
724
|
+
hasPath,
|
|
725
|
+
maxPathSpaces,
|
|
726
|
+
maxPathVertices,
|
|
581
727
|
deformCount,
|
|
582
728
|
eventWrapOffByOne: plant.eventWrapOffByOne === true,
|
|
583
729
|
restoreEventsFromStart: plant.restoreEventsFromStart === true,
|
|
@@ -643,6 +789,12 @@ function meshAttachment(at: AttachmentHead, vertices: ModelVertices, indexOf: (n
|
|
|
643
789
|
return { ...at, deformCount: ids.length * 2, draw: DRAW_WEIGHTED, local: NO_NUMBERS, count: counts.length * 2, bindingCounts: Int32Array.from(counts), bindingBones: Int32Array.from(ids), bindingX: Float64Array.from(xs), bindingY: Float64Array.from(ys), bindingWeights: Float64Array.from(ws) };
|
|
644
790
|
}
|
|
645
791
|
|
|
792
|
+
/** A path attachment's vertices and bindings: stored and deformed like a mesh, never drawn into `RuntimePose.vertices`. */
|
|
793
|
+
function pathAttachment(at: AttachmentHead, vertices: ModelVertices, indexOf: (name: string) => number, rewrite: RuntimePlant['bindingIndex'], bones: number): RuntimeAttachment {
|
|
794
|
+
const m = meshAttachment(at, vertices, indexOf, rewrite, bones);
|
|
795
|
+
return { ...m, draw: DRAW_NONE, count: 0 };
|
|
796
|
+
}
|
|
797
|
+
|
|
646
798
|
function compileIk(r: CoreIkRecord, index: number, applied: boolean, bones: Int32Array, indexOf: (name: string) => number): RuntimeIk {
|
|
647
799
|
return {
|
|
648
800
|
kind: 'ik', name: r.name, index, applied, bones, target: indexOf(r.target),
|
|
@@ -655,6 +807,43 @@ function ikFlags(bendPositive: boolean, compress: boolean, stretch: boolean): nu
|
|
|
655
807
|
return (bendPositive ? IK_BEND_POSITIVE : 0) | (compress ? IK_COMPRESS : 0) | (stretch ? IK_STRETCH : 0);
|
|
656
808
|
}
|
|
657
809
|
|
|
810
|
+
function compilePath(
|
|
811
|
+
r: CorePathRecord,
|
|
812
|
+
index: number,
|
|
813
|
+
applied: boolean,
|
|
814
|
+
bones: Int32Array,
|
|
815
|
+
indexOf: (name: string) => number,
|
|
816
|
+
slotIndexOf: (name: string) => number,
|
|
817
|
+
ev: SlotBoneEvent | null,
|
|
818
|
+
): RuntimePath {
|
|
819
|
+
return {
|
|
820
|
+
kind: 'path',
|
|
821
|
+
name: r.name,
|
|
822
|
+
index,
|
|
823
|
+
applied,
|
|
824
|
+
bones,
|
|
825
|
+
slot: slotIndexOf(r.slot),
|
|
826
|
+
slotBone: indexOf(r.slotBone),
|
|
827
|
+
attachment: -1,
|
|
828
|
+
pathVertices: 0,
|
|
829
|
+
closed: r.path?.closed ?? false,
|
|
830
|
+
constantSpeed: r.path?.constantSpeed ?? true,
|
|
831
|
+
lengths: Float64Array.from(r.path?.lengths ?? []),
|
|
832
|
+
positionMode: r.positionMode === 'fixed' ? PATH_POSITION_FIXED : PATH_POSITION_PERCENT,
|
|
833
|
+
spacingMode: r.spacingMode === 'fixed' ? PATH_SPACING_FIXED : r.spacingMode === 'percent' ? PATH_SPACING_PERCENT : r.spacingMode === 'proportional' ? PATH_SPACING_PROPORTIONAL : PATH_SPACING_LENGTH,
|
|
834
|
+
rotateMode: r.rotateMode === 'chain' ? PATH_ROTATE_CHAIN : r.rotateMode === 'chainScale' ? PATH_ROTATE_CHAIN_SCALE : PATH_ROTATE_TANGENT,
|
|
835
|
+
offsetRotation: r.offsetRotation,
|
|
836
|
+
position: r.position,
|
|
837
|
+
spacing: r.spacing,
|
|
838
|
+
mixRotate: r.mixRotate,
|
|
839
|
+
mixX: r.mixX,
|
|
840
|
+
mixY: r.mixY,
|
|
841
|
+
slotEventAt: ev === null ? -1 : ev.at,
|
|
842
|
+
slotEventAfter: ev?.when === 'after',
|
|
843
|
+
setup: r,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
658
847
|
function compileTransform(r: CoreTransformRecord, index: number, applied: boolean, bones: Int32Array, indexOf: (name: string) => number): RuntimeTransform {
|
|
659
848
|
const fromProperty: number[] = [];
|
|
660
849
|
const fromOffset: number[] = [];
|
|
@@ -714,18 +903,32 @@ function compileAnimation(
|
|
|
714
903
|
|
|
715
904
|
const lastBy = <T extends { name: string }>(targets: readonly T[]): Map<string, T> => new Map(targets.map((t) => [t.name, t]));
|
|
716
905
|
const bones: RuntimeBoneTimeline[] = [];
|
|
717
|
-
|
|
906
|
+
type BoneTarget = (typeof tl.bones)[number];
|
|
907
|
+
const byTarget = new Map<BoneTarget, RuntimeBoneTimeline[]>();
|
|
908
|
+
const compileBones = (target: BoneTarget, forSlider: boolean): RuntimeBoneTimeline[] => {
|
|
909
|
+
const cached = byTarget.get(target);
|
|
910
|
+
if (cached !== undefined) return cached;
|
|
911
|
+
const keyed: RuntimeBoneTimeline[] = [];
|
|
912
|
+
byTarget.set(target, keyed);
|
|
718
913
|
const bone = boneIndex.get(target.name);
|
|
719
|
-
if (bone === undefined)
|
|
914
|
+
if (bone === undefined) return keyed;
|
|
720
915
|
for (const t of target.timelines) {
|
|
721
916
|
if (t.kind === 'inherit') {
|
|
722
|
-
refusals.note('inherit-timeline', `bone "${target.name}" keys its inherit mode`, name);
|
|
917
|
+
if (!forSlider) refusals.note('inherit-timeline', `bone "${target.name}" keys its inherit mode`, name);
|
|
918
|
+
else for (const c of constraints) if (c.kind === 'slider' && c.setup.animation === name) {
|
|
919
|
+
refusals.note('inherit-timeline', `slider "${c.name}" applies animation "${name}" whose bone "${target.name}" keys its inherit mode`);
|
|
920
|
+
}
|
|
723
921
|
continue;
|
|
724
922
|
}
|
|
725
923
|
const spec = BONE_KINDS[t.kind];
|
|
726
|
-
|
|
924
|
+
keyed.push({ bone, curve: compileCurve(t.keys, spec.length), cursor: cursors++, fields: Int32Array.from(spec.map((s) => s[0])), multiply: Uint8Array.from(spec.map((s) => s[1])) });
|
|
727
925
|
}
|
|
728
|
-
|
|
926
|
+
return keyed;
|
|
927
|
+
};
|
|
928
|
+
// Keep the track's last-target Map order and reuse those exact curves/cursors.
|
|
929
|
+
for (const target of lastBy(tl.bones).values()) bones.push(...compileBones(target, false));
|
|
930
|
+
const sliderBones = constraints.some(c => c.kind === 'slider' && c.setup.animation === name)
|
|
931
|
+
? tl.bones.flatMap(target => compileBones(target, true)) : [];
|
|
729
932
|
const slotTimelines: RuntimeSlotTimeline[] = [];
|
|
730
933
|
const keyedSlots = [...lastBy(tl.slots).values()].flatMap((target) => {
|
|
731
934
|
const si = slotIndex.get(target.name);
|
|
@@ -752,12 +955,35 @@ function compileAnimation(
|
|
|
752
955
|
const keyed = anim.constraints.ik.find((x) => x.name === c.name);
|
|
753
956
|
if (keyed === undefined) continue;
|
|
754
957
|
constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, 2), cursor: cursors++, flags: Uint8Array.from(keyed.keys.map((k) => ikFlags(k.flags.bendPositive, k.flags.compress, k.flags.stretch))) };
|
|
755
|
-
} else {
|
|
958
|
+
} else if (c.kind === 'transform') {
|
|
756
959
|
const keyed = anim.constraints.transform.find((x) => x.name === c.name);
|
|
757
960
|
if (keyed === undefined) continue;
|
|
758
961
|
constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
|
|
759
962
|
}
|
|
760
963
|
}
|
|
964
|
+
const sliders = constraints.map((c): RuntimeSliderTimeline | null => {
|
|
965
|
+
if (c.kind !== 'slider') return null;
|
|
966
|
+
const keyed = anim.constraints.slider.find(t => t.name === c.name);
|
|
967
|
+
if (keyed === undefined) return null;
|
|
968
|
+
return { time: keyed.time === null ? null : compileCurve(keyed.time, 1), mix: keyed.mix === null ? null : compileCurve(keyed.mix, 1), timeCursor: cursors++, mixCursor: cursors++ };
|
|
969
|
+
});
|
|
970
|
+
const paths = constraints.map((c): RuntimePathTimeline | null => {
|
|
971
|
+
if (c.kind !== 'path') return null;
|
|
972
|
+
const keyed = anim.constraints.path.find((t) => t.name === c.name);
|
|
973
|
+
if (keyed === undefined) return null;
|
|
974
|
+
return {
|
|
975
|
+
position: keyed.position === undefined ? null : compileCurve(keyed.position, 1),
|
|
976
|
+
spacing: keyed.spacing === undefined ? null : compileCurve(keyed.spacing, 1),
|
|
977
|
+
mix: keyed.mix === undefined ? null : compileCurve(keyed.mix, 3),
|
|
978
|
+
positionCursor: cursors++,
|
|
979
|
+
spacingCursor: cursors++,
|
|
980
|
+
mixCursor: cursors++,
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
const physics: RuntimePhysicsTimeline[] = (anim.constraints.physicsKeyed ?? []).map(t => ({
|
|
984
|
+
kind: t.kind, cursor: cursors++, curve: compileCurve(t.keys, t.kind === 'reset' ? 0 : 1),
|
|
985
|
+
targets: Int32Array.from(constraints.filter((c): c is RuntimePhysics => c.kind === 'physics' && c.applied && (t.name === '*' ? t.kind === 'reset' || c.setup.global[t.kind] : c.name === t.name)).map(c => c.index)),
|
|
986
|
+
}));
|
|
761
987
|
const deforms: RuntimeDeformTimeline[] = deform.map(a => {
|
|
762
988
|
const g = doc.skins.find(s => s.name === a.skin)?.attachments[a.slot]?.[a.attachment]?.geometry;
|
|
763
989
|
if (g === undefined || g.kind === 'region' || g.kind === 'linkedmesh') throw new RuntimeInputError(`deform "${a.skin}/${a.slot}/${a.attachment}" has no vertex geometry (readModel refuses it first)`);
|
|
@@ -765,5 +991,5 @@ function compileAnimation(
|
|
|
765
991
|
const arrays = keys.map(k => heldArray(g.vertices, k));
|
|
766
992
|
return { identity: `${a.skin}/${a.slot}/${a.attachment}`, cursor: cursors++, curve: compileDeformCurve(keys), count: arrays[0].length, held: Float64Array.from(arrays.flat()) };
|
|
767
993
|
});
|
|
768
|
-
return { name, duration: tl.duration, deforms, bones, 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 }))) };
|
|
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 }))) };
|
|
769
995
|
}
|