@energy8platform/golem 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/editor.js +502 -87
- package/dist/lib/cli.js +1309 -359
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/editor/stage.d.ts +7 -0
- package/dist/lib/editor/store.d.ts +72 -32
- package/dist/lib/editor-entry.js +1309 -359
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +162 -27
- package/dist/lib/preview/export-video.d.ts +12 -0
- package/dist/lib/preview/harness.d.ts +2 -0
- package/dist/lib/preview/render-preview.d.ts +6 -0
- package/dist/lib/rig-anim.d.ts +1 -1
- package/dist/lib/rig-check.d.ts +2 -1
- package/dist/lib/rig-constraints.d.ts +63 -0
- package/dist/lib/rig-format.d.ts +551 -325
- package/dist/lib/rig-gates.d.ts +209 -0
- package/dist/lib/rig-mesh.d.ts +1 -0
- package/dist/lib/rig-motion.d.ts +22 -0
- package/dist/lib/rig-qa.d.ts +35 -0
- package/dist/lib/rig-quality.d.ts +12 -0
- package/dist/lib/rig-runtime.d.ts +10 -1
- package/dist/lib/rig-state.d.ts +3 -0
- package/dist/lib/rig-tools.d.ts +5 -1
- package/dist/lib/runtime.js +146 -25
- package/dist/lib/runtime.js.map +3 -3
- package/dist/lib/tools.js +1291 -341
- package/dist/lib/tools.js.map +4 -4
- package/package.json +2 -2
package/dist/lib/runtime.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/rig-runtime.ts
|
|
2
|
-
import { Container, Sprite, Mesh, MeshGeometry, MeshPlane, Texture, Matrix, Rectangle } from "pixi.js";
|
|
2
|
+
import { Container, Sprite, Mesh, MeshGeometry, MeshPlane, Texture, Matrix, Rectangle, Graphics } from "pixi.js";
|
|
3
3
|
|
|
4
4
|
// src/rig-format.ts
|
|
5
5
|
import { z } from "zod";
|
|
@@ -48,7 +48,7 @@ function gridPositions(offsets, w, h, pivot, n, out = new Float32Array(2 * n * n
|
|
|
48
48
|
var ID_RE = /^[a-z][a-z0-9_]*$/;
|
|
49
49
|
var Id = z.string().regex(ID_RE, "snake_case id");
|
|
50
50
|
var Ease = z.union([
|
|
51
|
-
z.enum(["linear", "step", "ease_in", "ease_out", "ease_in_out", "back_out", "elastic_out"]),
|
|
51
|
+
z.enum(["linear", "step", "ease_in", "ease_out", "ease_in_out", "minimum_jerk", "back_out", "elastic_out"]),
|
|
52
52
|
// cubic-bezier control points, same semantics as CSS
|
|
53
53
|
z.object({ bezier: z.tuple([z.number(), z.number(), z.number(), z.number()]) })
|
|
54
54
|
]);
|
|
@@ -80,6 +80,7 @@ var Asset = z.object({
|
|
|
80
80
|
/** where this layer came from — provenance for agents */
|
|
81
81
|
origin: z.object({
|
|
82
82
|
sourceImage: z.string().optional(),
|
|
83
|
+
bboxSpace: z.enum(["source", "document"]).optional(),
|
|
83
84
|
/** bbox in source image: [x, y, w, h] */
|
|
84
85
|
bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
|
|
85
86
|
inpainted: z.boolean().default(false),
|
|
@@ -102,6 +103,8 @@ var Bone = z.object({
|
|
|
102
103
|
var Slot = z.object({
|
|
103
104
|
id: Id,
|
|
104
105
|
bone: Id,
|
|
106
|
+
/** Closed path attachment used as a moving clipping mask. */
|
|
107
|
+
clip: Id.optional(),
|
|
105
108
|
/** active attachment id; undefined = hidden */
|
|
106
109
|
attachment: Id.optional(),
|
|
107
110
|
alpha: z.number().min(0).max(1).default(1),
|
|
@@ -164,6 +167,8 @@ var IkConstraint = z.object({
|
|
|
164
167
|
order: z.number().int().default(0),
|
|
165
168
|
bones: z.array(Id).min(1),
|
|
166
169
|
target: Id,
|
|
170
|
+
iterations: z.number().int().min(1).max(1e3).optional(),
|
|
171
|
+
tolerance: z.number().positive().optional(),
|
|
167
172
|
mix: z.number().default(1),
|
|
168
173
|
softness: z.number().default(0),
|
|
169
174
|
bendPositive: z.boolean().default(true),
|
|
@@ -211,7 +216,7 @@ var BoneProp = z.enum(["x", "y", "rotation", "scaleX", "scaleY", "shearX", "shea
|
|
|
211
216
|
var SlotProp = z.enum(["alpha", "attachment", "z", "color"]);
|
|
212
217
|
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");
|
|
213
218
|
var IkProp = z.enum(["mix", "softness", "bendPositive", "compress", "stretch"]);
|
|
214
|
-
var TransformProp = z.enum(["mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY"]);
|
|
219
|
+
var TransformProp = z.enum(["mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY", "x", "y", "rotation", "scaleX", "scaleY", "shearY"]);
|
|
215
220
|
var PathProp = z.enum(["position", "spacing", "mixRotate", "mixX", "mixY"]);
|
|
216
221
|
var KeyValue = z.union([z.number(), z.string(), z.null(), z.array(z.number()), z.array(z.string())]);
|
|
217
222
|
var Key = z.object({
|
|
@@ -220,14 +225,15 @@ var Key = z.object({
|
|
|
220
225
|
/** easing INTO the next key */
|
|
221
226
|
ease: Ease.default("ease_in_out")
|
|
222
227
|
});
|
|
228
|
+
var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
|
|
223
229
|
var Track = z.discriminatedUnion("target", [
|
|
224
|
-
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, keys: z.array(Key).min(1) }),
|
|
225
|
-
z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, keys: z.array(Key).min(1) }),
|
|
226
|
-
z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, keys: z.array(Key).min(1) }),
|
|
227
|
-
z.object({ target: z.literal("ik"), id: Id, prop: IkProp, keys: z.array(Key).min(1) }),
|
|
228
|
-
z.object({ target: z.literal("transform"), id: Id, prop: TransformProp, keys: z.array(Key).min(1) }),
|
|
229
|
-
z.object({ target: z.literal("path"), id: Id, prop: PathProp, keys: z.array(Key).min(1) }),
|
|
230
|
-
z.object({ target: z.literal("drawOrder"), id: Id.default("order"), prop: z.literal("order").default("order"), keys: z.array(Key).min(1) })
|
|
230
|
+
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
231
|
+
z.object({ target: z.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
232
|
+
z.object({ target: z.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
233
|
+
z.object({ target: z.literal("ik"), id: Id, prop: IkProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
234
|
+
z.object({ target: z.literal("transform"), id: Id, prop: TransformProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
235
|
+
z.object({ target: z.literal("path"), id: Id, prop: PathProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
236
|
+
z.object({ target: z.literal("drawOrder"), id: Id.default("order"), prop: z.literal("order").default("order"), ...Interpolation, keys: z.array(Key).min(1) })
|
|
231
237
|
]);
|
|
232
238
|
var Event = z.object({
|
|
233
239
|
t: z.number().nonnegative(),
|
|
@@ -369,6 +375,10 @@ function validate(doc) {
|
|
|
369
375
|
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)`);
|
|
370
376
|
}
|
|
371
377
|
for (const s of doc.slots) {
|
|
378
|
+
if (s.clip) {
|
|
379
|
+
const mask = attachments.get(s.clip);
|
|
380
|
+
if (mask?.type !== "path" || !mask.closed) errs.push(`slot "${s.id}": clip must name a closed path attachment`);
|
|
381
|
+
}
|
|
372
382
|
if (!bones.has(s.bone)) errs.push(`slot "${s.id}": unknown bone "${s.bone}"`);
|
|
373
383
|
if (s.attachment) {
|
|
374
384
|
const a = attachments.get(s.attachment);
|
|
@@ -400,7 +410,8 @@ function validate(doc) {
|
|
|
400
410
|
}
|
|
401
411
|
}
|
|
402
412
|
for (const c of doc.ik) {
|
|
403
|
-
|
|
413
|
+
for (let i = 1; i < c.bones.length; i++) if (doc.bones.find((b) => b.id === c.bones[i])?.parent !== c.bones[i - 1])
|
|
414
|
+
errs.push(`ik "${c.id}": bones must form a contiguous parent-to-child chain`);
|
|
404
415
|
for (const b of c.bones) if (!bones.has(b)) errs.push(`ik "${c.id}": unknown bone "${b}"`);
|
|
405
416
|
if (!bones.has(c.target)) errs.push(`ik "${c.id}": unknown target bone "${c.target}"`);
|
|
406
417
|
}
|
|
@@ -472,6 +483,7 @@ var named = {
|
|
|
472
483
|
ease_in: (t) => t * t,
|
|
473
484
|
ease_out: (t) => 1 - (1 - t) * (1 - t),
|
|
474
485
|
ease_in_out: (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2,
|
|
486
|
+
minimum_jerk: (t) => t * t * t * (10 + t * (-15 + 6 * t)),
|
|
475
487
|
back_out: (t) => 1 + 2.70158 * (t - 1) ** 3 + 1.70158 * (t - 1) ** 2,
|
|
476
488
|
elastic_out: (t) => t === 0 ? 0 : t === 1 ? 1 : 2 ** (-10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI / 3)) + 1
|
|
477
489
|
};
|
|
@@ -503,7 +515,25 @@ function ease(spec) {
|
|
|
503
515
|
return f;
|
|
504
516
|
}
|
|
505
517
|
var pk = (target, id, prop) => `${target}:${id}:${prop}`;
|
|
506
|
-
function
|
|
518
|
+
function smoothValue(track, i, t, component, loop) {
|
|
519
|
+
const k = track.keys, n = k.length;
|
|
520
|
+
const v = (j) => component === void 0 ? k[j].v : k[j].v[component];
|
|
521
|
+
const h = (j) => k[j + 1].t - k[j].t;
|
|
522
|
+
const d = (j) => (v(j + 1) - v(j)) / h(j);
|
|
523
|
+
const harmonic = (a, b, h0, h1) => a * b <= 0 ? 0 : 3 * (h0 + h1) / ((2 * h1 + h0) / a + (h1 + 2 * h0) / b);
|
|
524
|
+
const tangent = (j) => {
|
|
525
|
+
if (j === 0 || j === n - 1) {
|
|
526
|
+
if (loop && n > 2 && v(0) === v(n - 1) && k[0].ease !== "step" && k[n - 2].ease !== "step")
|
|
527
|
+
return harmonic(d(n - 2), d(0), h(n - 2), h(0));
|
|
528
|
+
return 0;
|
|
529
|
+
}
|
|
530
|
+
if (k[j - 1].ease === "step" || k[j].ease === "step") return 0;
|
|
531
|
+
return harmonic(d(j - 1), d(j), h(j - 1), h(j));
|
|
532
|
+
};
|
|
533
|
+
const u = (t - k[i].t) / h(i), u2 = u * u, u3 = u2 * u;
|
|
534
|
+
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);
|
|
535
|
+
}
|
|
536
|
+
function sample(track, t, loop = false) {
|
|
507
537
|
const keys = track.keys;
|
|
508
538
|
if (t <= keys[0].t) return keys[0].v;
|
|
509
539
|
const last = keys[keys.length - 1];
|
|
@@ -511,31 +541,38 @@ function sample(track, t) {
|
|
|
511
541
|
let i = 0;
|
|
512
542
|
while (keys[i + 1].t <= t) i++;
|
|
513
543
|
const a = keys[i], b = keys[i + 1];
|
|
544
|
+
if (a.ease === "step") return a.v;
|
|
514
545
|
if (typeof a.v === "number" && typeof b.v === "number") {
|
|
546
|
+
if (track.interpolation === "pchip") return smoothValue(track, i, t, void 0, loop);
|
|
515
547
|
const u = ease(a.ease)((t - a.t) / (b.t - a.t));
|
|
516
548
|
return a.v + (b.v - a.v) * u;
|
|
517
549
|
}
|
|
518
550
|
if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
|
|
551
|
+
if (track.interpolation === "pchip") return a.v.map((_, j) => smoothValue(track, i, t, j, loop));
|
|
519
552
|
const u = ease(a.ease)((t - a.t) / (b.t - a.t)), av = a.v, bv = b.v;
|
|
520
553
|
return av.map((v, k) => v + ((bv[k] ?? v) - v) * u);
|
|
521
554
|
}
|
|
522
555
|
return a.v;
|
|
523
556
|
}
|
|
524
557
|
function poseOf(anim, t, out = /* @__PURE__ */ new Map()) {
|
|
525
|
-
for (const tr of anim.tracks) out.set(pk(tr.target, tr.id, tr.prop), sample(tr, t));
|
|
558
|
+
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));
|
|
526
559
|
return out;
|
|
527
560
|
}
|
|
528
561
|
function blendPoses(from, to, w, restOf) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
562
|
+
if (w >= 1) return new Map(to);
|
|
563
|
+
const out = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const k of /* @__PURE__ */ new Set([...from.keys(), ...to.keys()])) {
|
|
565
|
+
const f = from.has(k) ? from.get(k) : restOf(k);
|
|
566
|
+
const cur = to.has(k) ? to.get(k) : restOf(k);
|
|
567
|
+
if (typeof f === "number" && !Array.isArray(cur)) {
|
|
533
568
|
const target = typeof cur === "number" ? cur : restOf(k);
|
|
534
569
|
out.set(k, f + (target - f) * w);
|
|
535
570
|
} else if (Array.isArray(f) && typeof f[0] === "number") {
|
|
536
571
|
const fv = f, tv = Array.isArray(cur) && typeof cur[0] === "number" ? cur : fv.map(() => 0);
|
|
537
572
|
out.set(k, fv.map((v, i) => v + ((tv[i] ?? 0) - v) * w));
|
|
538
|
-
} else if (cur
|
|
573
|
+
} else if (Array.isArray(cur) && typeof cur[0] === "number" && typeof f !== "string") {
|
|
574
|
+
out.set(k, cur.map((v) => v * w));
|
|
575
|
+
} else out.set(k, f ?? null);
|
|
539
576
|
}
|
|
540
577
|
return out;
|
|
541
578
|
}
|
|
@@ -704,10 +741,43 @@ function aimAt(skel, boneId, tx, ty, mix, compress = false, stretch = false, uni
|
|
|
704
741
|
updateSubtree(skel, boneId);
|
|
705
742
|
}
|
|
706
743
|
var shortest = (deg) => ((deg + 180) % 360 + 360) % 360 - 180;
|
|
744
|
+
function applyLongIk(skel, c, tx, ty, mix) {
|
|
745
|
+
const initial = c.bones.map((id) => ({ ...skel.bones.get(id) }));
|
|
746
|
+
const last = c.bones.at(-1), len = boneOf(skel.doc, last).length;
|
|
747
|
+
const end = () => apply(skel.bones.get(last).world, len, 0);
|
|
748
|
+
const tolerance = c.tolerance ?? 0.05;
|
|
749
|
+
const root = skel.bones.get(c.bones[0]).world, tip = end();
|
|
750
|
+
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) {
|
|
751
|
+
skel.bones.get(last).rotation += cnum(skel, "ik", c.id, "bendPositive", c.bendPositive) > 0.5 ? 5 : -5;
|
|
752
|
+
updateSubtree(skel, last);
|
|
753
|
+
}
|
|
754
|
+
for (let iter = 0; iter < (c.iterations ?? 100); iter++) {
|
|
755
|
+
const e = end();
|
|
756
|
+
if (Math.hypot(e[0] - tx, e[1] - ty) <= tolerance) break;
|
|
757
|
+
for (let i = c.bones.length - 1; i >= 0; i--) {
|
|
758
|
+
const id = c.bones[i], b = boneOf(skel.doc, id), st = skel.bones.get(id), w = st.world, e2 = end();
|
|
759
|
+
const angle = wrapRad(Math.atan2(ty - w.ty, tx - w.tx) - Math.atan2(e2[1] - w.ty, e2[0] - w.tx));
|
|
760
|
+
const cos = Math.cos(angle), sin = Math.sin(angle);
|
|
761
|
+
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 });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (mix < 1) {
|
|
765
|
+
c.bones.forEach((id, i) => {
|
|
766
|
+
const st = skel.bones.get(id), from = initial[i];
|
|
767
|
+
for (const p of ["rotation", "shearX", "shearY"]) st[p] = from[p] + shortest(st[p] - from[p]) * mix;
|
|
768
|
+
for (const p of ["x", "y", "scaleX", "scaleY"]) st[p] = from[p] + (st[p] - from[p]) * mix;
|
|
769
|
+
});
|
|
770
|
+
updateSubtree(skel, c.bones[0]);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
707
773
|
function applyIk(skel, c) {
|
|
708
774
|
const mix = cnum(skel, "ik", c.id, "mix", c.mix);
|
|
709
775
|
if (mix <= 0) return;
|
|
710
776
|
const target = skel.bones.get(c.target).world;
|
|
777
|
+
if (c.bones.length > 2) {
|
|
778
|
+
applyLongIk(skel, c, target.tx, target.ty, mix);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
711
781
|
const bendDir = cnum(skel, "ik", c.id, "bendPositive", c.bendPositive) > 0.5 ? 1 : -1;
|
|
712
782
|
const compress = cnum(skel, "ik", c.id, "compress", c.compress) > 0.5, stretch = cnum(skel, "ik", c.id, "stretch", c.stretch) > 0.5;
|
|
713
783
|
let softness = cnum(skel, "ik", c.id, "softness", c.softness);
|
|
@@ -842,6 +912,7 @@ function applyTransformConstraint(skel, c) {
|
|
|
842
912
|
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);
|
|
843
913
|
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);
|
|
844
914
|
if (!mixRotate && !mixX && !mixY && !mixScaleX && !mixScaleY && !mixShearY) return;
|
|
915
|
+
c = { ...c, ...Object.fromEntries(["x", "y", "rotation", "scaleX", "scaleY", "shearY"].map((p) => [p, cnum(skel, "transform", c.id, p, c[p])])) };
|
|
845
916
|
const target = skel.bones.get(c.target), tw = target.world;
|
|
846
917
|
const offsetRotation = c.rotation * DEG2, offsetShearY = c.shearY * DEG2;
|
|
847
918
|
for (const id of c.bones) {
|
|
@@ -1169,6 +1240,7 @@ function regionSize(doc, att) {
|
|
|
1169
1240
|
var AnimationState = class {
|
|
1170
1241
|
constructor(sink, opts = {}) {
|
|
1171
1242
|
this.sink = sink;
|
|
1243
|
+
this.opts = opts;
|
|
1172
1244
|
this.defaultMix = opts.mix ?? 0;
|
|
1173
1245
|
}
|
|
1174
1246
|
onEvent;
|
|
@@ -1193,11 +1265,18 @@ var AnimationState = class {
|
|
|
1193
1265
|
}
|
|
1194
1266
|
channel(id, loop, priority = 0) {
|
|
1195
1267
|
const anim = this.sink.doc.animations.find((a) => a.id === id);
|
|
1196
|
-
if (!anim)
|
|
1268
|
+
if (!anim) {
|
|
1269
|
+
const message = `unknown animation "${id}"; available: ${this.sink.doc.animations.map((a) => a.id).join(", ")}`;
|
|
1270
|
+
if (this.opts.missingAnimation === "throw") throw new Error(message);
|
|
1271
|
+
(this.opts.onWarning ?? console.warn)(message);
|
|
1272
|
+
return void 0;
|
|
1273
|
+
}
|
|
1197
1274
|
return { anim, time: 0, loop: loop ?? anim.loop, done: false, priority, fresh: true };
|
|
1198
1275
|
}
|
|
1199
1276
|
setBase(id, opts = {}) {
|
|
1200
|
-
|
|
1277
|
+
const next = id === void 0 ? void 0 : this.channel(id);
|
|
1278
|
+
if (id !== void 0 && !next) return;
|
|
1279
|
+
this.base = next;
|
|
1201
1280
|
if (!this.main || this.main.done) {
|
|
1202
1281
|
this.main = void 0;
|
|
1203
1282
|
this.beginMix(opts.mix);
|
|
@@ -1206,6 +1285,16 @@ var AnimationState = class {
|
|
|
1206
1285
|
}
|
|
1207
1286
|
play(id, opts = {}) {
|
|
1208
1287
|
const ch = this.channel(id, opts.loop, opts.priority ?? 0);
|
|
1288
|
+
if (!ch) {
|
|
1289
|
+
this.main?.settle?.("interrupted");
|
|
1290
|
+
for (const queued of this.queue) queued.settle?.("dropped");
|
|
1291
|
+
this.main = void 0;
|
|
1292
|
+
this.queue = [];
|
|
1293
|
+
this.beginMix(opts.mix);
|
|
1294
|
+
this.update(0);
|
|
1295
|
+
opts.onComplete?.("dropped");
|
|
1296
|
+
return Promise.resolve("dropped");
|
|
1297
|
+
}
|
|
1209
1298
|
const promise = new Promise((resolve) => {
|
|
1210
1299
|
ch.settle = (r) => {
|
|
1211
1300
|
ch.settle = void 0;
|
|
@@ -1226,7 +1315,10 @@ var AnimationState = class {
|
|
|
1226
1315
|
/** Independent loop on top of the shown pose; `undefined` removes it. */
|
|
1227
1316
|
setOverlay(name, id) {
|
|
1228
1317
|
if (id === void 0) this.overlays.delete(name);
|
|
1229
|
-
else
|
|
1318
|
+
else {
|
|
1319
|
+
const ch = this.channel(id);
|
|
1320
|
+
if (ch) this.overlays.set(name, ch);
|
|
1321
|
+
}
|
|
1230
1322
|
}
|
|
1231
1323
|
/** Clear everything and show the setup pose. Pending plays resolve as "stopped". */
|
|
1232
1324
|
stop() {
|
|
@@ -1311,6 +1403,7 @@ function hit(t, prev, cur, d, loop) {
|
|
|
1311
1403
|
// src/rig-runtime.ts
|
|
1312
1404
|
var DEG3 = Math.PI / 180;
|
|
1313
1405
|
var MESH_GRID = 9;
|
|
1406
|
+
var GHOST_ALPHA = 0.5;
|
|
1314
1407
|
async function createTextures(doc, loadSource) {
|
|
1315
1408
|
const bySrc = /* @__PURE__ */ new Map(), out = /* @__PURE__ */ new Map();
|
|
1316
1409
|
for (const a of doc.assets) {
|
|
@@ -1350,7 +1443,7 @@ function atlasUV(asset, u, v, sourceW, sourceH) {
|
|
|
1350
1443
|
var rgbOf = (c) => Math.round(c[0] * 255) << 16 | Math.round(c[1] * 255) << 8 | Math.round(c[2] * 255);
|
|
1351
1444
|
var sameNumbers = (a, b) => a === b || a.length === b.length && a.every((v, i) => v === b[i]);
|
|
1352
1445
|
var RigPlayer = class {
|
|
1353
|
-
/**
|
|
1446
|
+
/** Default crossfade is 0.18 seconds; explicit mix: 0 requests a hard cut. */
|
|
1354
1447
|
constructor(doc, textures, opts = {}) {
|
|
1355
1448
|
this.doc = doc;
|
|
1356
1449
|
parse(doc);
|
|
@@ -1389,7 +1482,7 @@ var RigPlayer = class {
|
|
|
1389
1482
|
this.items.push(item);
|
|
1390
1483
|
this.view.addChild(item.node);
|
|
1391
1484
|
}
|
|
1392
|
-
this.state = new AnimationState(this, { mix: opts.mix ?? 0 });
|
|
1485
|
+
this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
|
|
1393
1486
|
this.setPose(/* @__PURE__ */ new Map());
|
|
1394
1487
|
}
|
|
1395
1488
|
view = new Container();
|
|
@@ -1400,6 +1493,9 @@ var RigPlayer = class {
|
|
|
1400
1493
|
pages = /* @__PURE__ */ new Map();
|
|
1401
1494
|
/** slots the editor hides; honoured by setPose */
|
|
1402
1495
|
hidden = /* @__PURE__ */ new Set();
|
|
1496
|
+
/** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
|
|
1497
|
+
ghosts = /* @__PURE__ */ new Set();
|
|
1498
|
+
clipGraphics = /* @__PURE__ */ new Map();
|
|
1403
1499
|
/** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
|
|
1404
1500
|
pageTexture(tex) {
|
|
1405
1501
|
let page = this.pages.get(tex.source);
|
|
@@ -1411,7 +1507,7 @@ var RigPlayer = class {
|
|
|
1411
1507
|
this.state.onEvent = opts.onEvent;
|
|
1412
1508
|
return this.state.play(animId, {
|
|
1413
1509
|
loop: opts.loop,
|
|
1414
|
-
mix: opts.mix
|
|
1510
|
+
mix: opts.mix,
|
|
1415
1511
|
ifBusy: "interrupt",
|
|
1416
1512
|
onComplete: (r) => {
|
|
1417
1513
|
if (r === "completed") opts.onComplete?.();
|
|
@@ -1440,6 +1536,10 @@ var RigPlayer = class {
|
|
|
1440
1536
|
if (visible) this.hidden.delete(slotId);
|
|
1441
1537
|
else this.hidden.add(slotId);
|
|
1442
1538
|
}
|
|
1539
|
+
/** Editor-only: draw these slots at GHOST_ALPHA at least, whatever the pose says (an empty list clears it). */
|
|
1540
|
+
setGhostSlots(slotIds) {
|
|
1541
|
+
this.ghosts = new Set(slotIds);
|
|
1542
|
+
}
|
|
1443
1543
|
/** Stop playback and immediately restore the complete setup pose. */
|
|
1444
1544
|
stop() {
|
|
1445
1545
|
this.state.stop();
|
|
@@ -1451,12 +1551,32 @@ var RigPlayer = class {
|
|
|
1451
1551
|
/** Apply a pose (rest where silent) to the display objects. */
|
|
1452
1552
|
setPose(pose) {
|
|
1453
1553
|
const skel = this.skel = evaluate(this.doc, pose);
|
|
1554
|
+
const clips = new Set(this.doc.slots.flatMap((s) => s.clip ? [s.clip] : []));
|
|
1555
|
+
for (const [id, g] of this.clipGraphics) if (!clips.has(id)) {
|
|
1556
|
+
g.destroy();
|
|
1557
|
+
this.clipGraphics.delete(id);
|
|
1558
|
+
}
|
|
1559
|
+
for (const id of clips) {
|
|
1560
|
+
const att = this.doc.attachments.find((a) => a.id === id);
|
|
1561
|
+
if (att?.type !== "path" || !att.closed) continue;
|
|
1562
|
+
let g = this.clipGraphics.get(id);
|
|
1563
|
+
if (!g) {
|
|
1564
|
+
g = new Graphics();
|
|
1565
|
+
this.clipGraphics.set(id, g);
|
|
1566
|
+
this.view.addChild(g);
|
|
1567
|
+
}
|
|
1568
|
+
const vertices = attachmentWorldVertices(skel, att.slot, att);
|
|
1569
|
+
const n = Math.max(32, vertices.length * 4);
|
|
1570
|
+
const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n }, (_, i) => i / n), true);
|
|
1571
|
+
g.clear().poly(points.flatMap((p) => [p.x, p.y]), true).fill(16777215);
|
|
1572
|
+
}
|
|
1454
1573
|
const m = new Matrix();
|
|
1455
1574
|
const orderV = pose.get(pk("drawOrder", "order", "order"));
|
|
1456
1575
|
const order = /* @__PURE__ */ new Map();
|
|
1457
1576
|
if (Array.isArray(orderV)) orderV.forEach((id, i) => order.set(id, i));
|
|
1458
1577
|
for (const it of this.items) {
|
|
1459
1578
|
const s = it.slot, shown = skel.attachment.get(s.id);
|
|
1579
|
+
it.node.mask = s.clip ? this.clipGraphics.get(s.clip) ?? null : null;
|
|
1460
1580
|
if (shown !== it.att || this.hidden.has(s.id)) {
|
|
1461
1581
|
it.node.visible = false;
|
|
1462
1582
|
continue;
|
|
@@ -1464,7 +1584,8 @@ var RigPlayer = class {
|
|
|
1464
1584
|
it.node.visible = true;
|
|
1465
1585
|
const colorV = pose.get(pk("slot", s.id, "color"));
|
|
1466
1586
|
const color = Array.isArray(colorV) && typeof colorV[0] === "number" ? colorV : colorToArray(s.color);
|
|
1467
|
-
|
|
1587
|
+
const alpha = numOf(pose, pk("slot", s.id, "alpha"), s.alpha) * color[3];
|
|
1588
|
+
it.node.alpha = this.ghosts.has(s.id) ? Math.max(alpha, GHOST_ALPHA) : alpha;
|
|
1468
1589
|
it.node.tint = rgbOf(color);
|
|
1469
1590
|
it.node.zIndex = order.size ? order.get(s.id) ?? 0 : numOf(pose, pk("slot", s.id, "z"), s.z);
|
|
1470
1591
|
if (it.skinned) {
|