@energy8platform/golem 0.3.0 → 0.6.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/README.md +21 -0
- package/dist/editor.css +50 -0
- package/dist/editor.js +1384 -983
- package/dist/lib/cli.js +487 -83
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/editor/controls-panel.d.ts +2 -0
- package/dist/lib/editor/store.d.ts +245 -1
- package/dist/lib/editor-entry.js +487 -83
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +236 -19
- package/dist/lib/interactive-bones.d.ts +37 -0
- package/dist/lib/interactive-editor.d.ts +80 -0
- package/dist/lib/interactive-editor.js +744 -0
- package/dist/lib/interactive-editor.js.map +7 -0
- package/dist/lib/rig-anim.d.ts +7 -2
- package/dist/lib/rig-control-qa.d.ts +346 -0
- package/dist/lib/rig-controls.d.ts +13 -0
- package/dist/lib/rig-format.d.ts +1903 -2
- package/dist/lib/rig-gates.d.ts +6 -6
- package/dist/lib/rig-runtime.d.ts +39 -2
- package/dist/lib/runtime.d.ts +3 -1
- package/dist/lib/runtime.js +239 -19
- package/dist/lib/runtime.js.map +4 -4
- package/dist/lib/spine-import.d.ts +4 -0
- package/dist/lib/tools.js +483 -79
- package/dist/lib/tools.js.map +4 -4
- package/package.json +7 -1
package/dist/lib/rig-gates.d.ts
CHANGED
|
@@ -50,17 +50,17 @@ export declare const GateConfig: z.ZodObject<{
|
|
|
50
50
|
}, "strip", z.ZodTypeAny, {
|
|
51
51
|
b: string;
|
|
52
52
|
a: string;
|
|
53
|
-
during?: [number, number] | undefined;
|
|
54
53
|
min?: number | undefined;
|
|
55
54
|
max?: number | undefined;
|
|
55
|
+
during?: [number, number] | undefined;
|
|
56
56
|
minRatio?: number | undefined;
|
|
57
57
|
maxRatio?: number | undefined;
|
|
58
58
|
}, {
|
|
59
59
|
b: string;
|
|
60
60
|
a: string;
|
|
61
|
-
during?: [number, number] | undefined;
|
|
62
61
|
min?: number | undefined;
|
|
63
62
|
max?: number | undefined;
|
|
63
|
+
during?: [number, number] | undefined;
|
|
64
64
|
minRatio?: number | undefined;
|
|
65
65
|
maxRatio?: number | undefined;
|
|
66
66
|
}>, "many">>;
|
|
@@ -84,9 +84,9 @@ export declare const GateConfig: z.ZodObject<{
|
|
|
84
84
|
bone: string;
|
|
85
85
|
prop: "x" | "y" | "rotation";
|
|
86
86
|
whenSlotVisible: string;
|
|
87
|
-
space?: "local" | "world" | undefined;
|
|
88
87
|
min?: number | undefined;
|
|
89
88
|
max?: number | undefined;
|
|
89
|
+
space?: "local" | "world" | undefined;
|
|
90
90
|
required?: boolean | undefined;
|
|
91
91
|
}>, "many">>;
|
|
92
92
|
transitions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -130,9 +130,9 @@ export declare const GateConfig: z.ZodObject<{
|
|
|
130
130
|
distances: {
|
|
131
131
|
b: string;
|
|
132
132
|
a: string;
|
|
133
|
-
during?: [number, number] | undefined;
|
|
134
133
|
min?: number | undefined;
|
|
135
134
|
max?: number | undefined;
|
|
135
|
+
during?: [number, number] | undefined;
|
|
136
136
|
minRatio?: number | undefined;
|
|
137
137
|
maxRatio?: number | undefined;
|
|
138
138
|
}[];
|
|
@@ -172,9 +172,9 @@ export declare const GateConfig: z.ZodObject<{
|
|
|
172
172
|
distances?: {
|
|
173
173
|
b: string;
|
|
174
174
|
a: string;
|
|
175
|
-
during?: [number, number] | undefined;
|
|
176
175
|
min?: number | undefined;
|
|
177
176
|
max?: number | undefined;
|
|
177
|
+
during?: [number, number] | undefined;
|
|
178
178
|
minRatio?: number | undefined;
|
|
179
179
|
maxRatio?: number | undefined;
|
|
180
180
|
}[] | undefined;
|
|
@@ -182,9 +182,9 @@ export declare const GateConfig: z.ZodObject<{
|
|
|
182
182
|
bone: string;
|
|
183
183
|
prop: "x" | "y" | "rotation";
|
|
184
184
|
whenSlotVisible: string;
|
|
185
|
-
space?: "local" | "world" | undefined;
|
|
186
185
|
min?: number | undefined;
|
|
187
186
|
max?: number | undefined;
|
|
187
|
+
space?: "local" | "world" | undefined;
|
|
188
188
|
required?: boolean | undefined;
|
|
189
189
|
}[] | undefined;
|
|
190
190
|
transitions?: {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - play/tick/stop are a thin wrapper over an AnimationState (see rig-state.ts) kept for simple consumers
|
|
9
9
|
* (harness, viewer). Games should drive `state` directly for queues, priorities and overlays.
|
|
10
10
|
*/
|
|
11
|
-
import { Container, Texture } from "pixi.js";
|
|
12
|
-
import { type RigDocument, type Asset } from "./rig-format";
|
|
11
|
+
import { Container, Sprite, Mesh, MeshPlane, Texture } from "pixi.js";
|
|
12
|
+
import { type RigDocument, type Slot, type Attachment, type Asset } from "./rig-format";
|
|
13
13
|
import { type Pose, type Affine } from "./rig-anim";
|
|
14
14
|
import { AnimationState, type PlayResult } from "./rig-state";
|
|
15
15
|
/** vertices per axis of a lattice-deformed region's render mesh (8 segments) */
|
|
@@ -23,6 +23,22 @@ export interface PlayOptions {
|
|
|
23
23
|
onComplete?: () => void;
|
|
24
24
|
onEvent?: (name: string, payload?: Record<string, unknown>) => void;
|
|
25
25
|
}
|
|
26
|
+
export interface RigRenderItem {
|
|
27
|
+
att: Attachment;
|
|
28
|
+
slot: Slot;
|
|
29
|
+
node: Sprite | MeshPlane | Mesh;
|
|
30
|
+
/** lattice-deformed region */
|
|
31
|
+
lattice?: {
|
|
32
|
+
plane: MeshPlane;
|
|
33
|
+
offsets: Float64Array;
|
|
34
|
+
applied: Float64Array;
|
|
35
|
+
};
|
|
36
|
+
/** skinned mesh */
|
|
37
|
+
skinned?: {
|
|
38
|
+
mesh: Mesh;
|
|
39
|
+
positions: Float32Array;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
26
42
|
/** Build the asset-id → Texture map, loading each `src` once and cutting atlas frames (with rotation and trim). */
|
|
27
43
|
export declare function createTextures(doc: RigDocument, loadSource: (src: string) => Promise<Texture>): Promise<Map<string, Texture>>;
|
|
28
44
|
/**
|
|
@@ -32,9 +48,17 @@ export declare function createTextures(doc: RigDocument, loadSource: (src: strin
|
|
|
32
48
|
export declare function atlasUV(asset: Asset, u: number, v: number, sourceW: number, sourceH: number): [number, number];
|
|
33
49
|
export declare class RigPlayer {
|
|
34
50
|
doc: RigDocument;
|
|
51
|
+
private inputPose;
|
|
52
|
+
private controlOverrides;
|
|
53
|
+
/** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
|
|
54
|
+
setControls(values: Record<string, number>): void;
|
|
55
|
+
resetControls(): void;
|
|
35
56
|
readonly view: Container<import("pixi.js").ContainerChild>;
|
|
36
57
|
readonly state: AnimationState;
|
|
37
58
|
private items;
|
|
59
|
+
private beforePose;
|
|
60
|
+
private afterPose;
|
|
61
|
+
private clipPoints;
|
|
38
62
|
private skel;
|
|
39
63
|
private textures;
|
|
40
64
|
private pages;
|
|
@@ -49,6 +73,19 @@ export declare class RigPlayer {
|
|
|
49
73
|
missingAnimation?: "warn" | "throw";
|
|
50
74
|
onWarning?: (message: string) => void;
|
|
51
75
|
});
|
|
76
|
+
/** Read-only render handles for in-page tools. Nodes remain owned by the player. */
|
|
77
|
+
get renderItems(): readonly Readonly<RigRenderItem>[];
|
|
78
|
+
/** Current document-space clipping polygons, keyed by path attachment id. */
|
|
79
|
+
get clipPolygons(): ReadonlyMap<string, readonly number[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Modify resolved control outputs before skeleton evaluation, constraints and skinning.
|
|
82
|
+
* Callbacks run in registration order, each with its own map. Change control inputs via setControls.
|
|
83
|
+
*/
|
|
84
|
+
onBeforePose(callback: (pose: Pose) => Pose): () => void;
|
|
85
|
+
/** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
|
|
86
|
+
onAfterPose(callback: (player: RigPlayer) => void): () => void;
|
|
87
|
+
/** Rebuild the current pose without advancing playback; useful for reversible overlays. */
|
|
88
|
+
refreshPose(): void;
|
|
52
89
|
/** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
|
|
53
90
|
private pageTexture;
|
|
54
91
|
/** Replace whatever is playing (no queueing). Resolves when the animation completes or is replaced. */
|
package/dist/lib/runtime.d.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* `@energy8platform/golem/runtime` — the browser half: play a rig document in PixiJS v8.
|
|
3
3
|
* Depends on pixi.js (peer) and zod only; no node, no tools, no editor.
|
|
4
4
|
*/
|
|
5
|
-
export { RigPlayer, createTextures, atlasUV, MESH_GRID, type PlayOptions } from "./rig-runtime";
|
|
5
|
+
export { RigPlayer, createTextures, atlasUV, MESH_GRID, type PlayOptions, type RigRenderItem } from "./rig-runtime";
|
|
6
6
|
export { RigSymbol, type RigSymbolOptions, type SymbolSize, type SymbolViewLike, type TickerLike } from "./rig-symbol";
|
|
7
7
|
export { AnimationState, type PlayResult, type PoseSink } from "./rig-state";
|
|
8
8
|
export { parse, validate, migrateV1, RigDocument as RigDocumentSchema } from "./rig-format";
|
|
9
9
|
export type { RigDocument, Bone, Slot, Asset, Attachment, Animation, Track, Key } from "./rig-format";
|
|
10
|
+
export type { RigControl, ControlBinding } from "./rig-format";
|
|
11
|
+
export { controlPose, resolveControls, sampleBinding } from "./rig-controls";
|
package/dist/lib/runtime.js
CHANGED
|
@@ -159,7 +159,9 @@ var PathAttachment = z.object({
|
|
|
159
159
|
/** curve lengths for constant-speed positioning */
|
|
160
160
|
lengths: z.array(z.number()).default([]),
|
|
161
161
|
closed: z.boolean().default(false),
|
|
162
|
-
constantSpeed: z.boolean().default(true)
|
|
162
|
+
constantSpeed: z.boolean().default(true),
|
|
163
|
+
/** Spine-style active clipping span in the posed draw order; end slot is inclusive. */
|
|
164
|
+
clipping: z.object({ end: Id.optional() }).optional()
|
|
163
165
|
});
|
|
164
166
|
var Attachment = z.discriminatedUnion("type", [RegionAttachment, MeshAttachment, PathAttachment]);
|
|
165
167
|
var IkConstraint = z.object({
|
|
@@ -223,10 +225,13 @@ var Key = z.object({
|
|
|
223
225
|
t: z.number().nonnegative(),
|
|
224
226
|
v: KeyValue,
|
|
225
227
|
/** easing INTO the next key */
|
|
226
|
-
ease: Ease.default("ease_in_out")
|
|
228
|
+
ease: Ease.default("ease_in_out"),
|
|
229
|
+
/** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
|
|
230
|
+
componentEase: z.array(Ease).min(1).optional()
|
|
227
231
|
});
|
|
228
232
|
var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
|
|
229
233
|
var Track = z.discriminatedUnion("target", [
|
|
234
|
+
z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
|
|
230
235
|
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
231
236
|
z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
232
237
|
z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
@@ -249,6 +254,25 @@ var Animation = z.object({
|
|
|
249
254
|
/** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
|
|
250
255
|
preset: z.object({ name: z.string(), params: z.record(z.unknown()) }).optional()
|
|
251
256
|
});
|
|
257
|
+
var RigControl = z.object({
|
|
258
|
+
id: Id,
|
|
259
|
+
label: z.string().optional(),
|
|
260
|
+
min: z.number().finite(),
|
|
261
|
+
max: z.number().finite(),
|
|
262
|
+
default: z.number().finite()
|
|
263
|
+
}).strict();
|
|
264
|
+
var ControlValues = z.array(z.union([z.number().finite(), z.array(z.number().finite()).min(1)])).min(2);
|
|
265
|
+
var ControlChannel = z.discriminatedUnion("target", [
|
|
266
|
+
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
|
|
267
|
+
z.object({ target: z.literal("slot"), id: Id, prop: z.literal("alpha"), values: ControlValues }).strict(),
|
|
268
|
+
z.object({ target: z.literal("deform"), id: Id, prop: z.literal("vertices"), values: ControlValues }).strict()
|
|
269
|
+
]);
|
|
270
|
+
var ControlBinding = z.object({
|
|
271
|
+
id: Id,
|
|
272
|
+
axes: z.array(z.object({ control: Id, keys: z.array(z.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
|
|
273
|
+
/** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
|
|
274
|
+
channels: z.array(ControlChannel).min(1)
|
|
275
|
+
}).strict();
|
|
252
276
|
var Reference = z.object({
|
|
253
277
|
src: z.string(),
|
|
254
278
|
opacity: z.number().min(0).max(1).default(0.5),
|
|
@@ -274,7 +298,9 @@ var RigSchema = z.object({
|
|
|
274
298
|
ik: z.array(IkConstraint).default([]),
|
|
275
299
|
transform: z.array(TransformConstraint).default([]),
|
|
276
300
|
path: z.array(PathConstraint).default([]),
|
|
277
|
-
animations: z.array(Animation)
|
|
301
|
+
animations: z.array(Animation),
|
|
302
|
+
controls: z.array(RigControl).optional(),
|
|
303
|
+
controlBindings: z.array(ControlBinding).optional()
|
|
278
304
|
});
|
|
279
305
|
var RigDocument = z.preprocess((input) => {
|
|
280
306
|
const v = input?.version;
|
|
@@ -349,6 +375,8 @@ function validate(doc) {
|
|
|
349
375
|
dup(doc.ik, "ik");
|
|
350
376
|
dup(doc.transform, "transform");
|
|
351
377
|
dup(doc.path, "path");
|
|
378
|
+
dup(doc.controls ?? [], "control");
|
|
379
|
+
dup(doc.controlBindings ?? [], "control binding");
|
|
352
380
|
const bones = new Set(doc.bones.map((b) => b.id));
|
|
353
381
|
const assets = new Map(doc.assets.map((a) => [a.id, a]));
|
|
354
382
|
const slots = new Set(doc.slots.map((s) => s.id));
|
|
@@ -386,6 +414,10 @@ function validate(doc) {
|
|
|
386
414
|
else if (a.slot !== s.id) errs.push(`slot "${s.id}": attachment "${s.attachment}" belongs to slot "${a.slot}"`);
|
|
387
415
|
}
|
|
388
416
|
}
|
|
417
|
+
for (const a of doc.attachments) if (a.type === "path" && a.clipping) {
|
|
418
|
+
if (!a.closed) errs.push(`attachment "${a.id}": clipping path must be closed`);
|
|
419
|
+
if (a.clipping.end && !slots.has(a.clipping.end)) errs.push(`attachment "${a.id}": unknown clipping end slot "${a.clipping.end}"`);
|
|
420
|
+
}
|
|
389
421
|
const vertexCount = (a) => {
|
|
390
422
|
if (a.type === "region") return 4;
|
|
391
423
|
if (a.type === "mesh") return a.uvs.length / 2;
|
|
@@ -423,20 +455,59 @@ function validate(doc) {
|
|
|
423
455
|
for (const b of c.bones) if (!bones.has(b)) errs.push(`path "${c.id}": unknown bone "${b}"`);
|
|
424
456
|
if (!slots.has(c.target)) errs.push(`path "${c.id}": unknown target slot "${c.target}"`);
|
|
425
457
|
}
|
|
458
|
+
const controls = new Map((doc.controls ?? []).map((c) => [c.id, c]));
|
|
459
|
+
const driven = /* @__PURE__ */ new Set();
|
|
460
|
+
for (const c of controls.values()) if (!(c.min < c.max && c.default >= c.min && c.default <= c.max))
|
|
461
|
+
errs.push(`control "${c.id}": require min < max and default within range`);
|
|
462
|
+
for (const b of doc.controlBindings ?? []) {
|
|
463
|
+
const prefix = `control binding "${b.id}"`;
|
|
464
|
+
if (new Set(b.axes.map((a) => a.control)).size !== b.axes.length) errs.push(`${prefix}: axes must use different controls`);
|
|
465
|
+
for (const axis of b.axes) {
|
|
466
|
+
const c = controls.get(axis.control);
|
|
467
|
+
if (!c) errs.push(`${prefix}: unknown control "${axis.control}"`);
|
|
468
|
+
else if (axis.keys[0] !== c.min || axis.keys.at(-1) !== c.max) errs.push(`${prefix}: keys must span the control's full range`);
|
|
469
|
+
if (axis.keys.some((v, i) => i > 0 && v <= axis.keys[i - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
|
|
470
|
+
}
|
|
471
|
+
const count = b.axes.reduce((n, a) => n * a.keys.length, 1);
|
|
472
|
+
for (const ch of b.channels) {
|
|
473
|
+
const key = `${ch.target}.${ch.id}.${ch.prop}`;
|
|
474
|
+
if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
|
|
475
|
+
driven.add(key);
|
|
476
|
+
if (ch.values.length !== count) errs.push(`${prefix}: ${key} needs ${count} grid values`);
|
|
477
|
+
if (ch.target === "deform") {
|
|
478
|
+
const att = attachments.get(ch.id);
|
|
479
|
+
if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
|
|
480
|
+
else {
|
|
481
|
+
const n = att.weights ? att.weights.reduce((s, a) => s + a.length * 2, 0) : att.vertices.length;
|
|
482
|
+
if (ch.values.some((v) => !Array.isArray(v) || v.length !== n)) errs.push(`${prefix}: ${key} needs arrays of ${n} offsets`);
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
|
|
486
|
+
if (ch.values.some((v) => typeof v !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
|
|
487
|
+
if (ch.target === "slot" && ch.values.some((v) => typeof v === "number" && (v < 0 || v > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
426
491
|
for (const a of doc.animations) {
|
|
427
492
|
const tracks = /* @__PURE__ */ new Set();
|
|
428
493
|
for (const t of a.tracks) {
|
|
429
494
|
const tid = trackId(t);
|
|
430
495
|
if (tracks.has(tid)) errs.push(`animation "${a.id}": duplicate track ${tid}`);
|
|
431
496
|
tracks.add(tid);
|
|
432
|
-
|
|
497
|
+
if (driven.has(tid)) errs.push(`animation "${a.id}": ${tid} is controlled by a binding; animate its control instead`);
|
|
498
|
+
const known = t.target === "bone" ? bones.has(t.id) : t.target === "slot" ? slots.has(t.id) : t.target === "deform" ? attachments.has(t.id) : t.target === "ik" ? iks.has(t.id) : t.target === "transform" ? transforms.has(t.id) : t.target === "path" ? paths.has(t.id) : t.target === "control" ? controls.has(t.id) : true;
|
|
433
499
|
if (!known) errs.push(`animation "${a.id}": unknown ${t.target} "${t.id}"`);
|
|
434
500
|
const bound = t.target === "deform" ? DEFORM_BOUNDS[t.prop] : void 0;
|
|
435
501
|
const att = t.target === "deform" ? attachments.get(t.id) : void 0;
|
|
436
502
|
let prev = -1;
|
|
437
503
|
for (const k of t.keys) {
|
|
504
|
+
if (k.componentEase && (!Array.isArray(k.v) || !k.v.every((v) => typeof v === "number") || k.componentEase.length !== k.v.length)) errs.push(`animation "${a.id}" ${tid}: componentEase must match the numeric-array value length`);
|
|
438
505
|
if (k.t <= prev && prev >= 0) errs.push(`animation "${a.id}" ${tid}: keys not strictly increasing`);
|
|
439
506
|
if (k.t > a.duration) errs.push(`animation "${a.id}" ${tid}: key at ${k.t}s exceeds duration`);
|
|
507
|
+
if (t.target === "control") {
|
|
508
|
+
const c = controls.get(t.id);
|
|
509
|
+
if (typeof k.v !== "number" || !Number.isFinite(k.v) || c && (k.v < c.min || k.v > c.max)) errs.push(`animation "${a.id}" ${tid}: control key outside its finite range`);
|
|
510
|
+
}
|
|
440
511
|
if (t.target === "slot" && t.prop === "attachment") {
|
|
441
512
|
if (k.v !== null && typeof k.v !== "string") errs.push(`animation "${a.id}" ${tid}: attachment key must be an attachment id or null`);
|
|
442
513
|
else if (typeof k.v === "string") {
|
|
@@ -475,6 +546,52 @@ function parse(input) {
|
|
|
475
546
|
return doc;
|
|
476
547
|
}
|
|
477
548
|
|
|
549
|
+
// src/rig-controls.ts
|
|
550
|
+
function controlPose(doc, values) {
|
|
551
|
+
const out = /* @__PURE__ */ new Map();
|
|
552
|
+
for (const [id, value] of Object.entries(values)) {
|
|
553
|
+
const c = doc.controls?.find((c2) => c2.id === id);
|
|
554
|
+
if (!c) throw new Error(`unknown control "${id}"`);
|
|
555
|
+
if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
|
|
556
|
+
out.set(`control:${id}:value`, Math.max(c.min, Math.min(c.max, value)));
|
|
557
|
+
}
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
function bracket(keys, value) {
|
|
561
|
+
let i = 0;
|
|
562
|
+
while (i < keys.length - 2 && value > keys[i + 1]) i++;
|
|
563
|
+
return [i, Math.max(0, Math.min(1, (value - keys[i]) / (keys[i + 1] - keys[i])))];
|
|
564
|
+
}
|
|
565
|
+
function sampleBinding(binding, values) {
|
|
566
|
+
const [x, u] = bracket(binding.axes[0].keys, values[0]);
|
|
567
|
+
const [y, v] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
|
|
568
|
+
const width = binding.axes[0].keys.length, a = y * width + x;
|
|
569
|
+
const mix = (a2, b, u2) => a2 + (b - a2) * u2;
|
|
570
|
+
return binding.channels.map((ch) => {
|
|
571
|
+
const read = (i, j) => j === void 0 ? ch.values[i] : ch.values[i][j];
|
|
572
|
+
const at = (j) => {
|
|
573
|
+
const top = mix(read(a, j), read(a + 1, j), u);
|
|
574
|
+
return binding.axes[1] ? mix(top, mix(read(a + width, j), read(a + width + 1, j), u), v) : top;
|
|
575
|
+
};
|
|
576
|
+
return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j) => at(j)) : at();
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function resolveControls(doc, input) {
|
|
580
|
+
if (!doc.controlBindings?.length) return input;
|
|
581
|
+
const out = new Map(input), controls = new Map(doc.controls?.map((c) => [c.id, c]));
|
|
582
|
+
for (const b of doc.controlBindings) {
|
|
583
|
+
const values = b.axes.map((axis) => {
|
|
584
|
+
const c = controls.get(axis.control);
|
|
585
|
+
const value = input.get(`control:${c.id}:value`) ?? c.default;
|
|
586
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c.id}" must be finite`);
|
|
587
|
+
return Math.max(c.min, Math.min(c.max, value));
|
|
588
|
+
});
|
|
589
|
+
const sampled = sampleBinding(b, values);
|
|
590
|
+
b.channels.forEach((ch, i) => out.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i]));
|
|
591
|
+
}
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
|
|
478
595
|
// src/rig-anim.ts
|
|
479
596
|
var DEG2 = Math.PI / 180;
|
|
480
597
|
var named = {
|
|
@@ -549,8 +666,8 @@ function sample(track, t, loop = false) {
|
|
|
549
666
|
}
|
|
550
667
|
if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
|
|
551
668
|
if (track.interpolation === "pchip") return a.v.map((_, j) => smoothValue(track, i, t, j, loop));
|
|
552
|
-
const
|
|
553
|
-
return av.map((v, k) => v + ((bv[k] ?? v) - v) *
|
|
669
|
+
const phase = (t - a.t) / (b.t - a.t), av = a.v, bv = b.v;
|
|
670
|
+
return av.map((v, k) => v + ((bv[k] ?? v) - v) * ease(a.componentEase?.[k] ?? a.ease)(phase));
|
|
554
671
|
}
|
|
555
672
|
return a.v;
|
|
556
673
|
}
|
|
@@ -578,6 +695,7 @@ function blendPoses(from, to, w, restOf) {
|
|
|
578
695
|
}
|
|
579
696
|
function restValue(doc, key) {
|
|
580
697
|
const [target, id, prop] = key.split(":");
|
|
698
|
+
if (target === "control") return doc.controls?.find((c) => c.id === id)?.default ?? 0;
|
|
581
699
|
if (target === "bone") return doc.bones.find((b) => b.id === id).transform[prop];
|
|
582
700
|
if (target === "slot") {
|
|
583
701
|
const s = doc.slots.find((s2) => s2.id === id);
|
|
@@ -637,6 +755,13 @@ function boneWorldOf(bone, s, parent) {
|
|
|
637
755
|
if (!parent) return local;
|
|
638
756
|
if (bone.inherit === "normal") return mul(parent, local);
|
|
639
757
|
const [tx, ty] = apply(parent, s.x, s.y);
|
|
758
|
+
if (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection") {
|
|
759
|
+
const r = s.rotation * DEG2, x = parent.a * Math.cos(r) + parent.c * Math.sin(r), y = parent.b * Math.cos(r) + parent.d * Math.sin(r);
|
|
760
|
+
const length = Math.hypot(x, y), a = length > 1e-5 ? x / length : x, b = length > 1e-5 ? y / length : y;
|
|
761
|
+
const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
|
|
762
|
+
const basis2 = { a, b, c: -b * sign, d: a * sign, tx: 0, ty: 0 };
|
|
763
|
+
return { ...mul(basis2, affineOf(0, 0, 0, s.scaleX, s.scaleY, s.shearX, s.shearY)), tx, ty };
|
|
764
|
+
}
|
|
640
765
|
return { ...mul(inheritedParentLinear(bone, parent), { ...local, tx: 0, ty: 0 }), tx, ty };
|
|
641
766
|
}
|
|
642
767
|
var orderCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -669,18 +794,20 @@ var localOf = (doc, pose, b) => {
|
|
|
669
794
|
shearY: num(pose, pk("bone", id, "shearY"), r.shearY)
|
|
670
795
|
};
|
|
671
796
|
};
|
|
672
|
-
function updateSubtree(skel, root) {
|
|
797
|
+
function updateSubtree(skel, root, includeRoot = true) {
|
|
673
798
|
const doc = skel.doc;
|
|
674
799
|
const kids = /* @__PURE__ */ new Map();
|
|
675
800
|
for (const b of doc.bones) if (b.parent) (kids.get(b.parent) ?? kids.set(b.parent, []).get(b.parent)).push(b);
|
|
676
|
-
const stack = [root];
|
|
801
|
+
const stack = includeRoot ? [root] : (kids.get(root) ?? []).map((b) => b.id);
|
|
677
802
|
while (stack.length) {
|
|
678
803
|
const id = stack.pop(), b = boneOf(doc, id), st = skel.bones.get(id);
|
|
679
804
|
st.world = boneWorldOf(b, st, b.parent ? skel.bones.get(b.parent).world : void 0);
|
|
680
805
|
for (const k of kids.get(id) ?? []) stack.push(k.id);
|
|
681
806
|
}
|
|
682
807
|
}
|
|
683
|
-
function evaluate(doc, pose) {
|
|
808
|
+
function evaluate(doc, pose, modifiers) {
|
|
809
|
+
pose = resolveControls(doc, pose);
|
|
810
|
+
if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
|
|
684
811
|
const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
|
|
685
812
|
for (const b of boneOrder(doc)) {
|
|
686
813
|
const s = localOf(doc, pose, b);
|
|
@@ -705,6 +832,23 @@ function evaluate(doc, pose) {
|
|
|
705
832
|
}
|
|
706
833
|
return skel;
|
|
707
834
|
}
|
|
835
|
+
function slotClips(skel) {
|
|
836
|
+
const keyed = skel.pose.get(pk("drawOrder", "order", "order"));
|
|
837
|
+
const order = Array.isArray(keyed) ? keyed : [...skel.doc.slots].sort((a, b) => a.z - b.z).map((s) => s.id);
|
|
838
|
+
const masks = /* @__PURE__ */ new Map();
|
|
839
|
+
let active;
|
|
840
|
+
for (const id of order) {
|
|
841
|
+
const att = skel.attachment.get(id);
|
|
842
|
+
if (!active && att?.type === "path" && att.clipping) {
|
|
843
|
+
active = att;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (active) masks.set(id, active.id);
|
|
847
|
+
if (active?.clipping?.end === id) active = void 0;
|
|
848
|
+
}
|
|
849
|
+
for (const s of skel.doc.slots) if (s.clip) masks.set(s.id, s.clip);
|
|
850
|
+
return masks;
|
|
851
|
+
}
|
|
708
852
|
var cnum = (skel, kind, id, prop, rest) => num(skel.pose, pk(kind, id, prop), typeof rest === "boolean" ? rest ? 1 : 0 : rest);
|
|
709
853
|
var IDENTITY = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
710
854
|
var wrapRad = (r) => r > Math.PI ? r - 2 * Math.PI : r < -Math.PI ? r + 2 * Math.PI : r;
|
|
@@ -914,7 +1058,8 @@ function applyTransformConstraint(skel, c) {
|
|
|
914
1058
|
if (!mixRotate && !mixX && !mixY && !mixScaleX && !mixScaleY && !mixShearY) return;
|
|
915
1059
|
c = { ...c, ...Object.fromEntries(["x", "y", "rotation", "scaleX", "scaleY", "shearY"].map((p) => [p, cnum(skel, "transform", c.id, p, c[p])])) };
|
|
916
1060
|
const target = skel.bones.get(c.target), tw = target.world;
|
|
917
|
-
const
|
|
1061
|
+
const handedness = tw.a * tw.d - tw.b * tw.c < 0 ? -1 : 1;
|
|
1062
|
+
const offsetRotation = c.rotation * DEG2 * (c.local ? 1 : handedness), offsetShearY = c.shearY * DEG2 * (c.local ? 1 : handedness);
|
|
918
1063
|
for (const id of c.bones) {
|
|
919
1064
|
const b = boneOf(skel.doc, id), st = skel.bones.get(id);
|
|
920
1065
|
if (c.local) {
|
|
@@ -1076,6 +1221,7 @@ function applyPathConstraint(skel, c) {
|
|
|
1076
1221
|
const position = cnum(skel, "path", c.id, "position", c.position), spacing = cnum(skel, "path", c.id, "spacing", c.spacing);
|
|
1077
1222
|
const tangents = c.rotateMode === "tangent", scale = c.rotateMode === "chainScale";
|
|
1078
1223
|
const bones = c.bones.map((id) => ({ b: boneOf(skel.doc, id), st: skel.bones.get(id) }));
|
|
1224
|
+
const inputWorlds = bones.map(({ st }) => ({ ...st.world }));
|
|
1079
1225
|
const spacesCount = tangents ? bones.length : bones.length + 1;
|
|
1080
1226
|
const spaces = new Array(spacesCount).fill(0), worldLengths = [];
|
|
1081
1227
|
let sum = 0;
|
|
@@ -1110,9 +1256,10 @@ function applyPathConstraint(skel, c) {
|
|
|
1110
1256
|
if (tw.a * tw.d - tw.b * tw.c < 0) offsetRotation = -offsetRotation;
|
|
1111
1257
|
}
|
|
1112
1258
|
let boneX = samples[0].x, boneY = samples[0].y;
|
|
1259
|
+
const solvedWorlds = /* @__PURE__ */ new Map();
|
|
1113
1260
|
for (let i = 0; i < bones.length; i++) {
|
|
1114
1261
|
const { b, st } = bones[i];
|
|
1115
|
-
let w = { ...
|
|
1262
|
+
let w = { ...inputWorlds[i] };
|
|
1116
1263
|
w.tx += (boneX - w.tx) * mixX;
|
|
1117
1264
|
w.ty += (boneY - w.ty) * mixY;
|
|
1118
1265
|
const next = samples[i + 1] ?? { x: 0, y: 0, angle: 0 };
|
|
@@ -1142,7 +1289,11 @@ function applyPathConstraint(skel, c) {
|
|
|
1142
1289
|
const cos = Math.cos(r), sin = Math.sin(r);
|
|
1143
1290
|
w = { a: cos * a - sin * bb, b: sin * a + cos * bb, c: cos * cc - sin * d, d: sin * cc + cos * d, tx: w.tx, ty: w.ty };
|
|
1144
1291
|
}
|
|
1145
|
-
|
|
1292
|
+
solvedWorlds.set(b.id, w);
|
|
1293
|
+
}
|
|
1294
|
+
for (const b of boneOrder(skel.doc)) {
|
|
1295
|
+
const world = solvedWorlds.get(b.id);
|
|
1296
|
+
if (world) setEffectiveWorld(skel, b, skel.bones.get(b.id), world);
|
|
1146
1297
|
}
|
|
1147
1298
|
}
|
|
1148
1299
|
function measuredPathLength(points, closed, lengths, constantSpeed) {
|
|
@@ -1171,9 +1322,14 @@ function inheritedParentLinear(bone, parent) {
|
|
|
1171
1322
|
case "onlyTranslation":
|
|
1172
1323
|
return { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
1173
1324
|
case "noRotationOrReflection": {
|
|
1174
|
-
const lenX = Math.hypot(parent.a, parent.b)
|
|
1325
|
+
const lenX = Math.hypot(parent.a, parent.b);
|
|
1175
1326
|
const det = parent.a * parent.d - parent.b * parent.c;
|
|
1176
|
-
|
|
1327
|
+
if (lenX * lenX <= 1e-4) {
|
|
1328
|
+
const lenY = Math.hypot(parent.c, parent.d), ux2 = lenY ? parent.c / lenY : 0, uy2 = lenY ? parent.d / lenY : 0;
|
|
1329
|
+
return { a: lenY * ux2 * ux2, b: lenY * ux2 * uy2, c: lenY * ux2 * uy2, d: lenY * uy2 * uy2, tx: 0, ty: 0 };
|
|
1330
|
+
}
|
|
1331
|
+
const ux = parent.a / lenX, uy = parent.b / lenX, sy = Math.abs(det) / lenX;
|
|
1332
|
+
return { a: lenX * ux * ux + sy * uy * uy, b: (lenX - sy) * ux * uy, c: (lenX - sy) * ux * uy, d: lenX * uy * uy + sy * ux * ux, tx: 0, ty: 0 };
|
|
1177
1333
|
}
|
|
1178
1334
|
case "noScale":
|
|
1179
1335
|
case "noScaleOrReflection": {
|
|
@@ -1199,7 +1355,16 @@ function setEffectiveWorld(skel, bone, st, world) {
|
|
|
1199
1355
|
st.scaleY = Math.hypot(lin.c, lin.d);
|
|
1200
1356
|
st.shearX = 0;
|
|
1201
1357
|
st.shearY = shortest(Math.atan2(lin.d, lin.c) / DEG2 - st.rotation - 90);
|
|
1202
|
-
|
|
1358
|
+
if (parent && (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection")) {
|
|
1359
|
+
const direction = mul(invert({ ...parent, tx: 0, ty: 0 }), { ...world, tx: 0, ty: 0 });
|
|
1360
|
+
st.rotation = Math.atan2(direction.b, direction.a) / DEG2;
|
|
1361
|
+
st.scaleX = Math.hypot(world.a, world.b);
|
|
1362
|
+
st.scaleY = Math.hypot(world.c, world.d);
|
|
1363
|
+
const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
|
|
1364
|
+
st.shearY = shortest((Math.atan2(world.d, world.c) - Math.atan2(world.b, world.a)) / DEG2 * sign - 90);
|
|
1365
|
+
}
|
|
1366
|
+
st.world = world;
|
|
1367
|
+
updateSubtree(skel, bone.id, false);
|
|
1203
1368
|
}
|
|
1204
1369
|
function attachmentWorldVertices(skel, slotId, att) {
|
|
1205
1370
|
const deform = skel.pose.get(pk("deform", att.id, "vertices"));
|
|
@@ -1485,9 +1650,23 @@ var RigPlayer = class {
|
|
|
1485
1650
|
this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
|
|
1486
1651
|
this.setPose(/* @__PURE__ */ new Map());
|
|
1487
1652
|
}
|
|
1653
|
+
inputPose = /* @__PURE__ */ new Map();
|
|
1654
|
+
controlOverrides = /* @__PURE__ */ new Map();
|
|
1655
|
+
/** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
|
|
1656
|
+
setControls(values) {
|
|
1657
|
+
this.controlOverrides = controlPose(this.doc, values);
|
|
1658
|
+
this.setPose(this.inputPose);
|
|
1659
|
+
}
|
|
1660
|
+
resetControls() {
|
|
1661
|
+
this.controlOverrides.clear();
|
|
1662
|
+
this.setPose(this.inputPose);
|
|
1663
|
+
}
|
|
1488
1664
|
view = new Container();
|
|
1489
1665
|
state;
|
|
1490
1666
|
items = [];
|
|
1667
|
+
beforePose = /* @__PURE__ */ new Set();
|
|
1668
|
+
afterPose = /* @__PURE__ */ new Set();
|
|
1669
|
+
clipPoints = /* @__PURE__ */ new Map();
|
|
1491
1670
|
skel;
|
|
1492
1671
|
textures;
|
|
1493
1672
|
pages = /* @__PURE__ */ new Map();
|
|
@@ -1496,6 +1675,35 @@ var RigPlayer = class {
|
|
|
1496
1675
|
/** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
|
|
1497
1676
|
ghosts = /* @__PURE__ */ new Set();
|
|
1498
1677
|
clipGraphics = /* @__PURE__ */ new Map();
|
|
1678
|
+
/** Read-only render handles for in-page tools. Nodes remain owned by the player. */
|
|
1679
|
+
get renderItems() {
|
|
1680
|
+
return this.items;
|
|
1681
|
+
}
|
|
1682
|
+
/** Current document-space clipping polygons, keyed by path attachment id. */
|
|
1683
|
+
get clipPolygons() {
|
|
1684
|
+
return this.clipPoints;
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Modify resolved control outputs before skeleton evaluation, constraints and skinning.
|
|
1688
|
+
* Callbacks run in registration order, each with its own map. Change control inputs via setControls.
|
|
1689
|
+
*/
|
|
1690
|
+
onBeforePose(callback) {
|
|
1691
|
+
this.beforePose.add(callback);
|
|
1692
|
+
return () => {
|
|
1693
|
+
this.beforePose.delete(callback);
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
/** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
|
|
1697
|
+
onAfterPose(callback) {
|
|
1698
|
+
this.afterPose.add(callback);
|
|
1699
|
+
return () => {
|
|
1700
|
+
this.afterPose.delete(callback);
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
/** Rebuild the current pose without advancing playback; useful for reversible overlays. */
|
|
1704
|
+
refreshPose() {
|
|
1705
|
+
this.setPose(this.inputPose);
|
|
1706
|
+
}
|
|
1499
1707
|
/** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
|
|
1500
1708
|
pageTexture(tex) {
|
|
1501
1709
|
let page = this.pages.get(tex.source);
|
|
@@ -1550,8 +1758,12 @@ var RigPlayer = class {
|
|
|
1550
1758
|
}
|
|
1551
1759
|
/** Apply a pose (rest where silent) to the display objects. */
|
|
1552
1760
|
setPose(pose) {
|
|
1553
|
-
|
|
1554
|
-
|
|
1761
|
+
this.inputPose = new Map(pose);
|
|
1762
|
+
if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
|
|
1763
|
+
this.clipPoints.clear();
|
|
1764
|
+
const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
|
|
1765
|
+
pose = skel.pose;
|
|
1766
|
+
const masks = slotClips(skel), clips = new Set(masks.values());
|
|
1555
1767
|
for (const [id, g] of this.clipGraphics) if (!clips.has(id)) {
|
|
1556
1768
|
g.destroy();
|
|
1557
1769
|
this.clipGraphics.delete(id);
|
|
@@ -1568,7 +1780,9 @@ var RigPlayer = class {
|
|
|
1568
1780
|
const vertices = attachmentWorldVertices(skel, att.slot, att);
|
|
1569
1781
|
const n = Math.max(32, vertices.length * 4);
|
|
1570
1782
|
const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n }, (_, i) => i / n), true);
|
|
1571
|
-
|
|
1783
|
+
const polygon = points.flatMap((p) => [p.x, p.y]);
|
|
1784
|
+
this.clipPoints.set(id, polygon);
|
|
1785
|
+
g.clear().poly(polygon, true).fill(16777215);
|
|
1572
1786
|
}
|
|
1573
1787
|
const m = new Matrix();
|
|
1574
1788
|
const orderV = pose.get(pk("drawOrder", "order", "order"));
|
|
@@ -1576,7 +1790,8 @@ var RigPlayer = class {
|
|
|
1576
1790
|
if (Array.isArray(orderV)) orderV.forEach((id, i) => order.set(id, i));
|
|
1577
1791
|
for (const it of this.items) {
|
|
1578
1792
|
const s = it.slot, shown = skel.attachment.get(s.id);
|
|
1579
|
-
|
|
1793
|
+
const mask = masks.get(s.id);
|
|
1794
|
+
it.node.mask = mask ? this.clipGraphics.get(mask) ?? null : null;
|
|
1580
1795
|
if (shown !== it.att || this.hidden.has(s.id)) {
|
|
1581
1796
|
it.node.visible = false;
|
|
1582
1797
|
continue;
|
|
@@ -1589,6 +1804,7 @@ var RigPlayer = class {
|
|
|
1589
1804
|
it.node.tint = rgbOf(color);
|
|
1590
1805
|
it.node.zIndex = order.size ? order.get(s.id) ?? 0 : numOf(pose, pk("slot", s.id, "z"), s.z);
|
|
1591
1806
|
if (it.skinned) {
|
|
1807
|
+
it.node.setFromMatrix(m.identity());
|
|
1592
1808
|
const v = attachmentWorldVertices(skel, s.id, it.att);
|
|
1593
1809
|
const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
|
|
1594
1810
|
buf.data.set(v);
|
|
@@ -1621,6 +1837,7 @@ var RigPlayer = class {
|
|
|
1621
1837
|
it.node.scale.y *= h / (this.textures.get(att.asset)?.height ?? h);
|
|
1622
1838
|
}
|
|
1623
1839
|
}
|
|
1840
|
+
for (const callback of this.afterPose) callback(this);
|
|
1624
1841
|
}
|
|
1625
1842
|
/** World-space bone positions — used by editor gizmos and renderPreview overlays */
|
|
1626
1843
|
bonePositions() {
|
|
@@ -1697,9 +1914,12 @@ export {
|
|
|
1697
1914
|
RigPlayer,
|
|
1698
1915
|
RigSymbol,
|
|
1699
1916
|
atlasUV,
|
|
1917
|
+
controlPose,
|
|
1700
1918
|
createTextures,
|
|
1701
1919
|
migrateV1,
|
|
1702
1920
|
parse,
|
|
1921
|
+
resolveControls,
|
|
1922
|
+
sampleBinding,
|
|
1703
1923
|
validate
|
|
1704
1924
|
};
|
|
1705
1925
|
//# sourceMappingURL=runtime.js.map
|