@energy8platform/golem 0.6.0 → 0.7.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.
Files changed (64) hide show
  1. package/README.md +14 -0
  2. package/dist/editor.css +3 -4
  3. package/dist/editor.js +108 -61
  4. package/dist/lib/cli.js +101 -70
  5. package/dist/lib/cli.js.map +4 -4
  6. package/dist/lib/e8/agent.d.ts +35 -0
  7. package/dist/lib/e8/host.d.ts +22 -0
  8. package/dist/lib/e8/node.d.ts +66 -0
  9. package/dist/lib/e8/runtime.d.ts +10 -0
  10. package/dist/lib/e8/schema.d.ts +3 -0
  11. package/dist/lib/e8/types.d.ts +80 -0
  12. package/dist/lib/e8-agent.js +6781 -0
  13. package/dist/lib/e8-agent.js.map +7 -0
  14. package/dist/lib/e8-client.js +116 -0
  15. package/dist/lib/e8-host.js +6929 -0
  16. package/dist/lib/e8-host.js.map +7 -0
  17. package/dist/lib/e8-runtime.js +2024 -0
  18. package/dist/lib/e8-runtime.js.map +7 -0
  19. package/dist/lib/e8-schema.js +7 -0
  20. package/dist/lib/e8-schema.js.map +7 -0
  21. package/dist/lib/editor/api.d.ts +1 -0
  22. package/dist/lib/editor/embed.d.ts +9 -0
  23. package/dist/lib/editor/server.d.ts +23 -1
  24. package/dist/lib/editor/store.d.ts +2 -0
  25. package/dist/lib/editor/timeline.d.ts +15 -0
  26. package/dist/lib/editor-entry.d.ts +1 -1
  27. package/dist/lib/editor-entry.js +102 -70
  28. package/dist/lib/editor-entry.js.map +4 -4
  29. package/dist/lib/rig-import-layers.d.ts +2 -0
  30. package/dist/lib/rig-version.d.ts +12 -0
  31. package/dist/lib/tool-schema.d.ts +3 -0
  32. package/dist/lib/tools.d.ts +2 -0
  33. package/dist/lib/tools.js +49 -10
  34. package/dist/lib/tools.js.map +4 -4
  35. package/editor.html +2 -2
  36. package/package.json +38 -3
  37. package/skills/e8-golem/SKILL.md +71 -0
  38. package/skills/golem-symbol-animation/SKILL.md +87 -0
  39. package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
  40. package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
  41. package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
  42. package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
  43. package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
  44. package/skills/golem-symbol-animation/references/motion-craft.md +113 -0
  45. package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
  46. package/skills/golem-symbol-animation/references/quantitative-qa.md +106 -0
  47. package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
  48. package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
  49. package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
  50. package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
  51. package/skills/golem-symbol-cutting/SKILL.md +70 -0
  52. package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
  53. package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
  54. package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
  55. package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
  56. package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
  57. package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
  58. package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
  59. package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
  60. package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
  61. package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
  62. package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
  63. package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
  64. package/skills/golem-symbol-cutting/scripts/hybrid_parts.py +279 -0
