@energy8platform/golem 0.3.0 → 0.5.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 +17 -0
- package/dist/editor.css +50 -0
- package/dist/editor.js +1315 -969
- package/dist/lib/cli.js +278 -19
- 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 +140 -1
- package/dist/lib/editor-entry.js +278 -19
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +167 -5
- 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 +5 -2
- package/dist/lib/rig-control-qa.d.ts +319 -0
- package/dist/lib/rig-controls.d.ts +13 -0
- package/dist/lib/rig-format.d.ts +912 -0
- 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 +170 -5
- package/dist/lib/runtime.js.map +4 -4
- package/dist/lib/tools.js +274 -15
- 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
|
@@ -227,6 +227,7 @@ var Key = z.object({
|
|
|
227
227
|
});
|
|
228
228
|
var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
|
|
229
229
|
var Track = z.discriminatedUnion("target", [
|
|
230
|
+
z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
|
|
230
231
|
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
231
232
|
z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
232
233
|
z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
@@ -249,6 +250,25 @@ var Animation = z.object({
|
|
|
249
250
|
/** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
|
|
250
251
|
preset: z.object({ name: z.string(), params: z.record(z.unknown()) }).optional()
|
|
251
252
|
});
|
|
253
|
+
var RigControl = z.object({
|
|
254
|
+
id: Id,
|
|
255
|
+
label: z.string().optional(),
|
|
256
|
+
min: z.number().finite(),
|
|
257
|
+
max: z.number().finite(),
|
|
258
|
+
default: z.number().finite()
|
|
259
|
+
}).strict();
|
|
260
|
+
var ControlValues = z.array(z.union([z.number().finite(), z.array(z.number().finite()).min(1)])).min(2);
|
|
261
|
+
var ControlChannel = z.discriminatedUnion("target", [
|
|
262
|
+
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
|
|
263
|
+
z.object({ target: z.literal("slot"), id: Id, prop: z.literal("alpha"), values: ControlValues }).strict(),
|
|
264
|
+
z.object({ target: z.literal("deform"), id: Id, prop: z.literal("vertices"), values: ControlValues }).strict()
|
|
265
|
+
]);
|
|
266
|
+
var ControlBinding = z.object({
|
|
267
|
+
id: Id,
|
|
268
|
+
axes: z.array(z.object({ control: Id, keys: z.array(z.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
|
|
269
|
+
/** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
|
|
270
|
+
channels: z.array(ControlChannel).min(1)
|
|
271
|
+
}).strict();
|
|
252
272
|
var Reference = z.object({
|
|
253
273
|
src: z.string(),
|
|
254
274
|
opacity: z.number().min(0).max(1).default(0.5),
|
|
@@ -274,7 +294,9 @@ var RigSchema = z.object({
|
|
|
274
294
|
ik: z.array(IkConstraint).default([]),
|
|
275
295
|
transform: z.array(TransformConstraint).default([]),
|
|
276
296
|
path: z.array(PathConstraint).default([]),
|
|
277
|
-
animations: z.array(Animation)
|
|
297
|
+
animations: z.array(Animation),
|
|
298
|
+
controls: z.array(RigControl).optional(),
|
|
299
|
+
controlBindings: z.array(ControlBinding).optional()
|
|
278
300
|
});
|
|
279
301
|
var RigDocument = z.preprocess((input) => {
|
|
280
302
|
const v = input?.version;
|
|
@@ -349,6 +371,8 @@ function validate(doc) {
|
|
|
349
371
|
dup(doc.ik, "ik");
|
|
350
372
|
dup(doc.transform, "transform");
|
|
351
373
|
dup(doc.path, "path");
|
|
374
|
+
dup(doc.controls ?? [], "control");
|
|
375
|
+
dup(doc.controlBindings ?? [], "control binding");
|
|
352
376
|
const bones = new Set(doc.bones.map((b) => b.id));
|
|
353
377
|
const assets = new Map(doc.assets.map((a) => [a.id, a]));
|
|
354
378
|
const slots = new Set(doc.slots.map((s) => s.id));
|
|
@@ -423,13 +447,47 @@ function validate(doc) {
|
|
|
423
447
|
for (const b of c.bones) if (!bones.has(b)) errs.push(`path "${c.id}": unknown bone "${b}"`);
|
|
424
448
|
if (!slots.has(c.target)) errs.push(`path "${c.id}": unknown target slot "${c.target}"`);
|
|
425
449
|
}
|
|
450
|
+
const controls = new Map((doc.controls ?? []).map((c) => [c.id, c]));
|
|
451
|
+
const driven = /* @__PURE__ */ new Set();
|
|
452
|
+
for (const c of controls.values()) if (!(c.min < c.max && c.default >= c.min && c.default <= c.max))
|
|
453
|
+
errs.push(`control "${c.id}": require min < max and default within range`);
|
|
454
|
+
for (const b of doc.controlBindings ?? []) {
|
|
455
|
+
const prefix = `control binding "${b.id}"`;
|
|
456
|
+
if (new Set(b.axes.map((a) => a.control)).size !== b.axes.length) errs.push(`${prefix}: axes must use different controls`);
|
|
457
|
+
for (const axis of b.axes) {
|
|
458
|
+
const c = controls.get(axis.control);
|
|
459
|
+
if (!c) errs.push(`${prefix}: unknown control "${axis.control}"`);
|
|
460
|
+
else if (axis.keys[0] !== c.min || axis.keys.at(-1) !== c.max) errs.push(`${prefix}: keys must span the control's full range`);
|
|
461
|
+
if (axis.keys.some((v, i) => i > 0 && v <= axis.keys[i - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
|
|
462
|
+
}
|
|
463
|
+
const count = b.axes.reduce((n, a) => n * a.keys.length, 1);
|
|
464
|
+
for (const ch of b.channels) {
|
|
465
|
+
const key = `${ch.target}.${ch.id}.${ch.prop}`;
|
|
466
|
+
if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
|
|
467
|
+
driven.add(key);
|
|
468
|
+
if (ch.values.length !== count) errs.push(`${prefix}: ${key} needs ${count} grid values`);
|
|
469
|
+
if (ch.target === "deform") {
|
|
470
|
+
const att = attachments.get(ch.id);
|
|
471
|
+
if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
|
|
472
|
+
else {
|
|
473
|
+
const n = att.weights ? att.weights.reduce((s, a) => s + a.length * 2, 0) : att.vertices.length;
|
|
474
|
+
if (ch.values.some((v) => !Array.isArray(v) || v.length !== n)) errs.push(`${prefix}: ${key} needs arrays of ${n} offsets`);
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
|
|
478
|
+
if (ch.values.some((v) => typeof v !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
|
|
479
|
+
if (ch.target === "slot" && ch.values.some((v) => typeof v === "number" && (v < 0 || v > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
426
483
|
for (const a of doc.animations) {
|
|
427
484
|
const tracks = /* @__PURE__ */ new Set();
|
|
428
485
|
for (const t of a.tracks) {
|
|
429
486
|
const tid = trackId(t);
|
|
430
487
|
if (tracks.has(tid)) errs.push(`animation "${a.id}": duplicate track ${tid}`);
|
|
431
488
|
tracks.add(tid);
|
|
432
|
-
|
|
489
|
+
if (driven.has(tid)) errs.push(`animation "${a.id}": ${tid} is controlled by a binding; animate its control instead`);
|
|
490
|
+
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
491
|
if (!known) errs.push(`animation "${a.id}": unknown ${t.target} "${t.id}"`);
|
|
434
492
|
const bound = t.target === "deform" ? DEFORM_BOUNDS[t.prop] : void 0;
|
|
435
493
|
const att = t.target === "deform" ? attachments.get(t.id) : void 0;
|
|
@@ -437,6 +495,10 @@ function validate(doc) {
|
|
|
437
495
|
for (const k of t.keys) {
|
|
438
496
|
if (k.t <= prev && prev >= 0) errs.push(`animation "${a.id}" ${tid}: keys not strictly increasing`);
|
|
439
497
|
if (k.t > a.duration) errs.push(`animation "${a.id}" ${tid}: key at ${k.t}s exceeds duration`);
|
|
498
|
+
if (t.target === "control") {
|
|
499
|
+
const c = controls.get(t.id);
|
|
500
|
+
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`);
|
|
501
|
+
}
|
|
440
502
|
if (t.target === "slot" && t.prop === "attachment") {
|
|
441
503
|
if (k.v !== null && typeof k.v !== "string") errs.push(`animation "${a.id}" ${tid}: attachment key must be an attachment id or null`);
|
|
442
504
|
else if (typeof k.v === "string") {
|
|
@@ -475,6 +537,52 @@ function parse(input) {
|
|
|
475
537
|
return doc;
|
|
476
538
|
}
|
|
477
539
|
|
|
540
|
+
// src/rig-controls.ts
|
|
541
|
+
function controlPose(doc, values) {
|
|
542
|
+
const out = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const [id, value] of Object.entries(values)) {
|
|
544
|
+
const c = doc.controls?.find((c2) => c2.id === id);
|
|
545
|
+
if (!c) throw new Error(`unknown control "${id}"`);
|
|
546
|
+
if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
|
|
547
|
+
out.set(`control:${id}:value`, Math.max(c.min, Math.min(c.max, value)));
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function bracket(keys, value) {
|
|
552
|
+
let i = 0;
|
|
553
|
+
while (i < keys.length - 2 && value > keys[i + 1]) i++;
|
|
554
|
+
return [i, Math.max(0, Math.min(1, (value - keys[i]) / (keys[i + 1] - keys[i])))];
|
|
555
|
+
}
|
|
556
|
+
function sampleBinding(binding, values) {
|
|
557
|
+
const [x, u] = bracket(binding.axes[0].keys, values[0]);
|
|
558
|
+
const [y, v] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
|
|
559
|
+
const width = binding.axes[0].keys.length, a = y * width + x;
|
|
560
|
+
const mix = (a2, b, u2) => a2 + (b - a2) * u2;
|
|
561
|
+
return binding.channels.map((ch) => {
|
|
562
|
+
const read = (i, j) => j === void 0 ? ch.values[i] : ch.values[i][j];
|
|
563
|
+
const at = (j) => {
|
|
564
|
+
const top = mix(read(a, j), read(a + 1, j), u);
|
|
565
|
+
return binding.axes[1] ? mix(top, mix(read(a + width, j), read(a + width + 1, j), u), v) : top;
|
|
566
|
+
};
|
|
567
|
+
return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j) => at(j)) : at();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function resolveControls(doc, input) {
|
|
571
|
+
if (!doc.controlBindings?.length) return input;
|
|
572
|
+
const out = new Map(input), controls = new Map(doc.controls?.map((c) => [c.id, c]));
|
|
573
|
+
for (const b of doc.controlBindings) {
|
|
574
|
+
const values = b.axes.map((axis) => {
|
|
575
|
+
const c = controls.get(axis.control);
|
|
576
|
+
const value = input.get(`control:${c.id}:value`) ?? c.default;
|
|
577
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c.id}" must be finite`);
|
|
578
|
+
return Math.max(c.min, Math.min(c.max, value));
|
|
579
|
+
});
|
|
580
|
+
const sampled = sampleBinding(b, values);
|
|
581
|
+
b.channels.forEach((ch, i) => out.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i]));
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
|
|
478
586
|
// src/rig-anim.ts
|
|
479
587
|
var DEG2 = Math.PI / 180;
|
|
480
588
|
var named = {
|
|
@@ -578,6 +686,7 @@ function blendPoses(from, to, w, restOf) {
|
|
|
578
686
|
}
|
|
579
687
|
function restValue(doc, key) {
|
|
580
688
|
const [target, id, prop] = key.split(":");
|
|
689
|
+
if (target === "control") return doc.controls?.find((c) => c.id === id)?.default ?? 0;
|
|
581
690
|
if (target === "bone") return doc.bones.find((b) => b.id === id).transform[prop];
|
|
582
691
|
if (target === "slot") {
|
|
583
692
|
const s = doc.slots.find((s2) => s2.id === id);
|
|
@@ -680,7 +789,9 @@ function updateSubtree(skel, root) {
|
|
|
680
789
|
for (const k of kids.get(id) ?? []) stack.push(k.id);
|
|
681
790
|
}
|
|
682
791
|
}
|
|
683
|
-
function evaluate(doc, pose) {
|
|
792
|
+
function evaluate(doc, pose, modifiers) {
|
|
793
|
+
pose = resolveControls(doc, pose);
|
|
794
|
+
if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
|
|
684
795
|
const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
|
|
685
796
|
for (const b of boneOrder(doc)) {
|
|
686
797
|
const s = localOf(doc, pose, b);
|
|
@@ -1485,9 +1596,23 @@ var RigPlayer = class {
|
|
|
1485
1596
|
this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
|
|
1486
1597
|
this.setPose(/* @__PURE__ */ new Map());
|
|
1487
1598
|
}
|
|
1599
|
+
inputPose = /* @__PURE__ */ new Map();
|
|
1600
|
+
controlOverrides = /* @__PURE__ */ new Map();
|
|
1601
|
+
/** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
|
|
1602
|
+
setControls(values) {
|
|
1603
|
+
this.controlOverrides = controlPose(this.doc, values);
|
|
1604
|
+
this.setPose(this.inputPose);
|
|
1605
|
+
}
|
|
1606
|
+
resetControls() {
|
|
1607
|
+
this.controlOverrides.clear();
|
|
1608
|
+
this.setPose(this.inputPose);
|
|
1609
|
+
}
|
|
1488
1610
|
view = new Container();
|
|
1489
1611
|
state;
|
|
1490
1612
|
items = [];
|
|
1613
|
+
beforePose = /* @__PURE__ */ new Set();
|
|
1614
|
+
afterPose = /* @__PURE__ */ new Set();
|
|
1615
|
+
clipPoints = /* @__PURE__ */ new Map();
|
|
1491
1616
|
skel;
|
|
1492
1617
|
textures;
|
|
1493
1618
|
pages = /* @__PURE__ */ new Map();
|
|
@@ -1496,6 +1621,35 @@ var RigPlayer = class {
|
|
|
1496
1621
|
/** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
|
|
1497
1622
|
ghosts = /* @__PURE__ */ new Set();
|
|
1498
1623
|
clipGraphics = /* @__PURE__ */ new Map();
|
|
1624
|
+
/** Read-only render handles for in-page tools. Nodes remain owned by the player. */
|
|
1625
|
+
get renderItems() {
|
|
1626
|
+
return this.items;
|
|
1627
|
+
}
|
|
1628
|
+
/** Current document-space clipping polygons, keyed by path attachment id. */
|
|
1629
|
+
get clipPolygons() {
|
|
1630
|
+
return this.clipPoints;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Modify resolved control outputs before skeleton evaluation, constraints and skinning.
|
|
1634
|
+
* Callbacks run in registration order, each with its own map. Change control inputs via setControls.
|
|
1635
|
+
*/
|
|
1636
|
+
onBeforePose(callback) {
|
|
1637
|
+
this.beforePose.add(callback);
|
|
1638
|
+
return () => {
|
|
1639
|
+
this.beforePose.delete(callback);
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
/** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
|
|
1643
|
+
onAfterPose(callback) {
|
|
1644
|
+
this.afterPose.add(callback);
|
|
1645
|
+
return () => {
|
|
1646
|
+
this.afterPose.delete(callback);
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
/** Rebuild the current pose without advancing playback; useful for reversible overlays. */
|
|
1650
|
+
refreshPose() {
|
|
1651
|
+
this.setPose(this.inputPose);
|
|
1652
|
+
}
|
|
1499
1653
|
/** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
|
|
1500
1654
|
pageTexture(tex) {
|
|
1501
1655
|
let page = this.pages.get(tex.source);
|
|
@@ -1550,7 +1704,11 @@ var RigPlayer = class {
|
|
|
1550
1704
|
}
|
|
1551
1705
|
/** Apply a pose (rest where silent) to the display objects. */
|
|
1552
1706
|
setPose(pose) {
|
|
1553
|
-
|
|
1707
|
+
this.inputPose = new Map(pose);
|
|
1708
|
+
if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
|
|
1709
|
+
this.clipPoints.clear();
|
|
1710
|
+
const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
|
|
1711
|
+
pose = skel.pose;
|
|
1554
1712
|
const clips = new Set(this.doc.slots.flatMap((s) => s.clip ? [s.clip] : []));
|
|
1555
1713
|
for (const [id, g] of this.clipGraphics) if (!clips.has(id)) {
|
|
1556
1714
|
g.destroy();
|
|
@@ -1568,7 +1726,9 @@ var RigPlayer = class {
|
|
|
1568
1726
|
const vertices = attachmentWorldVertices(skel, att.slot, att);
|
|
1569
1727
|
const n = Math.max(32, vertices.length * 4);
|
|
1570
1728
|
const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n }, (_, i) => i / n), true);
|
|
1571
|
-
|
|
1729
|
+
const polygon = points.flatMap((p) => [p.x, p.y]);
|
|
1730
|
+
this.clipPoints.set(id, polygon);
|
|
1731
|
+
g.clear().poly(polygon, true).fill(16777215);
|
|
1572
1732
|
}
|
|
1573
1733
|
const m = new Matrix();
|
|
1574
1734
|
const orderV = pose.get(pk("drawOrder", "order", "order"));
|
|
@@ -1589,6 +1749,7 @@ var RigPlayer = class {
|
|
|
1589
1749
|
it.node.tint = rgbOf(color);
|
|
1590
1750
|
it.node.zIndex = order.size ? order.get(s.id) ?? 0 : numOf(pose, pk("slot", s.id, "z"), s.z);
|
|
1591
1751
|
if (it.skinned) {
|
|
1752
|
+
it.node.setFromMatrix(m.identity());
|
|
1592
1753
|
const v = attachmentWorldVertices(skel, s.id, it.att);
|
|
1593
1754
|
const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
|
|
1594
1755
|
buf.data.set(v);
|
|
@@ -1621,6 +1782,7 @@ var RigPlayer = class {
|
|
|
1621
1782
|
it.node.scale.y *= h / (this.textures.get(att.asset)?.height ?? h);
|
|
1622
1783
|
}
|
|
1623
1784
|
}
|
|
1785
|
+
for (const callback of this.afterPose) callback(this);
|
|
1624
1786
|
}
|
|
1625
1787
|
/** World-space bone positions — used by editor gizmos and renderPreview overlays */
|
|
1626
1788
|
bonePositions() {
|
|
@@ -1697,9 +1859,12 @@ export {
|
|
|
1697
1859
|
RigPlayer,
|
|
1698
1860
|
RigSymbol,
|
|
1699
1861
|
atlasUV,
|
|
1862
|
+
controlPose,
|
|
1700
1863
|
createTextures,
|
|
1701
1864
|
migrateV1,
|
|
1702
1865
|
parse,
|
|
1866
|
+
resolveControls,
|
|
1867
|
+
sampleBinding,
|
|
1703
1868
|
validate
|
|
1704
1869
|
};
|
|
1705
1870
|
//# sourceMappingURL=runtime.js.map
|