@almadar/ui 6.55.0 → 6.57.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/dist/{ArbitraryClassCompiler-DvzdeHxc.d.ts → ArbitraryClassCompiler-tChQ3nVq.d.ts} +15 -1
- package/dist/{FileTree-DUi7e7Z8.d.ts → FileTree-BSfgp-HH.d.ts} +29 -2
- package/dist/Typography-LZTP72Y4.js +5 -0
- package/dist/{UISlotRenderer-QTHFT6Z4.js → UISlotRenderer-NCGDIXN4.js} +8 -8
- package/dist/avl/index.cjs +4832 -4212
- package/dist/avl/index.d.ts +74 -5
- package/dist/avl/index.js +68 -711
- package/dist/{avl-schema-parser-C0wnAp5x.d.ts → avl-schema-parser-CH1QoZw1.d.ts} +1 -1
- package/dist/{chunk-HCSJHRC4.js → chunk-4ZGXIW3B.js} +1 -1
- package/dist/{chunk-RNY5LJXJ.js → chunk-723MJUU3.js} +1 -1
- package/dist/{chunk-6EV3OEBQ.js → chunk-CHNC4UYE.js} +3 -2
- package/dist/{chunk-SGJHVPHC.js → chunk-GZGDBS4S.js} +2 -2
- package/dist/{chunk-Y53Q55H5.js → chunk-HA7CJWQX.js} +1123 -1121
- package/dist/{chunk-JRW33U35.js → chunk-I5QOYW2Z.js} +1 -1
- package/dist/{chunk-KQXXO42S.js → chunk-KJGVLXEN.js} +78 -1
- package/dist/{chunk-HRCR53OW.js → chunk-LZRW6GXG.js} +18 -3
- package/dist/{chunk-2LFBQ4IS.js → chunk-MZIDOJMD.js} +2 -2
- package/dist/{chunk-QGH4NAQO.js → chunk-N7JIG57B.js} +9 -4
- package/dist/{chunk-XZKGRSIO.js → chunk-PYJ2UQWM.js} +2487 -1156
- package/dist/{chunk-FQM37C56.js → chunk-RHP5WL55.js} +61 -55
- package/dist/chunk-YYYHH5BA.js +2519 -0
- package/dist/{cn-ClXGlIOY.d.ts → cn-DIql5232.d.ts} +152 -24
- package/dist/components/index.cjs +6876 -3072
- package/dist/components/index.d.ts +45 -94
- package/dist/components/index.js +14 -16
- package/dist/context/index.cjs +23 -0
- package/dist/context/index.d.ts +2 -2
- package/dist/context/index.js +20 -1
- package/dist/hooks/index.cjs +2544 -7
- package/dist/hooks/index.d.ts +4 -1
- package/dist/hooks/index.js +7 -6
- package/dist/lib/drawable/three/index.cjs +22 -6
- package/dist/lib/drawable/three/index.d.ts +1 -1
- package/dist/lib/drawable/three/index.js +7 -7
- package/dist/lib/index.cjs +18 -3
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +3 -4
- package/dist/locales/index.cjs +54 -9
- package/dist/locales/index.js +2 -2489
- package/dist/providers/index.cjs +4035 -2382
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +24 -13
- package/dist/runtime/index.cjs +4092 -2447
- package/dist/runtime/index.d.ts +0 -13
- package/dist/runtime/index.js +11 -10
- package/dist/ssr/index.cjs +23 -6
- package/dist/ssr/index.js +23 -6
- package/dist/{useEventBus-JUAH3EFW.js → useEventBus-SWRN475O.js} +1 -1
- package/locales/ar.json +18 -3
- package/locales/en.json +18 -3
- package/locales/sl.json +18 -3
- package/package.json +6 -6
- package/tailwind-preset.cjs +6 -0
- package/themes/_fonts.css +1 -1
- package/themes/_presets.css +1 -0
- package/themes/kflow.css +299 -0
- package/dist/Typography-7PDT62PG.js +0 -5
- package/dist/chunk-F4ZTPKZL.js +0 -284
- package/dist/chunk-OKWD7LPO.js +0 -80
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useTapReveal } from './chunk-XJTHMW4G.js';
|
|
2
|
-
import { useEventBus } from './chunk-
|
|
2
|
+
import { useEventBus } from './chunk-CHNC4UYE.js';
|
|
3
3
|
import { updateAssetStatus } from './chunk-Q47ZN2DW.js';
|
|
4
4
|
import { cn, domPassthrough } from './chunk-64WACXM7.js';
|
|
5
5
|
import { __publicField } from './chunk-QPXIAUJD.js';
|
|
6
6
|
import { createLogger } from '@almadar/logger';
|
|
7
|
-
import React, { useCallback, createContext,
|
|
7
|
+
import React, { useCallback, createContext, useState, useLayoutEffect, useContext } from 'react';
|
|
8
8
|
import { useThemeScope, useTheme } from '@almadar/ui/context';
|
|
9
9
|
import { jsx } from 'react/jsx-runtime';
|
|
10
10
|
import { renderUiEntriesOf } from '@almadar/core/patterns';
|
|
@@ -182,6 +182,7 @@ var EFFECT_OPERATOR_CATEGORY = {
|
|
|
182
182
|
"notify": "communication",
|
|
183
183
|
"send-server": "communication",
|
|
184
184
|
"call-service": "communication",
|
|
185
|
+
"cancel-call": "communication",
|
|
185
186
|
"integration/http": "communication",
|
|
186
187
|
"integration/github-get-repo": "communication",
|
|
187
188
|
"integration/github-create-issue": "communication",
|
|
@@ -253,6 +254,7 @@ var EFFECT_OPERATOR_ZONE = {
|
|
|
253
254
|
"send-server": "bus",
|
|
254
255
|
"notify": "toast",
|
|
255
256
|
"call-service": "remote",
|
|
257
|
+
"cancel-call": "remote",
|
|
256
258
|
"integration/http": "remote",
|
|
257
259
|
"integration/github-get-repo": "remote",
|
|
258
260
|
"integration/github-create-issue": "remote",
|
|
@@ -1149,593 +1151,409 @@ var CONFETTI_BURST_KEYFRAMES = `
|
|
|
1149
1151
|
}
|
|
1150
1152
|
}
|
|
1151
1153
|
`;
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
if (!isValidScenePos(node.position)) return "invalid-position";
|
|
1159
|
-
return typeof node.asset?.url === "string" && node.asset.url.length > 0 ? "ready" : "asset-unset";
|
|
1154
|
+
var log2 = createLogger("almadar:ui:canvas-theme");
|
|
1155
|
+
var toneVar = (tone) => `--color-${DIAGRAM_TONE_COLOR_KEYS[tone]}`;
|
|
1156
|
+
var SERIES_TONES = ["series-1", "series-2", "series-3", "series-4", "series-5", "series-6", "series-7", "series-8"];
|
|
1157
|
+
var TONE_SET = new Set(DIAGRAM_TONES);
|
|
1158
|
+
function isDiagramTone(value) {
|
|
1159
|
+
return TONE_SET.has(value);
|
|
1160
1160
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
function spriteRect(projector, node, natural) {
|
|
1164
|
-
const tw = projector.tileWidth;
|
|
1165
|
-
const fallbackW = projector.worldPixelDirect ? natural?.w ?? 0 : tw;
|
|
1166
|
-
const fallbackH = projector.worldPixelDirect ? natural?.h ?? 0 : tw;
|
|
1167
|
-
const w = node.width !== void 0 ? node.width * tw : fallbackW;
|
|
1168
|
-
const h = node.height !== void 0 ? node.height * tw : fallbackH;
|
|
1169
|
-
const anchor = node.anchor ?? "top-left";
|
|
1170
|
-
const p = projector.anchorPoint(node.position, anchor);
|
|
1171
|
-
return {
|
|
1172
|
-
x: anchor === "top-left" ? p.x : p.x - w / 2,
|
|
1173
|
-
y: anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y,
|
|
1174
|
-
w,
|
|
1175
|
-
h
|
|
1176
|
-
};
|
|
1161
|
+
function colorCss(c) {
|
|
1162
|
+
return c.a >= 1 ? c.css : `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;
|
|
1177
1163
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
"
|
|
1182
|
-
|
|
1183
|
-
"opacity",
|
|
1184
|
-
"radiusX",
|
|
1185
|
-
"radiusY",
|
|
1186
|
-
"width",
|
|
1187
|
-
"height",
|
|
1188
|
-
"strokeWidth",
|
|
1189
|
-
"strokeDashOffset",
|
|
1190
|
-
"blur"
|
|
1191
|
-
];
|
|
1192
|
-
function isAnimatedShape(node) {
|
|
1193
|
-
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1164
|
+
function oneOf(value, allowed, fallback, name) {
|
|
1165
|
+
const hit = allowed.find((a) => a === value);
|
|
1166
|
+
if (hit !== void 0) return hit;
|
|
1167
|
+
if (value !== "") log2.warn("invalid-theme-keyword", { var: name, value, allowed });
|
|
1168
|
+
return fallback;
|
|
1194
1169
|
}
|
|
1195
|
-
var
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
for (const f of defined) {
|
|
1209
|
-
if (f.at <= t) prev = f;
|
|
1210
|
-
else if (!next) next = f;
|
|
1211
|
-
}
|
|
1212
|
-
if (!prev) return defined[0][key];
|
|
1213
|
-
if (!next) return prev[key];
|
|
1214
|
-
const span = next.at - prev.at;
|
|
1215
|
-
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
1216
|
-
const a = prev[key];
|
|
1217
|
-
const b = next[key];
|
|
1218
|
-
if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
|
|
1219
|
-
return a;
|
|
1220
|
-
};
|
|
1221
|
-
for (const key of NUMERIC_TRACKS) {
|
|
1222
|
-
const v = trackValue(key);
|
|
1223
|
-
if (v !== void 0) out[key] = v;
|
|
1170
|
+
var PLAIN_LENGTH = /^(-?[\d.]+)(px)?$/;
|
|
1171
|
+
var ScopeReader = class {
|
|
1172
|
+
constructor(scope) {
|
|
1173
|
+
__publicField(this, "scope", scope);
|
|
1174
|
+
__publicField(this, "style");
|
|
1175
|
+
__publicField(this, "probe");
|
|
1176
|
+
this.style = getComputedStyle(scope);
|
|
1177
|
+
const doc = scope.ownerDocument;
|
|
1178
|
+
this.probe = doc.createElement("span");
|
|
1179
|
+
this.probe.style.position = "absolute";
|
|
1180
|
+
this.probe.style.visibility = "hidden";
|
|
1181
|
+
this.probe.style.pointerEvents = "none";
|
|
1182
|
+
scope.appendChild(this.probe);
|
|
1224
1183
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
const stroke = trackValue("stroke");
|
|
1228
|
-
if (stroke !== void 0) out.stroke = stroke;
|
|
1229
|
-
const shadowFrames = frames.filter((f) => f.shadow !== void 0);
|
|
1230
|
-
if (shadowFrames.length > 0) {
|
|
1231
|
-
const sh = trackValue("shadow");
|
|
1232
|
-
if (sh !== void 0) {
|
|
1233
|
-
let prevSh;
|
|
1234
|
-
let nextSh;
|
|
1235
|
-
for (const f of shadowFrames) {
|
|
1236
|
-
if (f.at <= t) prevSh = f;
|
|
1237
|
-
else if (!nextSh) nextSh = f;
|
|
1238
|
-
}
|
|
1239
|
-
if (prevSh?.shadow && nextSh?.shadow) {
|
|
1240
|
-
const span = nextSh.at - prevSh.at;
|
|
1241
|
-
const k = span > 0 ? (t - prevSh.at) / span : 1;
|
|
1242
|
-
out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
|
|
1243
|
-
} else {
|
|
1244
|
-
out.shadow = sh;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1184
|
+
dispose() {
|
|
1185
|
+
this.probe.remove();
|
|
1247
1186
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
function gradientStyle(g, ox, oy, scale) {
|
|
1251
|
-
if (g.kind === "linear") {
|
|
1252
|
-
if (!g.from || !g.to) return void 0;
|
|
1253
|
-
return {
|
|
1254
|
-
kind: "linear",
|
|
1255
|
-
x1: ox + g.from.x * scale,
|
|
1256
|
-
y1: oy + g.from.y * scale,
|
|
1257
|
-
x2: ox + g.to.x * scale,
|
|
1258
|
-
y2: oy + g.to.y * scale,
|
|
1259
|
-
stops: g.stops
|
|
1260
|
-
};
|
|
1187
|
+
raw(name) {
|
|
1188
|
+
return this.style.getPropertyValue(name).trim();
|
|
1261
1189
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1190
|
+
/** A length var in px: a plain value parses directly, a derived one (`calc`, `var`) through the probe. */
|
|
1191
|
+
length(name, fallback) {
|
|
1192
|
+
const raw = this.raw(name);
|
|
1193
|
+
const plain = PLAIN_LENGTH.exec(raw);
|
|
1194
|
+
if (plain) return Number(plain[1]);
|
|
1195
|
+
if (raw === "") return fallback;
|
|
1196
|
+
this.probe.style.width = `var(${name})`;
|
|
1197
|
+
const computed = PLAIN_LENGTH.exec(getComputedStyle(this.probe).width.trim());
|
|
1198
|
+
this.probe.style.width = "";
|
|
1199
|
+
if (computed) return Number(computed[1]);
|
|
1200
|
+
log2.warn("unresolvable-theme-length", { var: name, raw });
|
|
1201
|
+
return fallback;
|
|
1202
|
+
}
|
|
1203
|
+
number(name, fallback) {
|
|
1204
|
+
const raw = this.raw(name);
|
|
1205
|
+
if (raw === "") return fallback;
|
|
1206
|
+
const n = Number(raw);
|
|
1207
|
+
if (Number.isFinite(n)) return n;
|
|
1208
|
+
log2.warn("unresolvable-theme-number", { var: name, raw });
|
|
1209
|
+
return fallback;
|
|
1210
|
+
}
|
|
1211
|
+
/** A duration var in ms (`250ms`, `0.25s`, or a `var()` alias resolved through the probe). */
|
|
1212
|
+
durationMs(name, fallback) {
|
|
1213
|
+
const parse = (v) => {
|
|
1214
|
+
const m = /^([\d.]+)(ms|s)$/.exec(v.trim());
|
|
1215
|
+
return m ? Number(m[1]) * (m[2] === "s" ? 1e3 : 1) : null;
|
|
1270
1216
|
};
|
|
1217
|
+
const raw = this.raw(name);
|
|
1218
|
+
const direct = parse(raw);
|
|
1219
|
+
if (direct !== null) return direct;
|
|
1220
|
+
if (raw === "") return fallback;
|
|
1221
|
+
this.probe.style.transitionDuration = `var(${name})`;
|
|
1222
|
+
const computed = parse(getComputedStyle(this.probe).transitionDuration.split(",")[0] ?? "");
|
|
1223
|
+
this.probe.style.transitionDuration = "";
|
|
1224
|
+
return computed ?? fallback;
|
|
1271
1225
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
cx: ox + g.center.x * scale,
|
|
1276
|
-
cy: oy + g.center.y * scale,
|
|
1277
|
-
r: g.radius * scale,
|
|
1278
|
-
stops: g.stops
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
function patternStyle(p, unit) {
|
|
1282
|
-
if (p.kind === "image") {
|
|
1283
|
-
if (!p.url) return void 0;
|
|
1284
|
-
return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
|
|
1226
|
+
color(name) {
|
|
1227
|
+
const raw = this.raw(name);
|
|
1228
|
+
return parseComputedColor(raw) ?? resolveThemeColor(`var(${name})`, this.scope);
|
|
1285
1229
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
if (node.blendMode) painter.setBlend(node.blendMode);
|
|
1298
|
-
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1299
|
-
painter.setLineDash(
|
|
1300
|
-
node.strokeDash.map((v) => v / groupScale),
|
|
1301
|
-
(node.strokeDashOffset ?? 0) / groupScale
|
|
1302
|
-
);
|
|
1230
|
+
shadow(name) {
|
|
1231
|
+
const raw = this.raw(name);
|
|
1232
|
+
if (raw === "" || raw === "none") return null;
|
|
1233
|
+
this.probe.style.boxShadow = `var(${name})`;
|
|
1234
|
+
const computed = getComputedStyle(this.probe).boxShadow.trim() || raw;
|
|
1235
|
+
this.probe.style.boxShadow = "";
|
|
1236
|
+
const colorMatch = /(rgba?\([^)]*\)|#[0-9a-f]{3,8}|oklch\([^)]*\))/i.exec(computed);
|
|
1237
|
+
const lengths = computed.replace(colorMatch?.[0] ?? "", "").trim().split(/\s+/).map((t) => PLAIN_LENGTH.exec(t)).filter((m) => m !== null).map((m) => Number(m[1]));
|
|
1238
|
+
if (lengths.length < 2) return null;
|
|
1239
|
+
const color = colorMatch ? parseComputedColor(colorMatch[0]) : null;
|
|
1240
|
+
return { offsetX: lengths[0], offsetY: lengths[1], blur: lengths[2] ?? 0, color: color ? colorCss(color) : "rgba(0, 0, 0, 0.25)" };
|
|
1303
1241
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
|
|
1307
|
-
const px = origin.x + pivot.x * tileWidth;
|
|
1308
|
-
const py = origin.y + pivot.y * tileWidth;
|
|
1309
|
-
painter.translate(px, py);
|
|
1310
|
-
painter.rotate(node.rotate);
|
|
1311
|
-
painter.translate(-px, -py);
|
|
1242
|
+
family(name, fallback) {
|
|
1243
|
+
return this.raw(name) || fallback;
|
|
1312
1244
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
const x = p.x + (node.offsetX ?? 0) * tw;
|
|
1331
|
-
const y = p.y + (node.offsetY ?? 0) * tw;
|
|
1332
|
-
const w = (node.width ?? 0) * tw;
|
|
1333
|
-
const h = (node.height ?? 0) * tw;
|
|
1334
|
-
if (pxFill) painter.fillRect(x, y, w, h, pxFill);
|
|
1335
|
-
if (patFill) painter.fillRect(x, y, w, h, patFill);
|
|
1336
|
-
if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
|
|
1337
|
-
break;
|
|
1338
|
-
}
|
|
1339
|
-
case "ellipse": {
|
|
1340
|
-
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "ground");
|
|
1341
|
-
const tw = dctx.projector.tileWidth;
|
|
1342
|
-
const cx = p.x + (node.offsetX ?? 0) * tw;
|
|
1343
|
-
const cy = p.y + (node.offsetY ?? 0) * tw;
|
|
1344
|
-
const rx = (node.radiusX ?? 0) * tw;
|
|
1345
|
-
const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
|
|
1346
|
-
if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
|
|
1347
|
-
if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
|
|
1348
|
-
if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
|
|
1349
|
-
break;
|
|
1350
|
-
}
|
|
1351
|
-
case "poly": {
|
|
1352
|
-
const pts = (node.points ?? []).map((pt) => ({
|
|
1353
|
-
x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
|
|
1354
|
-
y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
|
|
1355
|
-
}));
|
|
1356
|
-
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1357
|
-
if (patFill) painter.fillPoly(pts, patFill);
|
|
1358
|
-
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1359
|
-
break;
|
|
1245
|
+
};
|
|
1246
|
+
function dashList(raw, fallback) {
|
|
1247
|
+
if (raw === "") return fallback;
|
|
1248
|
+
const parts = raw.split(/[\s,]+/).map(Number);
|
|
1249
|
+
return parts.every((n) => Number.isFinite(n) && n >= 0) ? parts : fallback;
|
|
1250
|
+
}
|
|
1251
|
+
function prefersReducedMotion(scope) {
|
|
1252
|
+
const view = scope.ownerDocument.defaultView;
|
|
1253
|
+
return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1254
|
+
}
|
|
1255
|
+
function resolveCanvasTheme(scope) {
|
|
1256
|
+
const r = new ScopeReader(scope);
|
|
1257
|
+
try {
|
|
1258
|
+
const tones = {};
|
|
1259
|
+
for (const tone of DIAGRAM_TONES) {
|
|
1260
|
+
const c = r.color(toneVar(tone));
|
|
1261
|
+
tones[tone] = c ? colorCss(c) : "currentColor";
|
|
1360
1262
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1263
|
+
const ground = r.color("--surface-diagram");
|
|
1264
|
+
const text = {};
|
|
1265
|
+
const TEXT_FALLBACK = { xs: 12, sm: 14, base: 16, lg: 18, xl: 20, "2xl": 24 };
|
|
1266
|
+
for (const size of DIAGRAM_TEXT_SIZES) text[size] = r.length(`--text-${size}`, TEXT_FALLBACK[size]);
|
|
1267
|
+
const body = r.family("--font-family-body", r.family("--font-family", "system-ui, sans-serif"));
|
|
1268
|
+
return {
|
|
1269
|
+
ground: ground ? colorCss(ground) : "transparent",
|
|
1270
|
+
tones,
|
|
1271
|
+
series: SERIES_TONES.map((t) => tones[t]),
|
|
1272
|
+
strokes: {
|
|
1273
|
+
thin: r.length("--diagram-stroke-thin", 1),
|
|
1274
|
+
normal: r.length("--diagram-stroke-normal", 1.5),
|
|
1275
|
+
bold: r.length("--diagram-stroke-bold", 3)
|
|
1276
|
+
},
|
|
1277
|
+
lineCap: oneOf(r.raw("--diagram-line-cap"), ["butt", "round", "square"], "round", "--diagram-line-cap"),
|
|
1278
|
+
lineJoin: oneOf(r.raw("--diagram-line-join"), ["miter", "round", "bevel"], "round", "--diagram-line-join"),
|
|
1279
|
+
dash: dashList(r.raw("--diagram-dash"), [6, 4]),
|
|
1280
|
+
dot: dashList(r.raw("--diagram-dot"), [1, 4]),
|
|
1281
|
+
fillStyle: oneOf(r.raw("--diagram-fill-style"), ["solid", "tint", "outline", "hatch"], "tint", "--diagram-fill-style"),
|
|
1282
|
+
fillOpacity: r.number("--diagram-fill-opacity", 0.18),
|
|
1283
|
+
roughness: r.number("--diagram-roughness", 0),
|
|
1284
|
+
glow: r.number("--diagram-glow", 0),
|
|
1285
|
+
shadow: r.shadow("--diagram-shadow"),
|
|
1286
|
+
marker: oneOf(r.raw("--diagram-marker"), ["triangle", "open", "line", "dot"], "triangle", "--diagram-marker"),
|
|
1287
|
+
faces: {
|
|
1288
|
+
body,
|
|
1289
|
+
display: r.family("--font-family-display", body),
|
|
1290
|
+
mono: r.family("--font-family-mono", "ui-monospace, monospace")
|
|
1291
|
+
},
|
|
1292
|
+
label: {
|
|
1293
|
+
font: oneOf(r.raw("--diagram-label-font"), ["body", "display", "mono"], "body", "--diagram-label-font"),
|
|
1294
|
+
size: oneOf(r.raw("--diagram-label-size"), DIAGRAM_TEXT_SIZES, "sm", "--diagram-label-size"),
|
|
1295
|
+
weight: r.raw("--diagram-label-weight") || "500",
|
|
1296
|
+
uppercase: oneOf(r.raw("--diagram-label-case"), ["none", "uppercase"], "none", "--diagram-label-case") === "uppercase"
|
|
1297
|
+
},
|
|
1298
|
+
text,
|
|
1299
|
+
corner: r.length("--diagram-corner", 4),
|
|
1300
|
+
motion: {
|
|
1301
|
+
enabled: r.raw("--motion-enable") !== "off" && !prefersReducedMotion(scope),
|
|
1302
|
+
fastMs: r.durationMs("--duration-fast", 150),
|
|
1303
|
+
normalMs: r.durationMs("--duration-normal", 250),
|
|
1304
|
+
slowMs: r.durationMs("--duration-slow", 400),
|
|
1305
|
+
easing: r.raw("--easing-standard") || "ease"
|
|
1306
|
+
},
|
|
1307
|
+
scene: {
|
|
1308
|
+
roughness: r.number("--material-roughness", 0.6),
|
|
1309
|
+
metalness: r.number("--material-metalness", 0),
|
|
1310
|
+
flat: r.number("--material-flat", 0) >= 1,
|
|
1311
|
+
outline: r.number("--material-outline", 0),
|
|
1312
|
+
ambient: r.number("--light-ambient", 0.55),
|
|
1313
|
+
key: r.number("--light-key", 1),
|
|
1314
|
+
keyColor: (() => {
|
|
1315
|
+
const c = r.color("--light-key-color");
|
|
1316
|
+
return c ? colorCss(c) : "#ffffff";
|
|
1317
|
+
})(),
|
|
1318
|
+
fog: r.number("--scene-fog", 0)
|
|
1370
1319
|
}
|
|
1371
|
-
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
|
|
1372
|
-
const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
|
|
1373
|
-
const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
|
|
1374
|
-
const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
|
|
1375
|
-
if (localFill) painter.fillPath(node.d, localFill);
|
|
1376
|
-
if (localPat) painter.fillPath(node.d, localPat);
|
|
1377
|
-
if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
|
|
1378
|
-
break;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
painter.restore();
|
|
1382
|
-
};
|
|
1383
|
-
function DrawShape(props) {
|
|
1384
|
-
const register = useContext(DrawableRegistryContext);
|
|
1385
|
-
if (register) {
|
|
1386
|
-
const { opacity, ...rest } = props;
|
|
1387
|
-
const node = {
|
|
1388
|
-
...rest,
|
|
1389
|
-
type: "draw-shape",
|
|
1390
|
-
...opacity !== void 0 && opacity > 0 ? { opacity } : {}
|
|
1391
1320
|
};
|
|
1392
|
-
|
|
1321
|
+
} finally {
|
|
1322
|
+
r.dispose();
|
|
1393
1323
|
}
|
|
1394
|
-
return null;
|
|
1395
1324
|
}
|
|
1396
|
-
function
|
|
1397
|
-
|
|
1325
|
+
function withSeries(theme, series, scope) {
|
|
1326
|
+
if (!theme || !series || series.length === 0 || !scope) return theme;
|
|
1327
|
+
const colors = series.map((c, i) => markColor(theme, c, scope, SERIES_TONES[i % SERIES_TONES.length]));
|
|
1328
|
+
const tones = { ...theme.tones };
|
|
1329
|
+
SERIES_TONES.forEach((tone, i) => {
|
|
1330
|
+
tones[tone] = colors[i % colors.length];
|
|
1331
|
+
});
|
|
1332
|
+
return { ...theme, tones, series: SERIES_TONES.map((t) => tones[t]) };
|
|
1398
1333
|
}
|
|
1399
|
-
function
|
|
1400
|
-
|
|
1401
|
-
if (
|
|
1402
|
-
|
|
1334
|
+
function markColor(theme, value, scope, fallback) {
|
|
1335
|
+
if (value === void 0 || value === "") return theme.tones[fallback];
|
|
1336
|
+
if (isDiagramTone(value)) return theme.tones[value];
|
|
1337
|
+
if (value.startsWith("var(") || value.startsWith("color-mix(")) {
|
|
1338
|
+
const resolved = resolveThemeColor(value, scope);
|
|
1339
|
+
return resolved ? colorCss(resolved) : theme.tones[fallback];
|
|
1340
|
+
}
|
|
1341
|
+
return value;
|
|
1403
1342
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
var RETRY_AFTER_MS = 5e3;
|
|
1407
|
-
var cache = /* @__PURE__ */ new Map();
|
|
1408
|
-
function startLoad(url, onReady, existing) {
|
|
1409
|
-
const img = new Image();
|
|
1410
|
-
img.crossOrigin = "anonymous";
|
|
1411
|
-
const entry = existing ?? { img, status: "pending" };
|
|
1412
|
-
entry.img = img;
|
|
1413
|
-
if (onReady) entry.onReady = onReady;
|
|
1414
|
-
img.onload = () => {
|
|
1415
|
-
entry.status = "loaded";
|
|
1416
|
-
updateAssetStatus(url, "loaded");
|
|
1417
|
-
entry.onReady?.();
|
|
1418
|
-
};
|
|
1419
|
-
img.onerror = () => {
|
|
1420
|
-
entry.status = "failed";
|
|
1421
|
-
entry.failedAt = Date.now();
|
|
1422
|
-
updateAssetStatus(url, "failed");
|
|
1423
|
-
entry.onReady?.();
|
|
1424
|
-
};
|
|
1425
|
-
img.src = url;
|
|
1426
|
-
return entry;
|
|
1343
|
+
function markStrokeWidth(theme, literal, role, fallback = "normal") {
|
|
1344
|
+
return literal ?? theme.strokes[role ?? fallback];
|
|
1427
1345
|
}
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
if (existing.status === "loaded") return existing.img;
|
|
1433
|
-
if (existing.status === "pending" && onReady) existing.onReady = onReady;
|
|
1434
|
-
if (existing.status === "failed" && Date.now() - (existing.failedAt ?? 0) >= RETRY_AFTER_MS) {
|
|
1435
|
-
existing.failedAt = Date.now();
|
|
1436
|
-
startLoad(url, onReady, existing);
|
|
1437
|
-
}
|
|
1438
|
-
return null;
|
|
1439
|
-
}
|
|
1440
|
-
const entry = startLoad(url, onReady);
|
|
1441
|
-
cache.set(url, entry);
|
|
1442
|
-
updateAssetStatus(url, "pending");
|
|
1443
|
-
return null;
|
|
1346
|
+
function labelFont(theme, roles, literalPx, literalFamily) {
|
|
1347
|
+
const px = literalPx ?? theme.text[roles.textSize ?? theme.label.size];
|
|
1348
|
+
const family = literalFamily ?? theme.faces[roles.font ?? theme.label.font];
|
|
1349
|
+
return { font: `${theme.label.weight} ${px}px ${family}`, px };
|
|
1444
1350
|
}
|
|
1445
|
-
function
|
|
1446
|
-
return
|
|
1351
|
+
function labelText(theme, text, textCase = "theme") {
|
|
1352
|
+
return theme.label.uppercase && textCase === "theme" ? text.toUpperCase() : text;
|
|
1447
1353
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
1458
|
-
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
1459
|
-
];
|
|
1354
|
+
function fillLabel(ctx, theme, text, x, y) {
|
|
1355
|
+
if (!text) return;
|
|
1356
|
+
ctx.save();
|
|
1357
|
+
ctx.strokeStyle = theme.ground;
|
|
1358
|
+
ctx.lineWidth = Math.max(2, theme.text.xs * 0.3);
|
|
1359
|
+
ctx.lineJoin = "round";
|
|
1360
|
+
ctx.strokeText(text, x, y);
|
|
1361
|
+
ctx.restore();
|
|
1362
|
+
ctx.fillText(text, x, y);
|
|
1460
1363
|
}
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1463
|
-
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return IDENTITY;
|
|
1464
|
-
const inv = 1 / det;
|
|
1465
|
-
return [
|
|
1466
|
-
m[3] * inv,
|
|
1467
|
-
-m[1] * inv,
|
|
1468
|
-
-m[2] * inv,
|
|
1469
|
-
m[0] * inv,
|
|
1470
|
-
(m[2] * m[5] - m[3] * m[4]) * inv,
|
|
1471
|
-
(m[1] * m[4] - m[0] * m[5]) * inv
|
|
1472
|
-
];
|
|
1364
|
+
function outlineStroke(stroke, fill, style, fillTone) {
|
|
1365
|
+
return outlineCarriesFill(fill, style, fillTone) && fill !== void 0 ? fill : stroke;
|
|
1473
1366
|
}
|
|
1474
|
-
function
|
|
1475
|
-
return
|
|
1367
|
+
function outlineCarriesFill(fill, style, fillTone) {
|
|
1368
|
+
return style === "outline" && fill !== void 0 && fillTone !== "fill";
|
|
1476
1369
|
}
|
|
1477
|
-
function
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1370
|
+
function applyLineCharacter(ctx, theme, strokeColor) {
|
|
1371
|
+
ctx.lineCap = theme.lineCap;
|
|
1372
|
+
ctx.lineJoin = theme.lineJoin;
|
|
1373
|
+
if (theme.glow > 0) {
|
|
1374
|
+
ctx.shadowColor = strokeColor;
|
|
1375
|
+
ctx.shadowBlur = theme.glow;
|
|
1376
|
+
ctx.shadowOffsetX = 0;
|
|
1377
|
+
ctx.shadowOffsetY = 0;
|
|
1378
|
+
}
|
|
1483
1379
|
}
|
|
1484
|
-
function
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1489
|
-
const resolve = (i) => {
|
|
1490
|
-
const cached = world[i];
|
|
1491
|
-
if (cached) return cached;
|
|
1492
|
-
if (visiting.has(i)) return IDENTITY;
|
|
1493
|
-
visiting.add(i);
|
|
1494
|
-
const b = bones[i];
|
|
1495
|
-
const parentIndex = b.parent === null ? void 0 : indexByName.get(b.parent);
|
|
1496
|
-
const parentWorld = parentIndex === void 0 ? IDENTITY : resolve(parentIndex);
|
|
1497
|
-
const local = rotationAbout(b.pivot, pose[b.name] ?? 0);
|
|
1498
|
-
const m = composeAffine(parentWorld, local);
|
|
1499
|
-
visiting.delete(i);
|
|
1500
|
-
world[i] = m;
|
|
1501
|
-
return m;
|
|
1502
|
-
};
|
|
1503
|
-
return bones.map((_, i) => resolve(i));
|
|
1380
|
+
function markDash(theme, dash) {
|
|
1381
|
+
if (dash === "dashed") return theme.dash;
|
|
1382
|
+
if (dash === "dotted") return theme.dot;
|
|
1383
|
+
return [];
|
|
1504
1384
|
}
|
|
1505
|
-
function
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1385
|
+
function wobble(x, y, k) {
|
|
1386
|
+
const s = Math.sin(x * 12.9898 + y * 78.233 + k * 37.719) * 43758.5453;
|
|
1387
|
+
return (s - Math.floor(s)) * 2 - 1;
|
|
1388
|
+
}
|
|
1389
|
+
function tracePolyline(ctx, theme, points, closed) {
|
|
1390
|
+
ctx.beginPath();
|
|
1391
|
+
if (points.length === 0) return;
|
|
1392
|
+
const amp = theme.roughness;
|
|
1393
|
+
const pts = closed ? [...points, points[0]] : [...points];
|
|
1394
|
+
ctx.moveTo(pts[0].x + amp * wobble(pts[0].x, pts[0].y, 0), pts[0].y + amp * wobble(pts[0].y, pts[0].x, 1));
|
|
1395
|
+
for (let i = 1; i < pts.length; i++) {
|
|
1396
|
+
const a = pts[i - 1];
|
|
1397
|
+
const b = pts[i];
|
|
1398
|
+
if (amp === 0) {
|
|
1399
|
+
ctx.lineTo(b.x, b.y);
|
|
1400
|
+
continue;
|
|
1512
1401
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
for (const { bone, w } of v.weights) {
|
|
1520
|
-
const [bx, by] = applyAffine(skinMat(bone), v.x, v.y);
|
|
1521
|
-
x += w * bx;
|
|
1522
|
-
y += w * by;
|
|
1402
|
+
const steps = Math.max(1, Math.round(Math.hypot(b.x - a.x, b.y - a.y) / 24));
|
|
1403
|
+
for (let s = 1; s <= steps; s++) {
|
|
1404
|
+
const t = s / steps;
|
|
1405
|
+
const x = a.x + (b.x - a.x) * t;
|
|
1406
|
+
const y = a.y + (b.y - a.y) * t;
|
|
1407
|
+
ctx.lineTo(x + amp * wobble(x, y, 2), y + amp * wobble(y, x, 3));
|
|
1523
1408
|
}
|
|
1524
|
-
return [x, y];
|
|
1525
|
-
});
|
|
1526
|
-
}
|
|
1527
|
-
function triangleAffine(src, dst) {
|
|
1528
|
-
const [s0, s1, s2] = src;
|
|
1529
|
-
const det = s0[0] * (s1[1] - s2[1]) + s1[0] * (s2[1] - s0[1]) + s2[0] * (s0[1] - s1[1]);
|
|
1530
|
-
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
1531
|
-
const [d0, d1, d2] = dst;
|
|
1532
|
-
const a = (d0[0] * (s1[1] - s2[1]) + d1[0] * (s2[1] - s0[1]) + d2[0] * (s0[1] - s1[1])) / det;
|
|
1533
|
-
const c = (d0[0] * (s2[0] - s1[0]) + d1[0] * (s0[0] - s2[0]) + d2[0] * (s1[0] - s0[0])) / det;
|
|
1534
|
-
const e = (d0[0] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[0] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[0] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1535
|
-
const b = (d0[1] * (s1[1] - s2[1]) + d1[1] * (s2[1] - s0[1]) + d2[1] * (s0[1] - s1[1])) / det;
|
|
1536
|
-
const d = (d0[1] * (s2[0] - s1[0]) + d1[1] * (s0[0] - s2[0]) + d2[1] * (s1[0] - s0[0])) / det;
|
|
1537
|
-
const f = (d0[1] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[1] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[1] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1538
|
-
return [a, b, c, d, e, f];
|
|
1539
|
-
}
|
|
1540
|
-
var meshCache = /* @__PURE__ */ new Map();
|
|
1541
|
-
function isRigBundle(json) {
|
|
1542
|
-
return json.mesh !== void 0 || json.bones !== void 0;
|
|
1543
|
-
}
|
|
1544
|
-
function getRigBundle(url, onReady) {
|
|
1545
|
-
if (meshCache.has(url)) return meshCache.get(url) ?? void 0;
|
|
1546
|
-
meshCache.set(url, void 0);
|
|
1547
|
-
fetch(url).then((r) => {
|
|
1548
|
-
if (!r.ok) throw new Error(`rigbundle fetch failed: HTTP ${r.status}`);
|
|
1549
|
-
return r.json();
|
|
1550
|
-
}).then((json) => {
|
|
1551
|
-
const ok = isRigBundle(json) || Array.isArray(json.vertices);
|
|
1552
|
-
if (!ok) throw new Error("rigbundle payload is neither a SkinMesh nor a rigbundle");
|
|
1553
|
-
meshCache.set(url, json);
|
|
1554
|
-
onReady();
|
|
1555
|
-
}).catch(() => {
|
|
1556
|
-
meshCache.set(url, null);
|
|
1557
|
-
onReady();
|
|
1558
|
-
});
|
|
1559
|
-
return void 0;
|
|
1560
|
-
}
|
|
1561
|
-
function skinMeshFailed(url) {
|
|
1562
|
-
return meshCache.get(url) === null;
|
|
1563
|
-
}
|
|
1564
|
-
function vertexBounds(points) {
|
|
1565
|
-
if (points.length === 0) return null;
|
|
1566
|
-
let minX = Infinity;
|
|
1567
|
-
let minY = Infinity;
|
|
1568
|
-
let maxX = -Infinity;
|
|
1569
|
-
let maxY = -Infinity;
|
|
1570
|
-
for (const [x, y] of points) {
|
|
1571
|
-
if (x < minX) minX = x;
|
|
1572
|
-
if (y < minY) minY = y;
|
|
1573
|
-
if (x > maxX) maxX = x;
|
|
1574
|
-
if (y > maxY) maxY = y;
|
|
1575
1409
|
}
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
// components/game/atoms/DrawSkinnedMesh.tsx
|
|
1580
|
-
var skinnedLog = createLogger("almadar:ui:draw-skinned-mesh");
|
|
1581
|
-
var loggedMissing = /* @__PURE__ */ new Set();
|
|
1582
|
-
function warnMissingOnce(reason, node) {
|
|
1583
|
-
const key = `${reason}:${node.asset.url}:${String(node.meshUrl)}`;
|
|
1584
|
-
if (loggedMissing.has(key)) return;
|
|
1585
|
-
loggedMissing.add(key);
|
|
1586
|
-
skinnedLog.warn("draw-skinned-mesh unresolvable \u2014 painting fallback", { reason, url: node.asset.url, meshUrl: node.meshUrl });
|
|
1410
|
+
if (closed) ctx.closePath();
|
|
1587
1411
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
let
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
bestW = w;
|
|
1595
|
-
best = bone;
|
|
1596
|
-
}
|
|
1412
|
+
function arcPoints(cx, cy, rx, ry, startRad = 0, endRad = Math.PI * 2) {
|
|
1413
|
+
const steps = Math.max(12, Math.round(Math.abs(endRad - startRad) * Math.max(rx, ry) / 8));
|
|
1414
|
+
const out = [];
|
|
1415
|
+
for (let i = 0; i <= steps; i++) {
|
|
1416
|
+
const t = startRad + (endRad - startRad) * i / steps;
|
|
1417
|
+
out.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });
|
|
1597
1418
|
}
|
|
1598
|
-
return
|
|
1419
|
+
return out;
|
|
1599
1420
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1421
|
+
function fillMark(ctx, theme, color, style = theme.fillStyle) {
|
|
1422
|
+
if (style === "outline") return;
|
|
1423
|
+
ctx.save();
|
|
1424
|
+
ctx.shadowBlur = 0;
|
|
1425
|
+
ctx.shadowColor = "transparent";
|
|
1426
|
+
if (theme.shadow) {
|
|
1427
|
+
ctx.shadowColor = theme.shadow.color;
|
|
1428
|
+
ctx.shadowOffsetX = theme.shadow.offsetX;
|
|
1429
|
+
ctx.shadowOffsetY = theme.shadow.offsetY;
|
|
1430
|
+
ctx.shadowBlur = theme.shadow.blur;
|
|
1604
1431
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1432
|
+
if (style === "hatch") {
|
|
1433
|
+
ctx.clip();
|
|
1434
|
+
const span = ctx.canvas.width + ctx.canvas.height;
|
|
1435
|
+
ctx.strokeStyle = color;
|
|
1436
|
+
ctx.lineWidth = theme.strokes.thin;
|
|
1437
|
+
ctx.setLineDash([]);
|
|
1438
|
+
ctx.beginPath();
|
|
1439
|
+
for (let d = -span; d < span; d += 6) {
|
|
1440
|
+
ctx.moveTo(d, 0);
|
|
1441
|
+
ctx.lineTo(d + span, span);
|
|
1442
|
+
}
|
|
1443
|
+
ctx.stroke();
|
|
1444
|
+
} else {
|
|
1445
|
+
ctx.globalAlpha *= style === "tint" ? theme.fillOpacity : 1;
|
|
1446
|
+
ctx.fillStyle = color;
|
|
1447
|
+
ctx.fill();
|
|
1448
|
+
}
|
|
1449
|
+
ctx.restore();
|
|
1617
1450
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1451
|
+
function drawMarker(ctx, theme, x1, y1, x2, y2, color, width) {
|
|
1452
|
+
const size = Math.max(8, width * 4);
|
|
1453
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
1454
|
+
const left = { x: x2 - size * Math.cos(angle - Math.PI / 6), y: y2 - size * Math.sin(angle - Math.PI / 6) };
|
|
1455
|
+
const right = { x: x2 - size * Math.cos(angle + Math.PI / 6), y: y2 - size * Math.sin(angle + Math.PI / 6) };
|
|
1456
|
+
ctx.save();
|
|
1457
|
+
ctx.strokeStyle = color;
|
|
1458
|
+
ctx.fillStyle = color;
|
|
1459
|
+
ctx.lineWidth = width;
|
|
1460
|
+
ctx.setLineDash([]);
|
|
1461
|
+
switch (theme.marker) {
|
|
1462
|
+
case "triangle": {
|
|
1463
|
+
ctx.beginPath();
|
|
1464
|
+
ctx.moveTo(x2, y2);
|
|
1465
|
+
ctx.lineTo(left.x, left.y);
|
|
1466
|
+
ctx.lineTo(right.x, right.y);
|
|
1467
|
+
ctx.closePath();
|
|
1468
|
+
ctx.fill();
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
case "open":
|
|
1472
|
+
case "line": {
|
|
1473
|
+
ctx.beginPath();
|
|
1474
|
+
ctx.moveTo(left.x, left.y);
|
|
1475
|
+
ctx.lineTo(x2, y2);
|
|
1476
|
+
ctx.lineTo(right.x, right.y);
|
|
1477
|
+
if (theme.marker === "open") ctx.stroke();
|
|
1478
|
+
else {
|
|
1479
|
+
ctx.moveTo(x2 - size * 0.5 * Math.cos(angle + Math.PI / 2), y2 - size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
1480
|
+
ctx.lineTo(x2 + size * 0.5 * Math.cos(angle + Math.PI / 2), y2 + size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
1481
|
+
ctx.stroke();
|
|
1482
|
+
}
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
case "dot": {
|
|
1486
|
+
ctx.beginPath();
|
|
1487
|
+
ctx.arc(x2, y2, Math.max(2.5, width * 1.5), 0, Math.PI * 2);
|
|
1488
|
+
ctx.fill();
|
|
1626
1489
|
break;
|
|
1627
1490
|
}
|
|
1628
1491
|
}
|
|
1629
|
-
|
|
1630
|
-
return pixel;
|
|
1492
|
+
ctx.restore();
|
|
1631
1493
|
}
|
|
1632
|
-
function
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const v2 = mesh.vertices[i2];
|
|
1644
|
-
const p0 = skinned[i0];
|
|
1645
|
-
const p1 = skinned[i1];
|
|
1646
|
-
const p2 = skinned[i2];
|
|
1647
|
-
if (!v0 || !v1 || !v2 || !p0 || !p1 || !p2) continue;
|
|
1648
|
-
const dst = [
|
|
1649
|
-
[origin.x + p0[0] * tw, origin.y + p0[1] * tw],
|
|
1650
|
-
[origin.x + p1[0] * tw, origin.y + p1[1] * tw],
|
|
1651
|
-
[origin.x + p2[0] * tw, origin.y + p2[1] * tw]
|
|
1652
|
-
];
|
|
1653
|
-
if (node.weightsOverlay || !tex) {
|
|
1654
|
-
if (!node.weightsOverlay) continue;
|
|
1655
|
-
const bone = dominantBone(v0);
|
|
1656
|
-
painter.fillPoly(
|
|
1657
|
-
dst.map(([x, y]) => ({ x, y })),
|
|
1658
|
-
WEIGHT_PALETTE[(bone % WEIGHT_PALETTE.length + WEIGHT_PALETTE.length) % WEIGHT_PALETTE.length]
|
|
1659
|
-
);
|
|
1660
|
-
continue;
|
|
1661
|
-
}
|
|
1662
|
-
const pixelUV = meshHasPixelUVs(mesh);
|
|
1663
|
-
const uv = pixelUV ? [[v0.u, v0.v], [v1.u, v1.v], [v2.u, v2.v]] : [
|
|
1664
|
-
[v0.u * tex.width, v0.v * tex.height],
|
|
1665
|
-
[v1.u * tex.width, v1.v * tex.height],
|
|
1666
|
-
[v2.u * tex.width, v2.v * tex.height]
|
|
1667
|
-
];
|
|
1668
|
-
const affine = triangleAffine(uv, dst);
|
|
1669
|
-
if (!affine) continue;
|
|
1670
|
-
painter.save();
|
|
1671
|
-
painter.clipPath(`M ${dst[0][0]} ${dst[0][1]} L ${dst[1][0]} ${dst[1][1]} L ${dst[2][0]} ${dst[2][1]} Z`);
|
|
1672
|
-
painter.blitTransformed(tex, { x: 0, y: 0, w: tex.width, h: tex.height }, affine);
|
|
1673
|
-
painter.restore();
|
|
1674
|
-
}
|
|
1675
|
-
painter.restore();
|
|
1676
|
-
}
|
|
1677
|
-
var paintSkinnedMesh = (painter, node, dctx) => {
|
|
1678
|
-
if (!isValidScenePos(node.position)) return;
|
|
1679
|
-
const rig = resolveRig(node, dctx);
|
|
1680
|
-
if (!rig) {
|
|
1681
|
-
if (node.meshUrl && skinMeshFailed(node.meshUrl)) warnMissingOnce("mesh-failed", node);
|
|
1682
|
-
else if (!node.mesh && !node.meshUrl) warnMissingOnce("mesh-missing", node);
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
const tex = node.weightsOverlay ? null : painter.resolveTexture(node.asset.url);
|
|
1686
|
-
if (!node.weightsOverlay && !tex) {
|
|
1687
|
-
if (getImageStatus(node.asset.url) === "failed") warnMissingOnce("texture-failed", node);
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
if (node.ghost && rig.clip && rig.clip.frames.length > 0) {
|
|
1691
|
-
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, rig.clip.frames[node.ghost.frame % rig.clip.frames.length], node.ghost.opacity);
|
|
1692
|
-
}
|
|
1693
|
-
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, resolveSkinPose(node, rig.clip), node.opacity ?? 1);
|
|
1694
|
-
};
|
|
1695
|
-
function DrawSkinnedMesh(props) {
|
|
1696
|
-
const register = useContext(DrawableRegistryContext);
|
|
1697
|
-
if (register) register({ ...props, type: "draw-skinned-mesh" });
|
|
1698
|
-
return null;
|
|
1494
|
+
function useCanvasTheme(scopeRef) {
|
|
1495
|
+
const { appliedTheme } = useTheme();
|
|
1496
|
+
const scopeKey = JSON.stringify(useThemeScope());
|
|
1497
|
+
const [state, setState] = useState({ theme: null, version: 0 });
|
|
1498
|
+
useLayoutEffect(() => {
|
|
1499
|
+
const scope = scopeRef.current;
|
|
1500
|
+
if (!scope) return;
|
|
1501
|
+
const theme = resolveCanvasTheme(scope);
|
|
1502
|
+
setState((prev) => ({ theme, version: prev.version + 1 }));
|
|
1503
|
+
}, [appliedTheme, scopeKey, scopeRef]);
|
|
1504
|
+
return state;
|
|
1699
1505
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
const max = [-Infinity, -Infinity, -Infinity];
|
|
1705
|
-
for (const v of vertices) {
|
|
1706
|
-
if (v.length < 3 || !v.every((c) => Number.isFinite(c))) return null;
|
|
1707
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
1708
|
-
min[axis] = Math.min(min[axis], v[axis]);
|
|
1709
|
-
max[axis] = Math.max(max[axis], v[axis]);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
return { min, max };
|
|
1506
|
+
|
|
1507
|
+
// lib/drawable/contract.ts
|
|
1508
|
+
function isValidScenePos(pos) {
|
|
1509
|
+
return Number.isFinite(pos?.x) && Number.isFinite(pos?.y);
|
|
1713
1510
|
}
|
|
1714
|
-
function
|
|
1715
|
-
|
|
1716
|
-
return
|
|
1511
|
+
function spriteAssetState(node) {
|
|
1512
|
+
if (!isValidScenePos(node.position)) return "invalid-position";
|
|
1513
|
+
return typeof node.asset?.url === "string" && node.asset.url.length > 0 ? "ready" : "asset-unset";
|
|
1717
1514
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1515
|
+
var SPRITE_FALLBACK_FILL = "#9b8f7f";
|
|
1516
|
+
var SPRITE_FALLBACK_STROKE = "#5e564b";
|
|
1517
|
+
function spriteRect(projector, node, natural) {
|
|
1518
|
+
const tw = projector.tileWidth;
|
|
1519
|
+
const fallbackW = projector.worldPixelDirect ? natural?.w ?? 0 : tw;
|
|
1520
|
+
const fallbackH = projector.worldPixelDirect ? natural?.h ?? 0 : tw;
|
|
1521
|
+
const w = node.width !== void 0 ? node.width * tw : fallbackW;
|
|
1522
|
+
const h = node.height !== void 0 ? node.height * tw : fallbackH;
|
|
1523
|
+
const anchor = node.anchor ?? "top-left";
|
|
1524
|
+
const p = projector.anchorPoint(node.position, anchor);
|
|
1525
|
+
return {
|
|
1526
|
+
x: anchor === "top-left" ? p.x : p.x - w / 2,
|
|
1527
|
+
y: anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y,
|
|
1528
|
+
w,
|
|
1529
|
+
h
|
|
1530
|
+
};
|
|
1720
1531
|
}
|
|
1721
|
-
var
|
|
1722
|
-
var
|
|
1532
|
+
var DrawableRegistryContext = createContext(null);
|
|
1533
|
+
var NUMERIC_TRACKS = [
|
|
1723
1534
|
"offsetX",
|
|
1724
1535
|
"offsetY",
|
|
1725
|
-
"
|
|
1726
|
-
"rotateX",
|
|
1727
|
-
"rotateY",
|
|
1728
|
-
"rotateZ",
|
|
1729
|
-
"scale",
|
|
1536
|
+
"rotate",
|
|
1730
1537
|
"opacity",
|
|
1731
|
-
"
|
|
1538
|
+
"radiusX",
|
|
1539
|
+
"radiusY",
|
|
1540
|
+
"width",
|
|
1541
|
+
"height",
|
|
1542
|
+
"strokeWidth",
|
|
1543
|
+
"strokeDashOffset",
|
|
1544
|
+
"blur"
|
|
1732
1545
|
];
|
|
1733
|
-
function
|
|
1546
|
+
function isAnimatedShape(node) {
|
|
1547
|
+
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1548
|
+
}
|
|
1549
|
+
var lerp = (a, b, k) => a + (b - a) * k;
|
|
1550
|
+
function applyShapeAnimation(node, timeMs) {
|
|
1734
1551
|
const anim = node.animation;
|
|
1735
|
-
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return
|
|
1552
|
+
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
|
|
1736
1553
|
const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
|
|
1737
1554
|
const cycle = timeMs / anim.durationMs;
|
|
1738
1555
|
const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
|
|
1556
|
+
const out = { ...node };
|
|
1739
1557
|
const trackValue = (key) => {
|
|
1740
1558
|
const defined = frames.filter((f) => f[key] !== void 0);
|
|
1741
1559
|
if (defined.length === 0) return void 0;
|
|
@@ -1751,652 +1569,836 @@ function applyMeshAnimation(node, timeMs) {
|
|
|
1751
1569
|
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
1752
1570
|
const a = prev[key];
|
|
1753
1571
|
const b = next[key];
|
|
1754
|
-
if (typeof a === "number" && typeof b === "number") return
|
|
1572
|
+
if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
|
|
1755
1573
|
return a;
|
|
1756
1574
|
};
|
|
1757
|
-
const
|
|
1758
|
-
for (const key of NUMERIC_TRACKS2) {
|
|
1575
|
+
for (const key of NUMERIC_TRACKS) {
|
|
1759
1576
|
const v = trackValue(key);
|
|
1760
|
-
if (v !== void 0)
|
|
1577
|
+
if (v !== void 0) out[key] = v;
|
|
1761
1578
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1579
|
+
const fill = trackValue("fill");
|
|
1580
|
+
if (fill !== void 0) out.fill = fill;
|
|
1581
|
+
const stroke = trackValue("stroke");
|
|
1582
|
+
if (stroke !== void 0) out.stroke = stroke;
|
|
1583
|
+
const shadowFrames = frames.filter((f) => f.shadow !== void 0);
|
|
1584
|
+
if (shadowFrames.length > 0) {
|
|
1585
|
+
const sh = trackValue("shadow");
|
|
1586
|
+
if (sh !== void 0) {
|
|
1587
|
+
let prevSh;
|
|
1588
|
+
let nextSh;
|
|
1589
|
+
for (const f of shadowFrames) {
|
|
1590
|
+
if (f.at <= t) prevSh = f;
|
|
1591
|
+
else if (!nextSh) nextSh = f;
|
|
1592
|
+
}
|
|
1593
|
+
if (prevSh?.shadow && nextSh?.shadow) {
|
|
1594
|
+
const span = nextSh.at - prevSh.at;
|
|
1595
|
+
const k = span > 0 ? (t - prevSh.at) / span : 1;
|
|
1596
|
+
out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
|
|
1597
|
+
} else {
|
|
1598
|
+
out.shadow = sh;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return out;
|
|
1776
1603
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
var DEFAULT_TEXT_COLOR = "#ffe066";
|
|
1781
|
-
var DEFAULT_SPARK_COLOR = "#ffffff";
|
|
1782
|
-
var easeOut = (t) => 1 - (1 - t) * (1 - t);
|
|
1783
|
-
function fxPosition(view, dim, ageSec) {
|
|
1784
|
-
const g = view.gravity ?? 0;
|
|
1785
|
-
const fall = 0.5 * g * ageSec * ageSec;
|
|
1786
|
-
if (dim === "2d") {
|
|
1787
|
-
const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
|
|
1788
|
-
if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
|
|
1604
|
+
function gradientStyle(g, ox, oy, scale) {
|
|
1605
|
+
if (g.kind === "linear") {
|
|
1606
|
+
if (!g.from || !g.to) return void 0;
|
|
1789
1607
|
return {
|
|
1790
|
-
|
|
1791
|
-
|
|
1608
|
+
kind: "linear",
|
|
1609
|
+
x1: ox + g.from.x * scale,
|
|
1610
|
+
y1: oy + g.from.y * scale,
|
|
1611
|
+
x2: ox + g.to.x * scale,
|
|
1612
|
+
y2: oy + g.to.y * scale,
|
|
1613
|
+
stops: g.stops
|
|
1792
1614
|
};
|
|
1793
1615
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1616
|
+
if (g.kind === "conic") {
|
|
1617
|
+
if (!g.center) return void 0;
|
|
1618
|
+
return {
|
|
1619
|
+
kind: "conic",
|
|
1620
|
+
cx: ox + g.center.x * scale,
|
|
1621
|
+
cy: oy + g.center.y * scale,
|
|
1622
|
+
angle: g.angle ?? 0,
|
|
1623
|
+
stops: g.stops
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
if (!g.center || g.radius === void 0) return void 0;
|
|
1796
1627
|
return {
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1628
|
+
kind: "radial",
|
|
1629
|
+
cx: ox + g.center.x * scale,
|
|
1630
|
+
cy: oy + g.center.y * scale,
|
|
1631
|
+
r: g.radius * scale,
|
|
1632
|
+
stops: g.stops
|
|
1800
1633
|
};
|
|
1801
1634
|
}
|
|
1802
|
-
function
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
return {
|
|
1809
|
-
type: "draw-shape",
|
|
1810
|
-
shape: "ellipse",
|
|
1811
|
-
position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
|
|
1812
|
-
anchor: "center",
|
|
1813
|
-
radiusX: r,
|
|
1814
|
-
fill: color,
|
|
1815
|
-
blendMode: "lighter",
|
|
1816
|
-
opacity: fade,
|
|
1817
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1818
|
-
};
|
|
1635
|
+
function patternStyle(p, unit) {
|
|
1636
|
+
if (p.kind === "image") {
|
|
1637
|
+
if (!p.url) return void 0;
|
|
1638
|
+
return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
|
|
1639
|
+
}
|
|
1640
|
+
return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
|
|
1819
1641
|
}
|
|
1820
|
-
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1642
|
+
var paintShape = (painter, rawNode, dctx) => {
|
|
1643
|
+
const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
|
|
1644
|
+
if (!isValidScenePos(node.position)) return;
|
|
1645
|
+
painter.save();
|
|
1646
|
+
if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
|
|
1647
|
+
const origin = dctx.projector.project(node.position);
|
|
1648
|
+
const tileWidth = dctx.projector.tileWidth;
|
|
1649
|
+
const groupScale = dctx.groupScale ?? 1;
|
|
1650
|
+
const strokePx = (node.strokeWidth ?? 1) / groupScale;
|
|
1651
|
+
if (node.blendMode) painter.setBlend(node.blendMode);
|
|
1652
|
+
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1653
|
+
painter.setLineDash(
|
|
1654
|
+
node.strokeDash.map((v) => v / groupScale),
|
|
1655
|
+
(node.strokeDashOffset ?? 0) / groupScale
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
|
|
1659
|
+
if (node.rotate) {
|
|
1660
|
+
const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
|
|
1661
|
+
const px = origin.x + pivot.x * tileWidth;
|
|
1662
|
+
const py = origin.y + pivot.y * tileWidth;
|
|
1663
|
+
painter.translate(px, py);
|
|
1664
|
+
painter.rotate(node.rotate);
|
|
1665
|
+
painter.translate(-px, -py);
|
|
1666
|
+
}
|
|
1667
|
+
if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
|
|
1668
|
+
const fill = node.fill === "none" ? void 0 : node.fill;
|
|
1669
|
+
const stroke = node.stroke === "none" ? void 0 : node.stroke;
|
|
1670
|
+
const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
|
|
1671
|
+
const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
|
|
1672
|
+
const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
|
|
1673
|
+
switch (node.shape) {
|
|
1674
|
+
case "cell": {
|
|
1675
|
+
const pts = dctx.projector.cellPath(node.position);
|
|
1676
|
+
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1677
|
+
if (patFill) painter.fillPoly(pts, patFill);
|
|
1678
|
+
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1679
|
+
break;
|
|
1856
1680
|
}
|
|
1857
|
-
case "
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
offsetX: inner + len / 2,
|
|
1869
|
-
offsetY: 0,
|
|
1870
|
-
radiusX: len / 2,
|
|
1871
|
-
radiusY: size * 0.04,
|
|
1872
|
-
rotate: angle,
|
|
1873
|
-
fill: color,
|
|
1874
|
-
blendMode: "lighter",
|
|
1875
|
-
opacity: fade,
|
|
1876
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1877
|
-
};
|
|
1878
|
-
});
|
|
1681
|
+
case "rect": {
|
|
1682
|
+
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "top-left");
|
|
1683
|
+
const tw = dctx.projector.tileWidth;
|
|
1684
|
+
const x = p.x + (node.offsetX ?? 0) * tw;
|
|
1685
|
+
const y = p.y + (node.offsetY ?? 0) * tw;
|
|
1686
|
+
const w = (node.width ?? 0) * tw;
|
|
1687
|
+
const h = (node.height ?? 0) * tw;
|
|
1688
|
+
if (pxFill) painter.fillRect(x, y, w, h, pxFill);
|
|
1689
|
+
if (patFill) painter.fillRect(x, y, w, h, patFill);
|
|
1690
|
+
if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
|
|
1691
|
+
break;
|
|
1879
1692
|
}
|
|
1880
|
-
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1693
|
+
case "ellipse": {
|
|
1694
|
+
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "ground");
|
|
1695
|
+
const tw = dctx.projector.tileWidth;
|
|
1696
|
+
const cx = p.x + (node.offsetX ?? 0) * tw;
|
|
1697
|
+
const cy = p.y + (node.offsetY ?? 0) * tw;
|
|
1698
|
+
const rx = (node.radiusX ?? 0) * tw;
|
|
1699
|
+
const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
|
|
1700
|
+
if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
|
|
1701
|
+
if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
|
|
1702
|
+
if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
case "poly": {
|
|
1706
|
+
const pts = (node.points ?? []).map((pt) => ({
|
|
1707
|
+
x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
|
|
1708
|
+
y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
|
|
1709
|
+
}));
|
|
1710
|
+
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1711
|
+
if (patFill) painter.fillPoly(pts, patFill);
|
|
1712
|
+
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
case "path": {
|
|
1716
|
+
if (!node.d) break;
|
|
1717
|
+
painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
|
|
1718
|
+
painter.scale(tileWidth, tileWidth);
|
|
1719
|
+
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1720
|
+
painter.setLineDash(
|
|
1721
|
+
node.strokeDash.map((v) => v / (groupScale * tileWidth)),
|
|
1722
|
+
(node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
|
|
1726
|
+
const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
|
|
1727
|
+
const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
|
|
1728
|
+
const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
|
|
1729
|
+
if (localFill) painter.fillPath(node.d, localFill);
|
|
1730
|
+
if (localPat) painter.fillPath(node.d, localPat);
|
|
1731
|
+
if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
|
|
1732
|
+
break;
|
|
1883
1733
|
}
|
|
1884
1734
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
const sprite = node.sprites?.[view.type];
|
|
1896
|
-
if (Array.isArray(artItems)) {
|
|
1897
|
-
out.push({
|
|
1898
|
-
type: "draw-group",
|
|
1899
|
-
position: pos,
|
|
1900
|
-
opacity: fade,
|
|
1901
|
-
...view.size !== void 0 ? { scale: view.size } : {},
|
|
1902
|
-
items: artItems
|
|
1903
|
-
});
|
|
1904
|
-
} else if (sprite?.url) {
|
|
1905
|
-
const spriteSize = view.size ?? 0.6;
|
|
1906
|
-
out.push({
|
|
1907
|
-
type: "draw-sprite",
|
|
1908
|
-
position: pos,
|
|
1909
|
-
asset: sprite,
|
|
1910
|
-
anchor: "center",
|
|
1911
|
-
width: spriteSize,
|
|
1912
|
-
height: spriteSize,
|
|
1913
|
-
opacity: fade,
|
|
1914
|
-
animation: view.animation ?? sprite.animations?.[0],
|
|
1915
|
-
// One-shot fx rows (burst) play from the item's birth, not wall-time.
|
|
1916
|
-
clockMs: ageMs
|
|
1917
|
-
});
|
|
1918
|
-
} else {
|
|
1919
|
-
out.push(...proceduralShapes(view, pos, fade, progress));
|
|
1920
|
-
}
|
|
1921
|
-
if (view.message) {
|
|
1922
|
-
const pxSize = projector && view.size !== void 0 ? Math.max(10, Math.round(view.size * projector.tileWidth)) : void 0;
|
|
1923
|
-
const family = fontFamily ?? "system-ui, sans-serif";
|
|
1924
|
-
const text = {
|
|
1925
|
-
type: "draw-text",
|
|
1926
|
-
text: view.message,
|
|
1927
|
-
position: pos,
|
|
1928
|
-
offsetY: -(0.15 + 0.55 * progress),
|
|
1929
|
-
color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
|
|
1930
|
-
opacity: fade,
|
|
1931
|
-
font: `bold ${pxSize ?? 14}px ${family}`
|
|
1735
|
+
painter.restore();
|
|
1736
|
+
};
|
|
1737
|
+
function DrawShape(props) {
|
|
1738
|
+
const register = useContext(DrawableRegistryContext);
|
|
1739
|
+
if (register) {
|
|
1740
|
+
const { opacity, ...rest } = props;
|
|
1741
|
+
const node = {
|
|
1742
|
+
...rest,
|
|
1743
|
+
type: "draw-shape",
|
|
1744
|
+
...opacity !== void 0 && opacity > 0 ? { opacity } : {}
|
|
1932
1745
|
};
|
|
1933
|
-
|
|
1746
|
+
register(node);
|
|
1934
1747
|
}
|
|
1935
|
-
return
|
|
1748
|
+
return null;
|
|
1936
1749
|
}
|
|
1937
|
-
function
|
|
1938
|
-
|
|
1939
|
-
const out = [];
|
|
1940
|
-
for (const item of node.items) {
|
|
1941
|
-
if (!item || typeof item.id !== "string") continue;
|
|
1942
|
-
out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim, projector, fontFamily));
|
|
1943
|
-
}
|
|
1944
|
-
return out;
|
|
1750
|
+
function isAnimatedGroup(node) {
|
|
1751
|
+
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1945
1752
|
}
|
|
1946
|
-
function
|
|
1753
|
+
function DrawGroup(props) {
|
|
1754
|
+
const register = useContext(DrawableRegistryContext);
|
|
1755
|
+
if (register) register({ ...props, type: "draw-group" });
|
|
1947
1756
|
return null;
|
|
1948
1757
|
}
|
|
1949
1758
|
|
|
1950
|
-
// lib/
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
animation: {
|
|
1964
|
-
...node.animation,
|
|
1965
|
-
keyframes: node.animation.keyframes.map((k) => ({
|
|
1966
|
-
...k,
|
|
1967
|
-
...k.fill !== void 0 ? { fill: resolve(k.fill) } : {},
|
|
1968
|
-
...k.stroke !== void 0 ? { stroke: resolve(k.stroke) } : {}
|
|
1969
|
-
}))
|
|
1970
|
-
}
|
|
1971
|
-
} : {}
|
|
1759
|
+
// lib/imageCache.ts
|
|
1760
|
+
var RETRY_AFTER_MS = 5e3;
|
|
1761
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1762
|
+
function startLoad(url, onReady, existing) {
|
|
1763
|
+
const img = new Image();
|
|
1764
|
+
img.crossOrigin = "anonymous";
|
|
1765
|
+
const entry = existing ?? { img, status: "pending" };
|
|
1766
|
+
entry.img = img;
|
|
1767
|
+
if (onReady) entry.onReady = onReady;
|
|
1768
|
+
img.onload = () => {
|
|
1769
|
+
entry.status = "loaded";
|
|
1770
|
+
updateAssetStatus(url, "loaded");
|
|
1771
|
+
entry.onReady?.();
|
|
1972
1772
|
};
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
if (!material) return material;
|
|
1979
|
-
return {
|
|
1980
|
-
...material,
|
|
1981
|
-
...material.color !== void 0 ? { color: resolve(material.color) } : {},
|
|
1982
|
-
...material.emissive !== void 0 ? { emissive: resolve(material.emissive) } : {}
|
|
1773
|
+
img.onerror = () => {
|
|
1774
|
+
entry.status = "failed";
|
|
1775
|
+
entry.failedAt = Date.now();
|
|
1776
|
+
updateAssetStatus(url, "failed");
|
|
1777
|
+
entry.onReady?.();
|
|
1983
1778
|
};
|
|
1779
|
+
img.src = url;
|
|
1780
|
+
return entry;
|
|
1984
1781
|
}
|
|
1985
|
-
function
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
case "draw-shape-layer":
|
|
1995
|
-
return { ...node, items: node.items.map((item) => themeShape(item, resolve)) };
|
|
1996
|
-
case "draw-text-layer":
|
|
1997
|
-
return { ...node, items: node.items.map((item) => themeText(item, resolve)) };
|
|
1998
|
-
case "draw-fx-layer":
|
|
1999
|
-
return {
|
|
2000
|
-
...node,
|
|
2001
|
-
...node.textColor !== void 0 ? { textColor: resolve(node.textColor) } : {},
|
|
2002
|
-
...node.presets ? {
|
|
2003
|
-
presets: node.presets.map((p) => ({
|
|
2004
|
-
...p,
|
|
2005
|
-
...p.color !== void 0 ? { color: resolve(p.color) } : {},
|
|
2006
|
-
...p.color2 !== void 0 ? { color2: resolve(p.color2) } : {}
|
|
2007
|
-
}))
|
|
2008
|
-
} : {}
|
|
2009
|
-
};
|
|
2010
|
-
case "draw-mesh":
|
|
2011
|
-
return {
|
|
2012
|
-
...node,
|
|
2013
|
-
material: themeMaterial(node.material, resolve),
|
|
2014
|
-
...node.outline ? { outline: { ...node.outline, ...node.outline.color !== void 0 ? { color: resolve(node.outline.color) } : {} } } : {},
|
|
2015
|
-
...node.animation ? {
|
|
2016
|
-
animation: {
|
|
2017
|
-
...node.animation,
|
|
2018
|
-
keyframes: node.animation.keyframes.map((k) => ({
|
|
2019
|
-
...k,
|
|
2020
|
-
...k.color !== void 0 ? { color: resolve(k.color) } : {},
|
|
2021
|
-
...k.emissive !== void 0 ? { emissive: opt(k.emissive, resolve) } : {}
|
|
2022
|
-
}))
|
|
2023
|
-
}
|
|
2024
|
-
} : {}
|
|
2025
|
-
};
|
|
2026
|
-
default:
|
|
2027
|
-
return node;
|
|
1782
|
+
function getOrLoadImage(url, onReady) {
|
|
1783
|
+
if (!url || typeof window === "undefined") return null;
|
|
1784
|
+
const existing = cache.get(url);
|
|
1785
|
+
if (existing) {
|
|
1786
|
+
if (existing.status === "loaded") return existing.img;
|
|
1787
|
+
if (existing.status === "pending" && onReady) existing.onReady = onReady;
|
|
1788
|
+
if (existing.status === "failed" && Date.now() - (existing.failedAt ?? 0) >= RETRY_AFTER_MS) {
|
|
1789
|
+
existing.failedAt = Date.now();
|
|
1790
|
+
startLoad(url, onReady, existing);
|
|
2028
1791
|
}
|
|
2029
|
-
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const entry = startLoad(url, onReady);
|
|
1795
|
+
cache.set(url, entry);
|
|
1796
|
+
updateAssetStatus(url, "pending");
|
|
1797
|
+
return null;
|
|
2030
1798
|
}
|
|
2031
|
-
function
|
|
2032
|
-
return
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1799
|
+
function getImageStatus(url) {
|
|
1800
|
+
return cache.get(url)?.status;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// lib/skinning.ts
|
|
1804
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1805
|
+
function composeAffine(a, b) {
|
|
1806
|
+
return [
|
|
1807
|
+
a[0] * b[0] + a[2] * b[1],
|
|
1808
|
+
a[1] * b[0] + a[3] * b[1],
|
|
1809
|
+
a[0] * b[2] + a[2] * b[3],
|
|
1810
|
+
a[1] * b[2] + a[3] * b[3],
|
|
1811
|
+
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
1812
|
+
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
1813
|
+
];
|
|
1814
|
+
}
|
|
1815
|
+
function invertAffine(m) {
|
|
1816
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
1817
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return IDENTITY;
|
|
1818
|
+
const inv = 1 / det;
|
|
1819
|
+
return [
|
|
1820
|
+
m[3] * inv,
|
|
1821
|
+
-m[1] * inv,
|
|
1822
|
+
-m[2] * inv,
|
|
1823
|
+
m[0] * inv,
|
|
1824
|
+
(m[2] * m[5] - m[3] * m[4]) * inv,
|
|
1825
|
+
(m[1] * m[4] - m[0] * m[5]) * inv
|
|
1826
|
+
];
|
|
1827
|
+
}
|
|
1828
|
+
function applyAffine(m, x, y) {
|
|
1829
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
1830
|
+
}
|
|
1831
|
+
function rotationAbout(pivot, degrees) {
|
|
1832
|
+
const r = degrees * Math.PI / 180;
|
|
1833
|
+
const cos = Math.cos(r);
|
|
1834
|
+
const sin = Math.sin(r);
|
|
1835
|
+
const [px, py] = pivot;
|
|
1836
|
+
return [cos, sin, -sin, cos, px - cos * px + sin * py, py - sin * px - cos * py];
|
|
1837
|
+
}
|
|
1838
|
+
function computeWorldMatrices(bones, pose) {
|
|
1839
|
+
const indexByName = /* @__PURE__ */ new Map();
|
|
1840
|
+
bones.forEach((b, i) => indexByName.set(b.name, i));
|
|
1841
|
+
const world = new Array(bones.length);
|
|
1842
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1843
|
+
const resolve = (i) => {
|
|
1844
|
+
const cached = world[i];
|
|
1845
|
+
if (cached) return cached;
|
|
1846
|
+
if (visiting.has(i)) return IDENTITY;
|
|
1847
|
+
visiting.add(i);
|
|
1848
|
+
const b = bones[i];
|
|
1849
|
+
const parentIndex = b.parent === null ? void 0 : indexByName.get(b.parent);
|
|
1850
|
+
const parentWorld = parentIndex === void 0 ? IDENTITY : resolve(parentIndex);
|
|
1851
|
+
const local = rotationAbout(b.pivot, pose[b.name] ?? 0);
|
|
1852
|
+
const m = composeAffine(parentWorld, local);
|
|
1853
|
+
visiting.delete(i);
|
|
1854
|
+
world[i] = m;
|
|
1855
|
+
return m;
|
|
1856
|
+
};
|
|
1857
|
+
return bones.map((_, i) => resolve(i));
|
|
1858
|
+
}
|
|
1859
|
+
function skinVertices(mesh, bindWorld, curWorld) {
|
|
1860
|
+
const skinMats = /* @__PURE__ */ new Map();
|
|
1861
|
+
const skinMat = (bone) => {
|
|
1862
|
+
let m = skinMats.get(bone);
|
|
1863
|
+
if (!m) {
|
|
1864
|
+
m = composeAffine(curWorld[bone] ?? IDENTITY, invertAffine(bindWorld[bone] ?? IDENTITY));
|
|
1865
|
+
skinMats.set(bone, m);
|
|
1866
|
+
}
|
|
1867
|
+
return m;
|
|
1868
|
+
};
|
|
1869
|
+
return mesh.vertices.map((v) => {
|
|
1870
|
+
if (v.weights.length === 0) return [v.x, v.y];
|
|
1871
|
+
let x = 0;
|
|
1872
|
+
let y = 0;
|
|
1873
|
+
for (const { bone, w } of v.weights) {
|
|
1874
|
+
const [bx, by] = applyAffine(skinMat(bone), v.x, v.y);
|
|
1875
|
+
x += w * bx;
|
|
1876
|
+
y += w * by;
|
|
1877
|
+
}
|
|
1878
|
+
return [x, y];
|
|
2047
1879
|
});
|
|
2048
1880
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
1881
|
+
function triangleAffine(src, dst) {
|
|
1882
|
+
const [s0, s1, s2] = src;
|
|
1883
|
+
const det = s0[0] * (s1[1] - s2[1]) + s1[0] * (s2[1] - s0[1]) + s2[0] * (s0[1] - s1[1]);
|
|
1884
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
1885
|
+
const [d0, d1, d2] = dst;
|
|
1886
|
+
const a = (d0[0] * (s1[1] - s2[1]) + d1[0] * (s2[1] - s0[1]) + d2[0] * (s0[1] - s1[1])) / det;
|
|
1887
|
+
const c = (d0[0] * (s2[0] - s1[0]) + d1[0] * (s0[0] - s2[0]) + d2[0] * (s1[0] - s0[0])) / det;
|
|
1888
|
+
const e = (d0[0] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[0] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[0] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1889
|
+
const b = (d0[1] * (s1[1] - s2[1]) + d1[1] * (s2[1] - s0[1]) + d2[1] * (s0[1] - s1[1])) / det;
|
|
1890
|
+
const d = (d0[1] * (s2[0] - s1[0]) + d1[1] * (s0[0] - s2[0]) + d2[1] * (s1[0] - s0[0])) / det;
|
|
1891
|
+
const f = (d0[1] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[1] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[1] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1892
|
+
return [a, b, c, d, e, f];
|
|
2055
1893
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
1894
|
+
var meshCache = /* @__PURE__ */ new Map();
|
|
1895
|
+
function isRigBundle(json) {
|
|
1896
|
+
return json.mesh !== void 0 || json.bones !== void 0;
|
|
2058
1897
|
}
|
|
2059
|
-
function
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
1898
|
+
function getRigBundle(url, onReady) {
|
|
1899
|
+
if (meshCache.has(url)) return meshCache.get(url) ?? void 0;
|
|
1900
|
+
meshCache.set(url, void 0);
|
|
1901
|
+
fetch(url).then((r) => {
|
|
1902
|
+
if (!r.ok) throw new Error(`rigbundle fetch failed: HTTP ${r.status}`);
|
|
1903
|
+
return r.json();
|
|
1904
|
+
}).then((json) => {
|
|
1905
|
+
const ok = isRigBundle(json) || Array.isArray(json.vertices);
|
|
1906
|
+
if (!ok) throw new Error("rigbundle payload is neither a SkinMesh nor a rigbundle");
|
|
1907
|
+
meshCache.set(url, json);
|
|
1908
|
+
onReady();
|
|
1909
|
+
}).catch(() => {
|
|
1910
|
+
meshCache.set(url, null);
|
|
1911
|
+
onReady();
|
|
1912
|
+
});
|
|
1913
|
+
return void 0;
|
|
2064
1914
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
dispose() {
|
|
2080
|
-
this.probe.remove();
|
|
2081
|
-
}
|
|
2082
|
-
raw(name) {
|
|
2083
|
-
return this.style.getPropertyValue(name).trim();
|
|
2084
|
-
}
|
|
2085
|
-
/** A length var in px: a plain value parses directly, a derived one (`calc`, `var`) through the probe. */
|
|
2086
|
-
length(name, fallback) {
|
|
2087
|
-
const raw = this.raw(name);
|
|
2088
|
-
const plain = PLAIN_LENGTH.exec(raw);
|
|
2089
|
-
if (plain) return Number(plain[1]);
|
|
2090
|
-
if (raw === "") return fallback;
|
|
2091
|
-
this.probe.style.width = `var(${name})`;
|
|
2092
|
-
const computed = PLAIN_LENGTH.exec(getComputedStyle(this.probe).width.trim());
|
|
2093
|
-
this.probe.style.width = "";
|
|
2094
|
-
if (computed) return Number(computed[1]);
|
|
2095
|
-
log2.warn("unresolvable-theme-length", { var: name, raw });
|
|
2096
|
-
return fallback;
|
|
2097
|
-
}
|
|
2098
|
-
number(name, fallback) {
|
|
2099
|
-
const raw = this.raw(name);
|
|
2100
|
-
if (raw === "") return fallback;
|
|
2101
|
-
const n = Number(raw);
|
|
2102
|
-
if (Number.isFinite(n)) return n;
|
|
2103
|
-
log2.warn("unresolvable-theme-number", { var: name, raw });
|
|
2104
|
-
return fallback;
|
|
2105
|
-
}
|
|
2106
|
-
/** A duration var in ms (`250ms`, `0.25s`, or a `var()` alias resolved through the probe). */
|
|
2107
|
-
durationMs(name, fallback) {
|
|
2108
|
-
const parse = (v) => {
|
|
2109
|
-
const m = /^([\d.]+)(ms|s)$/.exec(v.trim());
|
|
2110
|
-
return m ? Number(m[1]) * (m[2] === "s" ? 1e3 : 1) : null;
|
|
2111
|
-
};
|
|
2112
|
-
const raw = this.raw(name);
|
|
2113
|
-
const direct = parse(raw);
|
|
2114
|
-
if (direct !== null) return direct;
|
|
2115
|
-
if (raw === "") return fallback;
|
|
2116
|
-
this.probe.style.transitionDuration = `var(${name})`;
|
|
2117
|
-
const computed = parse(getComputedStyle(this.probe).transitionDuration.split(",")[0] ?? "");
|
|
2118
|
-
this.probe.style.transitionDuration = "";
|
|
2119
|
-
return computed ?? fallback;
|
|
2120
|
-
}
|
|
2121
|
-
color(name) {
|
|
2122
|
-
const raw = this.raw(name);
|
|
2123
|
-
return parseComputedColor(raw) ?? resolveThemeColor(`var(${name})`, this.scope);
|
|
1915
|
+
function skinMeshFailed(url) {
|
|
1916
|
+
return meshCache.get(url) === null;
|
|
1917
|
+
}
|
|
1918
|
+
function vertexBounds(points) {
|
|
1919
|
+
if (points.length === 0) return null;
|
|
1920
|
+
let minX = Infinity;
|
|
1921
|
+
let minY = Infinity;
|
|
1922
|
+
let maxX = -Infinity;
|
|
1923
|
+
let maxY = -Infinity;
|
|
1924
|
+
for (const [x, y] of points) {
|
|
1925
|
+
if (x < minX) minX = x;
|
|
1926
|
+
if (y < minY) minY = y;
|
|
1927
|
+
if (x > maxX) maxX = x;
|
|
1928
|
+
if (y > maxY) maxY = y;
|
|
2124
1929
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
1930
|
+
return { minX, minY, maxX, maxY };
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// components/game/atoms/DrawSkinnedMesh.tsx
|
|
1934
|
+
var skinnedLog = createLogger("almadar:ui:draw-skinned-mesh");
|
|
1935
|
+
var loggedMissing = /* @__PURE__ */ new Set();
|
|
1936
|
+
function warnMissingOnce(reason, node) {
|
|
1937
|
+
const key = `${reason}:${node.asset.url}:${String(node.meshUrl)}`;
|
|
1938
|
+
if (loggedMissing.has(key)) return;
|
|
1939
|
+
loggedMissing.add(key);
|
|
1940
|
+
skinnedLog.warn("draw-skinned-mesh unresolvable \u2014 painting fallback", { reason, url: node.asset.url, meshUrl: node.meshUrl });
|
|
1941
|
+
}
|
|
1942
|
+
var WEIGHT_PALETTE = Array.from({ length: 10 }, (_, i) => `hsl(${i * 36 % 360}, 70%, 55%)`);
|
|
1943
|
+
function dominantBone(v) {
|
|
1944
|
+
let best = -1;
|
|
1945
|
+
let bestW = 0;
|
|
1946
|
+
for (const { bone, w } of v.weights) {
|
|
1947
|
+
if (w > bestW) {
|
|
1948
|
+
bestW = w;
|
|
1949
|
+
best = bone;
|
|
1950
|
+
}
|
|
2136
1951
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
1952
|
+
return best;
|
|
1953
|
+
}
|
|
1954
|
+
function resolveSkinPose(node, clip) {
|
|
1955
|
+
const effectiveClip = clip ?? node.clip;
|
|
1956
|
+
if (effectiveClip && node.frame !== void 0 && effectiveClip.frames.length > 0) {
|
|
1957
|
+
return effectiveClip.frames[node.frame % effectiveClip.frames.length];
|
|
2139
1958
|
}
|
|
2140
|
-
};
|
|
2141
|
-
function dashList(raw, fallback) {
|
|
2142
|
-
if (raw === "") return fallback;
|
|
2143
|
-
const parts = raw.split(/[\s,]+/).map(Number);
|
|
2144
|
-
return parts.every((n) => Number.isFinite(n) && n >= 0) ? parts : fallback;
|
|
1959
|
+
return node.pose ?? {};
|
|
2145
1960
|
}
|
|
2146
|
-
function
|
|
2147
|
-
const
|
|
2148
|
-
|
|
1961
|
+
function resolveRig(node, dctx) {
|
|
1962
|
+
const fetched = node.meshUrl ? getRigBundle(node.meshUrl, dctx.invalidate) : void 0;
|
|
1963
|
+
if (node.meshUrl && fetched === void 0) return void 0;
|
|
1964
|
+
const rb = fetched && isRigBundle(fetched) ? fetched : void 0;
|
|
1965
|
+
const mesh = node.mesh ?? rb?.mesh ?? (!rb ? fetched : void 0);
|
|
1966
|
+
if (!mesh) return void 0;
|
|
1967
|
+
const bones = node.bones.length > 0 ? node.bones : rb?.bones ?? [];
|
|
1968
|
+
if (bones.length === 0) return void 0;
|
|
1969
|
+
const clip = node.clip ?? (node.clipName ? rb?.clips?.[node.clipName] : void 0);
|
|
1970
|
+
return { mesh, bones, clip };
|
|
2149
1971
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
1972
|
+
var pixelUVByMesh = /* @__PURE__ */ new WeakMap();
|
|
1973
|
+
function meshHasPixelUVs(mesh) {
|
|
1974
|
+
const cached = pixelUVByMesh.get(mesh);
|
|
1975
|
+
if (cached !== void 0) return cached;
|
|
1976
|
+
let pixel = false;
|
|
1977
|
+
for (const v of mesh.vertices) {
|
|
1978
|
+
if (v.u < 0 || v.u > 1 || v.v < 0 || v.v > 1) {
|
|
1979
|
+
pixel = true;
|
|
1980
|
+
break;
|
|
2157
1981
|
}
|
|
2158
|
-
const ground = r.color("--surface-diagram");
|
|
2159
|
-
const text = {};
|
|
2160
|
-
const TEXT_FALLBACK = { xs: 12, sm: 14, base: 16, lg: 18, xl: 20, "2xl": 24 };
|
|
2161
|
-
for (const size of DIAGRAM_TEXT_SIZES) text[size] = r.length(`--text-${size}`, TEXT_FALLBACK[size]);
|
|
2162
|
-
const body = r.family("--font-family-body", r.family("--font-family", "system-ui, sans-serif"));
|
|
2163
|
-
return {
|
|
2164
|
-
ground: ground ? colorCss(ground) : "transparent",
|
|
2165
|
-
tones,
|
|
2166
|
-
series: SERIES_TONES.map((t) => tones[t]),
|
|
2167
|
-
strokes: {
|
|
2168
|
-
thin: r.length("--diagram-stroke-thin", 1),
|
|
2169
|
-
normal: r.length("--diagram-stroke-normal", 1.5),
|
|
2170
|
-
bold: r.length("--diagram-stroke-bold", 3)
|
|
2171
|
-
},
|
|
2172
|
-
lineCap: oneOf(r.raw("--diagram-line-cap"), ["butt", "round", "square"], "round", "--diagram-line-cap"),
|
|
2173
|
-
lineJoin: oneOf(r.raw("--diagram-line-join"), ["miter", "round", "bevel"], "round", "--diagram-line-join"),
|
|
2174
|
-
dash: dashList(r.raw("--diagram-dash"), [6, 4]),
|
|
2175
|
-
dot: dashList(r.raw("--diagram-dot"), [1, 4]),
|
|
2176
|
-
fillStyle: oneOf(r.raw("--diagram-fill-style"), ["solid", "tint", "outline", "hatch"], "tint", "--diagram-fill-style"),
|
|
2177
|
-
fillOpacity: r.number("--diagram-fill-opacity", 0.18),
|
|
2178
|
-
roughness: r.number("--diagram-roughness", 0),
|
|
2179
|
-
glow: r.number("--diagram-glow", 0),
|
|
2180
|
-
shadow: r.shadow("--diagram-shadow"),
|
|
2181
|
-
marker: oneOf(r.raw("--diagram-marker"), ["triangle", "open", "line", "dot"], "triangle", "--diagram-marker"),
|
|
2182
|
-
faces: {
|
|
2183
|
-
body,
|
|
2184
|
-
display: r.family("--font-family-display", body),
|
|
2185
|
-
mono: r.family("--font-family-mono", "ui-monospace, monospace")
|
|
2186
|
-
},
|
|
2187
|
-
label: {
|
|
2188
|
-
font: oneOf(r.raw("--diagram-label-font"), ["body", "display", "mono"], "body", "--diagram-label-font"),
|
|
2189
|
-
size: oneOf(r.raw("--diagram-label-size"), DIAGRAM_TEXT_SIZES, "sm", "--diagram-label-size"),
|
|
2190
|
-
weight: r.raw("--diagram-label-weight") || "500",
|
|
2191
|
-
uppercase: oneOf(r.raw("--diagram-label-case"), ["none", "uppercase"], "none", "--diagram-label-case") === "uppercase"
|
|
2192
|
-
},
|
|
2193
|
-
text,
|
|
2194
|
-
corner: r.length("--diagram-corner", 4),
|
|
2195
|
-
motion: {
|
|
2196
|
-
enabled: r.raw("--motion-enable") !== "off" && !prefersReducedMotion(scope),
|
|
2197
|
-
fastMs: r.durationMs("--duration-fast", 150),
|
|
2198
|
-
normalMs: r.durationMs("--duration-normal", 250),
|
|
2199
|
-
slowMs: r.durationMs("--duration-slow", 400),
|
|
2200
|
-
easing: r.raw("--easing-standard") || "ease"
|
|
2201
|
-
},
|
|
2202
|
-
scene: {
|
|
2203
|
-
roughness: r.number("--material-roughness", 0.6),
|
|
2204
|
-
metalness: r.number("--material-metalness", 0),
|
|
2205
|
-
flat: r.number("--material-flat", 0) >= 1,
|
|
2206
|
-
outline: r.number("--material-outline", 0),
|
|
2207
|
-
ambient: r.number("--light-ambient", 0.55),
|
|
2208
|
-
key: r.number("--light-key", 1),
|
|
2209
|
-
keyColor: (() => {
|
|
2210
|
-
const c = r.color("--light-key-color");
|
|
2211
|
-
return c ? colorCss(c) : "#ffffff";
|
|
2212
|
-
})(),
|
|
2213
|
-
fog: r.number("--scene-fog", 0)
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
} finally {
|
|
2217
|
-
r.dispose();
|
|
2218
1982
|
}
|
|
1983
|
+
pixelUVByMesh.set(mesh, pixel);
|
|
1984
|
+
return pixel;
|
|
1985
|
+
}
|
|
1986
|
+
function paintMeshPass(painter, node, bones, mesh, tex, dctx, pose, opacity) {
|
|
1987
|
+
const bindWorld = computeWorldMatrices(bones, {});
|
|
1988
|
+
const curWorld = computeWorldMatrices(bones, pose);
|
|
1989
|
+
const skinned = skinVertices(mesh, bindWorld, curWorld);
|
|
1990
|
+
const origin = dctx.projector.project(node.position);
|
|
1991
|
+
const tw = dctx.projector.tileWidth;
|
|
1992
|
+
painter.save();
|
|
1993
|
+
if (opacity !== 1) painter.setAlpha(opacity);
|
|
1994
|
+
for (const [i0, i1, i2] of mesh.triangles) {
|
|
1995
|
+
const v0 = mesh.vertices[i0];
|
|
1996
|
+
const v1 = mesh.vertices[i1];
|
|
1997
|
+
const v2 = mesh.vertices[i2];
|
|
1998
|
+
const p0 = skinned[i0];
|
|
1999
|
+
const p1 = skinned[i1];
|
|
2000
|
+
const p2 = skinned[i2];
|
|
2001
|
+
if (!v0 || !v1 || !v2 || !p0 || !p1 || !p2) continue;
|
|
2002
|
+
const dst = [
|
|
2003
|
+
[origin.x + p0[0] * tw, origin.y + p0[1] * tw],
|
|
2004
|
+
[origin.x + p1[0] * tw, origin.y + p1[1] * tw],
|
|
2005
|
+
[origin.x + p2[0] * tw, origin.y + p2[1] * tw]
|
|
2006
|
+
];
|
|
2007
|
+
if (node.weightsOverlay || !tex) {
|
|
2008
|
+
if (!node.weightsOverlay) continue;
|
|
2009
|
+
const bone = dominantBone(v0);
|
|
2010
|
+
painter.fillPoly(
|
|
2011
|
+
dst.map(([x, y]) => ({ x, y })),
|
|
2012
|
+
WEIGHT_PALETTE[(bone % WEIGHT_PALETTE.length + WEIGHT_PALETTE.length) % WEIGHT_PALETTE.length]
|
|
2013
|
+
);
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
const pixelUV = meshHasPixelUVs(mesh);
|
|
2017
|
+
const uv = pixelUV ? [[v0.u, v0.v], [v1.u, v1.v], [v2.u, v2.v]] : [
|
|
2018
|
+
[v0.u * tex.width, v0.v * tex.height],
|
|
2019
|
+
[v1.u * tex.width, v1.v * tex.height],
|
|
2020
|
+
[v2.u * tex.width, v2.v * tex.height]
|
|
2021
|
+
];
|
|
2022
|
+
const affine = triangleAffine(uv, dst);
|
|
2023
|
+
if (!affine) continue;
|
|
2024
|
+
painter.save();
|
|
2025
|
+
painter.clipPath(`M ${dst[0][0]} ${dst[0][1]} L ${dst[1][0]} ${dst[1][1]} L ${dst[2][0]} ${dst[2][1]} Z`);
|
|
2026
|
+
painter.blitTransformed(tex, { x: 0, y: 0, w: tex.width, h: tex.height }, affine);
|
|
2027
|
+
painter.restore();
|
|
2028
|
+
}
|
|
2029
|
+
painter.restore();
|
|
2219
2030
|
}
|
|
2220
|
-
|
|
2221
|
-
if (!
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
return { ...theme, tones, series: SERIES_TONES.map((t) => tones[t]) };
|
|
2228
|
-
}
|
|
2229
|
-
function markColor(theme, value, scope, fallback) {
|
|
2230
|
-
if (value === void 0 || value === "") return theme.tones[fallback];
|
|
2231
|
-
if (isDiagramTone(value)) return theme.tones[value];
|
|
2232
|
-
if (value.startsWith("var(") || value.startsWith("color-mix(")) {
|
|
2233
|
-
const resolved = resolveThemeColor(value, scope);
|
|
2234
|
-
return resolved ? colorCss(resolved) : theme.tones[fallback];
|
|
2031
|
+
var paintSkinnedMesh = (painter, node, dctx) => {
|
|
2032
|
+
if (!isValidScenePos(node.position)) return;
|
|
2033
|
+
const rig = resolveRig(node, dctx);
|
|
2034
|
+
if (!rig) {
|
|
2035
|
+
if (node.meshUrl && skinMeshFailed(node.meshUrl)) warnMissingOnce("mesh-failed", node);
|
|
2036
|
+
else if (!node.mesh && !node.meshUrl) warnMissingOnce("mesh-missing", node);
|
|
2037
|
+
return;
|
|
2235
2038
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2039
|
+
const tex = node.weightsOverlay ? null : painter.resolveTexture(node.asset.url);
|
|
2040
|
+
if (!node.weightsOverlay && !tex) {
|
|
2041
|
+
if (getImageStatus(node.asset.url) === "failed") warnMissingOnce("texture-failed", node);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (node.ghost && rig.clip && rig.clip.frames.length > 0) {
|
|
2045
|
+
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, rig.clip.frames[node.ghost.frame % rig.clip.frames.length], node.ghost.opacity);
|
|
2046
|
+
}
|
|
2047
|
+
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, resolveSkinPose(node, rig.clip), node.opacity ?? 1);
|
|
2048
|
+
};
|
|
2049
|
+
function DrawSkinnedMesh(props) {
|
|
2050
|
+
const register = useContext(DrawableRegistryContext);
|
|
2051
|
+
if (register) register({ ...props, type: "draw-skinned-mesh" });
|
|
2052
|
+
return null;
|
|
2240
2053
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2054
|
+
var MESH_SEGMENTS_DEFAULT = 24;
|
|
2055
|
+
function polyhedronBounds(vertices) {
|
|
2056
|
+
if (!vertices || vertices.length < 3) return null;
|
|
2057
|
+
const min = [Infinity, Infinity, Infinity];
|
|
2058
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
2059
|
+
for (const v of vertices) {
|
|
2060
|
+
if (v.length < 3 || !v.every((c) => Number.isFinite(c))) return null;
|
|
2061
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
2062
|
+
min[axis] = Math.min(min[axis], v[axis]);
|
|
2063
|
+
max[axis] = Math.max(max[axis], v[axis]);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
return { min, max };
|
|
2245
2067
|
}
|
|
2246
|
-
function
|
|
2247
|
-
|
|
2068
|
+
function clampSegments(segments) {
|
|
2069
|
+
const s = segments ?? MESH_SEGMENTS_DEFAULT;
|
|
2070
|
+
return Math.min(64, Math.max(3, Math.round(s)));
|
|
2248
2071
|
}
|
|
2249
|
-
function
|
|
2250
|
-
|
|
2251
|
-
ctx.save();
|
|
2252
|
-
ctx.strokeStyle = theme.ground;
|
|
2253
|
-
ctx.lineWidth = Math.max(2, theme.text.xs * 0.3);
|
|
2254
|
-
ctx.lineJoin = "round";
|
|
2255
|
-
ctx.strokeText(text, x, y);
|
|
2256
|
-
ctx.restore();
|
|
2257
|
-
ctx.fillText(text, x, y);
|
|
2072
|
+
function isAnimatedMesh(node) {
|
|
2073
|
+
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
2258
2074
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2075
|
+
var lerp2 = (a, b, k) => a + (b - a) * k;
|
|
2076
|
+
var NUMERIC_TRACKS2 = [
|
|
2077
|
+
"offsetX",
|
|
2078
|
+
"offsetY",
|
|
2079
|
+
"offsetZ",
|
|
2080
|
+
"rotateX",
|
|
2081
|
+
"rotateY",
|
|
2082
|
+
"rotateZ",
|
|
2083
|
+
"scale",
|
|
2084
|
+
"opacity",
|
|
2085
|
+
"emissiveIntensity"
|
|
2086
|
+
];
|
|
2087
|
+
function applyMeshAnimation(node, timeMs) {
|
|
2088
|
+
const anim = node.animation;
|
|
2089
|
+
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
|
|
2090
|
+
const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
|
|
2091
|
+
const cycle = timeMs / anim.durationMs;
|
|
2092
|
+
const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
|
|
2093
|
+
const trackValue = (key) => {
|
|
2094
|
+
const defined = frames.filter((f) => f[key] !== void 0);
|
|
2095
|
+
if (defined.length === 0) return void 0;
|
|
2096
|
+
let prev;
|
|
2097
|
+
let next;
|
|
2098
|
+
for (const f of defined) {
|
|
2099
|
+
if (f.at <= t) prev = f;
|
|
2100
|
+
else if (!next) next = f;
|
|
2101
|
+
}
|
|
2102
|
+
if (!prev) return defined[0][key];
|
|
2103
|
+
if (!next) return prev[key];
|
|
2104
|
+
const span = next.at - prev.at;
|
|
2105
|
+
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
2106
|
+
const a = prev[key];
|
|
2107
|
+
const b = next[key];
|
|
2108
|
+
if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
|
|
2109
|
+
return a;
|
|
2110
|
+
};
|
|
2111
|
+
const num = {};
|
|
2112
|
+
for (const key of NUMERIC_TRACKS2) {
|
|
2113
|
+
const v = trackValue(key);
|
|
2114
|
+
if (v !== void 0) num[key] = v;
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
|
|
2118
|
+
rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
|
|
2119
|
+
scale: num.scale ?? 1,
|
|
2120
|
+
opacity: num.opacity,
|
|
2121
|
+
emissiveIntensity: num.emissiveIntensity,
|
|
2122
|
+
color: trackValue("color"),
|
|
2123
|
+
emissive: trackValue("emissive")
|
|
2124
|
+
};
|
|
2261
2125
|
}
|
|
2262
|
-
function
|
|
2263
|
-
|
|
2126
|
+
function DrawMesh(props) {
|
|
2127
|
+
const register = useContext(DrawableRegistryContext);
|
|
2128
|
+
if (register) register({ ...props, type: "draw-mesh" });
|
|
2129
|
+
return null;
|
|
2264
2130
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2131
|
+
|
|
2132
|
+
// components/game/molecules/DrawFxLayer.tsx
|
|
2133
|
+
var DEFAULT_TICK_MS = 500;
|
|
2134
|
+
var DEFAULT_TEXT_COLOR = "#ffe066";
|
|
2135
|
+
var DEFAULT_SPARK_COLOR = "#ffffff";
|
|
2136
|
+
var easeOut = (t) => 1 - (1 - t) * (1 - t);
|
|
2137
|
+
function fxPosition(view, dim, ageSec) {
|
|
2138
|
+
const g = view.gravity ?? 0;
|
|
2139
|
+
const fall = 0.5 * g * ageSec * ageSec;
|
|
2140
|
+
if (dim === "2d") {
|
|
2141
|
+
const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
|
|
2142
|
+
if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
|
|
2143
|
+
return {
|
|
2144
|
+
x: base2.x + (view.vx ?? 0) * ageSec,
|
|
2145
|
+
y: base2.y + (view.vz ?? 0) * ageSec + fall
|
|
2146
|
+
};
|
|
2273
2147
|
}
|
|
2148
|
+
const base = view.position ?? { x: view.x, y: view.z ?? 0, z: view.y };
|
|
2149
|
+
if (!Number.isFinite(base.x) || !Number.isFinite(base.y)) return void 0;
|
|
2150
|
+
return {
|
|
2151
|
+
x: base.x + (view.vx ?? 0) * ageSec,
|
|
2152
|
+
y: base.y + (view.vz ?? 0) * ageSec,
|
|
2153
|
+
...base.z !== void 0 || view.vy !== void 0 || g !== 0 ? { z: (base.z ?? 0) + (view.vy ?? 0) * ageSec - fall } : {}
|
|
2154
|
+
};
|
|
2274
2155
|
}
|
|
2275
|
-
function
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2156
|
+
function sparkShape(view, pos, i, fade, progress) {
|
|
2157
|
+
const size = view.size ?? 0.5;
|
|
2158
|
+
const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
|
|
2159
|
+
const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut(progress);
|
|
2160
|
+
const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
|
|
2161
|
+
const color = view.color ?? DEFAULT_SPARK_COLOR;
|
|
2162
|
+
return {
|
|
2163
|
+
type: "draw-shape",
|
|
2164
|
+
shape: "ellipse",
|
|
2165
|
+
position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
|
|
2166
|
+
anchor: "center",
|
|
2167
|
+
radiusX: r,
|
|
2168
|
+
fill: color,
|
|
2169
|
+
blendMode: "lighter",
|
|
2170
|
+
opacity: fade,
|
|
2171
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2172
|
+
};
|
|
2283
2173
|
}
|
|
2284
|
-
function
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2174
|
+
function proceduralShapes(view, pos, fade, progress) {
|
|
2175
|
+
const size = view.size ?? 0.5;
|
|
2176
|
+
const color = view.color ?? DEFAULT_SPARK_COLOR;
|
|
2177
|
+
switch (view.shape ?? "spark") {
|
|
2178
|
+
case "ring": {
|
|
2179
|
+
return [
|
|
2180
|
+
{
|
|
2181
|
+
type: "draw-shape",
|
|
2182
|
+
shape: "ellipse",
|
|
2183
|
+
position: pos,
|
|
2184
|
+
anchor: "center",
|
|
2185
|
+
radiusX: size * easeOut(progress),
|
|
2186
|
+
stroke: view.color2 ?? color,
|
|
2187
|
+
strokeWidth: 2,
|
|
2188
|
+
blendMode: "lighter",
|
|
2189
|
+
opacity: fade,
|
|
2190
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2191
|
+
}
|
|
2192
|
+
];
|
|
2193
|
+
}
|
|
2194
|
+
case "puff": {
|
|
2195
|
+
const count = view.count ?? 3;
|
|
2196
|
+
return Array.from({ length: count }, (_, i) => {
|
|
2197
|
+
const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
|
|
2198
|
+
const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut(progress);
|
|
2199
|
+
return {
|
|
2200
|
+
type: "draw-shape",
|
|
2201
|
+
shape: "ellipse",
|
|
2202
|
+
position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
|
|
2203
|
+
anchor: "center",
|
|
2204
|
+
radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
|
|
2205
|
+
fill: i === 0 && view.color2 ? view.color2 : color,
|
|
2206
|
+
opacity: fade * 0.6,
|
|
2207
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2208
|
+
};
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
case "streak": {
|
|
2212
|
+
const count = view.count ?? 5;
|
|
2213
|
+
return Array.from({ length: count }, (_, i) => {
|
|
2214
|
+
const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
|
|
2215
|
+
const inner = size * (0.2 + 0.8 * easeOut(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
|
|
2216
|
+
const len = size * 0.35;
|
|
2217
|
+
return {
|
|
2218
|
+
type: "draw-shape",
|
|
2219
|
+
shape: "ellipse",
|
|
2220
|
+
position: pos,
|
|
2221
|
+
anchor: "center",
|
|
2222
|
+
offsetX: inner + len / 2,
|
|
2223
|
+
offsetY: 0,
|
|
2224
|
+
radiusX: len / 2,
|
|
2225
|
+
radiusY: size * 0.04,
|
|
2226
|
+
rotate: angle,
|
|
2227
|
+
fill: color,
|
|
2228
|
+
blendMode: "lighter",
|
|
2229
|
+
opacity: fade,
|
|
2230
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2231
|
+
};
|
|
2232
|
+
});
|
|
2296
2233
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const x = a.x + (b.x - a.x) * t;
|
|
2301
|
-
const y = a.y + (b.y - a.y) * t;
|
|
2302
|
-
ctx.lineTo(x + amp * wobble(x, y, 2), y + amp * wobble(y, x, 3));
|
|
2234
|
+
default: {
|
|
2235
|
+
const count = view.count ?? 6;
|
|
2236
|
+
return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
|
|
2303
2237
|
}
|
|
2304
2238
|
}
|
|
2305
|
-
if (closed) ctx.closePath();
|
|
2306
2239
|
}
|
|
2307
|
-
function
|
|
2308
|
-
|
|
2240
|
+
function expandFxItem(view, node, epochNowMs, dim, projector, fontFamily) {
|
|
2241
|
+
if (view.space === "screen") return [];
|
|
2242
|
+
const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
|
|
2243
|
+
const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
|
|
2244
|
+
if (progress >= 1) return [];
|
|
2245
|
+
const pos = fxPosition(view, dim, ageMs / 1e3);
|
|
2246
|
+
if (!pos) return [];
|
|
2309
2247
|
const out = [];
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2248
|
+
const artItems = node.art?.[view.type]?.["idle"];
|
|
2249
|
+
const sprite = node.sprites?.[view.type];
|
|
2250
|
+
if (Array.isArray(artItems)) {
|
|
2251
|
+
out.push({
|
|
2252
|
+
type: "draw-group",
|
|
2253
|
+
position: pos,
|
|
2254
|
+
opacity: fade,
|
|
2255
|
+
...view.size !== void 0 ? { scale: view.size } : {},
|
|
2256
|
+
items: artItems
|
|
2257
|
+
});
|
|
2258
|
+
} else if (sprite?.url) {
|
|
2259
|
+
const spriteSize = view.size ?? 0.6;
|
|
2260
|
+
out.push({
|
|
2261
|
+
type: "draw-sprite",
|
|
2262
|
+
position: pos,
|
|
2263
|
+
asset: sprite,
|
|
2264
|
+
anchor: "center",
|
|
2265
|
+
width: spriteSize,
|
|
2266
|
+
height: spriteSize,
|
|
2267
|
+
opacity: fade,
|
|
2268
|
+
animation: view.animation ?? sprite.animations?.[0],
|
|
2269
|
+
// One-shot fx rows (burst) play from the item's birth, not wall-time.
|
|
2270
|
+
clockMs: ageMs
|
|
2271
|
+
});
|
|
2272
|
+
} else {
|
|
2273
|
+
out.push(...proceduralShapes(view, pos, fade, progress));
|
|
2274
|
+
}
|
|
2275
|
+
if (view.message) {
|
|
2276
|
+
const pxSize = projector && view.size !== void 0 ? Math.max(10, Math.round(view.size * projector.tileWidth)) : void 0;
|
|
2277
|
+
const family = fontFamily ?? "system-ui, sans-serif";
|
|
2278
|
+
const text = {
|
|
2279
|
+
type: "draw-text",
|
|
2280
|
+
text: view.message,
|
|
2281
|
+
position: pos,
|
|
2282
|
+
offsetY: -(0.15 + 0.55 * progress),
|
|
2283
|
+
color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
|
|
2284
|
+
opacity: fade,
|
|
2285
|
+
font: `bold ${pxSize ?? 14}px ${family}`
|
|
2286
|
+
};
|
|
2287
|
+
out.push(text);
|
|
2313
2288
|
}
|
|
2314
2289
|
return out;
|
|
2315
2290
|
}
|
|
2316
|
-
function
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
ctx.shadowColor = theme.shadow.color;
|
|
2323
|
-
ctx.shadowOffsetX = theme.shadow.offsetX;
|
|
2324
|
-
ctx.shadowOffsetY = theme.shadow.offsetY;
|
|
2325
|
-
ctx.shadowBlur = theme.shadow.blur;
|
|
2326
|
-
}
|
|
2327
|
-
if (style === "hatch") {
|
|
2328
|
-
ctx.clip();
|
|
2329
|
-
const span = ctx.canvas.width + ctx.canvas.height;
|
|
2330
|
-
ctx.strokeStyle = color;
|
|
2331
|
-
ctx.lineWidth = theme.strokes.thin;
|
|
2332
|
-
ctx.setLineDash([]);
|
|
2333
|
-
ctx.beginPath();
|
|
2334
|
-
for (let d = -span; d < span; d += 6) {
|
|
2335
|
-
ctx.moveTo(d, 0);
|
|
2336
|
-
ctx.lineTo(d + span, span);
|
|
2337
|
-
}
|
|
2338
|
-
ctx.stroke();
|
|
2339
|
-
} else {
|
|
2340
|
-
ctx.globalAlpha *= style === "tint" ? theme.fillOpacity : 1;
|
|
2341
|
-
ctx.fillStyle = color;
|
|
2342
|
-
ctx.fill();
|
|
2291
|
+
function expandFxLayer(node, epochNowMs, dim, projector, fontFamily) {
|
|
2292
|
+
if (!Array.isArray(node.items)) return [];
|
|
2293
|
+
const out = [];
|
|
2294
|
+
for (const item of node.items) {
|
|
2295
|
+
if (!item || typeof item.id !== "string") continue;
|
|
2296
|
+
out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim, projector, fontFamily));
|
|
2343
2297
|
}
|
|
2344
|
-
|
|
2298
|
+
return out;
|
|
2345
2299
|
}
|
|
2346
|
-
function
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
ctx.lineTo(x2, y2);
|
|
2371
|
-
ctx.lineTo(right.x, right.y);
|
|
2372
|
-
if (theme.marker === "open") ctx.stroke();
|
|
2373
|
-
else {
|
|
2374
|
-
ctx.moveTo(x2 - size * 0.5 * Math.cos(angle + Math.PI / 2), y2 - size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
2375
|
-
ctx.lineTo(x2 + size * 0.5 * Math.cos(angle + Math.PI / 2), y2 + size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
2376
|
-
ctx.stroke();
|
|
2300
|
+
function DrawFxLayer(_props) {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// lib/drawable/themeDrawables.ts
|
|
2305
|
+
function opt(value, resolve) {
|
|
2306
|
+
return value === void 0 ? void 0 : resolve(value);
|
|
2307
|
+
}
|
|
2308
|
+
function themeShape(node, resolve) {
|
|
2309
|
+
return {
|
|
2310
|
+
...node,
|
|
2311
|
+
...node.fill !== void 0 ? { fill: resolve(node.fill) } : {},
|
|
2312
|
+
...node.stroke !== void 0 ? { stroke: resolve(node.stroke) } : {},
|
|
2313
|
+
...node.gradient ? { gradient: { ...node.gradient, stops: node.gradient.stops.map((s) => ({ ...s, color: resolve(s.color) })) } } : {},
|
|
2314
|
+
...node.fillPattern?.color !== void 0 ? { fillPattern: { ...node.fillPattern, color: resolve(node.fillPattern.color) } } : {},
|
|
2315
|
+
...node.shadow ? { shadow: { ...node.shadow, color: resolve(node.shadow.color) } } : {},
|
|
2316
|
+
...node.animation ? {
|
|
2317
|
+
animation: {
|
|
2318
|
+
...node.animation,
|
|
2319
|
+
keyframes: node.animation.keyframes.map((k) => ({
|
|
2320
|
+
...k,
|
|
2321
|
+
...k.fill !== void 0 ? { fill: resolve(k.fill) } : {},
|
|
2322
|
+
...k.stroke !== void 0 ? { stroke: resolve(k.stroke) } : {}
|
|
2323
|
+
}))
|
|
2377
2324
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2325
|
+
} : {}
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
function themeText(node, resolve) {
|
|
2329
|
+
return { ...node, color: resolve(node.color) };
|
|
2330
|
+
}
|
|
2331
|
+
function themeMaterial(material, resolve) {
|
|
2332
|
+
if (!material) return material;
|
|
2333
|
+
return {
|
|
2334
|
+
...material,
|
|
2335
|
+
...material.color !== void 0 ? { color: resolve(material.color) } : {},
|
|
2336
|
+
...material.emissive !== void 0 ? { emissive: resolve(material.emissive) } : {}
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
function resolveDrawableColors(nodes, resolve) {
|
|
2340
|
+
return nodes.map((node) => {
|
|
2341
|
+
switch (node.type) {
|
|
2342
|
+
case "draw-shape":
|
|
2343
|
+
return themeShape(node, resolve);
|
|
2344
|
+
case "draw-text":
|
|
2345
|
+
return themeText(node, resolve);
|
|
2346
|
+
case "draw-group":
|
|
2347
|
+
return { ...node, items: resolveDrawableColors(node.items, resolve) };
|
|
2348
|
+
case "draw-shape-layer":
|
|
2349
|
+
return { ...node, items: node.items.map((item) => themeShape(item, resolve)) };
|
|
2350
|
+
case "draw-text-layer":
|
|
2351
|
+
return { ...node, items: node.items.map((item) => themeText(item, resolve)) };
|
|
2352
|
+
case "draw-fx-layer":
|
|
2353
|
+
return {
|
|
2354
|
+
...node,
|
|
2355
|
+
...node.textColor !== void 0 ? { textColor: resolve(node.textColor) } : {},
|
|
2356
|
+
...node.presets ? {
|
|
2357
|
+
presets: node.presets.map((p) => ({
|
|
2358
|
+
...p,
|
|
2359
|
+
...p.color !== void 0 ? { color: resolve(p.color) } : {},
|
|
2360
|
+
...p.color2 !== void 0 ? { color2: resolve(p.color2) } : {}
|
|
2361
|
+
}))
|
|
2362
|
+
} : {}
|
|
2363
|
+
};
|
|
2364
|
+
case "draw-mesh":
|
|
2365
|
+
return {
|
|
2366
|
+
...node,
|
|
2367
|
+
material: themeMaterial(node.material, resolve),
|
|
2368
|
+
...node.outline ? { outline: { ...node.outline, ...node.outline.color !== void 0 ? { color: resolve(node.outline.color) } : {} } } : {},
|
|
2369
|
+
...node.animation ? {
|
|
2370
|
+
animation: {
|
|
2371
|
+
...node.animation,
|
|
2372
|
+
keyframes: node.animation.keyframes.map((k) => ({
|
|
2373
|
+
...k,
|
|
2374
|
+
...k.color !== void 0 ? { color: resolve(k.color) } : {},
|
|
2375
|
+
...k.emissive !== void 0 ? { emissive: opt(k.emissive, resolve) } : {}
|
|
2376
|
+
}))
|
|
2377
|
+
}
|
|
2378
|
+
} : {}
|
|
2379
|
+
};
|
|
2380
|
+
default:
|
|
2381
|
+
return node;
|
|
2385
2382
|
}
|
|
2386
|
-
}
|
|
2387
|
-
ctx.restore();
|
|
2383
|
+
});
|
|
2388
2384
|
}
|
|
2389
|
-
function
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2385
|
+
function applySceneMaterials(nodes, theme) {
|
|
2386
|
+
return nodes.map((node) => {
|
|
2387
|
+
if (node.type === "draw-group") return { ...node, items: applySceneMaterials(node.items, theme) };
|
|
2388
|
+
if (node.type !== "draw-mesh") return node;
|
|
2389
|
+
const m = node.material ?? {};
|
|
2390
|
+
const pbr = (m.kind ?? "standard") === "standard" || m.kind === "physical";
|
|
2391
|
+
return {
|
|
2392
|
+
...node,
|
|
2393
|
+
material: {
|
|
2394
|
+
...m,
|
|
2395
|
+
...pbr && m.roughness === void 0 ? { roughness: theme.scene.roughness } : {},
|
|
2396
|
+
...pbr && m.metalness === void 0 ? { metalness: theme.scene.metalness } : {},
|
|
2397
|
+
...m.flatShading === void 0 && theme.scene.flat ? { flatShading: true } : {}
|
|
2398
|
+
},
|
|
2399
|
+
...node.outline === void 0 && theme.scene.outline > 0 ? { outline: { color: theme.tones.ink, width: theme.scene.outline * 0.02 } } : {}
|
|
2400
|
+
};
|
|
2401
|
+
});
|
|
2400
2402
|
}
|
|
2401
2403
|
|
|
2402
2404
|
// lib/drawable/hitTest.ts
|