@energy8platform/golem 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/editor.css +50 -0
- package/dist/editor.js +1384 -983
- package/dist/lib/cli.js +487 -83
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/editor/controls-panel.d.ts +2 -0
- package/dist/lib/editor/store.d.ts +245 -1
- package/dist/lib/editor-entry.js +487 -83
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +236 -19
- package/dist/lib/interactive-bones.d.ts +37 -0
- package/dist/lib/interactive-editor.d.ts +80 -0
- package/dist/lib/interactive-editor.js +744 -0
- package/dist/lib/interactive-editor.js.map +7 -0
- package/dist/lib/rig-anim.d.ts +7 -2
- package/dist/lib/rig-control-qa.d.ts +346 -0
- package/dist/lib/rig-controls.d.ts +13 -0
- package/dist/lib/rig-format.d.ts +1903 -2
- package/dist/lib/rig-gates.d.ts +6 -6
- package/dist/lib/rig-runtime.d.ts +39 -2
- package/dist/lib/runtime.d.ts +3 -1
- package/dist/lib/runtime.js +239 -19
- package/dist/lib/runtime.js.map +4 -4
- package/dist/lib/spine-import.d.ts +4 -0
- package/dist/lib/tools.js +483 -79
- package/dist/lib/tools.js.map +4 -4
- package/package.json +7 -1
package/dist/lib/harness.js
CHANGED
|
@@ -49524,7 +49524,9 @@ ${parts.join("\n")}
|
|
|
49524
49524
|
/** curve lengths for constant-speed positioning */
|
|
49525
49525
|
lengths: external_exports.array(external_exports.number()).default([]),
|
|
49526
49526
|
closed: external_exports.boolean().default(false),
|
|
49527
|
-
constantSpeed: external_exports.boolean().default(true)
|
|
49527
|
+
constantSpeed: external_exports.boolean().default(true),
|
|
49528
|
+
/** Spine-style active clipping span in the posed draw order; end slot is inclusive. */
|
|
49529
|
+
clipping: external_exports.object({ end: Id.optional() }).optional()
|
|
49528
49530
|
});
|
|
49529
49531
|
var Attachment = external_exports.discriminatedUnion("type", [RegionAttachment, MeshAttachment, PathAttachment]);
|
|
49530
49532
|
var IkConstraint = external_exports.object({
|
|
@@ -49588,10 +49590,13 @@ ${parts.join("\n")}
|
|
|
49588
49590
|
t: external_exports.number().nonnegative(),
|
|
49589
49591
|
v: KeyValue,
|
|
49590
49592
|
/** easing INTO the next key */
|
|
49591
|
-
ease: Ease.default("ease_in_out")
|
|
49593
|
+
ease: Ease.default("ease_in_out"),
|
|
49594
|
+
/** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
|
|
49595
|
+
componentEase: external_exports.array(Ease).min(1).optional()
|
|
49592
49596
|
});
|
|
49593
49597
|
var Interpolation = { interpolation: external_exports.enum(["segment", "pchip"]).optional() };
|
|
49594
49598
|
var Track = external_exports.discriminatedUnion("target", [
|
|
49599
|
+
external_exports.object({ target: external_exports.literal("control"), id: Id, prop: external_exports.literal("value"), ...Interpolation, keys: external_exports.array(Key).min(1) }),
|
|
49595
49600
|
external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
|
|
49596
49601
|
external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
|
|
49597
49602
|
external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
|
|
@@ -49614,6 +49619,25 @@ ${parts.join("\n")}
|
|
|
49614
49619
|
/** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
|
|
49615
49620
|
preset: external_exports.object({ name: external_exports.string(), params: external_exports.record(external_exports.unknown()) }).optional()
|
|
49616
49621
|
});
|
|
49622
|
+
var RigControl = external_exports.object({
|
|
49623
|
+
id: Id,
|
|
49624
|
+
label: external_exports.string().optional(),
|
|
49625
|
+
min: external_exports.number().finite(),
|
|
49626
|
+
max: external_exports.number().finite(),
|
|
49627
|
+
default: external_exports.number().finite()
|
|
49628
|
+
}).strict();
|
|
49629
|
+
var ControlValues = external_exports.array(external_exports.union([external_exports.number().finite(), external_exports.array(external_exports.number().finite()).min(1)])).min(2);
|
|
49630
|
+
var ControlChannel = external_exports.discriminatedUnion("target", [
|
|
49631
|
+
external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
|
|
49632
|
+
external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: external_exports.literal("alpha"), values: ControlValues }).strict(),
|
|
49633
|
+
external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: external_exports.literal("vertices"), values: ControlValues }).strict()
|
|
49634
|
+
]);
|
|
49635
|
+
var ControlBinding = external_exports.object({
|
|
49636
|
+
id: Id,
|
|
49637
|
+
axes: external_exports.array(external_exports.object({ control: Id, keys: external_exports.array(external_exports.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
|
|
49638
|
+
/** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
|
|
49639
|
+
channels: external_exports.array(ControlChannel).min(1)
|
|
49640
|
+
}).strict();
|
|
49617
49641
|
var Reference = external_exports.object({
|
|
49618
49642
|
src: external_exports.string(),
|
|
49619
49643
|
opacity: external_exports.number().min(0).max(1).default(0.5),
|
|
@@ -49639,7 +49663,9 @@ ${parts.join("\n")}
|
|
|
49639
49663
|
ik: external_exports.array(IkConstraint).default([]),
|
|
49640
49664
|
transform: external_exports.array(TransformConstraint).default([]),
|
|
49641
49665
|
path: external_exports.array(PathConstraint).default([]),
|
|
49642
|
-
animations: external_exports.array(Animation)
|
|
49666
|
+
animations: external_exports.array(Animation),
|
|
49667
|
+
controls: external_exports.array(RigControl).optional(),
|
|
49668
|
+
controlBindings: external_exports.array(ControlBinding).optional()
|
|
49643
49669
|
});
|
|
49644
49670
|
var RigDocument = external_exports.preprocess((input) => {
|
|
49645
49671
|
const v2 = input?.version;
|
|
@@ -49714,6 +49740,8 @@ ${parts.join("\n")}
|
|
|
49714
49740
|
dup(doc.ik, "ik");
|
|
49715
49741
|
dup(doc.transform, "transform");
|
|
49716
49742
|
dup(doc.path, "path");
|
|
49743
|
+
dup(doc.controls ?? [], "control");
|
|
49744
|
+
dup(doc.controlBindings ?? [], "control binding");
|
|
49717
49745
|
const bones = new Set(doc.bones.map((b2) => b2.id));
|
|
49718
49746
|
const assets = new Map(doc.assets.map((a2) => [a2.id, a2]));
|
|
49719
49747
|
const slots = new Set(doc.slots.map((s2) => s2.id));
|
|
@@ -49751,6 +49779,10 @@ ${parts.join("\n")}
|
|
|
49751
49779
|
else if (a2.slot !== s2.id) errs.push(`slot "${s2.id}": attachment "${s2.attachment}" belongs to slot "${a2.slot}"`);
|
|
49752
49780
|
}
|
|
49753
49781
|
}
|
|
49782
|
+
for (const a2 of doc.attachments) if (a2.type === "path" && a2.clipping) {
|
|
49783
|
+
if (!a2.closed) errs.push(`attachment "${a2.id}": clipping path must be closed`);
|
|
49784
|
+
if (a2.clipping.end && !slots.has(a2.clipping.end)) errs.push(`attachment "${a2.id}": unknown clipping end slot "${a2.clipping.end}"`);
|
|
49785
|
+
}
|
|
49754
49786
|
const vertexCount = (a2) => {
|
|
49755
49787
|
if (a2.type === "region") return 4;
|
|
49756
49788
|
if (a2.type === "mesh") return a2.uvs.length / 2;
|
|
@@ -49788,20 +49820,59 @@ ${parts.join("\n")}
|
|
|
49788
49820
|
for (const b2 of c2.bones) if (!bones.has(b2)) errs.push(`path "${c2.id}": unknown bone "${b2}"`);
|
|
49789
49821
|
if (!slots.has(c2.target)) errs.push(`path "${c2.id}": unknown target slot "${c2.target}"`);
|
|
49790
49822
|
}
|
|
49823
|
+
const controls = new Map((doc.controls ?? []).map((c2) => [c2.id, c2]));
|
|
49824
|
+
const driven = /* @__PURE__ */ new Set();
|
|
49825
|
+
for (const c2 of controls.values()) if (!(c2.min < c2.max && c2.default >= c2.min && c2.default <= c2.max))
|
|
49826
|
+
errs.push(`control "${c2.id}": require min < max and default within range`);
|
|
49827
|
+
for (const b2 of doc.controlBindings ?? []) {
|
|
49828
|
+
const prefix = `control binding "${b2.id}"`;
|
|
49829
|
+
if (new Set(b2.axes.map((a2) => a2.control)).size !== b2.axes.length) errs.push(`${prefix}: axes must use different controls`);
|
|
49830
|
+
for (const axis of b2.axes) {
|
|
49831
|
+
const c2 = controls.get(axis.control);
|
|
49832
|
+
if (!c2) errs.push(`${prefix}: unknown control "${axis.control}"`);
|
|
49833
|
+
else if (axis.keys[0] !== c2.min || axis.keys.at(-1) !== c2.max) errs.push(`${prefix}: keys must span the control's full range`);
|
|
49834
|
+
if (axis.keys.some((v2, i2) => i2 > 0 && v2 <= axis.keys[i2 - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
|
|
49835
|
+
}
|
|
49836
|
+
const count2 = b2.axes.reduce((n2, a2) => n2 * a2.keys.length, 1);
|
|
49837
|
+
for (const ch of b2.channels) {
|
|
49838
|
+
const key = `${ch.target}.${ch.id}.${ch.prop}`;
|
|
49839
|
+
if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
|
|
49840
|
+
driven.add(key);
|
|
49841
|
+
if (ch.values.length !== count2) errs.push(`${prefix}: ${key} needs ${count2} grid values`);
|
|
49842
|
+
if (ch.target === "deform") {
|
|
49843
|
+
const att = attachments.get(ch.id);
|
|
49844
|
+
if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
|
|
49845
|
+
else {
|
|
49846
|
+
const n2 = att.weights ? att.weights.reduce((s2, a2) => s2 + a2.length * 2, 0) : att.vertices.length;
|
|
49847
|
+
if (ch.values.some((v2) => !Array.isArray(v2) || v2.length !== n2)) errs.push(`${prefix}: ${key} needs arrays of ${n2} offsets`);
|
|
49848
|
+
}
|
|
49849
|
+
} else {
|
|
49850
|
+
if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
|
|
49851
|
+
if (ch.values.some((v2) => typeof v2 !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
|
|
49852
|
+
if (ch.target === "slot" && ch.values.some((v2) => typeof v2 === "number" && (v2 < 0 || v2 > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
|
|
49853
|
+
}
|
|
49854
|
+
}
|
|
49855
|
+
}
|
|
49791
49856
|
for (const a2 of doc.animations) {
|
|
49792
49857
|
const tracks = /* @__PURE__ */ new Set();
|
|
49793
49858
|
for (const t2 of a2.tracks) {
|
|
49794
49859
|
const tid = trackId(t2);
|
|
49795
49860
|
if (tracks.has(tid)) errs.push(`animation "${a2.id}": duplicate track ${tid}`);
|
|
49796
49861
|
tracks.add(tid);
|
|
49797
|
-
|
|
49862
|
+
if (driven.has(tid)) errs.push(`animation "${a2.id}": ${tid} is controlled by a binding; animate its control instead`);
|
|
49863
|
+
const known = t2.target === "bone" ? bones.has(t2.id) : t2.target === "slot" ? slots.has(t2.id) : t2.target === "deform" ? attachments.has(t2.id) : t2.target === "ik" ? iks.has(t2.id) : t2.target === "transform" ? transforms.has(t2.id) : t2.target === "path" ? paths.has(t2.id) : t2.target === "control" ? controls.has(t2.id) : true;
|
|
49798
49864
|
if (!known) errs.push(`animation "${a2.id}": unknown ${t2.target} "${t2.id}"`);
|
|
49799
49865
|
const bound = t2.target === "deform" ? DEFORM_BOUNDS[t2.prop] : void 0;
|
|
49800
49866
|
const att = t2.target === "deform" ? attachments.get(t2.id) : void 0;
|
|
49801
49867
|
let prev = -1;
|
|
49802
49868
|
for (const k2 of t2.keys) {
|
|
49869
|
+
if (k2.componentEase && (!Array.isArray(k2.v) || !k2.v.every((v2) => typeof v2 === "number") || k2.componentEase.length !== k2.v.length)) errs.push(`animation "${a2.id}" ${tid}: componentEase must match the numeric-array value length`);
|
|
49803
49870
|
if (k2.t <= prev && prev >= 0) errs.push(`animation "${a2.id}" ${tid}: keys not strictly increasing`);
|
|
49804
49871
|
if (k2.t > a2.duration) errs.push(`animation "${a2.id}" ${tid}: key at ${k2.t}s exceeds duration`);
|
|
49872
|
+
if (t2.target === "control") {
|
|
49873
|
+
const c2 = controls.get(t2.id);
|
|
49874
|
+
if (typeof k2.v !== "number" || !Number.isFinite(k2.v) || c2 && (k2.v < c2.min || k2.v > c2.max)) errs.push(`animation "${a2.id}" ${tid}: control key outside its finite range`);
|
|
49875
|
+
}
|
|
49805
49876
|
if (t2.target === "slot" && t2.prop === "attachment") {
|
|
49806
49877
|
if (k2.v !== null && typeof k2.v !== "string") errs.push(`animation "${a2.id}" ${tid}: attachment key must be an attachment id or null`);
|
|
49807
49878
|
else if (typeof k2.v === "string") {
|
|
@@ -49840,6 +49911,52 @@ ${parts.join("\n")}
|
|
|
49840
49911
|
return doc;
|
|
49841
49912
|
}
|
|
49842
49913
|
|
|
49914
|
+
// src/rig-controls.ts
|
|
49915
|
+
function controlPose(doc, values) {
|
|
49916
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
49917
|
+
for (const [id, value] of Object.entries(values)) {
|
|
49918
|
+
const c2 = doc.controls?.find((c3) => c3.id === id);
|
|
49919
|
+
if (!c2) throw new Error(`unknown control "${id}"`);
|
|
49920
|
+
if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
|
|
49921
|
+
out2.set(`control:${id}:value`, Math.max(c2.min, Math.min(c2.max, value)));
|
|
49922
|
+
}
|
|
49923
|
+
return out2;
|
|
49924
|
+
}
|
|
49925
|
+
function bracket(keys, value) {
|
|
49926
|
+
let i2 = 0;
|
|
49927
|
+
while (i2 < keys.length - 2 && value > keys[i2 + 1]) i2++;
|
|
49928
|
+
return [i2, Math.max(0, Math.min(1, (value - keys[i2]) / (keys[i2 + 1] - keys[i2])))];
|
|
49929
|
+
}
|
|
49930
|
+
function sampleBinding(binding, values) {
|
|
49931
|
+
const [x2, u2] = bracket(binding.axes[0].keys, values[0]);
|
|
49932
|
+
const [y2, v2] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
|
|
49933
|
+
const width = binding.axes[0].keys.length, a2 = y2 * width + x2;
|
|
49934
|
+
const mix = (a3, b2, u3) => a3 + (b2 - a3) * u3;
|
|
49935
|
+
return binding.channels.map((ch) => {
|
|
49936
|
+
const read = (i2, j2) => j2 === void 0 ? ch.values[i2] : ch.values[i2][j2];
|
|
49937
|
+
const at = (j2) => {
|
|
49938
|
+
const top = mix(read(a2, j2), read(a2 + 1, j2), u2);
|
|
49939
|
+
return binding.axes[1] ? mix(top, mix(read(a2 + width, j2), read(a2 + width + 1, j2), u2), v2) : top;
|
|
49940
|
+
};
|
|
49941
|
+
return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j2) => at(j2)) : at();
|
|
49942
|
+
});
|
|
49943
|
+
}
|
|
49944
|
+
function resolveControls(doc, input) {
|
|
49945
|
+
if (!doc.controlBindings?.length) return input;
|
|
49946
|
+
const out2 = new Map(input), controls = new Map(doc.controls?.map((c2) => [c2.id, c2]));
|
|
49947
|
+
for (const b2 of doc.controlBindings) {
|
|
49948
|
+
const values = b2.axes.map((axis) => {
|
|
49949
|
+
const c2 = controls.get(axis.control);
|
|
49950
|
+
const value = input.get(`control:${c2.id}:value`) ?? c2.default;
|
|
49951
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c2.id}" must be finite`);
|
|
49952
|
+
return Math.max(c2.min, Math.min(c2.max, value));
|
|
49953
|
+
});
|
|
49954
|
+
const sampled = sampleBinding(b2, values);
|
|
49955
|
+
b2.channels.forEach((ch, i2) => out2.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i2]));
|
|
49956
|
+
}
|
|
49957
|
+
return out2;
|
|
49958
|
+
}
|
|
49959
|
+
|
|
49843
49960
|
// src/rig-anim.ts
|
|
49844
49961
|
var DEG2 = Math.PI / 180;
|
|
49845
49962
|
var named = {
|
|
@@ -49914,8 +50031,8 @@ ${parts.join("\n")}
|
|
|
49914
50031
|
}
|
|
49915
50032
|
if (Array.isArray(a2.v) && Array.isArray(b2.v) && typeof a2.v[0] === "number") {
|
|
49916
50033
|
if (track.interpolation === "pchip") return a2.v.map((_, j2) => smoothValue(track, i2, t2, j2, loop));
|
|
49917
|
-
const
|
|
49918
|
-
return av.map((v2, k2) => v2 + ((bv[k2] ?? v2) - v2) *
|
|
50034
|
+
const phase = (t2 - a2.t) / (b2.t - a2.t), av = a2.v, bv = b2.v;
|
|
50035
|
+
return av.map((v2, k2) => v2 + ((bv[k2] ?? v2) - v2) * ease(a2.componentEase?.[k2] ?? a2.ease)(phase));
|
|
49919
50036
|
}
|
|
49920
50037
|
return a2.v;
|
|
49921
50038
|
}
|
|
@@ -49943,6 +50060,7 @@ ${parts.join("\n")}
|
|
|
49943
50060
|
}
|
|
49944
50061
|
function restValue(doc, key) {
|
|
49945
50062
|
const [target, id, prop] = key.split(":");
|
|
50063
|
+
if (target === "control") return doc.controls?.find((c2) => c2.id === id)?.default ?? 0;
|
|
49946
50064
|
if (target === "bone") return doc.bones.find((b2) => b2.id === id).transform[prop];
|
|
49947
50065
|
if (target === "slot") {
|
|
49948
50066
|
const s2 = doc.slots.find((s3) => s3.id === id);
|
|
@@ -50002,6 +50120,13 @@ ${parts.join("\n")}
|
|
|
50002
50120
|
if (!parent) return local;
|
|
50003
50121
|
if (bone.inherit === "normal") return mul(parent, local);
|
|
50004
50122
|
const [tx, ty] = apply(parent, s2.x, s2.y);
|
|
50123
|
+
if (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection") {
|
|
50124
|
+
const r2 = s2.rotation * DEG2, x2 = parent.a * Math.cos(r2) + parent.c * Math.sin(r2), y2 = parent.b * Math.cos(r2) + parent.d * Math.sin(r2);
|
|
50125
|
+
const length2 = Math.hypot(x2, y2), a2 = length2 > 1e-5 ? x2 / length2 : x2, b2 = length2 > 1e-5 ? y2 / length2 : y2;
|
|
50126
|
+
const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
|
|
50127
|
+
const basis2 = { a: a2, b: b2, c: -b2 * sign, d: a2 * sign, tx: 0, ty: 0 };
|
|
50128
|
+
return { ...mul(basis2, affineOf(0, 0, 0, s2.scaleX, s2.scaleY, s2.shearX, s2.shearY)), tx, ty };
|
|
50129
|
+
}
|
|
50005
50130
|
return { ...mul(inheritedParentLinear(bone, parent), { ...local, tx: 0, ty: 0 }), tx, ty };
|
|
50006
50131
|
}
|
|
50007
50132
|
var orderCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -50034,18 +50159,20 @@ ${parts.join("\n")}
|
|
|
50034
50159
|
shearY: num(pose, pk("bone", id, "shearY"), r2.shearY)
|
|
50035
50160
|
};
|
|
50036
50161
|
};
|
|
50037
|
-
function updateSubtree(skel, root) {
|
|
50162
|
+
function updateSubtree(skel, root, includeRoot = true) {
|
|
50038
50163
|
const doc = skel.doc;
|
|
50039
50164
|
const kids = /* @__PURE__ */ new Map();
|
|
50040
50165
|
for (const b2 of doc.bones) if (b2.parent) (kids.get(b2.parent) ?? kids.set(b2.parent, []).get(b2.parent)).push(b2);
|
|
50041
|
-
const stack = [root];
|
|
50166
|
+
const stack = includeRoot ? [root] : (kids.get(root) ?? []).map((b2) => b2.id);
|
|
50042
50167
|
while (stack.length) {
|
|
50043
50168
|
const id = stack.pop(), b2 = boneOf(doc, id), st = skel.bones.get(id);
|
|
50044
50169
|
st.world = boneWorldOf(b2, st, b2.parent ? skel.bones.get(b2.parent).world : void 0);
|
|
50045
50170
|
for (const k2 of kids.get(id) ?? []) stack.push(k2.id);
|
|
50046
50171
|
}
|
|
50047
50172
|
}
|
|
50048
|
-
function evaluate(doc, pose) {
|
|
50173
|
+
function evaluate(doc, pose, modifiers) {
|
|
50174
|
+
pose = resolveControls(doc, pose);
|
|
50175
|
+
if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
|
|
50049
50176
|
const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
|
|
50050
50177
|
for (const b2 of boneOrder(doc)) {
|
|
50051
50178
|
const s2 = localOf(doc, pose, b2);
|
|
@@ -50070,6 +50197,23 @@ ${parts.join("\n")}
|
|
|
50070
50197
|
}
|
|
50071
50198
|
return skel;
|
|
50072
50199
|
}
|
|
50200
|
+
function slotClips(skel) {
|
|
50201
|
+
const keyed = skel.pose.get(pk("drawOrder", "order", "order"));
|
|
50202
|
+
const order = Array.isArray(keyed) ? keyed : [...skel.doc.slots].sort((a2, b2) => a2.z - b2.z).map((s2) => s2.id);
|
|
50203
|
+
const masks = /* @__PURE__ */ new Map();
|
|
50204
|
+
let active;
|
|
50205
|
+
for (const id of order) {
|
|
50206
|
+
const att = skel.attachment.get(id);
|
|
50207
|
+
if (!active && att?.type === "path" && att.clipping) {
|
|
50208
|
+
active = att;
|
|
50209
|
+
continue;
|
|
50210
|
+
}
|
|
50211
|
+
if (active) masks.set(id, active.id);
|
|
50212
|
+
if (active?.clipping?.end === id) active = void 0;
|
|
50213
|
+
}
|
|
50214
|
+
for (const s2 of skel.doc.slots) if (s2.clip) masks.set(s2.id, s2.clip);
|
|
50215
|
+
return masks;
|
|
50216
|
+
}
|
|
50073
50217
|
var cnum = (skel, kind, id, prop, rest) => num(skel.pose, pk(kind, id, prop), typeof rest === "boolean" ? rest ? 1 : 0 : rest);
|
|
50074
50218
|
var IDENTITY = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
50075
50219
|
var wrapRad = (r2) => r2 > Math.PI ? r2 - 2 * Math.PI : r2 < -Math.PI ? r2 + 2 * Math.PI : r2;
|
|
@@ -50279,7 +50423,8 @@ ${parts.join("\n")}
|
|
|
50279
50423
|
if (!mixRotate && !mixX && !mixY && !mixScaleX && !mixScaleY && !mixShearY) return;
|
|
50280
50424
|
c2 = { ...c2, ...Object.fromEntries(["x", "y", "rotation", "scaleX", "scaleY", "shearY"].map((p2) => [p2, cnum(skel, "transform", c2.id, p2, c2[p2])])) };
|
|
50281
50425
|
const target = skel.bones.get(c2.target), tw = target.world;
|
|
50282
|
-
const
|
|
50426
|
+
const handedness = tw.a * tw.d - tw.b * tw.c < 0 ? -1 : 1;
|
|
50427
|
+
const offsetRotation = c2.rotation * DEG2 * (c2.local ? 1 : handedness), offsetShearY = c2.shearY * DEG2 * (c2.local ? 1 : handedness);
|
|
50283
50428
|
for (const id of c2.bones) {
|
|
50284
50429
|
const b2 = boneOf(skel.doc, id), st = skel.bones.get(id);
|
|
50285
50430
|
if (c2.local) {
|
|
@@ -50441,6 +50586,7 @@ ${parts.join("\n")}
|
|
|
50441
50586
|
const position = cnum(skel, "path", c2.id, "position", c2.position), spacing = cnum(skel, "path", c2.id, "spacing", c2.spacing);
|
|
50442
50587
|
const tangents = c2.rotateMode === "tangent", scale = c2.rotateMode === "chainScale";
|
|
50443
50588
|
const bones = c2.bones.map((id) => ({ b: boneOf(skel.doc, id), st: skel.bones.get(id) }));
|
|
50589
|
+
const inputWorlds = bones.map(({ st }) => ({ ...st.world }));
|
|
50444
50590
|
const spacesCount = tangents ? bones.length : bones.length + 1;
|
|
50445
50591
|
const spaces = new Array(spacesCount).fill(0), worldLengths = [];
|
|
50446
50592
|
let sum = 0;
|
|
@@ -50475,9 +50621,10 @@ ${parts.join("\n")}
|
|
|
50475
50621
|
if (tw.a * tw.d - tw.b * tw.c < 0) offsetRotation = -offsetRotation;
|
|
50476
50622
|
}
|
|
50477
50623
|
let boneX = samples[0].x, boneY = samples[0].y;
|
|
50624
|
+
const solvedWorlds = /* @__PURE__ */ new Map();
|
|
50478
50625
|
for (let i2 = 0; i2 < bones.length; i2++) {
|
|
50479
50626
|
const { b: b2, st } = bones[i2];
|
|
50480
|
-
let w2 = { ...
|
|
50627
|
+
let w2 = { ...inputWorlds[i2] };
|
|
50481
50628
|
w2.tx += (boneX - w2.tx) * mixX;
|
|
50482
50629
|
w2.ty += (boneY - w2.ty) * mixY;
|
|
50483
50630
|
const next = samples[i2 + 1] ?? { x: 0, y: 0, angle: 0 };
|
|
@@ -50507,7 +50654,11 @@ ${parts.join("\n")}
|
|
|
50507
50654
|
const cos = Math.cos(r2), sin = Math.sin(r2);
|
|
50508
50655
|
w2 = { a: cos * a2 - sin * bb, b: sin * a2 + cos * bb, c: cos * cc - sin * d2, d: sin * cc + cos * d2, tx: w2.tx, ty: w2.ty };
|
|
50509
50656
|
}
|
|
50510
|
-
|
|
50657
|
+
solvedWorlds.set(b2.id, w2);
|
|
50658
|
+
}
|
|
50659
|
+
for (const b2 of boneOrder(skel.doc)) {
|
|
50660
|
+
const world = solvedWorlds.get(b2.id);
|
|
50661
|
+
if (world) setEffectiveWorld(skel, b2, skel.bones.get(b2.id), world);
|
|
50511
50662
|
}
|
|
50512
50663
|
}
|
|
50513
50664
|
function measuredPathLength(points, closed, lengths, constantSpeed) {
|
|
@@ -50536,9 +50687,14 @@ ${parts.join("\n")}
|
|
|
50536
50687
|
case "onlyTranslation":
|
|
50537
50688
|
return { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
50538
50689
|
case "noRotationOrReflection": {
|
|
50539
|
-
const lenX = Math.hypot(parent.a, parent.b)
|
|
50690
|
+
const lenX = Math.hypot(parent.a, parent.b);
|
|
50540
50691
|
const det = parent.a * parent.d - parent.b * parent.c;
|
|
50541
|
-
|
|
50692
|
+
if (lenX * lenX <= 1e-4) {
|
|
50693
|
+
const lenY = Math.hypot(parent.c, parent.d), ux3 = lenY ? parent.c / lenY : 0, uy3 = lenY ? parent.d / lenY : 0;
|
|
50694
|
+
return { a: lenY * ux3 * ux3, b: lenY * ux3 * uy3, c: lenY * ux3 * uy3, d: lenY * uy3 * uy3, tx: 0, ty: 0 };
|
|
50695
|
+
}
|
|
50696
|
+
const ux2 = parent.a / lenX, uy2 = parent.b / lenX, sy = Math.abs(det) / lenX;
|
|
50697
|
+
return { a: lenX * ux2 * ux2 + sy * uy2 * uy2, b: (lenX - sy) * ux2 * uy2, c: (lenX - sy) * ux2 * uy2, d: lenX * uy2 * uy2 + sy * ux2 * ux2, tx: 0, ty: 0 };
|
|
50542
50698
|
}
|
|
50543
50699
|
case "noScale":
|
|
50544
50700
|
case "noScaleOrReflection": {
|
|
@@ -50564,7 +50720,16 @@ ${parts.join("\n")}
|
|
|
50564
50720
|
st.scaleY = Math.hypot(lin.c, lin.d);
|
|
50565
50721
|
st.shearX = 0;
|
|
50566
50722
|
st.shearY = shortest(Math.atan2(lin.d, lin.c) / DEG2 - st.rotation - 90);
|
|
50567
|
-
|
|
50723
|
+
if (parent && (bone.inherit === "noScale" || bone.inherit === "noScaleOrReflection")) {
|
|
50724
|
+
const direction = mul(invert({ ...parent, tx: 0, ty: 0 }), { ...world, tx: 0, ty: 0 });
|
|
50725
|
+
st.rotation = Math.atan2(direction.b, direction.a) / DEG2;
|
|
50726
|
+
st.scaleX = Math.hypot(world.a, world.b);
|
|
50727
|
+
st.scaleY = Math.hypot(world.c, world.d);
|
|
50728
|
+
const sign = bone.inherit === "noScale" && parent.a * parent.d - parent.b * parent.c < 0 ? -1 : 1;
|
|
50729
|
+
st.shearY = shortest((Math.atan2(world.d, world.c) - Math.atan2(world.b, world.a)) / DEG2 * sign - 90);
|
|
50730
|
+
}
|
|
50731
|
+
st.world = world;
|
|
50732
|
+
updateSubtree(skel, bone.id, false);
|
|
50568
50733
|
}
|
|
50569
50734
|
function attachmentWorldVertices(skel, slotId, att) {
|
|
50570
50735
|
const deform = skel.pose.get(pk("deform", att.id, "vertices"));
|
|
@@ -50850,9 +51015,23 @@ ${parts.join("\n")}
|
|
|
50850
51015
|
this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
|
|
50851
51016
|
this.setPose(/* @__PURE__ */ new Map());
|
|
50852
51017
|
}
|
|
51018
|
+
inputPose = /* @__PURE__ */ new Map();
|
|
51019
|
+
controlOverrides = /* @__PURE__ */ new Map();
|
|
51020
|
+
/** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
|
|
51021
|
+
setControls(values) {
|
|
51022
|
+
this.controlOverrides = controlPose(this.doc, values);
|
|
51023
|
+
this.setPose(this.inputPose);
|
|
51024
|
+
}
|
|
51025
|
+
resetControls() {
|
|
51026
|
+
this.controlOverrides.clear();
|
|
51027
|
+
this.setPose(this.inputPose);
|
|
51028
|
+
}
|
|
50853
51029
|
view = new Container();
|
|
50854
51030
|
state;
|
|
50855
51031
|
items = [];
|
|
51032
|
+
beforePose = /* @__PURE__ */ new Set();
|
|
51033
|
+
afterPose = /* @__PURE__ */ new Set();
|
|
51034
|
+
clipPoints = /* @__PURE__ */ new Map();
|
|
50856
51035
|
skel;
|
|
50857
51036
|
textures;
|
|
50858
51037
|
pages = /* @__PURE__ */ new Map();
|
|
@@ -50861,6 +51040,35 @@ ${parts.join("\n")}
|
|
|
50861
51040
|
/** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
|
|
50862
51041
|
ghosts = /* @__PURE__ */ new Set();
|
|
50863
51042
|
clipGraphics = /* @__PURE__ */ new Map();
|
|
51043
|
+
/** Read-only render handles for in-page tools. Nodes remain owned by the player. */
|
|
51044
|
+
get renderItems() {
|
|
51045
|
+
return this.items;
|
|
51046
|
+
}
|
|
51047
|
+
/** Current document-space clipping polygons, keyed by path attachment id. */
|
|
51048
|
+
get clipPolygons() {
|
|
51049
|
+
return this.clipPoints;
|
|
51050
|
+
}
|
|
51051
|
+
/**
|
|
51052
|
+
* Modify resolved control outputs before skeleton evaluation, constraints and skinning.
|
|
51053
|
+
* Callbacks run in registration order, each with its own map. Change control inputs via setControls.
|
|
51054
|
+
*/
|
|
51055
|
+
onBeforePose(callback) {
|
|
51056
|
+
this.beforePose.add(callback);
|
|
51057
|
+
return () => {
|
|
51058
|
+
this.beforePose.delete(callback);
|
|
51059
|
+
};
|
|
51060
|
+
}
|
|
51061
|
+
/** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
|
|
51062
|
+
onAfterPose(callback) {
|
|
51063
|
+
this.afterPose.add(callback);
|
|
51064
|
+
return () => {
|
|
51065
|
+
this.afterPose.delete(callback);
|
|
51066
|
+
};
|
|
51067
|
+
}
|
|
51068
|
+
/** Rebuild the current pose without advancing playback; useful for reversible overlays. */
|
|
51069
|
+
refreshPose() {
|
|
51070
|
+
this.setPose(this.inputPose);
|
|
51071
|
+
}
|
|
50864
51072
|
/** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
|
|
50865
51073
|
pageTexture(tex) {
|
|
50866
51074
|
let page = this.pages.get(tex.source);
|
|
@@ -50915,8 +51123,12 @@ ${parts.join("\n")}
|
|
|
50915
51123
|
}
|
|
50916
51124
|
/** Apply a pose (rest where silent) to the display objects. */
|
|
50917
51125
|
setPose(pose) {
|
|
50918
|
-
|
|
50919
|
-
|
|
51126
|
+
this.inputPose = new Map(pose);
|
|
51127
|
+
if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
|
|
51128
|
+
this.clipPoints.clear();
|
|
51129
|
+
const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
|
|
51130
|
+
pose = skel.pose;
|
|
51131
|
+
const masks = slotClips(skel), clips = new Set(masks.values());
|
|
50920
51132
|
for (const [id, g2] of this.clipGraphics) if (!clips.has(id)) {
|
|
50921
51133
|
g2.destroy();
|
|
50922
51134
|
this.clipGraphics.delete(id);
|
|
@@ -50933,7 +51145,9 @@ ${parts.join("\n")}
|
|
|
50933
51145
|
const vertices = attachmentWorldVertices(skel, att.slot, att);
|
|
50934
51146
|
const n2 = Math.max(32, vertices.length * 4);
|
|
50935
51147
|
const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n2 }, (_, i2) => i2 / n2), true);
|
|
50936
|
-
|
|
51148
|
+
const polygon = points.flatMap((p2) => [p2.x, p2.y]);
|
|
51149
|
+
this.clipPoints.set(id, polygon);
|
|
51150
|
+
g2.clear().poly(polygon, true).fill(16777215);
|
|
50937
51151
|
}
|
|
50938
51152
|
const m2 = new Matrix();
|
|
50939
51153
|
const orderV = pose.get(pk("drawOrder", "order", "order"));
|
|
@@ -50941,7 +51155,8 @@ ${parts.join("\n")}
|
|
|
50941
51155
|
if (Array.isArray(orderV)) orderV.forEach((id, i2) => order.set(id, i2));
|
|
50942
51156
|
for (const it of this.items) {
|
|
50943
51157
|
const s2 = it.slot, shown = skel.attachment.get(s2.id);
|
|
50944
|
-
|
|
51158
|
+
const mask = masks.get(s2.id);
|
|
51159
|
+
it.node.mask = mask ? this.clipGraphics.get(mask) ?? null : null;
|
|
50945
51160
|
if (shown !== it.att || this.hidden.has(s2.id)) {
|
|
50946
51161
|
it.node.visible = false;
|
|
50947
51162
|
continue;
|
|
@@ -50954,6 +51169,7 @@ ${parts.join("\n")}
|
|
|
50954
51169
|
it.node.tint = rgbOf(color);
|
|
50955
51170
|
it.node.zIndex = order.size ? order.get(s2.id) ?? 0 : numOf(pose, pk("slot", s2.id, "z"), s2.z);
|
|
50956
51171
|
if (it.skinned) {
|
|
51172
|
+
it.node.setFromMatrix(m2.identity());
|
|
50957
51173
|
const v2 = attachmentWorldVertices(skel, s2.id, it.att);
|
|
50958
51174
|
const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
|
|
50959
51175
|
buf.data.set(v2);
|
|
@@ -50986,6 +51202,7 @@ ${parts.join("\n")}
|
|
|
50986
51202
|
it.node.scale.y *= h2 / (this.textures.get(att.asset)?.height ?? h2);
|
|
50987
51203
|
}
|
|
50988
51204
|
}
|
|
51205
|
+
for (const callback of this.afterPose) callback(this);
|
|
50989
51206
|
}
|
|
50990
51207
|
/** World-space bone positions — used by editor gizmos and renderPreview overlays */
|
|
50991
51208
|
bonePositions() {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RigPlayer } from "./rig-runtime";
|
|
2
|
+
import type { PartTransform } from "./interactive-editor";
|
|
3
|
+
export interface BoneEditPatch {
|
|
4
|
+
format: "golem.bone-edits";
|
|
5
|
+
version: 1;
|
|
6
|
+
documentId: string;
|
|
7
|
+
transforms: Record<string, PartTransform>;
|
|
8
|
+
}
|
|
9
|
+
export interface InteractiveBoneEditorOptions {
|
|
10
|
+
player: Pick<RigPlayer, "doc" | "onBeforePose" | "refreshPose">;
|
|
11
|
+
documentId: string;
|
|
12
|
+
onChange?: (patch: BoneEditPatch) => void;
|
|
13
|
+
onRender?: () => void;
|
|
14
|
+
historyLimit?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Add bone offsets after expression controls, before constraints and native skinning. */
|
|
17
|
+
export declare function createInteractiveBoneEditor(options: InteractiveBoneEditorOptions): {
|
|
18
|
+
getTransform: (id: string) => {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
rotation: number;
|
|
22
|
+
scaleX: number;
|
|
23
|
+
scaleY: number;
|
|
24
|
+
};
|
|
25
|
+
exportPatch: () => BoneEditPatch;
|
|
26
|
+
validatePatch: (value: unknown) => BoneEditPatch;
|
|
27
|
+
readonly canUndo: boolean;
|
|
28
|
+
readonly canRedo: boolean;
|
|
29
|
+
setTransform(id: string, value: Partial<PartTransform>): void;
|
|
30
|
+
importPatch(value: unknown): void;
|
|
31
|
+
reset(id?: string): void;
|
|
32
|
+
setVisible(value: boolean): void;
|
|
33
|
+
undo(): void;
|
|
34
|
+
redo(): void;
|
|
35
|
+
dispose(): void;
|
|
36
|
+
};
|
|
37
|
+
export type InteractiveBoneEditor = ReturnType<typeof createInteractiveBoneEditor>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { RigPlayer } from "./rig-runtime";
|
|
2
|
+
export interface EditorPoint {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PartTransform {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
rotation: number;
|
|
10
|
+
scaleX: number;
|
|
11
|
+
scaleY: number;
|
|
12
|
+
}
|
|
13
|
+
export interface InteractiveEditPatch {
|
|
14
|
+
format: "golem.interactive-edits";
|
|
15
|
+
version: 1;
|
|
16
|
+
documentId: string;
|
|
17
|
+
transforms: Record<string, PartTransform>;
|
|
18
|
+
}
|
|
19
|
+
export interface EditorGroup {
|
|
20
|
+
id: string;
|
|
21
|
+
members: readonly string[];
|
|
22
|
+
pivotBone?: string;
|
|
23
|
+
}
|
|
24
|
+
export type InteractivePlayer = Pick<RigPlayer, "doc" | "view" | "world" | "renderItems" | "clipPolygons" | "onAfterPose" | "refreshPose">;
|
|
25
|
+
export interface InteractiveEditorOptions {
|
|
26
|
+
player: InteractivePlayer;
|
|
27
|
+
/** Transparent overlay aligned with the player's rendered viewport. It is cleared by the editor. */
|
|
28
|
+
overlay: HTMLCanvasElement;
|
|
29
|
+
/** Host-defined stable rig fingerprint; mismatched patches are rejected. */
|
|
30
|
+
documentId: string;
|
|
31
|
+
groups?: readonly EditorGroup[];
|
|
32
|
+
/** Override when overlay backing pixels differ from the renderer's logical coordinates. */
|
|
33
|
+
screenToDocument?: (client: EditorPoint) => EditorPoint;
|
|
34
|
+
documentToOverlay?: (point: EditorPoint) => EditorPoint;
|
|
35
|
+
alphaFallback?: "skip" | "geometry";
|
|
36
|
+
onChange?: (patch: InteractiveEditPatch) => void;
|
|
37
|
+
onSelection?: (id: string | null) => void;
|
|
38
|
+
onHover?: (id: string | null) => void;
|
|
39
|
+
onError?: (error: Error) => void;
|
|
40
|
+
onGestureStart?: () => void;
|
|
41
|
+
/** Render the host scene after a manual refresh; never called from the pose hook. */
|
|
42
|
+
onRender?: () => void;
|
|
43
|
+
historyLimit?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface EditorFrame {
|
|
46
|
+
id: string;
|
|
47
|
+
center: EditorPoint;
|
|
48
|
+
corners: EditorPoint[];
|
|
49
|
+
handles: {
|
|
50
|
+
name: string;
|
|
51
|
+
p: EditorPoint;
|
|
52
|
+
}[];
|
|
53
|
+
}
|
|
54
|
+
/** Embedded part editor. Source documents are never mutated; edits replay after every evaluated pose. */
|
|
55
|
+
export declare function createInteractiveEditor(options: InteractiveEditorOptions): {
|
|
56
|
+
select: (id: string | null) => void;
|
|
57
|
+
hitTest: (p: EditorPoint) => string | null;
|
|
58
|
+
getFrame: (id: string) => EditorFrame | null;
|
|
59
|
+
getTransform: (id: string) => {
|
|
60
|
+
x: number;
|
|
61
|
+
y: number;
|
|
62
|
+
rotation: number;
|
|
63
|
+
scaleX: number;
|
|
64
|
+
scaleY: number;
|
|
65
|
+
};
|
|
66
|
+
readonly selection: string | null;
|
|
67
|
+
readonly canUndo: boolean;
|
|
68
|
+
readonly canRedo: boolean;
|
|
69
|
+
exportPatch: () => InteractiveEditPatch;
|
|
70
|
+
setEnabled(value: boolean): void;
|
|
71
|
+
setVisible(value: boolean): void;
|
|
72
|
+
setTransform(id: string, value: Partial<PartTransform>): void;
|
|
73
|
+
reset(id?: string): void;
|
|
74
|
+
undo(): void;
|
|
75
|
+
redo(): void;
|
|
76
|
+
importPatch(value: unknown): void;
|
|
77
|
+
dispose(): void;
|
|
78
|
+
};
|
|
79
|
+
export type InteractiveEditor = ReturnType<typeof createInteractiveEditor>;
|
|
80
|
+
export { createInteractiveBoneEditor, type BoneEditPatch, type InteractiveBoneEditorOptions } from "./interactive-bones";
|