@@ -0,0 +1,2024 @@
1
+ // src/e8/runtime.ts
2
+ import { extensions } from "pixi.js";
3
+
4
+ // src/e8/node.ts
5
+ import { Assets, Container as Container2, ExtensionType, Ticker } from "pixi.js";
6
+ import Schema from "schemastery";
7
+
8
+ // src/rig-runtime.ts
9
+ import { Container, Sprite, Mesh, MeshGeometry, MeshPlane, Texture, Matrix, Rectangle, Graphics } from "pixi.js";
10
+
11
+ // src/rig-format.ts
12
+ import { z } from "zod";
13
+
14
+ // src/rig-deform.ts
15
+ var DEFORM_BOUNDS = { bulge: 0.9, bend: 1, sway: 1, squash: 0.9 };
16
+ var T = [0, 0.5, 1];
17
+ var basis = (t) => [(1 - t) * (1 - 2 * t), 4 * t * (1 - t), t * (2 * t - 1)];
18
+ function controlOffsets(params, w, h, pivot, out = new Float64Array(18)) {
19
+ out.fill(0);
20
+ const bulge = params.bulge ?? 0, bend = params.bend ?? 0, sway = params.sway ?? 0, squash = params.squash ?? 0;
21
+ for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) {
22
+ const i = (r * 3 + c) * 2, u = T[c], v = T[r], mid = r === 1 ? 1 : 0;
23
+ out[i] = mid * (bulge * w * (u - 0.5) + bend * w) + sway * w * (v - pivot.y) + squash * w * (u - pivot.x) * 0.5 + 0;
24
+ out[i + 1] = -squash * h * (v - pivot.y) + 0;
25
+ }
26
+ for (const k in params) {
27
+ if (k.length !== 4 || k[0] !== "p") continue;
28
+ const r = +k[1], c = +k[2];
29
+ if (!(r >= 0 && r < 3 && c >= 0 && c < 3) || k[3] !== "x" && k[3] !== "y") continue;
30
+ out[(r * 3 + c) * 2 + (k[3] === "y" ? 1 : 0)] += params[k];
31
+ }
32
+ return out;
33
+ }
34
+ function evalPatch(offsets, u, v) {
35
+ const bu = basis(u), bv = basis(v);
36
+ let x = 0, y = 0;
37
+ for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) {
38
+ const wgt = bv[r] * bu[c], i = (r * 3 + c) * 2;
39
+ x += wgt * offsets[i];
40
+ y += wgt * offsets[i + 1];
41
+ }
42
+ return [x, y];
43
+ }
44
+ function gridPositions(offsets, w, h, pivot, n, out = new Float32Array(2 * n * n)) {
45
+ const px = pivot.x * w, py = pivot.y * h;
46
+ for (let j = 0; j < n; j++) for (let i = 0; i < n; i++) {
47
+ const u = i / (n - 1), v = j / (n - 1), [ox, oy] = evalPatch(offsets, u, v), k = (j * n + i) * 2;
48
+ out[k] = u * w - px + ox;
49
+ out[k + 1] = v * h - py + oy;
50
+ }
51
+ return out;
52
+ }
53
+
54
+ // src/rig-format.ts
55
+ var ID_RE = /^[a-z][a-z0-9_]*$/;
56
+ var Id = z.string().regex(ID_RE, "snake_case id");
57
+ var Ease = z.union([
58
+ z.enum(["linear", "step", "ease_in", "ease_out", "ease_in_out", "minimum_jerk", "back_out", "elastic_out"]),
59
+ // cubic-bezier control points, same semantics as CSS
60
+ z.object({ bezier: z.tuple([z.number(), z.number(), z.number(), z.number()]) })
61
+ ]);
62
+ var Transform = z.object({
63
+ x: z.number().default(0),
64
+ y: z.number().default(0),
65
+ rotation: z.number().default(0),
66
+ scaleX: z.number().default(1),
67
+ scaleY: z.number().default(1),
68
+ shearX: z.number().default(0),
69
+ shearY: z.number().default(0)
70
+ });
71
+ var Color = z.string().regex(/^#[0-9a-fA-F]{8}$/, "color must be #rrggbbaa");
72
+ var Blend = z.enum(["normal", "add", "multiply", "screen"]);
73
+ var Inherit = z.enum(["normal", "onlyTranslation", "noRotationOrReflection", "noScale", "noScaleOrReflection"]);
74
+ var Asset = z.object({
75
+ id: Id,
76
+ /** relative path inside the rig package, e.g. "layers/arm_l.png" */
77
+ src: z.string(),
78
+ /** original (untrimmed, unrotated) image size — the space attachments and uvs refer to */
79
+ width: z.number().int().positive(),
80
+ height: z.number().int().positive(),
81
+ /** packed region [x, y, w, h] inside `src` when `src` is an atlas (w/h as packed: trimmed, rotated) */
82
+ frame: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
83
+ /** the packed region is stored rotated 90° clockwise */
84
+ rotate: z.union([z.literal(0), z.literal(90)]).default(0),
85
+ /** offset [x, y] of the packed (trimmed) region inside the original image */
86
+ trim: z.tuple([z.number(), z.number()]).optional(),
87
+ /** where this layer came from — provenance for agents */
88
+ origin: z.object({
89
+ sourceImage: z.string().optional(),
90
+ bboxSpace: z.enum(["source", "document"]).optional(),
91
+ /** bbox in source image: [x, y, w, h] */
92
+ bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
93
+ inpainted: z.boolean().default(false),
94
+ prompt: z.string().optional()
95
+ }).optional()
96
+ });
97
+ var Bone = z.object({
98
+ id: Id,
99
+ parent: Id.optional(),
100
+ /** visual length for the editor gizmo and for chains (ik/path), no effect on child transforms */
101
+ length: z.number().nonnegative().default(0),
102
+ transform: Transform.default({}),
103
+ /** which parts of the parent's world transform this bone inherits */
104
+ inherit: Inherit.default("normal"),
105
+ /** semantic hint: "head", "arm_l", "torso"… drives auto-rig & presets */
106
+ role: z.string().optional(),
107
+ /** editor colour, no runtime effect */
108
+ color: z.string().optional()
109
+ });
110
+ var Slot = z.object({
111
+ id: Id,
112
+ bone: Id,
113
+ /** Closed path attachment used as a moving clipping mask. */
114
+ clip: Id.optional(),
115
+ /** active attachment id; undefined = hidden */
116
+ attachment: Id.optional(),
117
+ alpha: z.number().min(0).max(1).default(1),
118
+ /** draw order, higher = on top */
119
+ z: z.number().int().default(0),
120
+ /** blend mode: `add` for glows/fire on black, `multiply` for shadows */
121
+ blend: Blend.default("normal"),
122
+ /** tint, #rrggbbaa */
123
+ color: Color.default("#ffffffff")
124
+ });
125
+ var Influence = z.object({ bone: Id, x: z.number(), y: z.number(), w: z.number() });
126
+ var VertexData = {
127
+ /** unweighted: [x, y, …] in the slot bone's local space */
128
+ vertices: z.array(z.number()).optional(),
129
+ /** weighted: one influence list per vertex */
130
+ weights: z.array(z.array(Influence).min(1)).optional()
131
+ };
132
+ var RegionAttachment = z.object({
133
+ id: Id,
134
+ slot: Id,
135
+ type: z.literal("region"),
136
+ asset: Id,
137
+ /** placement of the image centre in the slot bone's local space */
138
+ x: z.number().default(0),
139
+ y: z.number().default(0),
140
+ rotation: z.number().default(0),
141
+ scaleX: z.number().default(1),
142
+ scaleY: z.number().default(1),
143
+ /** drawn size; default: the asset size */
144
+ width: z.number().positive().optional(),
145
+ height: z.number().positive().optional()
146
+ });
147
+ var MeshAttachment = z.object({
148
+ id: Id,
149
+ slot: Id,
150
+ type: z.literal("mesh"),
151
+ asset: Id,
152
+ /** texture coordinates 0..1 in the asset's original image, 2 per vertex */
153
+ uvs: z.array(z.number()),
154
+ triangles: z.array(z.number().int().nonnegative()),
155
+ ...VertexData,
156
+ /** number of hull vertices (the outline is uvs[0..hull)) */
157
+ hull: z.number().int().nonnegative().default(0),
158
+ width: z.number().positive().optional(),
159
+ height: z.number().positive().optional()
160
+ });
161
+ var PathAttachment = z.object({
162
+ id: Id,
163
+ slot: Id,
164
+ type: z.literal("path"),
165
+ ...VertexData,
166
+ /** curve lengths for constant-speed positioning */
167
+ lengths: z.array(z.number()).default([]),
168
+ closed: z.boolean().default(false),
169
+ constantSpeed: z.boolean().default(true),
170
+ /** Spine-style active clipping span in the posed draw order; end slot is inclusive. */
171
+ clipping: z.object({ end: Id.optional() }).optional()
172
+ });
173
+ var Attachment = z.discriminatedUnion("type", [RegionAttachment, MeshAttachment, PathAttachment]);
174
+ var IkConstraint = z.object({
175
+ id: Id,
176
+ order: z.number().int().default(0),
177
+ bones: z.array(Id).min(1),
178
+ target: Id,
179
+ iterations: z.number().int().min(1).max(1e3).optional(),
180
+ tolerance: z.number().positive().optional(),
181
+ mix: z.number().default(1),
182
+ softness: z.number().default(0),
183
+ bendPositive: z.boolean().default(true),
184
+ compress: z.boolean().default(false),
185
+ stretch: z.boolean().default(false),
186
+ uniform: z.boolean().default(false)
187
+ });
188
+ var TransformConstraint = z.object({
189
+ id: Id,
190
+ order: z.number().int().default(0),
191
+ bones: z.array(Id).min(1),
192
+ target: Id,
193
+ local: z.boolean().default(false),
194
+ relative: z.boolean().default(false),
195
+ rotation: z.number().default(0),
196
+ x: z.number().default(0),
197
+ y: z.number().default(0),
198
+ scaleX: z.number().default(0),
199
+ scaleY: z.number().default(0),
200
+ shearY: z.number().default(0),
201
+ mixRotate: z.number().default(1),
202
+ mixX: z.number().default(1),
203
+ mixY: z.number().default(1),
204
+ mixScaleX: z.number().default(1),
205
+ mixScaleY: z.number().default(1),
206
+ mixShearY: z.number().default(1)
207
+ });
208
+ var PathConstraint = z.object({
209
+ id: Id,
210
+ order: z.number().int().default(0),
211
+ bones: z.array(Id).min(1),
212
+ /** slot whose active attachment is a path */
213
+ target: Id,
214
+ positionMode: z.enum(["fixed", "percent"]).default("percent"),
215
+ spacingMode: z.enum(["length", "fixed", "percent", "proportional"]).default("length"),
216
+ rotateMode: z.enum(["tangent", "chain", "chainScale"]).default("tangent"),
217
+ rotation: z.number().default(0),
218
+ position: z.number().default(0),
219
+ spacing: z.number().default(0),
220
+ mixRotate: z.number().default(1),
221
+ mixX: z.number().default(1),
222
+ mixY: z.number().default(1)
223
+ });
224
+ var BoneProp = z.enum(["x", "y", "rotation", "scaleX", "scaleY", "shearX", "shearY"]);
225
+ var SlotProp = z.enum(["alpha", "attachment", "z", "color"]);
226
+ var DeformProp = z.string().regex(/^(bulge|bend|sway|squash|p[0-2][0-2][xy]|vertices)$/, "deform prop must be bulge|bend|sway|squash|p<row><col>x|y|vertices");
227
+ var IkProp = z.enum(["mix", "softness", "bendPositive", "compress", "stretch"]);
228
+ var TransformProp = z.enum(["mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY", "x", "y", "rotation", "scaleX", "scaleY", "shearY"]);
229
+ var PathProp = z.enum(["position", "spacing", "mixRotate", "mixX", "mixY"]);
230
+ var KeyValue = z.union([z.number(), z.string(), z.null(), z.array(z.number()), z.array(z.string())]);
231
+ var Key = z.object({
232
+ t: z.number().nonnegative(),
233
+ v: KeyValue,
234
+ /** easing INTO the next key */
235
+ ease: Ease.default("ease_in_out"),
236
+ /** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
237
+ componentEase: z.array(Ease).min(1).optional()
238
+ });
239
+ var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
240
+ var Track = z.discriminatedUnion("target", [
241
+ z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
242
+ z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
243
+ z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: z.array(Key).min(1) }),
244
+ z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: z.array(Key).min(1) }),
245
+ z.object({ target: z.literal("ik"), id: Id, prop: IkProp, ...Interpolation, keys: z.array(Key).min(1) }),
246
+ z.object({ target: z.literal("transform"), id: Id, prop: TransformProp, ...Interpolation, keys: z.array(Key).min(1) }),
247
+ z.object({ target: z.literal("path"), id: Id, prop: PathProp, ...Interpolation, keys: z.array(Key).min(1) }),
248
+ z.object({ target: z.literal("drawOrder"), id: Id.default("order"), prop: z.literal("order").default("order"), ...Interpolation, keys: z.array(Key).min(1) })
249
+ ]);
250
+ var Event = z.object({
251
+ t: z.number().nonnegative(),
252
+ name: z.string(),
253
+ payload: z.record(z.unknown()).optional()
254
+ });
255
+ var Animation = z.object({
256
+ id: Id,
257
+ duration: z.number().positive(),
258
+ loop: z.boolean().default(false),
259
+ tracks: z.array(Track).default([]),
260
+ events: z.array(Event).default([]),
261
+ /** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
262
+ preset: z.object({ name: z.string(), params: z.record(z.unknown()) }).optional()
263
+ });
264
+ var RigControl = z.object({
265
+ id: Id,
266
+ label: z.string().optional(),
267
+ min: z.number().finite(),
268
+ max: z.number().finite(),
269
+ default: z.number().finite()
270
+ }).strict();
271
+ var ControlValues = z.array(z.union([z.number().finite(), z.array(z.number().finite()).min(1)])).min(2);
272
+ var ControlChannel = z.discriminatedUnion("target", [
273
+ z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
274
+ z.object({ target: z.literal("slot"), id: Id, prop: z.literal("alpha"), values: ControlValues }).strict(),
275
+ z.object({ target: z.literal("deform"), id: Id, prop: z.literal("vertices"), values: ControlValues }).strict()
276
+ ]);
277
+ var ControlBinding = z.object({
278
+ id: Id,
279
+ axes: z.array(z.object({ control: Id, keys: z.array(z.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
280
+ /** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
281
+ channels: z.array(ControlChannel).min(1)
282
+ }).strict();
283
+ var Reference = z.object({
284
+ src: z.string(),
285
+ opacity: z.number().min(0).max(1).default(0.5),
286
+ x: z.number().default(0),
287
+ y: z.number().default(0),
288
+ scale: z.number().positive().default(1)
289
+ });
290
+ var RigSchema = z.object({
291
+ format: z.literal("energy8.rig"),
292
+ version: z.literal(2),
293
+ meta: z.object({
294
+ name: z.string(),
295
+ width: z.number().int().positive(),
296
+ height: z.number().int().positive(),
297
+ /** free-form notes for agents & humans: what this rig is, what's intentionally missing */
298
+ notes: z.string().optional(),
299
+ reference: Reference.optional()
300
+ }),
301
+ assets: z.array(Asset),
302
+ bones: z.array(Bone).min(1),
303
+ slots: z.array(Slot),
304
+ attachments: z.array(Attachment).default([]),
305
+ ik: z.array(IkConstraint).default([]),
306
+ transform: z.array(TransformConstraint).default([]),
307
+ path: z.array(PathConstraint).default([]),
308
+ animations: z.array(Animation),
309
+ controls: z.array(RigControl).optional(),
310
+ controlBindings: z.array(ControlBinding).optional()
311
+ });
312
+ var RigDocument = z.preprocess((input) => {
313
+ const v = input?.version;
314
+ return v === 0 || v === 1 ? migrateV1(input) : input;
315
+ }, RigSchema);
316
+ var DEG = Math.PI / 180;
317
+ function migrateV1(input) {
318
+ const doc = input;
319
+ const assets = doc.assets ?? [], slots = doc.slots ?? [], anims = doc.animations ?? [];
320
+ const assetById = new Map(assets.map((a) => [a.id, a]));
321
+ const shows = /* @__PURE__ */ new Map();
322
+ const add = (slot, asset) => (shows.get(slot) ?? shows.set(slot, /* @__PURE__ */ new Set()).get(slot)).add(asset);
323
+ for (const s of slots) if (s.attachment) add(s.id, s.attachment);
324
+ for (const a of anims) for (const t of a.tracks ?? [])
325
+ if (t.target === "slot" && t.prop === "attachment") {
326
+ for (const k of t.keys ?? []) if (typeof k.v === "string") add(t.id, k.v);
327
+ }
328
+ const usage = /* @__PURE__ */ new Map();
329
+ for (const set of shows.values()) for (const asset of set) usage.set(asset, (usage.get(asset) ?? 0) + 1);
330
+ const attachmentId = (slot, asset) => (usage.get(asset) ?? 0) > 1 ? `${slot}_${asset}` : asset;
331
+ const attachments = [];
332
+ for (const s of slots) {
333
+ const pivot = s.pivot ?? { x: 0.5, y: 0.5 }, o = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, ...s.offset ?? {} };
334
+ for (const asset of shows.get(s.id) ?? []) {
335
+ const a = assetById.get(asset);
336
+ if (!a) continue;
337
+ const cx = (0.5 - pivot.x) * a.width, cy = (0.5 - pivot.y) * a.height;
338
+ const r = o.rotation * DEG, cos = Math.cos(r), sin = Math.sin(r);
339
+ const x = o.x + cos * o.scaleX * cx - sin * o.scaleY * cy, y = o.y + sin * o.scaleX * cx + cos * o.scaleY * cy;
340
+ attachments.push({ id: attachmentId(s.id, asset), slot: s.id, type: "region", asset, x: round(x), y: round(y), rotation: o.rotation, scaleX: o.scaleX, scaleY: o.scaleY });
341
+ }
342
+ }
343
+ const restAttachment = (slot) => {
344
+ const s = slots.find((x) => x.id === slot);
345
+ const first = s?.attachment ?? [...shows.get(slot) ?? []][0];
346
+ return first ? attachmentId(slot, first) : void 0;
347
+ };
348
+ const newSlots = slots.map((s) => {
349
+ const { pivot, offset, attachment, ...rest } = s;
350
+ return { ...rest, ...attachment ? { attachment: attachmentId(s.id, attachment) } : {} };
351
+ });
352
+ const newAnims = anims.map((a) => ({
353
+ ...a,
354
+ tracks: (a.tracks ?? []).flatMap((t) => {
355
+ if (t.target === "slot" && t.prop === "attachment")
356
+ return [{ ...t, keys: t.keys.map((k) => ({ ...k, v: typeof k.v === "string" ? attachmentId(t.id, k.v) : k.v })) }];
357
+ if (t.target === "deform") {
358
+ const id = restAttachment(t.id);
359
+ return id ? [{ ...t, id }] : [];
360
+ }
361
+ return [t];
362
+ })
363
+ }));
364
+ return { ...doc, version: 2, slots: newSlots, attachments: [...doc.attachments ?? [], ...attachments], animations: newAnims };
365
+ }
366
+ var round = (v) => Math.round(v * 1e6) / 1e6 + 0;
367
+ var trackId = (t) => `${t.target}.${t.id}.${t.prop}`;
368
+ function validate(doc) {
369
+ const errs = [];
370
+ const dup = (xs, kind) => {
371
+ const seen = /* @__PURE__ */ new Set();
372
+ for (const x of xs) {
373
+ if (seen.has(x.id)) errs.push(`${kind}: duplicate id "${x.id}"`);
374
+ seen.add(x.id);
375
+ }
376
+ };
377
+ dup(doc.assets, "asset");
378
+ dup(doc.bones, "bone");
379
+ dup(doc.slots, "slot");
380
+ dup(doc.attachments, "attachment");
381
+ dup(doc.animations, "animation");
382
+ dup(doc.ik, "ik");
383
+ dup(doc.transform, "transform");
384
+ dup(doc.path, "path");
385
+ dup(doc.controls ?? [], "control");
386
+ dup(doc.controlBindings ?? [], "control binding");
387
+ const bones = new Set(doc.bones.map((b) => b.id));
388
+ const assets = new Map(doc.assets.map((a) => [a.id, a]));
389
+ const slots = new Set(doc.slots.map((s) => s.id));
390
+ const attachments = new Map(doc.attachments.map((a) => [a.id, a]));
391
+ const iks = new Set(doc.ik.map((c) => c.id)), transforms = new Set(doc.transform.map((c) => c.id)), paths = new Set(doc.path.map((c) => c.id));
392
+ for (const b of doc.bones) {
393
+ if (b.parent && !bones.has(b.parent)) errs.push(`bone "${b.id}": unknown parent "${b.parent}"`);
394
+ }
395
+ for (const b of doc.bones) {
396
+ const seen = /* @__PURE__ */ new Set();
397
+ let cur = b;
398
+ while (cur?.parent) {
399
+ if (seen.has(cur.id)) {
400
+ errs.push(`bone "${b.id}": parent cycle`);
401
+ break;
402
+ }
403
+ seen.add(cur.id);
404
+ cur = doc.bones.find((x) => x.id === cur.parent);
405
+ }
406
+ }
407
+ if (!doc.bones.some((b) => !b.parent)) errs.push("no root bone");
408
+ for (const a of doc.assets) {
409
+ if (a.frame && (a.frame[2] <= 0 || a.frame[3] <= 0)) errs.push(`asset "${a.id}": frame must have positive size`);
410
+ if (a.frame && !a.trim && !a.rotate && (a.frame[2] !== a.width || a.frame[3] !== a.height)) errs.push(`asset "${a.id}": frame size must equal width\xD7height (or declare trim/rotate)`);
411
+ }
412
+ for (const s of doc.slots) {
413
+ if (s.clip) {
414
+ const mask = attachments.get(s.clip);
415
+ if (mask?.type !== "path" || !mask.closed) errs.push(`slot "${s.id}": clip must name a closed path attachment`);
416
+ }
417
+ if (!bones.has(s.bone)) errs.push(`slot "${s.id}": unknown bone "${s.bone}"`);
418
+ if (s.attachment) {
419
+ const a = attachments.get(s.attachment);
420
+ if (!a) errs.push(`slot "${s.id}": unknown attachment "${s.attachment}"`);
421
+ else if (a.slot !== s.id) errs.push(`slot "${s.id}": attachment "${s.attachment}" belongs to slot "${a.slot}"`);
422
+ }
423
+ }
424
+ for (const a of doc.attachments) if (a.type === "path" && a.clipping) {
425
+ if (!a.closed) errs.push(`attachment "${a.id}": clipping path must be closed`);
426
+ if (a.clipping.end && !slots.has(a.clipping.end)) errs.push(`attachment "${a.id}": unknown clipping end slot "${a.clipping.end}"`);
427
+ }
428
+ const vertexCount = (a) => {
429
+ if (a.type === "region") return 4;
430
+ if (a.type === "mesh") return a.uvs.length / 2;
431
+ return a.vertices ? a.vertices.length / 2 : a.weights?.length ?? 0;
432
+ };
433
+ for (const a of doc.attachments) {
434
+ if (!slots.has(a.slot)) errs.push(`attachment "${a.id}": unknown slot "${a.slot}"`);
435
+ if (a.type !== "path" && !assets.has(a.asset)) errs.push(`attachment "${a.id}": unknown asset "${a.asset}"`);
436
+ if (a.type === "mesh" || a.type === "path") {
437
+ const n = a.type === "mesh" ? a.uvs.length / 2 : vertexCount(a);
438
+ if (a.type === "mesh") {
439
+ if (a.uvs.length % 2 || n < 3) errs.push(`attachment "${a.id}": uvs must have 2 numbers per vertex, at least 3 vertices`);
440
+ if (a.triangles.length % 3) errs.push(`attachment "${a.id}": triangles must have 3 indices each`);
441
+ for (const i of a.triangles) if (i >= n) errs.push(`attachment "${a.id}": triangle index ${i} out of range (${n} vertices)`);
442
+ }
443
+ if ((a.vertices ? 1 : 0) + (a.weights ? 1 : 0) !== 1) errs.push(`attachment "${a.id}": exactly one of vertices / weights`);
444
+ if (a.vertices && a.vertices.length !== 2 * n) errs.push(`attachment "${a.id}": vertices must have 2 numbers per vertex (${2 * n}, got ${a.vertices.length})`);
445
+ if (a.weights) {
446
+ if (a.weights.length !== n) errs.push(`attachment "${a.id}": weights must have one influence list per vertex (${n}, got ${a.weights.length})`);
447
+ for (const list of a.weights) for (const inf of list) if (!bones.has(inf.bone)) errs.push(`attachment "${a.id}": weights reference unknown bone "${inf.bone}"`);
448
+ }
449
+ }
450
+ }
451
+ for (const c of doc.ik) {
452
+ for (let i = 1; i < c.bones.length; i++) if (doc.bones.find((b) => b.id === c.bones[i])?.parent !== c.bones[i - 1])
453
+ errs.push(`ik "${c.id}": bones must form a contiguous parent-to-child chain`);
454
+ for (const b of c.bones) if (!bones.has(b)) errs.push(`ik "${c.id}": unknown bone "${b}"`);
455
+ if (!bones.has(c.target)) errs.push(`ik "${c.id}": unknown target bone "${c.target}"`);
456
+ }
457
+ for (const c of doc.transform) {
458
+ for (const b of c.bones) if (!bones.has(b)) errs.push(`transform "${c.id}": unknown bone "${b}"`);
459
+ if (!bones.has(c.target)) errs.push(`transform "${c.id}": unknown target bone "${c.target}"`);
460
+ }
461
+ for (const c of doc.path) {
462
+ for (const b of c.bones) if (!bones.has(b)) errs.push(`path "${c.id}": unknown bone "${b}"`);
463
+ if (!slots.has(c.target)) errs.push(`path "${c.id}": unknown target slot "${c.target}"`);
464
+ }
465
+ const controls = new Map((doc.controls ?? []).map((c) => [c.id, c]));
466
+ const driven = /* @__PURE__ */ new Set();
467
+ for (const c of controls.values()) if (!(c.min < c.max && c.default >= c.min && c.default <= c.max))
468
+ errs.push(`control "${c.id}": require min < max and default within range`);
469
+ for (const b of doc.controlBindings ?? []) {
470
+ const prefix = `control binding "${b.id}"`;
471
+ if (new Set(b.axes.map((a) => a.control)).size !== b.axes.length) errs.push(`${prefix}: axes must use different controls`);
472
+ for (const axis of b.axes) {
473
+ const c = controls.get(axis.control);
474
+ if (!c) errs.push(`${prefix}: unknown control "${axis.control}"`);
475
+ else if (axis.keys[0] !== c.min || axis.keys.at(-1) !== c.max) errs.push(`${prefix}: keys must span the control's full range`);
476
+ if (axis.keys.some((v, i) => i > 0 && v <= axis.keys[i - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
477
+ }
478
+ const count = b.axes.reduce((n, a) => n * a.keys.length, 1);
479
+ for (const ch of b.channels) {
480
+ const key = `${ch.target}.${ch.id}.${ch.prop}`;
481
+ if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
482
+ driven.add(key);
483
+ if (ch.values.length !== count) errs.push(`${prefix}: ${key} needs ${count} grid values`);
484
+ if (ch.target === "deform") {
485
+ const att = attachments.get(ch.id);
486
+ if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
487
+ else {
488
+ const n = att.weights ? att.weights.reduce((s, a) => s + a.length * 2, 0) : att.vertices.length;
489
+ if (ch.values.some((v) => !Array.isArray(v) || v.length !== n)) errs.push(`${prefix}: ${key} needs arrays of ${n} offsets`);
490
+ }
491
+ } else {
492
+ if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
493
+ if (ch.values.some((v) => typeof v !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
494
+ if (ch.target === "slot" && ch.values.some((v) => typeof v === "number" && (v < 0 || v > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
495
+ }
496
+ }
497
+ }
498
+ for (const a of doc.animations) {
499
+ const tracks = /* @__PURE__ */ new Set();
500
+ for (const t of a.tracks) {
501
+ const tid = trackId(t);
502
+ if (tracks.has(tid)) errs.push(`animation "${a.id}": duplicate track ${tid}`);
503
+ tracks.add(tid);
504
+ if (driven.has(tid)) errs.push(`animation "${a.id}": ${tid} is controlled by a binding; animate its control instead`);
505
+ 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;
506
+ if (!known) errs.push(`animation "${a.id}": unknown ${t.target} "${t.id}"`);
507
+ const bound = t.target === "deform" ? DEFORM_BOUNDS[t.prop] : void 0;
508
+ const att = t.target === "deform" ? attachments.get(t.id) : void 0;
509
+ let prev = -1;
510
+ for (const k of t.keys) {
511
+ 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`);
512
+ if (k.t <= prev && prev >= 0) errs.push(`animation "${a.id}" ${tid}: keys not strictly increasing`);
513
+ if (k.t > a.duration) errs.push(`animation "${a.id}" ${tid}: key at ${k.t}s exceeds duration`);
514
+ if (t.target === "control") {
515
+ const c = controls.get(t.id);
516
+ 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`);
517
+ }
518
+ if (t.target === "slot" && t.prop === "attachment") {
519
+ if (k.v !== null && typeof k.v !== "string") errs.push(`animation "${a.id}" ${tid}: attachment key must be an attachment id or null`);
520
+ else if (typeof k.v === "string") {
521
+ const target = attachments.get(k.v);
522
+ if (!target) errs.push(`animation "${a.id}": unknown attachment "${k.v}"`);
523
+ else if (target.slot !== t.id) errs.push(`animation "${a.id}" ${tid}: attachment "${k.v}" belongs to slot "${target.slot}"`);
524
+ }
525
+ } else if (t.target === "slot" && t.prop === "color") {
526
+ const ok = Array.isArray(k.v) && k.v.length === 4 && k.v.every((c) => typeof c === "number" && c >= 0 && c <= 1);
527
+ if (!ok) errs.push(`animation "${a.id}" ${tid}: color key must be [r, g, b, a] in 0..1`);
528
+ } else if (t.target === "drawOrder") {
529
+ const order = Array.isArray(k.v) ? k.v : [];
530
+ const ok = order.length === doc.slots.length && new Set(order).size === order.length && order.every((s) => typeof s === "string" && slots.has(s));
531
+ if (!ok) errs.push(`animation "${a.id}" drawOrder key at ${k.t}s must list every slot exactly once`);
532
+ } else if (t.target === "deform" && t.prop === "vertices") {
533
+ if (!Array.isArray(k.v) || !k.v.every((v) => typeof v === "number")) errs.push(`animation "${a.id}" ${tid}: vertices key must be a number array`);
534
+ else if (att && att.type !== "region") {
535
+ const expected = att.weights ? 2 * att.weights.reduce((s, list) => s + list.length, 0) : 2 * vertexCount(att);
536
+ if (k.v.length !== expected) errs.push(`animation "${a.id}" ${tid}: vertices key needs ${expected} numbers, got ${k.v.length}`);
537
+ } else if (att) errs.push(`animation "${a.id}" ${tid}: vertices deform needs a mesh or path attachment`);
538
+ } else if (typeof k.v !== "number") errs.push(`animation "${a.id}" ${tid}: key must be a number`);
539
+ else if (bound !== void 0 && Math.abs(k.v) > bound) errs.push(`animation "${a.id}" ${tid}: value ${k.v} out of range [-${bound}, ${bound}]`);
540
+ prev = k.t;
541
+ }
542
+ }
543
+ for (const e of a.events) {
544
+ if (e.t > a.duration) errs.push(`animation "${a.id}": event "${e.name}" exceeds duration`);
545
+ }
546
+ }
547
+ return errs;
548
+ }
549
+ function parse(input) {
550
+ const doc = RigDocument.parse(input);
551
+ const errs = validate(doc);
552
+ if (errs.length) throw new Error("rig validation failed:\n" + errs.join("\n"));
553
+ return doc;
554
+ }
555
+
556
+ // src/rig-controls.ts
557
+ function controlPose(doc, values) {
558
+ const out = /* @__PURE__ */ new Map();
559
+ for (const [id, value] of Object.entries(values)) {
560
+ const c = doc.controls?.find((c2) => c2.id === id);
561
+ if (!c) throw new Error(`unknown control "${id}"`);
562
+ if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
563
+ out.set(`control:${id}:value`, Math.max(c.min, Math.min(c.max, value)));
564
+ }
565
+ return out;
566
+ }
567
+ function bracket(keys, value) {
568
+ let i = 0;
569
+ while (i < keys.length - 2 && value > keys[i + 1]) i++;
570
+ return [i, Math.max(0, Math.min(1, (value - keys[i]) / (keys[i + 1] - keys[i])))];
571
+ }
572
+ function sampleBinding(binding, values) {
573
+ const [x, u] = bracket(binding.axes[0].keys, values[0]);
574
+ const [y, v] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
575
+ const width = binding.axes[0].keys.length, a = y * width + x;
576
+ const mix = (a2, b, u2) => a2 + (b - a2) * u2;
577
+ return binding.channels.map((ch) => {
578
+ const read = (i, j) => j === void 0 ? ch.values[i] : ch.values[i][j];
579
+ const at = (j) => {
580
+ const top = mix(read(a, j), read(a + 1, j), u);
581
+ return binding.axes[1] ? mix(top, mix(read(a + width, j), read(a + width + 1, j), u), v) : top;
582
+ };
583
+ return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j) => at(j)) : at();
584
+ });
585
+ }
586
+ function resolveControls(doc, input) {
587
+ if (!doc.controlBindings?.length) return input;
588
+ const out = new Map(input), controls = new Map(doc.controls?.map((c) => [c.id, c]));
589
+ for (const b of doc.controlBindings) {
590
+ const values = b.axes.map((axis) => {
591
+ const c = controls.get(axis.control);
592
+ const value = input.get(`control:${c.id}:value`) ?? c.default;
593
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c.id}" must be finite`);
594
+ return Math.max(c.min, Math.min(c.max, value));
595
+ });
596
+ const sampled = sampleBinding(b, values);
597
+ b.channels.forEach((ch, i) => out.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i]));
598
+ }
599
+ return out;
600
+ }
601
+
602
+ // src/rig-anim.ts
603
+ var DEG2 = Math.PI / 180;
604
+ var named = {
605
+ linear: (t) => t,
606
+ step: () => 0,
607
+ ease_in: (t) => t * t,
608
+ ease_out: (t) => 1 - (1 - t) * (1 - t),
609
+ ease_in_out: (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2,
610
+ minimum_jerk: (t) => t * t * t * (10 + t * (-15 + 6 * t)),
611
+ back_out: (t) => 1 + 2.70158 * (t - 1) ** 3 + 1.70158 * (t - 1) ** 2,
612
+ elastic_out: (t) => t === 0 ? 0 : t === 1 ? 1 : 2 ** (-10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI / 3)) + 1
613
+ };
614
+ var BEZIER_SEGMENTS = 10;
615
+ function bezier(x1, y1, x2, y2) {
616
+ const px = [0], py = [0];
617
+ for (let i = 1; i <= BEZIER_SEGMENTS; i++) {
618
+ const t = i / BEZIER_SEGMENTS, u = 1 - t;
619
+ px.push(3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t);
620
+ py.push(3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t);
621
+ }
622
+ return (x) => {
623
+ if (x <= 0) return 0;
624
+ for (let i = 1; i <= BEZIER_SEGMENTS; i++) {
625
+ if (px[i] >= x) {
626
+ const span = px[i] - px[i - 1];
627
+ return py[i - 1] + (span > 0 ? (x - px[i - 1]) / span * (py[i] - py[i - 1]) : 0);
628
+ }
629
+ }
630
+ return 1;
631
+ };
632
+ }
633
+ var easeCache = /* @__PURE__ */ new Map();
634
+ function ease(spec) {
635
+ if (typeof spec === "string") return named[spec];
636
+ const key = spec.bezier.join(",");
637
+ let f = easeCache.get(key);
638
+ if (!f) easeCache.set(key, f = bezier(...spec.bezier));
639
+ return f;
640
+ }
641
+ var pk = (target, id, prop) => `${target}:${id}:${prop}`;
642
+ function smoothValue(track, i, t, component, loop) {
643
+ const k = track.keys, n = k.length;
644
+ const v = (j) => component === void 0 ? k[j].v : k[j].v[component];
645
+ const h = (j) => k[j + 1].t - k[j].t;
646
+ const d = (j) => (v(j + 1) - v(j)) / h(j);
647
+ const harmonic = (a, b, h0, h1) => a * b <= 0 ? 0 : 3 * (h0 + h1) / ((2 * h1 + h0) / a + (h1 + 2 * h0) / b);
648
+ const tangent = (j) => {
649
+ if (j === 0 || j === n - 1) {
650
+ if (loop && n > 2 && v(0) === v(n - 1) && k[0].ease !== "step" && k[n - 2].ease !== "step")
651
+ return harmonic(d(n - 2), d(0), h(n - 2), h(0));
652
+ return 0;
653
+ }
654
+ if (k[j - 1].ease === "step" || k[j].ease === "step") return 0;
655
+ return harmonic(d(j - 1), d(j), h(j - 1), h(j));
656
+ };
657
+ const u = (t - k[i].t) / h(i), u2 = u * u, u3 = u2 * u;
658
+ return (2 * u3 - 3 * u2 + 1) * v(i) + (u3 - 2 * u2 + u) * h(i) * tangent(i) + (-2 * u3 + 3 * u2) * v(i + 1) + (u3 - u2) * h(i) * tangent(i + 1);
659
+ }
660
+ function sample(track, t, loop = false) {
661
+ const keys = track.keys;
662
+ if (t <= keys[0].t) return keys[0].v;
663
+ const last = keys[keys.length - 1];
664
+ if (t >= last.t) return last.v;
665
+ let i = 0;
666
+ while (keys[i + 1].t <= t) i++;
667
+ const a = keys[i], b = keys[i + 1];
668
+ if (a.ease === "step") return a.v;
669
+ if (typeof a.v === "number" && typeof b.v === "number") {
670
+ if (track.interpolation === "pchip") return smoothValue(track, i, t, void 0, loop);
671
+ const u = ease(a.ease)((t - a.t) / (b.t - a.t));
672
+ return a.v + (b.v - a.v) * u;
673
+ }
674
+ if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
675
+ if (track.interpolation === "pchip") return a.v.map((_, j) => smoothValue(track, i, t, j, loop));
676
+ const phase = (t - a.t) / (b.t - a.t), av = a.v, bv = b.v;
677
+ return av.map((v, k) => v + ((bv[k] ?? v) - v) * ease(a.componentEase?.[k] ?? a.ease)(phase));
678
+ }
679
+ return a.v;
680
+ }
681
+ function poseOf(anim, t, out = /* @__PURE__ */ new Map()) {
682
+ for (const tr of anim.tracks) out.set(pk(tr.target, tr.id, tr.prop), sample(tr, t, anim.loop && tr.keys[0].t === 0 && tr.keys.at(-1).t === anim.duration));
683
+ return out;
684
+ }
685
+ function blendPoses(from, to, w, restOf) {
686
+ if (w >= 1) return new Map(to);
687
+ const out = /* @__PURE__ */ new Map();
688
+ for (const k of /* @__PURE__ */ new Set([...from.keys(), ...to.keys()])) {
689
+ const f = from.has(k) ? from.get(k) : restOf(k);
690
+ const cur = to.has(k) ? to.get(k) : restOf(k);
691
+ if (typeof f === "number" && !Array.isArray(cur)) {
692
+ const target = typeof cur === "number" ? cur : restOf(k);
693
+ out.set(k, f + (target - f) * w);
694
+ } else if (Array.isArray(f) && typeof f[0] === "number") {
695
+ const fv = f, tv = Array.isArray(cur) && typeof cur[0] === "number" ? cur : fv.map(() => 0);
696
+ out.set(k, fv.map((v, i) => v + ((tv[i] ?? 0) - v) * w));
697
+ } else if (Array.isArray(cur) && typeof cur[0] === "number" && typeof f !== "string") {
698
+ out.set(k, cur.map((v) => v * w));
699
+ } else out.set(k, f ?? null);
700
+ }
701
+ return out;
702
+ }
703
+ function restValue(doc, key) {
704
+ const [target, id, prop] = key.split(":");
705
+ if (target === "control") return doc.controls?.find((c) => c.id === id)?.default ?? 0;
706
+ if (target === "bone") return doc.bones.find((b) => b.id === id).transform[prop];
707
+ if (target === "slot") {
708
+ const s = doc.slots.find((s2) => s2.id === id);
709
+ if (prop === "color") return colorToArray(s.color);
710
+ return s[prop];
711
+ }
712
+ if (target === "ik" || target === "transform" || target === "path") {
713
+ const c = doc[target].find((c2) => c2.id === id);
714
+ const v = c?.[prop];
715
+ return typeof v === "boolean" ? v ? 1 : 0 : v ?? 0;
716
+ }
717
+ if (target === "drawOrder") return doc.slots.map((s) => s.id);
718
+ return 0;
719
+ }
720
+ var colorToArray = (c) => [0, 2, 4, 6].map((k) => parseInt(c.slice(1 + k, 3 + k), 16) / 255);
721
+ function deformParams(pose, attachmentId) {
722
+ let out;
723
+ const prefix = `deform:${attachmentId}:`;
724
+ for (const [k, v] of pose)
725
+ if (k.startsWith(prefix) && typeof v === "number") (out ??= {})[k.slice(prefix.length)] = v;
726
+ return out;
727
+ }
728
+ function affineOf(x, y, rotationDeg, sx, sy, shearX = 0, shearY = 0) {
729
+ const rx = (rotationDeg + shearX) * DEG2, ry = (rotationDeg + 90 + shearY) * DEG2;
730
+ return { a: Math.cos(rx) * sx, b: Math.sin(rx) * sx, c: Math.cos(ry) * sy, d: Math.sin(ry) * sy, tx: x, ty: y };
731
+ }
732
+ function mul(p, l) {
733
+ return {
734
+ a: p.a * l.a + p.c * l.b,
735
+ b: p.b * l.a + p.d * l.b,
736
+ c: p.a * l.c + p.c * l.d,
737
+ d: p.b * l.c + p.d * l.d,
738
+ tx: p.a * l.tx + p.c * l.ty + p.tx,
739
+ ty: p.b * l.tx + p.d * l.ty + p.ty
740
+ };
741
+ }
742
+ var apply = (m, x, y) => [m.a * x + m.c * y + m.tx, m.b * x + m.d * y + m.ty];
743
+ function invert(m) {
744
+ const det = m.a * m.d - m.b * m.c || 1e-12;
745
+ const a = m.d / det, b = -m.b / det, c = -m.c / det, d = m.a / det;
746
+ return { a, b, c, d, tx: -(a * m.tx + c * m.ty), ty: -(b * m.tx + d * m.ty) };
747
+ }
748
+ var num = (pose, key, rest) => {
749
+ const v = pose.get(key);
750
+ return typeof v === "number" ? v : rest;
751
+ };
752
+ var boneIndex = /* @__PURE__ */ new WeakMap();
753
+ function boneOf(doc, id) {
754
+ let idx = boneIndex.get(doc);
755
+ if (!idx) boneIndex.set(doc, idx = new Map(doc.bones.map((b2) => [b2.id, b2])));
756
+ const b = idx.get(id);
757
+ if (!b) throw new Error(`unknown bone "${id}"`);
758
+ return b;
759
+ }
760
+ function boneWorldOf(bone, s, parent) {
761
+ const local = affineOf(s.x, s.y, s.rotation, s.scaleX, s.scaleY, s.shearX, s.shearY);
762
+ if (!parent) return local;
763
+ if (bone.inherit === "normal") return mul(parent, local);
764
+ const [tx, ty] = apply(parent, s.x, s.y);
765
+ if (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection") {
766
+ 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);
767
+ const length = Math.hypot(x, y), a = length > 1e-5 ? x / length : x, b = length > 1e-5 ? y / length : y;
768
+ const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
769
+ const basis2 = { a, b, c: -b * sign, d: a * sign, tx: 0, ty: 0 };
770
+ return { ...mul(basis2, affineOf(0, 0, 0, s.scaleX, s.scaleY, s.shearX, s.shearY)), tx, ty };
771
+ }
772
+ return { ...mul(inheritedParentLinear(bone, parent), { ...local, tx: 0, ty: 0 }), tx, ty };
773
+ }
774
+ var orderCache = /* @__PURE__ */ new WeakMap();
775
+ function boneOrder(doc) {
776
+ let order = orderCache.get(doc);
777
+ if (order) return order;
778
+ order = [];
779
+ const emitted = /* @__PURE__ */ new Set();
780
+ while (order.length < doc.bones.length) {
781
+ let progressed = false;
782
+ for (const b of doc.bones) if (!emitted.has(b.id) && (!b.parent || emitted.has(b.parent))) {
783
+ emitted.add(b.id);
784
+ order.push(b);
785
+ progressed = true;
786
+ }
787
+ if (!progressed) throw new Error("bone hierarchy has a cycle");
788
+ }
789
+ orderCache.set(doc, order);
790
+ return order;
791
+ }
792
+ var localOf = (doc, pose, b) => {
793
+ const r = b.transform, id = b.id;
794
+ return {
795
+ x: num(pose, pk("bone", id, "x"), r.x),
796
+ y: num(pose, pk("bone", id, "y"), r.y),
797
+ rotation: num(pose, pk("bone", id, "rotation"), r.rotation),
798
+ scaleX: num(pose, pk("bone", id, "scaleX"), r.scaleX),
799
+ scaleY: num(pose, pk("bone", id, "scaleY"), r.scaleY),
800
+ shearX: num(pose, pk("bone", id, "shearX"), r.shearX),
801
+ shearY: num(pose, pk("bone", id, "shearY"), r.shearY)
802
+ };
803
+ };
804
+ function updateSubtree(skel, root, includeRoot = true) {
805
+ const doc = skel.doc;
806
+ const kids = /* @__PURE__ */ new Map();
807
+ for (const b of doc.bones) if (b.parent) (kids.get(b.parent) ?? kids.set(b.parent, []).get(b.parent)).push(b);
808
+ const stack = includeRoot ? [root] : (kids.get(root) ?? []).map((b) => b.id);
809
+ while (stack.length) {
810
+ const id = stack.pop(), b = boneOf(doc, id), st = skel.bones.get(id);
811
+ st.world = boneWorldOf(b, st, b.parent ? skel.bones.get(b.parent).world : void 0);
812
+ for (const k of kids.get(id) ?? []) stack.push(k.id);
813
+ }
814
+ }
815
+ function evaluate(doc, pose, modifiers) {
816
+ pose = resolveControls(doc, pose);
817
+ if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
818
+ const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
819
+ for (const b of boneOrder(doc)) {
820
+ const s = localOf(doc, pose, b);
821
+ const world = boneWorldOf(b, s, b.parent ? skel.bones.get(b.parent).world : void 0);
822
+ skel.bones.set(b.id, { ...s, world });
823
+ }
824
+ const attachments = new Map(doc.attachments.map((a) => [a.id, a]));
825
+ for (const s of doc.slots) {
826
+ const v = pose.get(pk("slot", s.id, "attachment"));
827
+ const id = v === void 0 ? s.attachment : v;
828
+ skel.attachment.set(s.id, id ? attachments.get(id) ?? null : null);
829
+ }
830
+ const all = [
831
+ ...doc.ik.map((c) => ({ order: c.order, kind: "ik", c })),
832
+ ...doc.transform.map((c) => ({ order: c.order, kind: "transform", c })),
833
+ ...doc.path.map((c) => ({ order: c.order, kind: "path", c }))
834
+ ].sort((a, b) => a.order - b.order);
835
+ for (const { kind, c } of all) {
836
+ if (kind === "ik") applyIk(skel, c);
837
+ else if (kind === "transform") applyTransformConstraint(skel, c);
838
+ else applyPathConstraint(skel, c);
839
+ }
840
+ return skel;
841
+ }
842
+ function slotClips(skel) {
843
+ const keyed = skel.pose.get(pk("drawOrder", "order", "order"));
844
+ const order = Array.isArray(keyed) ? keyed : [...skel.doc.slots].sort((a, b) => a.z - b.z).map((s) => s.id);
845
+ const masks = /* @__PURE__ */ new Map();
846
+ let active;
847
+ for (const id of order) {
848
+ const att = skel.attachment.get(id);
849
+ if (!active && att?.type === "path" && att.clipping) {
850
+ active = att;
851
+ continue;
852
+ }
853
+ if (active) masks.set(id, active.id);
854
+ if (active?.clipping?.end === id) active = void 0;
855
+ }
856
+ for (const s of skel.doc.slots) if (s.clip) masks.set(s.id, s.clip);
857
+ return masks;
858
+ }
859
+ var cnum = (skel, kind, id, prop, rest) => num(skel.pose, pk(kind, id, prop), typeof rest === "boolean" ? rest ? 1 : 0 : rest);
860
+ var IDENTITY = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
861
+ var wrapRad = (r) => r > Math.PI ? r - 2 * Math.PI : r < -Math.PI ? r + 2 * Math.PI : r;
862
+ function aimAt(skel, boneId, tx, ty, mix, compress = false, stretch = false, uniform = false) {
863
+ const b = boneOf(skel.doc, boneId), st = skel.bones.get(boneId);
864
+ const parent = b.parent ? skel.bones.get(b.parent).world : void 0;
865
+ let dx, dy;
866
+ if (parent && b.inherit === "onlyTranslation") {
867
+ dx = tx - st.world.tx;
868
+ dy = ty - st.world.ty;
869
+ } else {
870
+ const [px, py] = parent ? apply(invert(parent), tx, ty) : [tx, ty];
871
+ dx = px - st.x;
872
+ dy = py - st.y;
873
+ }
874
+ let r = Math.atan2(dy, dx) / DEG2 - st.shearX - st.rotation;
875
+ if (st.scaleX < 0) r += 180;
876
+ r = shortest(r);
877
+ let sx = st.scaleX, sy = st.scaleY;
878
+ if (compress || stretch) {
879
+ const len = b.length * sx;
880
+ if (len > 1e-4) {
881
+ const dd = dx * dx + dy * dy;
882
+ if (compress && dd < len * len || stretch && dd > len * len) {
883
+ const f = (Math.sqrt(dd) / len - 1) * mix + 1;
884
+ sx *= f;
885
+ if (uniform) sy *= f;
886
+ }
887
+ }
888
+ }
889
+ st.rotation += r * mix;
890
+ st.scaleX = sx;
891
+ st.scaleY = sy;
892
+ updateSubtree(skel, boneId);
893
+ }
894
+ var shortest = (deg) => ((deg + 180) % 360 + 360) % 360 - 180;
895
+ function applyLongIk(skel, c, tx, ty, mix) {
896
+ const initial = c.bones.map((id) => ({ ...skel.bones.get(id) }));
897
+ const last = c.bones.at(-1), len = boneOf(skel.doc, last).length;
898
+ const end = () => apply(skel.bones.get(last).world, len, 0);
899
+ const tolerance = c.tolerance ?? 0.05;
900
+ const root = skel.bones.get(c.bones[0]).world, tip = end();
901
+ if (Math.abs((tip[0] - root.tx) * (ty - root.ty) - (tip[1] - root.ty) * (tx - root.tx)) < 1e-7 && Math.hypot(tx - root.tx, ty - root.ty) < Math.hypot(tip[0] - root.tx, tip[1] - root.ty) - tolerance) {
902
+ skel.bones.get(last).rotation += cnum(skel, "ik", c.id, "bendPositive", c.bendPositive) > 0.5 ? 5 : -5;
903
+ updateSubtree(skel, last);
904
+ }
905
+ for (let iter = 0; iter < (c.iterations ?? 100); iter++) {
906
+ const e = end();
907
+ if (Math.hypot(e[0] - tx, e[1] - ty) <= tolerance) break;
908
+ for (let i = c.bones.length - 1; i >= 0; i--) {
909
+ const id = c.bones[i], b = boneOf(skel.doc, id), st = skel.bones.get(id), w = st.world, e2 = end();
910
+ const angle = wrapRad(Math.atan2(ty - w.ty, tx - w.tx) - Math.atan2(e2[1] - w.ty, e2[0] - w.tx));
911
+ const cos = Math.cos(angle), sin = Math.sin(angle);
912
+ setEffectiveWorld(skel, b, st, { ...w, a: cos * w.a - sin * w.b, b: sin * w.a + cos * w.b, c: cos * w.c - sin * w.d, d: sin * w.c + cos * w.d });
913
+ }
914
+ }
915
+ if (mix < 1) {
916
+ c.bones.forEach((id, i) => {
917
+ const st = skel.bones.get(id), from = initial[i];
918
+ for (const p of ["rotation", "shearX", "shearY"]) st[p] = from[p] + shortest(st[p] - from[p]) * mix;
919
+ for (const p of ["x", "y", "scaleX", "scaleY"]) st[p] = from[p] + (st[p] - from[p]) * mix;
920
+ });
921
+ updateSubtree(skel, c.bones[0]);
922
+ }
923
+ }
924
+ function applyIk(skel, c) {
925
+ const mix = cnum(skel, "ik", c.id, "mix", c.mix);
926
+ if (mix <= 0) return;
927
+ const target = skel.bones.get(c.target).world;
928
+ if (c.bones.length > 2) {
929
+ applyLongIk(skel, c, target.tx, target.ty, mix);
930
+ return;
931
+ }
932
+ const bendDir = cnum(skel, "ik", c.id, "bendPositive", c.bendPositive) > 0.5 ? 1 : -1;
933
+ const compress = cnum(skel, "ik", c.id, "compress", c.compress) > 0.5, stretch = cnum(skel, "ik", c.id, "stretch", c.stretch) > 0.5;
934
+ let softness = cnum(skel, "ik", c.id, "softness", c.softness);
935
+ if (c.bones.length === 1) {
936
+ aimAt(skel, c.bones[0], target.tx, target.ty, mix, compress, stretch, c.uniform);
937
+ return;
938
+ }
939
+ const [pId, cId] = c.bones;
940
+ const parentBone = boneOf(skel.doc, pId), childBone = boneOf(skel.doc, cId);
941
+ const p = skel.bones.get(pId), ch = skel.bones.get(cId);
942
+ const px = p.x, py = p.y;
943
+ let psx = p.scaleX, psy = p.scaleY, csx = ch.scaleX, sx = psx, sy = psy;
944
+ let os1 = 0, os2 = 0, s2 = 1;
945
+ if (psx < 0) {
946
+ psx = -psx;
947
+ os1 = 180;
948
+ s2 = -1;
949
+ }
950
+ if (psy < 0) {
951
+ psy = -psy;
952
+ s2 = -s2;
953
+ }
954
+ if (csx < 0) {
955
+ csx = -csx;
956
+ os2 = 180;
957
+ }
958
+ const uniform = Math.abs(psx - psy) <= 1e-4;
959
+ const cx = ch.x, cy = !uniform || stretch ? 0 : ch.y;
960
+ const [cwx, cwy] = apply(p.world, cx, cy);
961
+ const ppInv = invert(parentBone.parent ? skel.bones.get(parentBone.parent).world : IDENTITY);
962
+ const [clx, cly] = apply(ppInv, cwx, cwy);
963
+ const dx = clx - px, dy = cly - py, l1 = Math.hypot(dx, dy);
964
+ let l2 = childBone.length * csx;
965
+ if (l1 < 1e-4) {
966
+ aimAt(skel, pId, target.tx, target.ty, mix, false, stretch, false);
967
+ ch.x = cx;
968
+ ch.y = cy;
969
+ ch.rotation = 0;
970
+ updateSubtree(skel, pId);
971
+ return;
972
+ }
973
+ const [tlx, tly] = apply(ppInv, target.tx, target.ty);
974
+ let tx = tlx - px, ty = tly - py, dd = tx * tx + ty * ty;
975
+ if (softness !== 0) {
976
+ softness *= psx * (csx + 1) * 0.5;
977
+ const td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness;
978
+ if (sd > 0) {
979
+ let q = Math.min(1, sd / (softness * 2)) - 1;
980
+ q = (sd - softness * (1 - q * q)) / td;
981
+ tx -= q * tx;
982
+ ty -= q * ty;
983
+ dd = tx * tx + ty * ty;
984
+ }
985
+ }
986
+ let a1, a2;
987
+ if (uniform) {
988
+ l2 *= psx;
989
+ let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
990
+ if (cos < -1) {
991
+ cos = -1;
992
+ a2 = Math.PI * bendDir;
993
+ } else if (cos > 1) {
994
+ cos = 1;
995
+ a2 = 0;
996
+ if (stretch) {
997
+ const f = (Math.sqrt(dd) / (l1 + l2) - 1) * mix + 1;
998
+ sx *= f;
999
+ if (c.uniform) sy *= f;
1000
+ }
1001
+ } else a2 = Math.acos(cos) * bendDir;
1002
+ const a = l1 + l2 * cos, b = l2 * Math.sin(a2);
1003
+ a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b);
1004
+ } else {
1005
+ const a = psx * l2, b = psy * l2, aa = a * a, bb = b * b, ta = Math.atan2(ty, tx);
1006
+ const c0 = bb * l1 * l1 + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
1007
+ const disc = c1 * c1 - 4 * c2 * c0;
1008
+ let solved = false;
1009
+ a1 = a2 = 0;
1010
+ if (disc >= 0) {
1011
+ let q = Math.sqrt(disc);
1012
+ if (c1 < 0) q = -q;
1013
+ q = -(c1 + q) * 0.5;
1014
+ const r0 = q / c2, r1 = c0 / q, r = Math.abs(r0) < Math.abs(r1) ? r0 : r1, yy = dd - r * r;
1015
+ if (yy >= 0) {
1016
+ const y = Math.sqrt(yy) * bendDir;
1017
+ a1 = ta - Math.atan2(y, r);
1018
+ a2 = Math.atan2(y / psy, (r - l1) / psx);
1019
+ solved = true;
1020
+ }
1021
+ }
1022
+ if (!solved) {
1023
+ let minAngle = Math.PI, minX = l1 - a, minDist = minX * minX, minY = 0, maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
1024
+ const k = -a * l1 / (aa - bb);
1025
+ if (k >= -1 && k <= 1) {
1026
+ const ang = Math.acos(k), x = a * Math.cos(ang) + l1, y = b * Math.sin(ang), dist = x * x + y * y;
1027
+ if (dist < minDist) {
1028
+ minAngle = ang;
1029
+ minDist = dist;
1030
+ minX = x;
1031
+ minY = y;
1032
+ }
1033
+ if (dist > maxDist) {
1034
+ maxAngle = ang;
1035
+ maxDist = dist;
1036
+ maxX = x;
1037
+ maxY = y;
1038
+ }
1039
+ }
1040
+ if (dd <= (minDist + maxDist) * 0.5) {
1041
+ a1 = ta - Math.atan2(minY * bendDir, minX);
1042
+ a2 = minAngle * bendDir;
1043
+ } else {
1044
+ a1 = ta - Math.atan2(maxY * bendDir, maxX);
1045
+ a2 = maxAngle * bendDir;
1046
+ }
1047
+ }
1048
+ }
1049
+ const os = Math.atan2(cy, cx) * s2;
1050
+ const d1 = shortest((a1 - os) / DEG2 + os1 - p.rotation);
1051
+ const d2 = shortest(((a2 + os) / DEG2 - ch.shearX) * s2 + os2 - ch.rotation);
1052
+ p.rotation += d1 * mix;
1053
+ p.scaleX = sx;
1054
+ p.scaleY = sy;
1055
+ p.shearX = 0;
1056
+ p.shearY = 0;
1057
+ ch.x = cx;
1058
+ ch.y = cy;
1059
+ ch.rotation += d2 * mix;
1060
+ updateSubtree(skel, pId);
1061
+ }
1062
+ function applyTransformConstraint(skel, c) {
1063
+ const mixRotate = cnum(skel, "transform", c.id, "mixRotate", c.mixRotate), mixX = cnum(skel, "transform", c.id, "mixX", c.mixX), mixY = cnum(skel, "transform", c.id, "mixY", c.mixY);
1064
+ const mixScaleX = cnum(skel, "transform", c.id, "mixScaleX", c.mixScaleX), mixScaleY = cnum(skel, "transform", c.id, "mixScaleY", c.mixScaleY), mixShearY = cnum(skel, "transform", c.id, "mixShearY", c.mixShearY);
1065
+ if (!mixRotate && !mixX && !mixY && !mixScaleX && !mixScaleY && !mixShearY) return;
1066
+ c = { ...c, ...Object.fromEntries(["x", "y", "rotation", "scaleX", "scaleY", "shearY"].map((p) => [p, cnum(skel, "transform", c.id, p, c[p])])) };
1067
+ const target = skel.bones.get(c.target), tw = target.world;
1068
+ const handedness = tw.a * tw.d - tw.b * tw.c < 0 ? -1 : 1;
1069
+ const offsetRotation = c.rotation * DEG2 * (c.local ? 1 : handedness), offsetShearY = c.shearY * DEG2 * (c.local ? 1 : handedness);
1070
+ for (const id of c.bones) {
1071
+ const b = boneOf(skel.doc, id), st = skel.bones.get(id);
1072
+ if (c.local) {
1073
+ if (c.relative) {
1074
+ st.rotation += (target.rotation + c.rotation) * mixRotate;
1075
+ st.x += (target.x + c.x) * mixX;
1076
+ st.y += (target.y + c.y) * mixY;
1077
+ st.scaleX *= (target.scaleX - 1 + c.scaleX) * mixScaleX + 1;
1078
+ st.scaleY *= (target.scaleY - 1 + c.scaleY) * mixScaleY + 1;
1079
+ st.shearY += (target.shearY + c.shearY) * mixShearY;
1080
+ } else {
1081
+ st.rotation += (target.rotation - st.rotation + c.rotation) * mixRotate;
1082
+ st.x += (target.x - st.x + c.x) * mixX;
1083
+ st.y += (target.y - st.y + c.y) * mixY;
1084
+ if (st.scaleX !== 0) st.scaleX += (target.scaleX - st.scaleX + c.scaleX) * mixScaleX;
1085
+ if (st.scaleY !== 0) st.scaleY += (target.scaleY - st.scaleY + c.scaleY) * mixScaleY;
1086
+ st.shearY += (target.shearY - st.shearY + c.shearY) * mixShearY;
1087
+ }
1088
+ updateSubtree(skel, id);
1089
+ continue;
1090
+ }
1091
+ let w = { ...st.world };
1092
+ const targetRot = Math.atan2(tw.b, tw.a), rotate = (m, r) => {
1093
+ const cos = Math.cos(r), sin = Math.sin(r);
1094
+ return { a: cos * m.a - sin * m.b, b: sin * m.a + cos * m.b, c: cos * m.c - sin * m.d, d: sin * m.c + cos * m.d, tx: m.tx, ty: m.ty };
1095
+ };
1096
+ if (mixRotate !== 0) {
1097
+ const r = wrapRad(c.relative ? targetRot + offsetRotation : targetRot - Math.atan2(w.b, w.a) + offsetRotation);
1098
+ w = rotate(w, r * mixRotate);
1099
+ }
1100
+ if (mixX !== 0 || mixY !== 0) {
1101
+ const [ox, oy] = apply(tw, c.x, c.y);
1102
+ if (c.relative) {
1103
+ w.tx += ox * mixX;
1104
+ w.ty += oy * mixY;
1105
+ } else {
1106
+ w.tx += (ox - w.tx) * mixX;
1107
+ w.ty += (oy - w.ty) * mixY;
1108
+ }
1109
+ }
1110
+ if (mixScaleX !== 0) {
1111
+ const ts = Math.hypot(tw.a, tw.b);
1112
+ let f;
1113
+ if (c.relative) f = (ts - 1 + c.scaleX) * mixScaleX + 1;
1114
+ else {
1115
+ const s0 = Math.hypot(w.a, w.b);
1116
+ f = s0 === 0 ? 1 : (s0 + (ts - s0 + c.scaleX) * mixScaleX) / s0;
1117
+ }
1118
+ w.a *= f;
1119
+ w.b *= f;
1120
+ }
1121
+ if (mixScaleY !== 0) {
1122
+ const ts = Math.hypot(tw.c, tw.d);
1123
+ let f;
1124
+ if (c.relative) f = (ts - 1 + c.scaleY) * mixScaleY + 1;
1125
+ else {
1126
+ const s0 = Math.hypot(w.c, w.d);
1127
+ f = s0 === 0 ? 1 : (s0 + (ts - s0 + c.scaleY) * mixScaleY) / s0;
1128
+ }
1129
+ w.c *= f;
1130
+ w.d *= f;
1131
+ }
1132
+ if (mixShearY > 0) {
1133
+ const by = Math.atan2(w.d, w.c), targetShear = Math.atan2(tw.d, tw.c) - targetRot;
1134
+ const r = c.relative ? by + (wrapRad(targetShear) - Math.PI / 2 + offsetShearY) * mixShearY : by + (wrapRad(targetShear - (by - Math.atan2(w.b, w.a))) + offsetShearY) * mixShearY;
1135
+ const len = Math.hypot(w.c, w.d);
1136
+ w.c = Math.cos(r) * len;
1137
+ w.d = Math.sin(r) * len;
1138
+ }
1139
+ setEffectiveWorld(skel, b, st, w);
1140
+ }
1141
+ }
1142
+ function pathWorldVertices(skel, slotId, att) {
1143
+ return attachmentWorldVertices(skel, slotId, att);
1144
+ }
1145
+ function pathLength(vertexCount, closed, lengths) {
1146
+ const curveCount = closed ? vertexCount / 3 : vertexCount / 3 - 1;
1147
+ return curveCount >= 1 && lengths.length >= curveCount ? lengths[curveCount - 1] : lengths[lengths.length - 1] ?? 1;
1148
+ }
1149
+ function samplePath(points, closed, lengths, constantSpeed, positions, percent) {
1150
+ const n = points.length / 2;
1151
+ const curveCount = closed ? n / 3 : n / 3 - 1;
1152
+ if (curveCount < 1) return positions.map(() => ({ x: points[2] ?? 0, y: points[3] ?? 0, angle: 0 }));
1153
+ const curve = (i) => {
1154
+ const a = (i * 3 + 1) % n * 2, c1 = (i * 3 + 2) % n * 2, c2 = (i * 3 + 3) % n * 2, b = (i * 3 + 4) % n * 2;
1155
+ return [points[a], points[a + 1], points[c1], points[c1 + 1], points[c2], points[c2 + 1], points[b], points[b + 1]];
1156
+ };
1157
+ const pointAt = (i, t) => {
1158
+ const [x0, y0, x1, y1, x2, y2, x3, y3] = curve(i), u = 1 - t;
1159
+ return { x: u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3, y: u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3 };
1160
+ };
1161
+ const angleAt = (i, t) => {
1162
+ const [x0, y0, x1, y1, x2, y2, x3, y3] = curve(i), u = 1 - t;
1163
+ const dx = 3 * u * u * (x1 - x0) + 6 * u * t * (x2 - x1) + 3 * t * t * (x3 - x2);
1164
+ const dy = 3 * u * u * (y1 - y0) + 6 * u * t * (y2 - y1) + 3 * t * t * (y3 - y2);
1165
+ return Math.atan2(dy, dx) / DEG2;
1166
+ };
1167
+ const chords = (i, count) => {
1168
+ const acc = [];
1169
+ let sum = 0, prev = pointAt(i, 0);
1170
+ for (let k = 1; k <= count; k++) {
1171
+ const q = pointAt(i, k / count);
1172
+ sum += Math.hypot(q.x - prev.x, q.y - prev.y);
1173
+ acc.push(sum);
1174
+ prev = q;
1175
+ }
1176
+ return acc;
1177
+ };
1178
+ const cum = [];
1179
+ if (constantSpeed) {
1180
+ let acc = 0;
1181
+ for (let i = 0; i < curveCount; i++) {
1182
+ acc += chords(i, 4)[3];
1183
+ cum.push(acc);
1184
+ }
1185
+ } else for (let i = 0; i < curveCount; i++) cum.push(lengths[i] ?? (i ? cum[i - 1] : 0));
1186
+ const total = cum[curveCount - 1];
1187
+ const out = [];
1188
+ for (let pos of positions) {
1189
+ if (percent) pos *= total;
1190
+ if (closed) {
1191
+ if (total > 0) {
1192
+ pos %= total;
1193
+ if (pos < 0) pos += total;
1194
+ } else pos = 0;
1195
+ } else if (pos < 0) {
1196
+ const r = Math.atan2(points[5] - points[3], points[4] - points[2]);
1197
+ out.push({ x: points[2] + pos * Math.cos(r), y: points[3] + pos * Math.sin(r), angle: r / DEG2 });
1198
+ continue;
1199
+ } else if (pos > total) {
1200
+ const a = (curveCount * 3 + 1) * 2, c = curveCount * 3 * 2, e = pos - total;
1201
+ const r = Math.atan2(points[a + 1] - points[c + 1], points[a] - points[c]);
1202
+ out.push({ x: points[a] + e * Math.cos(r), y: points[a + 1] + e * Math.sin(r), angle: r / DEG2 });
1203
+ continue;
1204
+ }
1205
+ let i = 0;
1206
+ while (i < curveCount - 1 && pos > cum[i]) i++;
1207
+ const prev = i ? cum[i - 1] : 0, span = cum[i] - prev;
1208
+ const f = span > 0 ? (pos - prev) / span : 0;
1209
+ let t = f;
1210
+ if (constantSpeed) {
1211
+ const seg = chords(i, 10), target = f * seg[9];
1212
+ let k = 0;
1213
+ while (k < 9 && target > seg[k]) k++;
1214
+ const sPrev = k ? seg[k - 1] : 0, sSpan = seg[k] - sPrev;
1215
+ t = (k + (sSpan > 0 ? (target - sPrev) / sSpan : 0)) / 10;
1216
+ }
1217
+ out.push({ ...pointAt(i, t), angle: angleAt(i, t) });
1218
+ }
1219
+ return out;
1220
+ }
1221
+ var PATH_EPSILON = 1e-5;
1222
+ function applyPathConstraint(skel, c) {
1223
+ const mixRotate = cnum(skel, "path", c.id, "mixRotate", c.mixRotate), mixX = cnum(skel, "path", c.id, "mixX", c.mixX), mixY = cnum(skel, "path", c.id, "mixY", c.mixY);
1224
+ if (!mixRotate && !mixX && !mixY) return;
1225
+ const att = skel.attachment.get(c.target);
1226
+ if (!att || att.type !== "path") return;
1227
+ const points = pathWorldVertices(skel, c.target, att);
1228
+ const position = cnum(skel, "path", c.id, "position", c.position), spacing = cnum(skel, "path", c.id, "spacing", c.spacing);
1229
+ const tangents = c.rotateMode === "tangent", scale = c.rotateMode === "chainScale";
1230
+ const bones = c.bones.map((id) => ({ b: boneOf(skel.doc, id), st: skel.bones.get(id) }));
1231
+ const inputWorlds = bones.map(({ st }) => ({ ...st.world }));
1232
+ const spacesCount = tangents ? bones.length : bones.length + 1;
1233
+ const spaces = new Array(spacesCount).fill(0), worldLengths = [];
1234
+ let sum = 0;
1235
+ for (let i = 0; i < spacesCount - 1; i++) {
1236
+ const { b, st } = bones[i], setupLength = b.length;
1237
+ const len = setupLength < PATH_EPSILON ? 0 : setupLength * Math.hypot(st.world.a, st.world.b);
1238
+ worldLengths.push(len);
1239
+ if (c.spacingMode === "percent") spaces[i + 1] = spacing;
1240
+ else if (setupLength < PATH_EPSILON) spaces[i + 1] = spacing;
1241
+ else if (c.spacingMode === "proportional") {
1242
+ spaces[i + 1] = len;
1243
+ sum += len;
1244
+ } else spaces[i + 1] = (c.spacingMode === "length" ? setupLength + spacing : spacing) * len / setupLength;
1245
+ }
1246
+ if (c.spacingMode === "proportional" && sum > 0) {
1247
+ const f = spacesCount / sum * spacing;
1248
+ for (let i = 1; i < spacesCount; i++) spaces[i] *= f;
1249
+ }
1250
+ const total = measuredPathLength(points, att.closed, att.lengths, att.constantSpeed);
1251
+ const multiplier = c.spacingMode === "percent" ? total : c.spacingMode === "proportional" ? total / spacesCount : 1;
1252
+ let p = c.positionMode === "percent" ? position * total : position;
1253
+ const positions = [];
1254
+ for (let i = 0; i < spacesCount; i++) {
1255
+ p += spaces[i] * multiplier;
1256
+ positions.push(p);
1257
+ }
1258
+ const samples = samplePath(points, att.closed, att.lengths, att.constantSpeed, positions, false);
1259
+ const tip = c.rotation === 0 && c.rotateMode === "chain";
1260
+ let offsetRotation = c.rotation * DEG2;
1261
+ if (c.rotation !== 0) {
1262
+ const slot = skel.doc.slots.find((sl) => sl.id === c.target), tw = skel.bones.get(slot.bone).world;
1263
+ if (tw.a * tw.d - tw.b * tw.c < 0) offsetRotation = -offsetRotation;
1264
+ }
1265
+ let boneX = samples[0].x, boneY = samples[0].y;
1266
+ const solvedWorlds = /* @__PURE__ */ new Map();
1267
+ for (let i = 0; i < bones.length; i++) {
1268
+ const { b, st } = bones[i];
1269
+ let w = { ...inputWorlds[i] };
1270
+ w.tx += (boneX - w.tx) * mixX;
1271
+ w.ty += (boneY - w.ty) * mixY;
1272
+ const next = samples[i + 1] ?? { x: 0, y: 0, angle: 0 };
1273
+ const dx = next.x - boneX, dy = next.y - boneY;
1274
+ if (scale) {
1275
+ const len = worldLengths[i];
1276
+ if (len !== 0) {
1277
+ const f = (Math.hypot(dx, dy) / len - 1) * mixRotate + 1;
1278
+ w.a *= f;
1279
+ w.b *= f;
1280
+ }
1281
+ }
1282
+ boneX = next.x;
1283
+ boneY = next.y;
1284
+ if (mixRotate > 0) {
1285
+ const { a, b: bb, c: cc, d } = w;
1286
+ let r = (tangents ? samples[i].angle : spaces[i + 1] === 0 ? next.angle : Math.atan2(dy, dx) / DEG2) * DEG2;
1287
+ r -= Math.atan2(bb, a);
1288
+ if (tip) {
1289
+ const cos2 = Math.cos(r), sin2 = Math.sin(r), length = b.length;
1290
+ boneX += (length * (cos2 * a - sin2 * bb) - dx) * mixRotate;
1291
+ boneY += (length * (sin2 * a + cos2 * bb) - dy) * mixRotate;
1292
+ } else r += offsetRotation;
1293
+ if (r > Math.PI) r -= 2 * Math.PI;
1294
+ else if (r < -Math.PI) r += 2 * Math.PI;
1295
+ r *= mixRotate;
1296
+ const cos = Math.cos(r), sin = Math.sin(r);
1297
+ 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 };
1298
+ }
1299
+ solvedWorlds.set(b.id, w);
1300
+ }
1301
+ for (const b of boneOrder(skel.doc)) {
1302
+ const world = solvedWorlds.get(b.id);
1303
+ if (world) setEffectiveWorld(skel, b, skel.bones.get(b.id), world);
1304
+ }
1305
+ }
1306
+ function measuredPathLength(points, closed, lengths, constantSpeed) {
1307
+ const n = points.length / 2, curveCount = closed ? n / 3 : n / 3 - 1;
1308
+ if (curveCount < 1) return 0;
1309
+ if (!constantSpeed) return pathLength(n, closed, lengths);
1310
+ let total = 0;
1311
+ for (let i = 0; i < curveCount; i++) {
1312
+ const a = (i * 3 + 1) % n * 2, c1 = (i * 3 + 2) % n * 2, c2 = (i * 3 + 3) % n * 2, b = (i * 3 + 4) % n * 2;
1313
+ let px = points[a], py = points[a + 1];
1314
+ for (let k = 1; k <= 4; k++) {
1315
+ const t = k / 4, u = 1 - t;
1316
+ const x = u * u * u * points[a] + 3 * u * u * t * points[c1] + 3 * u * t * t * points[c2] + t * t * t * points[b];
1317
+ const y = u * u * u * points[a + 1] + 3 * u * u * t * points[c1 + 1] + 3 * u * t * t * points[c2 + 1] + t * t * t * points[b + 1];
1318
+ total += Math.hypot(x - px, y - py);
1319
+ px = x;
1320
+ py = y;
1321
+ }
1322
+ }
1323
+ return total;
1324
+ }
1325
+ function inheritedParentLinear(bone, parent) {
1326
+ switch (bone.inherit) {
1327
+ case "normal":
1328
+ return { ...parent, tx: 0, ty: 0 };
1329
+ case "onlyTranslation":
1330
+ return { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
1331
+ case "noRotationOrReflection": {
1332
+ const lenX = Math.hypot(parent.a, parent.b);
1333
+ const det = parent.a * parent.d - parent.b * parent.c;
1334
+ if (lenX * lenX <= 1e-4) {
1335
+ const lenY = Math.hypot(parent.c, parent.d), ux2 = lenY ? parent.c / lenY : 0, uy2 = lenY ? parent.d / lenY : 0;
1336
+ return { a: lenY * ux2 * ux2, b: lenY * ux2 * uy2, c: lenY * ux2 * uy2, d: lenY * uy2 * uy2, tx: 0, ty: 0 };
1337
+ }
1338
+ const ux = parent.a / lenX, uy = parent.b / lenX, sy = Math.abs(det) / lenX;
1339
+ 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 };
1340
+ }
1341
+ case "noScale":
1342
+ case "noScaleOrReflection": {
1343
+ const lenX = Math.hypot(parent.a, parent.b) || 1;
1344
+ const ua = parent.a / lenX, ub = parent.b / lenX;
1345
+ const det = parent.a * parent.d - parent.b * parent.c;
1346
+ const reflect = det < 0 && bone.inherit === "noScale" ? -1 : 1;
1347
+ return { a: ua, b: ub, c: -ub * reflect, d: ua * reflect, tx: 0, ty: 0 };
1348
+ }
1349
+ }
1350
+ }
1351
+ function setEffectiveWorld(skel, bone, st, world) {
1352
+ const parent = bone.parent ? skel.bones.get(bone.parent).world : void 0;
1353
+ let lin = world, tx = world.tx, ty = world.ty;
1354
+ if (parent) {
1355
+ [tx, ty] = apply(invert(parent), world.tx, world.ty);
1356
+ lin = mul(invert(inheritedParentLinear(bone, parent)), { ...world, tx: 0, ty: 0 });
1357
+ }
1358
+ st.x = tx;
1359
+ st.y = ty;
1360
+ st.rotation = Math.atan2(lin.b, lin.a) / DEG2;
1361
+ st.scaleX = Math.hypot(lin.a, lin.b);
1362
+ st.scaleY = Math.hypot(lin.c, lin.d);
1363
+ st.shearX = 0;
1364
+ st.shearY = shortest(Math.atan2(lin.d, lin.c) / DEG2 - st.rotation - 90);
1365
+ if (parent && (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection")) {
1366
+ const direction = mul(invert({ ...parent, tx: 0, ty: 0 }), { ...world, tx: 0, ty: 0 });
1367
+ st.rotation = Math.atan2(direction.b, direction.a) / DEG2;
1368
+ st.scaleX = Math.hypot(world.a, world.b);
1369
+ st.scaleY = Math.hypot(world.c, world.d);
1370
+ const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
1371
+ st.shearY = shortest((Math.atan2(world.d, world.c) - Math.atan2(world.b, world.a)) / DEG2 * sign - 90);
1372
+ }
1373
+ st.world = world;
1374
+ updateSubtree(skel, bone.id, false);
1375
+ }
1376
+ function attachmentWorldVertices(skel, slotId, att) {
1377
+ const deform = skel.pose.get(pk("deform", att.id, "vertices"));
1378
+ const off = Array.isArray(deform) && typeof deform[0] === "number" ? deform : void 0;
1379
+ const out = [];
1380
+ if (att.weights) {
1381
+ let k = 0;
1382
+ for (const list of att.weights) {
1383
+ let x = 0, y = 0;
1384
+ for (const inf of list) {
1385
+ const w = skel.bones.get(inf.bone).world;
1386
+ const vx = inf.x + (off ? off[k] : 0), vy = inf.y + (off ? off[k + 1] : 0);
1387
+ k += 2;
1388
+ x += (w.a * vx + w.c * vy + w.tx) * inf.w;
1389
+ y += (w.b * vx + w.d * vy + w.ty) * inf.w;
1390
+ }
1391
+ out.push(x, y);
1392
+ }
1393
+ } else {
1394
+ const slot = skel.doc.slots.find((s) => s.id === slotId), w = skel.bones.get(slot.bone).world, v = att.vertices;
1395
+ for (let i = 0; i < v.length; i += 2) {
1396
+ const vx = v[i] + (off ? off[i] : 0), vy = v[i + 1] + (off ? off[i + 1] : 0);
1397
+ out.push(w.a * vx + w.c * vy + w.tx, w.b * vx + w.d * vy + w.ty);
1398
+ }
1399
+ }
1400
+ return out;
1401
+ }
1402
+ function regionMatrix(skel, slotId, att) {
1403
+ const slot = skel.doc.slots.find((s) => s.id === slotId);
1404
+ return mul(skel.bones.get(slot.bone).world, affineOf(att.x, att.y, att.rotation, att.scaleX, att.scaleY));
1405
+ }
1406
+ function regionSize(doc, att) {
1407
+ const asset = doc.assets.find((a) => a.id === att.asset);
1408
+ return [att.width ?? asset.width, att.height ?? asset.height];
1409
+ }
1410
+
1411
+ // src/rig-state.ts
1412
+ var AnimationState = class {
1413
+ constructor(sink, opts = {}) {
1414
+ this.sink = sink;
1415
+ this.opts = opts;
1416
+ this.defaultMix = opts.mix ?? 0;
1417
+ }
1418
+ onEvent;
1419
+ base;
1420
+ main;
1421
+ queue = [];
1422
+ overlays = /* @__PURE__ */ new Map();
1423
+ /** last composed pose without overlays — mix snapshots start here */
1424
+ pose = /* @__PURE__ */ new Map();
1425
+ from;
1426
+ mixLeft = 0;
1427
+ mixTotal = 0;
1428
+ /** a mix begun in this update shows weight 0 for the frame it starts on */
1429
+ mixFresh = false;
1430
+ defaultMix;
1431
+ /** id of the animation currently shown (main, else base) */
1432
+ get current() {
1433
+ return (this.main ?? this.base)?.anim.id;
1434
+ }
1435
+ get baseId() {
1436
+ return this.base?.anim.id;
1437
+ }
1438
+ channel(id, loop, priority = 0) {
1439
+ const anim = this.sink.doc.animations.find((a) => a.id === id);
1440
+ if (!anim) {
1441
+ const message = `unknown animation "${id}"; available: ${this.sink.doc.animations.map((a) => a.id).join(", ")}`;
1442
+ if (this.opts.missingAnimation === "throw") throw new Error(message);
1443
+ (this.opts.onWarning ?? console.warn)(message);
1444
+ return void 0;
1445
+ }
1446
+ return { anim, time: 0, loop: loop ?? anim.loop, done: false, priority, fresh: true };
1447
+ }
1448
+ setBase(id, opts = {}) {
1449
+ const next = id === void 0 ? void 0 : this.channel(id);
1450
+ if (id !== void 0 && !next) return;
1451
+ this.base = next;
1452
+ if (!this.main || this.main.done) {
1453
+ this.main = void 0;
1454
+ this.beginMix(opts.mix);
1455
+ this.update(0);
1456
+ }
1457
+ }
1458
+ play(id, opts = {}) {
1459
+ const ch = this.channel(id, opts.loop, opts.priority ?? 0);
1460
+ if (!ch) {
1461
+ this.main?.settle?.("interrupted");
1462
+ for (const queued of this.queue) queued.settle?.("dropped");
1463
+ this.main = void 0;
1464
+ this.queue = [];
1465
+ this.beginMix(opts.mix);
1466
+ this.update(0);
1467
+ opts.onComplete?.("dropped");
1468
+ return Promise.resolve("dropped");
1469
+ }
1470
+ const promise = new Promise((resolve) => {
1471
+ ch.settle = (r) => {
1472
+ ch.settle = void 0;
1473
+ opts.onComplete?.(r);
1474
+ resolve(r);
1475
+ };
1476
+ });
1477
+ const cur = this.main, busy = cur !== void 0 && !cur.done, mode = opts.ifBusy ?? "queue";
1478
+ if (!busy || mode === "interrupt" || ch.priority >= cur.priority) {
1479
+ if (busy) cur.settle?.("interrupted");
1480
+ this.main = ch;
1481
+ this.beginMix(opts.mix);
1482
+ this.update(0);
1483
+ } else if (mode === "drop") ch.settle("dropped");
1484
+ else this.queue.push(ch);
1485
+ return promise;
1486
+ }
1487
+ /** Independent loop on top of the shown pose; `undefined` removes it. */
1488
+ setOverlay(name2, id) {
1489
+ if (id === void 0) this.overlays.delete(name2);
1490
+ else {
1491
+ const ch = this.channel(id);
1492
+ if (ch) this.overlays.set(name2, ch);
1493
+ }
1494
+ }
1495
+ /** Clear everything and show the setup pose. Pending plays resolve as "stopped". */
1496
+ stop() {
1497
+ this.main?.settle?.("stopped");
1498
+ for (const ch of this.queue) ch.settle?.("stopped");
1499
+ this.main = this.base = void 0;
1500
+ this.queue = [];
1501
+ this.overlays.clear();
1502
+ this.from = void 0;
1503
+ this.mixLeft = this.mixTotal = 0;
1504
+ this.mixFresh = false;
1505
+ this.pose = /* @__PURE__ */ new Map();
1506
+ this.sink.setPose(/* @__PURE__ */ new Map());
1507
+ }
1508
+ update(dt) {
1509
+ if (this.base) this.advance(this.base, dt, this.main === void 0);
1510
+ if (this.main) {
1511
+ const wasDone = this.main.done;
1512
+ this.advance(this.main, dt, true);
1513
+ if (this.main.done && !wasDone) {
1514
+ this.main.settle?.("completed");
1515
+ const next = this.queue.shift();
1516
+ if (next || this.base) {
1517
+ this.main = next;
1518
+ this.beginMix();
1519
+ }
1520
+ }
1521
+ }
1522
+ const shown = this.main ?? this.base;
1523
+ let pose = shown ? poseOf(shown.anim, shown.time) : /* @__PURE__ */ new Map();
1524
+ if (this.from) {
1525
+ if (!this.mixFresh) this.mixLeft = Math.max(0, this.mixLeft - dt);
1526
+ this.mixFresh = false;
1527
+ const w = this.mixTotal > 0 ? 1 - this.mixLeft / this.mixTotal : 1;
1528
+ pose = blendPoses(this.from, pose, w, (k) => restValue(this.sink.doc, k));
1529
+ if (this.mixLeft === 0) this.from = void 0;
1530
+ }
1531
+ this.pose = pose;
1532
+ if (this.overlays.size) {
1533
+ pose = new Map(pose);
1534
+ for (const [name2, ov] of this.overlays) {
1535
+ this.advance(ov, dt, false);
1536
+ poseOf(ov.anim, ov.time, pose);
1537
+ if (ov.done) this.overlays.delete(name2);
1538
+ }
1539
+ }
1540
+ this.sink.setPose(pose);
1541
+ }
1542
+ beginMix(mix = this.defaultMix) {
1543
+ if (mix > 0) {
1544
+ this.from = new Map(this.pose);
1545
+ this.mixLeft = this.mixTotal = mix;
1546
+ this.mixFresh = true;
1547
+ } else {
1548
+ this.from = void 0;
1549
+ this.mixLeft = this.mixTotal = 0;
1550
+ this.mixFresh = false;
1551
+ }
1552
+ }
1553
+ advance(ch, dt, emit) {
1554
+ if (ch.done) return;
1555
+ const prev = ch.fresh ? -1 : ch.time, d = ch.anim.duration;
1556
+ ch.fresh = false;
1557
+ ch.time += dt;
1558
+ if (emit) {
1559
+ for (const e of ch.anim.events) if (hit(e.t, prev, ch.time, d, ch.loop)) this.onEvent?.(e.name, e.payload);
1560
+ }
1561
+ if (ch.time >= d) {
1562
+ if (ch.loop) ch.time %= d;
1563
+ else {
1564
+ ch.time = d;
1565
+ ch.done = true;
1566
+ }
1567
+ }
1568
+ }
1569
+ };
1570
+ function hit(t, prev, cur, d, loop) {
1571
+ if (cur < d || !loop) return t > prev && t <= cur;
1572
+ return t > prev && t <= d || t <= cur % d;
1573
+ }
1574
+
1575
+ // src/rig-runtime.ts
1576
+ var DEG3 = Math.PI / 180;
1577
+ var MESH_GRID = 9;
1578
+ var GHOST_ALPHA = 0.5;
1579
+ async function createTextures(doc, loadSource) {
1580
+ const bySrc = /* @__PURE__ */ new Map(), out = /* @__PURE__ */ new Map();
1581
+ for (const a of doc.assets) {
1582
+ let base = bySrc.get(a.src);
1583
+ if (!base) bySrc.set(a.src, base = await loadSource(a.src));
1584
+ if (!a.frame) {
1585
+ out.set(a.id, base);
1586
+ continue;
1587
+ }
1588
+ const [fx, fy, w, h] = a.frame;
1589
+ const trimmed = !!a.trim || w !== a.width || h !== a.height;
1590
+ const [ox, oy] = a.trim ?? [0, 0];
1591
+ out.set(a.id, new Texture({
1592
+ source: base.source,
1593
+ frame: new Rectangle(fx, fy, a.rotate ? h : w, a.rotate ? w : h),
1594
+ orig: new Rectangle(0, 0, a.width, a.height),
1595
+ // packer offsets: x from the left, y from the BOTTOM: top-left of the trimmed image in the original
1596
+ trim: trimmed ? new Rectangle(ox, a.height - oy - h, w, h) : void 0,
1597
+ rotate: a.rotate ? 2 : 0
1598
+ }));
1599
+ }
1600
+ return out;
1601
+ }
1602
+ function atlasUV(asset, u, v, sourceW, sourceH) {
1603
+ if (!asset.frame) return [u, v];
1604
+ const [fx, fy, w, h] = asset.frame, [ox, oy] = asset.trim ?? [0, 0], W = asset.width, H = asset.height;
1605
+ let ax, ay;
1606
+ if (asset.rotate) {
1607
+ ax = fx - (H - oy - h) + v * H;
1608
+ ay = fy - (W - ox - w) + (1 - u) * W;
1609
+ } else {
1610
+ ax = fx - ox + u * W;
1611
+ ay = fy - (H - oy - h) + v * H;
1612
+ }
1613
+ return [ax / sourceW, ay / sourceH];
1614
+ }
1615
+ var rgbOf = (c) => Math.round(c[0] * 255) << 16 | Math.round(c[1] * 255) << 8 | Math.round(c[2] * 255);
1616
+ var sameNumbers = (a, b) => a === b || a.length === b.length && a.every((v, i) => v === b[i]);
1617
+ var RigPlayer = class {
1618
+ /** Default crossfade is 0.18 seconds; explicit mix: 0 requests a hard cut. */
1619
+ constructor(doc, textures, opts = {}) {
1620
+ this.doc = doc;
1621
+ parse(doc);
1622
+ this.textures = textures;
1623
+ this.view.sortableChildren = true;
1624
+ const latticed = /* @__PURE__ */ new Set();
1625
+ for (const a of doc.animations) for (const t of a.tracks) if (t.target === "deform" && t.prop !== "vertices") latticed.add(t.id);
1626
+ const slots = new Map(doc.slots.map((s) => [s.id, s]));
1627
+ for (const att of doc.attachments) {
1628
+ const slot = slots.get(att.slot);
1629
+ if (!slot || att.type === "path") continue;
1630
+ let item;
1631
+ if (att.type === "mesh") {
1632
+ const asset = doc.assets.find((a) => a.id === att.asset);
1633
+ const tex = this.textures.get(att.asset) ?? Texture.EMPTY;
1634
+ const page = this.pageTexture(tex);
1635
+ const n = att.uvs.length / 2, uvs = new Float32Array(n * 2), positions = new Float32Array(n * 2);
1636
+ for (let i = 0; i < n; i++) {
1637
+ const [u, v] = atlasUV(asset, att.uvs[i * 2], att.uvs[i * 2 + 1], page.width, page.height);
1638
+ uvs[i * 2] = u;
1639
+ uvs[i * 2 + 1] = v;
1640
+ }
1641
+ const geometry = new MeshGeometry({ positions, uvs, indices: new Uint32Array(att.triangles) });
1642
+ const mesh = new Mesh({ geometry, texture: page });
1643
+ item = { att, slot, node: mesh, skinned: { mesh, positions } };
1644
+ } else if (latticed.has(att.id)) {
1645
+ const plane = new MeshPlane({ texture: this.textures.get(att.asset) ?? Texture.EMPTY, verticesX: MESH_GRID, verticesY: MESH_GRID });
1646
+ plane.autoResize = false;
1647
+ item = { att, slot, node: plane, lattice: { plane, offsets: new Float64Array(18), applied: new Float64Array(18).fill(NaN) } };
1648
+ } else {
1649
+ const sp = new Sprite(this.textures.get(att.asset) ?? Texture.EMPTY);
1650
+ sp.anchor.set(0.5, 0.5);
1651
+ item = { att, slot, node: sp };
1652
+ }
1653
+ item.node.blendMode = slot.blend;
1654
+ this.items.push(item);
1655
+ this.view.addChild(item.node);
1656
+ }
1657
+ this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
1658
+ this.setPose(/* @__PURE__ */ new Map());
1659
+ }
1660
+ inputPose = /* @__PURE__ */ new Map();
1661
+ controlOverrides = /* @__PURE__ */ new Map();
1662
+ /** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
1663
+ setControls(values) {
1664
+ this.controlOverrides = controlPose(this.doc, values);
1665
+ this.setPose(this.inputPose);
1666
+ }
1667
+ resetControls() {
1668
+ this.controlOverrides.clear();
1669
+ this.setPose(this.inputPose);
1670
+ }
1671
+ view = new Container();
1672
+ state;
1673
+ items = [];
1674
+ beforePose = /* @__PURE__ */ new Set();
1675
+ afterPose = /* @__PURE__ */ new Set();
1676
+ clipPoints = /* @__PURE__ */ new Map();
1677
+ skel;
1678
+ textures;
1679
+ pages = /* @__PURE__ */ new Map();
1680
+ /** slots the editor hides; honoured by setPose */
1681
+ hidden = /* @__PURE__ */ new Set();
1682
+ /** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
1683
+ ghosts = /* @__PURE__ */ new Set();
1684
+ clipGraphics = /* @__PURE__ */ new Map();
1685
+ /** Read-only render handles for in-page tools. Nodes remain owned by the player. */
1686
+ get renderItems() {
1687
+ return this.items;
1688
+ }
1689
+ /** Current document-space clipping polygons, keyed by path attachment id. */
1690
+ get clipPolygons() {
1691
+ return this.clipPoints;
1692
+ }
1693
+ /**
1694
+ * Modify resolved control outputs before skeleton evaluation, constraints and skinning.
1695
+ * Callbacks run in registration order, each with its own map. Change control inputs via setControls.
1696
+ */
1697
+ onBeforePose(callback) {
1698
+ this.beforePose.add(callback);
1699
+ return () => {
1700
+ this.beforePose.delete(callback);
1701
+ };
1702
+ }
1703
+ /** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
1704
+ onAfterPose(callback) {
1705
+ this.afterPose.add(callback);
1706
+ return () => {
1707
+ this.afterPose.delete(callback);
1708
+ };
1709
+ }
1710
+ /** Rebuild the current pose without advancing playback; useful for reversible overlays. */
1711
+ refreshPose() {
1712
+ this.setPose(this.inputPose);
1713
+ }
1714
+ /** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
1715
+ pageTexture(tex) {
1716
+ let page = this.pages.get(tex.source);
1717
+ if (!page) this.pages.set(tex.source, page = tex.frame.x === 0 && tex.frame.y === 0 && tex.frame.width === tex.source.width && tex.frame.height === tex.source.height && !tex.rotate ? tex : new Texture({ source: tex.source }));
1718
+ return page;
1719
+ }
1720
+ /** Replace whatever is playing (no queueing). Resolves when the animation completes or is replaced. */
1721
+ play(animId, opts = {}) {
1722
+ this.state.onEvent = opts.onEvent;
1723
+ return this.state.play(animId, {
1724
+ loop: opts.loop,
1725
+ mix: opts.mix,
1726
+ ifBusy: "interrupt",
1727
+ onComplete: (r) => {
1728
+ if (r === "completed") opts.onComplete?.();
1729
+ }
1730
+ });
1731
+ }
1732
+ /**
1733
+ * Swap the document for pose evaluation without rebuilding display objects — valid only when slots/attachments
1734
+ * keep their ids and types (the editor uses it during drags). Throws otherwise: rebuild the player.
1735
+ */
1736
+ updateDoc(doc) {
1737
+ const atts = new Map(doc.attachments.map((a) => [a.id, a])), slots = new Map(doc.slots.map((s) => [s.id, s]));
1738
+ for (const it of this.items) {
1739
+ const a = atts.get(it.att.id), s = slots.get(it.slot.id);
1740
+ if (!a || !s || a.type !== it.att.type) throw new Error("updateDoc: structure changed \u2014 rebuild the player");
1741
+ if (a.type === "mesh" && it.att.type === "mesh" && (!sameNumbers(a.uvs, it.att.uvs) || !sameNumbers(a.triangles, it.att.triangles)))
1742
+ throw new Error("updateDoc: structure changed \u2014 rebuild the player");
1743
+ it.att = a;
1744
+ it.slot = s;
1745
+ it.node.blendMode = s.blend;
1746
+ }
1747
+ this.doc = doc;
1748
+ }
1749
+ /** Editor-only: hide a slot's display object regardless of the pose (restored by passing `true`). */
1750
+ setSlotVisible(slotId, visible) {
1751
+ if (visible) this.hidden.delete(slotId);
1752
+ else this.hidden.add(slotId);
1753
+ }
1754
+ /** Editor-only: draw these slots at GHOST_ALPHA at least, whatever the pose says (an empty list clears it). */
1755
+ setGhostSlots(slotIds) {
1756
+ this.ghosts = new Set(slotIds);
1757
+ }
1758
+ /** Stop playback and immediately restore the complete setup pose. */
1759
+ stop() {
1760
+ this.state.stop();
1761
+ }
1762
+ /** dt in seconds */
1763
+ tick(dt) {
1764
+ this.state.update(dt);
1765
+ }
1766
+ /** Apply a pose (rest where silent) to the display objects. */
1767
+ setPose(pose) {
1768
+ this.inputPose = new Map(pose);
1769
+ if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
1770
+ this.clipPoints.clear();
1771
+ const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
1772
+ pose = skel.pose;
1773
+ const masks = slotClips(skel), clips = new Set(masks.values());
1774
+ for (const [id, g] of this.clipGraphics) if (!clips.has(id)) {
1775
+ g.destroy();
1776
+ this.clipGraphics.delete(id);
1777
+ }
1778
+ for (const id of clips) {
1779
+ const att = this.doc.attachments.find((a) => a.id === id);
1780
+ if (att?.type !== "path" || !att.closed) continue;
1781
+ let g = this.clipGraphics.get(id);
1782
+ if (!g) {
1783
+ g = new Graphics();
1784
+ this.clipGraphics.set(id, g);
1785
+ this.view.addChild(g);
1786
+ }
1787
+ const vertices = attachmentWorldVertices(skel, att.slot, att);
1788
+ const n = Math.max(32, vertices.length * 4);
1789
+ const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n }, (_, i) => i / n), true);
1790
+ const polygon = points.flatMap((p) => [p.x, p.y]);
1791
+ this.clipPoints.set(id, polygon);
1792
+ g.clear().poly(polygon, true).fill(16777215);
1793
+ }
1794
+ const m = new Matrix();
1795
+ const orderV = pose.get(pk("drawOrder", "order", "order"));
1796
+ const order = /* @__PURE__ */ new Map();
1797
+ if (Array.isArray(orderV)) orderV.forEach((id, i) => order.set(id, i));
1798
+ for (const it of this.items) {
1799
+ const s = it.slot, shown = skel.attachment.get(s.id);
1800
+ const mask = masks.get(s.id);
1801
+ it.node.mask = mask ? this.clipGraphics.get(mask) ?? null : null;
1802
+ if (shown !== it.att || this.hidden.has(s.id)) {
1803
+ it.node.visible = false;
1804
+ continue;
1805
+ }
1806
+ it.node.visible = true;
1807
+ const colorV = pose.get(pk("slot", s.id, "color"));
1808
+ const color = Array.isArray(colorV) && typeof colorV[0] === "number" ? colorV : colorToArray(s.color);
1809
+ const alpha = numOf(pose, pk("slot", s.id, "alpha"), s.alpha) * color[3];
1810
+ it.node.alpha = this.ghosts.has(s.id) ? Math.max(alpha, GHOST_ALPHA) : alpha;
1811
+ it.node.tint = rgbOf(color);
1812
+ it.node.zIndex = order.size ? order.get(s.id) ?? 0 : numOf(pose, pk("slot", s.id, "z"), s.z);
1813
+ if (it.skinned) {
1814
+ it.node.setFromMatrix(m.identity());
1815
+ const v = attachmentWorldVertices(skel, s.id, it.att);
1816
+ const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
1817
+ buf.data.set(v);
1818
+ buf.update();
1819
+ continue;
1820
+ }
1821
+ const att = it.att;
1822
+ const [w, h] = regionSize(this.doc, att);
1823
+ if (it.lattice) {
1824
+ const off = controlOffsets(deformParams(pose, att.id) ?? {}, w, h, { x: 0.5, y: 0.5 }, it.lattice.offsets);
1825
+ if (!same(off, it.lattice.applied)) {
1826
+ const buf = it.lattice.plane.geometry.getBuffer("aPosition");
1827
+ gridPositions(off, w, h, { x: 0.5, y: 0.5 }, MESH_GRID, buf.data);
1828
+ buf.update();
1829
+ it.lattice.applied.set(off);
1830
+ }
1831
+ } else {
1832
+ const sp = it.node, tex = this.textures.get(att.asset);
1833
+ if (tex && sp.texture !== tex) sp.texture = tex;
1834
+ if (tex && (att.width || att.height)) {
1835
+ sp.width = w;
1836
+ sp.height = h;
1837
+ }
1838
+ }
1839
+ const a = regionMatrix(skel, s.id, att);
1840
+ m.set(a.a, a.b, a.c, a.d, a.tx, a.ty);
1841
+ it.node.setFromMatrix(m);
1842
+ if (!it.lattice && (att.width || att.height)) {
1843
+ it.node.scale.x *= w / (this.textures.get(att.asset)?.width ?? w);
1844
+ it.node.scale.y *= h / (this.textures.get(att.asset)?.height ?? h);
1845
+ }
1846
+ }
1847
+ for (const callback of this.afterPose) callback(this);
1848
+ }
1849
+ /** World-space bone positions — used by editor gizmos and renderPreview overlays */
1850
+ bonePositions() {
1851
+ const out = {};
1852
+ for (const [id, st] of this.skel.bones) out[id] = { x: st.world.tx, y: st.world.ty, rotation: Math.atan2(st.world.b, st.world.a) / DEG3 };
1853
+ return out;
1854
+ }
1855
+ /** current world transforms (for tests and tools) */
1856
+ get world() {
1857
+ return new Map([...this.skel.bones].map(([id, s]) => [id, s.world]));
1858
+ }
1859
+ };
1860
+ var numOf = (pose, key, rest) => {
1861
+ const v = pose.get(key);
1862
+ return typeof v === "number" ? v : rest;
1863
+ };
1864
+ var same = (a, b) => {
1865
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1866
+ return true;
1867
+ };
1868
+
1869
+ // src/e8/node.ts
1870
+ var isLoadedRig = (v) => !!v && typeof v === "object" && "doc" in v && v.textures instanceof Map;
1871
+ var plain = (p) => {
1872
+ try {
1873
+ return decodeURIComponent(p);
1874
+ } catch {
1875
+ return p;
1876
+ }
1877
+ };
1878
+ var BASE = "http://golem.invalid/";
1879
+ function resolveSrc(rigUrl, src) {
1880
+ const href = new URL(src, new URL(rigUrl, BASE)).href;
1881
+ return href.startsWith(BASE) ? `/${href.slice(BASE.length)}` : href;
1882
+ }
1883
+ var rigPrivate = (url, rig) => `${url}${url.includes("?") ? "&" : "?"}golem=${encodeURIComponent(rig)}`;
1884
+ function createRigLoader(io = {
1885
+ load: (url) => Assets.load(url),
1886
+ unload: (urls) => Assets.unload(urls)
1887
+ }) {
1888
+ return {
1889
+ extension: { type: ExtensionType.LoadParser, name: "golemRigLoader" },
1890
+ name: "golemRigLoader",
1891
+ async testParse(asset) {
1892
+ return !!asset && typeof asset === "object" && asset.format === "energy8.rig";
1893
+ },
1894
+ async parse(asset, resolved) {
1895
+ const doc = parse(asset);
1896
+ const sources = [];
1897
+ const textures = await createTextures(doc, (src) => {
1898
+ const url = rigPrivate(resolveSrc(resolved?.src ?? "", src), resolved?.src ?? "");
1899
+ sources.push(url);
1900
+ return io.load(url);
1901
+ });
1902
+ return { doc, textures, sources };
1903
+ },
1904
+ async unload(rig) {
1905
+ if (isLoadedRig(rig) && rig.sources.length) await io.unload(rig.sources);
1906
+ }
1907
+ };
1908
+ }
1909
+ var rigLoader = createRigLoader();
1910
+ var SCHEMA = Schema.object({
1911
+ rig: Schema.string().required().extra("asset", ["json"]).description("rig.json, project-relative"),
1912
+ animation: Schema.string().description("animation id (empty: the rig's first)"),
1913
+ loop: Schema.boolean().default(true),
1914
+ speed: Schema.number().default(1).description("playback speed multiplier"),
1915
+ controls: Schema.dict(Schema.number()).description("expression controls by id; a set one overrides that control's animated track")
1916
+ });
1917
+ function createGolemNode(ticker) {
1918
+ const live = /* @__PURE__ */ new WeakMap();
1919
+ const drop = (view) => {
1920
+ const s = live.get(view);
1921
+ if (!s) return;
1922
+ live.delete(view);
1923
+ ticker.remove(s.tick);
1924
+ s.player.stop();
1925
+ s.player.view.destroy({ children: true });
1926
+ };
1927
+ const animationOf = (doc, wanted) => wanted && doc.animations.some((a) => a.id === wanted) ? wanted : doc.animations[0]?.id;
1928
+ const node = {
1929
+ type: "golem",
1930
+ schema: SCHEMA,
1931
+ accept: { extensions: ["rig.json"], prop: "rig" },
1932
+ assetProps: ["rig"],
1933
+ assets: (p) => p.rig ? [p.rig] : [],
1934
+ dependsOn(props, ctx) {
1935
+ const got = props.rig ? ctx.reflect.get("assetLoader", false)?.get(props.rig) : void 0;
1936
+ if (!isLoadedRig(got)) return [];
1937
+ const files = got.doc.assets.map((a) => resolveSrc(`/${props.rig}`, a.src.replace(/[?#].*$/, ""))).filter((u) => u.startsWith("/")).map((u) => plain(u.slice(1)));
1938
+ return [...new Set(files)];
1939
+ },
1940
+ mount(props, ctx) {
1941
+ const view = new Container2();
1942
+ node.update(view, props, ctx);
1943
+ return view;
1944
+ },
1945
+ update(view, props, ctx) {
1946
+ const loader = ctx.reflect.get("assetLoader", false);
1947
+ const got = props.rig ? loader?.get(props.rig) : void 0;
1948
+ if (live.has(view) && live.get(view).rig !== got) drop(view);
1949
+ if (got === void 0 || got === null) return;
1950
+ if (!isLoadedRig(got)) throw new Error(`not a golem rig: ${props.rig} (a rig.json has "format": "energy8.rig")`);
1951
+ let s = live.get(view);
1952
+ if (!s) {
1953
+ const player = new RigPlayer(got.doc, got.textures);
1954
+ player.view.position.set(-got.doc.meta.width / 2, -got.doc.meta.height / 2);
1955
+ view.addChild(player.view);
1956
+ const state = { rig: got, player, speed: 1, controls: "{}", tick: (t) => player.tick(t.deltaMS / 1e3 * state.speed) };
1957
+ ticker.add(state.tick);
1958
+ live.set(view, s = state);
1959
+ }
1960
+ s.speed = props.speed ?? 1;
1961
+ const animation = animationOf(got.doc, props.animation), loop = props.loop ?? true;
1962
+ if (animation !== s.animation || loop !== s.loop) {
1963
+ s.animation = animation;
1964
+ s.loop = loop;
1965
+ if (animation) void s.player.play(animation, { loop });
1966
+ else s.player.stop();
1967
+ }
1968
+ const controls = JSON.stringify(props.controls ?? {});
1969
+ if (controls !== s.controls) {
1970
+ s.controls = controls;
1971
+ if (Object.keys(props.controls ?? {}).length) s.player.setControls(props.controls);
1972
+ else s.player.resetControls();
1973
+ }
1974
+ },
1975
+ once(view, props) {
1976
+ const s = live.get(view);
1977
+ const animation = s && animationOf(s.rig.doc, props.animation);
1978
+ if (!s || !animation) return Promise.resolve();
1979
+ s.animation = animation;
1980
+ s.loop = false;
1981
+ return new Promise((resolve) => {
1982
+ let settled = false;
1983
+ const done = () => {
1984
+ if (settled) return;
1985
+ settled = true;
1986
+ view.off("destroyed", done);
1987
+ resolve();
1988
+ };
1989
+ view.once("destroyed", done);
1990
+ void s.player.play(animation, { loop: false }).then(done);
1991
+ });
1992
+ },
1993
+ // not one of the runtime's own types: it does not destroy the view; textures belong to the Assets cache
1994
+ unmount(view) {
1995
+ drop(view);
1996
+ view.destroy({ children: true });
1997
+ }
1998
+ };
1999
+ return node;
2000
+ }
2001
+ var golemNode = createGolemNode(Ticker.shared);
2002
+
2003
+ // src/e8/runtime.ts
2004
+ var name = "golem";
2005
+ var inject = ["nodeTypes"];
2006
+ function apply2(ctx) {
2007
+ ctx.effect(() => {
2008
+ extensions.add(rigLoader);
2009
+ return () => extensions.remove(rigLoader);
2010
+ }, "golem:loader");
2011
+ ctx.effect(() => ctx.nodeTypes.register(golemNode), "golem:node");
2012
+ }
2013
+ export {
2014
+ apply2 as apply,
2015
+ createGolemNode,
2016
+ createRigLoader,
2017
+ golemNode,
2018
+ inject,
2019
+ isLoadedRig,
2020
+ name,
2021
+ resolveSrc,
2022
+ rigLoader
2023
+ };
2024
+ //# sourceMappingURL=e8-runtime.js.map