@almadar/ui 5.140.0 → 5.141.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.
@@ -8887,9 +8887,26 @@ var init_imageCache = __esm({
8887
8887
  });
8888
8888
 
8889
8889
  // lib/webPainter2d.ts
8890
+ function makeNoiseTile(alpha, color) {
8891
+ const tile = document.createElement("canvas");
8892
+ tile.width = NOISE_CELLS;
8893
+ tile.height = NOISE_CELLS;
8894
+ const t = tile.getContext("2d");
8895
+ if (t) {
8896
+ t.fillStyle = color;
8897
+ for (let y = 0; y < NOISE_CELLS; y++) {
8898
+ for (let x = 0; x < NOISE_CELLS; x++) {
8899
+ t.globalAlpha = noiseHash(x, y) * alpha;
8900
+ t.fillRect(x, y, 1, 1);
8901
+ }
8902
+ }
8903
+ }
8904
+ return tile;
8905
+ }
8890
8906
  function createWebPainter(ctx, onAssetLoad) {
8891
8907
  let vw = 0;
8892
8908
  let vh = 0;
8909
+ const patternCache = /* @__PURE__ */ new Map();
8893
8910
  const tracePoly = (points, closed) => {
8894
8911
  if (points.length === 0) return;
8895
8912
  ctx.beginPath();
@@ -8897,9 +8914,28 @@ function createWebPainter(ctx, onAssetLoad) {
8897
8914
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
8898
8915
  if (closed) ctx.closePath();
8899
8916
  };
8917
+ const toCanvasPattern = (style) => {
8918
+ const key = JSON.stringify(style);
8919
+ let pattern = patternCache.get(key);
8920
+ if (pattern === void 0) {
8921
+ if (style.kind === "noise") {
8922
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
8923
+ } else {
8924
+ const img = getOrLoadImage(style.url, onAssetLoad);
8925
+ if (!img) return "rgba(0,0,0,0)";
8926
+ pattern = ctx.createPattern(img, "repeat");
8927
+ }
8928
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
8929
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
8930
+ }
8931
+ patternCache.set(key, pattern);
8932
+ }
8933
+ return pattern ?? "rgba(0,0,0,0)";
8934
+ };
8900
8935
  const toCanvasStyle = (style) => {
8901
8936
  if (typeof style === "string") return style;
8902
- const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
8937
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
8938
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : style.kind === "conic" ? ctx.createConicGradient(style.angle, style.cx, style.cy) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
8903
8939
  for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
8904
8940
  return g;
8905
8941
  };
@@ -8934,6 +8970,19 @@ function createWebPainter(ctx, onAssetLoad) {
8934
8970
  ctx.shadowColor = shadow ? shadow.color : "transparent";
8935
8971
  ctx.shadowBlur = shadow ? shadow.blur : 0;
8936
8972
  },
8973
+ setBlend(mode) {
8974
+ ctx.globalCompositeOperation = mode ?? "source-over";
8975
+ },
8976
+ setLineDash(pattern, offset = 0) {
8977
+ ctx.setLineDash(pattern ? [...pattern] : []);
8978
+ ctx.lineDashOffset = offset;
8979
+ },
8980
+ setBlur(px) {
8981
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
8982
+ },
8983
+ clipPath(d) {
8984
+ ctx.clip(new Path2D(d));
8985
+ },
8937
8986
  resolveTexture(url) {
8938
8987
  const img = getOrLoadImage(url, onAssetLoad);
8939
8988
  if (!img) return null;
@@ -9009,13 +9058,18 @@ function createWebPainter(ctx, onAssetLoad) {
9009
9058
  }
9010
9059
  };
9011
9060
  }
9012
- var handleByImage, imageByHandle;
9061
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
9013
9062
  var init_webPainter2d = __esm({
9014
9063
  "lib/webPainter2d.ts"() {
9015
9064
  "use client";
9016
9065
  init_imageCache();
9017
9066
  handleByImage = /* @__PURE__ */ new WeakMap();
9018
9067
  imageByHandle = /* @__PURE__ */ new WeakMap();
9068
+ noiseHash = (x, y) => {
9069
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
9070
+ return s - Math.floor(s);
9071
+ };
9072
+ NOISE_CELLS = 32;
9019
9073
  }
9020
9074
  });
9021
9075
 
@@ -9240,6 +9294,16 @@ function gradientStyle(g, ox, oy, scale) {
9240
9294
  stops: g.stops
9241
9295
  };
9242
9296
  }
9297
+ if (g.kind === "conic") {
9298
+ if (!g.center) return void 0;
9299
+ return {
9300
+ kind: "conic",
9301
+ cx: ox + g.center.x * scale,
9302
+ cy: oy + g.center.y * scale,
9303
+ angle: g.angle ?? 0,
9304
+ stops: g.stops
9305
+ };
9306
+ }
9243
9307
  if (!g.center || g.radius === void 0) return void 0;
9244
9308
  return {
9245
9309
  kind: "radial",
@@ -9249,6 +9313,13 @@ function gradientStyle(g, ox, oy, scale) {
9249
9313
  stops: g.stops
9250
9314
  };
9251
9315
  }
9316
+ function patternStyle(p, unit) {
9317
+ if (p.kind === "image") {
9318
+ if (!p.url) return void 0;
9319
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9320
+ }
9321
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9322
+ }
9252
9323
  function DrawShape(props) {
9253
9324
  const register = React86.useContext(DrawableRegistryContext);
9254
9325
  if (register) {
@@ -9277,7 +9348,9 @@ var init_DrawShape = __esm({
9277
9348
  "radiusY",
9278
9349
  "width",
9279
9350
  "height",
9280
- "strokeWidth"
9351
+ "strokeWidth",
9352
+ "strokeDashOffset",
9353
+ "blur"
9281
9354
  ];
9282
9355
  lerp = (a, b, k) => a + (b - a) * k;
9283
9356
  paintShape = (painter, rawNode, dctx) => {
@@ -9287,6 +9360,16 @@ var init_DrawShape = __esm({
9287
9360
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9288
9361
  const origin = dctx.projector.project(node.position);
9289
9362
  const tileWidth = dctx.projector.tileWidth;
9363
+ const groupScale = dctx.groupScale ?? 1;
9364
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9365
+ if (node.blendMode) painter.setBlend(node.blendMode);
9366
+ if (node.strokeDash && node.strokeDash.length > 0) {
9367
+ painter.setLineDash(
9368
+ node.strokeDash.map((v) => v / groupScale),
9369
+ (node.strokeDashOffset ?? 0) / groupScale
9370
+ );
9371
+ }
9372
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9290
9373
  if (node.rotate) {
9291
9374
  const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9292
9375
  const px = origin.x + pivot.x * tileWidth;
@@ -9295,15 +9378,18 @@ var init_DrawShape = __esm({
9295
9378
  painter.rotate(node.rotate);
9296
9379
  painter.translate(-px, -py);
9297
9380
  }
9298
- if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9381
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9299
9382
  const fill = node.fill === "none" ? void 0 : node.fill;
9300
9383
  const stroke = node.stroke === "none" ? void 0 : node.stroke;
9301
9384
  const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9385
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9386
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9302
9387
  switch (node.shape) {
9303
9388
  case "cell": {
9304
9389
  const pts = dctx.projector.cellPath(node.position);
9305
9390
  if (pxFill) painter.fillPoly(pts, pxFill);
9306
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9391
+ if (patFill) painter.fillPoly(pts, patFill);
9392
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9307
9393
  break;
9308
9394
  }
9309
9395
  case "rect": {
@@ -9314,7 +9400,8 @@ var init_DrawShape = __esm({
9314
9400
  const w = (node.width ?? 0) * tw;
9315
9401
  const h = (node.height ?? 0) * tw;
9316
9402
  if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9317
- if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9403
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9404
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9318
9405
  break;
9319
9406
  }
9320
9407
  case "ellipse": {
@@ -9325,7 +9412,8 @@ var init_DrawShape = __esm({
9325
9412
  const rx = (node.radiusX ?? 0) * tw;
9326
9413
  const ry = (node.radiusY ?? rx) * tw;
9327
9414
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9328
- if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9415
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9416
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9329
9417
  break;
9330
9418
  }
9331
9419
  case "poly": {
@@ -9334,16 +9422,27 @@ var init_DrawShape = __esm({
9334
9422
  y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9335
9423
  }));
9336
9424
  if (pxFill) painter.fillPoly(pts, pxFill);
9337
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9425
+ if (patFill) painter.fillPoly(pts, patFill);
9426
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9338
9427
  break;
9339
9428
  }
9340
9429
  case "path": {
9341
9430
  if (!node.d) break;
9342
9431
  painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9343
9432
  painter.scale(tileWidth, tileWidth);
9433
+ if (node.strokeDash && node.strokeDash.length > 0) {
9434
+ painter.setLineDash(
9435
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9436
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9437
+ );
9438
+ }
9439
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9344
9440
  const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9441
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9442
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9345
9443
  if (localFill) painter.fillPath(node.d, localFill);
9346
- if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9444
+ if (localPat) painter.fillPath(node.d, localPat);
9445
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9347
9446
  break;
9348
9447
  }
9349
9448
  }
@@ -9424,8 +9523,6 @@ var init_DrawTextLayer = __esm({
9424
9523
  };
9425
9524
  }
9426
9525
  });
9427
-
9428
- // lib/drawable/paintDispatch.ts
9429
9526
  function paintDrawable(painter, node, dctx) {
9430
9527
  switch (node.type) {
9431
9528
  case "draw-sprite":
@@ -9446,10 +9543,20 @@ function paintDrawable(painter, node, dctx) {
9446
9543
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9447
9544
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9448
9545
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9449
- for (const item of node.items) paintDrawable(painter, item, dctx);
9546
+ if (node.clip) {
9547
+ const tw = dctx.projector.tileWidth;
9548
+ painter.scale(tw, tw);
9549
+ painter.clipPath(node.clip);
9550
+ painter.scale(1 / tw, 1 / tw);
9551
+ }
9552
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9553
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9450
9554
  painter.restore();
9451
9555
  break;
9452
9556
  }
9557
+ case "draw-mesh":
9558
+ warnUnsupported2d("draw-mesh");
9559
+ break;
9453
9560
  case "draw-sprite-layer":
9454
9561
  paintSpriteLayer(painter, node, dctx);
9455
9562
  break;
@@ -9461,6 +9568,7 @@ function paintDrawable(painter, node, dctx) {
9461
9568
  break;
9462
9569
  }
9463
9570
  }
9571
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9464
9572
  var init_paintDispatch = __esm({
9465
9573
  "lib/drawable/paintDispatch.ts"() {
9466
9574
  init_contract();
@@ -9470,6 +9578,13 @@ var init_paintDispatch = __esm({
9470
9578
  init_DrawSpriteLayer();
9471
9579
  init_DrawShapeLayer();
9472
9580
  init_DrawTextLayer();
9581
+ paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
9582
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9583
+ warnUnsupported2d = (kind) => {
9584
+ if (warnedUnsupported2d.has(kind)) return;
9585
+ warnedUnsupported2d.add(kind);
9586
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9587
+ };
9473
9588
  }
9474
9589
  });
9475
9590
 
@@ -9484,6 +9599,7 @@ function collectDrawnItems(nodes) {
9484
9599
  case "draw-shape":
9485
9600
  case "draw-text":
9486
9601
  case "draw-group":
9602
+ case "draw-mesh":
9487
9603
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9488
9604
  break;
9489
9605
  case "draw-sprite-layer":
@@ -10101,6 +10217,7 @@ function Canvas({
10101
10217
  isLoading,
10102
10218
  cameraMode: to3DCameraMode(camera?.mode),
10103
10219
  ...zoom !== void 0 ? { scale: zoom } : {},
10220
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10104
10221
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10105
10222
  unitScale,
10106
10223
  backgroundColor,
@@ -38928,14 +39045,26 @@ var init_DetailPanel = __esm({
38928
39045
  DetailPanel.displayName = "DetailPanel";
38929
39046
  }
38930
39047
  });
38931
-
38932
- // components/game/atoms/DrawGroup.tsx
38933
- function DrawGroup(_props) {
39048
+ function DrawGroup(props) {
39049
+ const register = React86.useContext(DrawableRegistryContext);
39050
+ if (register) register({ ...props, type: "draw-group" });
38934
39051
  return null;
38935
39052
  }
38936
39053
  var init_DrawGroup = __esm({
38937
39054
  "components/game/atoms/DrawGroup.tsx"() {
38938
39055
  "use client";
39056
+ init_registry();
39057
+ }
39058
+ });
39059
+ function DrawMesh(props) {
39060
+ const register = React86.useContext(DrawableRegistryContext);
39061
+ if (register) register({ ...props, type: "draw-mesh" });
39062
+ return null;
39063
+ }
39064
+ var init_DrawMesh = __esm({
39065
+ "components/game/atoms/DrawMesh.tsx"() {
39066
+ "use client";
39067
+ init_registry();
38939
39068
  }
38940
39069
  });
38941
39070
  function extractTitle(children) {
@@ -44642,6 +44771,7 @@ var init_component_registry_generated = __esm({
44642
44771
  init_DocTOC();
44643
44772
  init_DocumentViewer();
44644
44773
  init_DrawGroup();
44774
+ init_DrawMesh();
44645
44775
  init_DrawShape();
44646
44776
  init_DrawShapeLayer();
44647
44777
  init_DrawSprite();
@@ -44910,6 +45040,7 @@ var init_component_registry_generated = __esm({
44910
45040
  "DocTOC": DocTOC,
44911
45041
  "DocumentViewer": DocumentViewer,
44912
45042
  "DrawGroup": DrawGroup,
45043
+ "DrawMesh": DrawMesh,
44913
45044
  "DrawShape": DrawShape,
44914
45045
  "DrawShapeLayer": DrawShapeLayer,
44915
45046
  "DrawSprite": DrawSprite,
@@ -8813,9 +8813,26 @@ var init_imageCache = __esm({
8813
8813
  });
8814
8814
 
8815
8815
  // lib/webPainter2d.ts
8816
+ function makeNoiseTile(alpha, color) {
8817
+ const tile = document.createElement("canvas");
8818
+ tile.width = NOISE_CELLS;
8819
+ tile.height = NOISE_CELLS;
8820
+ const t = tile.getContext("2d");
8821
+ if (t) {
8822
+ t.fillStyle = color;
8823
+ for (let y = 0; y < NOISE_CELLS; y++) {
8824
+ for (let x = 0; x < NOISE_CELLS; x++) {
8825
+ t.globalAlpha = noiseHash(x, y) * alpha;
8826
+ t.fillRect(x, y, 1, 1);
8827
+ }
8828
+ }
8829
+ }
8830
+ return tile;
8831
+ }
8816
8832
  function createWebPainter(ctx, onAssetLoad) {
8817
8833
  let vw = 0;
8818
8834
  let vh = 0;
8835
+ const patternCache = /* @__PURE__ */ new Map();
8819
8836
  const tracePoly = (points, closed) => {
8820
8837
  if (points.length === 0) return;
8821
8838
  ctx.beginPath();
@@ -8823,9 +8840,28 @@ function createWebPainter(ctx, onAssetLoad) {
8823
8840
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
8824
8841
  if (closed) ctx.closePath();
8825
8842
  };
8843
+ const toCanvasPattern = (style) => {
8844
+ const key = JSON.stringify(style);
8845
+ let pattern = patternCache.get(key);
8846
+ if (pattern === void 0) {
8847
+ if (style.kind === "noise") {
8848
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
8849
+ } else {
8850
+ const img = getOrLoadImage(style.url, onAssetLoad);
8851
+ if (!img) return "rgba(0,0,0,0)";
8852
+ pattern = ctx.createPattern(img, "repeat");
8853
+ }
8854
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
8855
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
8856
+ }
8857
+ patternCache.set(key, pattern);
8858
+ }
8859
+ return pattern ?? "rgba(0,0,0,0)";
8860
+ };
8826
8861
  const toCanvasStyle = (style) => {
8827
8862
  if (typeof style === "string") return style;
8828
- const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
8863
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
8864
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : style.kind === "conic" ? ctx.createConicGradient(style.angle, style.cx, style.cy) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
8829
8865
  for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
8830
8866
  return g;
8831
8867
  };
@@ -8860,6 +8896,19 @@ function createWebPainter(ctx, onAssetLoad) {
8860
8896
  ctx.shadowColor = shadow ? shadow.color : "transparent";
8861
8897
  ctx.shadowBlur = shadow ? shadow.blur : 0;
8862
8898
  },
8899
+ setBlend(mode) {
8900
+ ctx.globalCompositeOperation = mode ?? "source-over";
8901
+ },
8902
+ setLineDash(pattern, offset = 0) {
8903
+ ctx.setLineDash(pattern ? [...pattern] : []);
8904
+ ctx.lineDashOffset = offset;
8905
+ },
8906
+ setBlur(px) {
8907
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
8908
+ },
8909
+ clipPath(d) {
8910
+ ctx.clip(new Path2D(d));
8911
+ },
8863
8912
  resolveTexture(url) {
8864
8913
  const img = getOrLoadImage(url, onAssetLoad);
8865
8914
  if (!img) return null;
@@ -8935,13 +8984,18 @@ function createWebPainter(ctx, onAssetLoad) {
8935
8984
  }
8936
8985
  };
8937
8986
  }
8938
- var handleByImage, imageByHandle;
8987
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
8939
8988
  var init_webPainter2d = __esm({
8940
8989
  "lib/webPainter2d.ts"() {
8941
8990
  "use client";
8942
8991
  init_imageCache();
8943
8992
  handleByImage = /* @__PURE__ */ new WeakMap();
8944
8993
  imageByHandle = /* @__PURE__ */ new WeakMap();
8994
+ noiseHash = (x, y) => {
8995
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
8996
+ return s - Math.floor(s);
8997
+ };
8998
+ NOISE_CELLS = 32;
8945
8999
  }
8946
9000
  });
8947
9001
 
@@ -9166,6 +9220,16 @@ function gradientStyle(g, ox, oy, scale) {
9166
9220
  stops: g.stops
9167
9221
  };
9168
9222
  }
9223
+ if (g.kind === "conic") {
9224
+ if (!g.center) return void 0;
9225
+ return {
9226
+ kind: "conic",
9227
+ cx: ox + g.center.x * scale,
9228
+ cy: oy + g.center.y * scale,
9229
+ angle: g.angle ?? 0,
9230
+ stops: g.stops
9231
+ };
9232
+ }
9169
9233
  if (!g.center || g.radius === void 0) return void 0;
9170
9234
  return {
9171
9235
  kind: "radial",
@@ -9175,6 +9239,13 @@ function gradientStyle(g, ox, oy, scale) {
9175
9239
  stops: g.stops
9176
9240
  };
9177
9241
  }
9242
+ function patternStyle(p, unit) {
9243
+ if (p.kind === "image") {
9244
+ if (!p.url) return void 0;
9245
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9246
+ }
9247
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9248
+ }
9178
9249
  function DrawShape(props) {
9179
9250
  const register = useContext(DrawableRegistryContext);
9180
9251
  if (register) {
@@ -9203,7 +9274,9 @@ var init_DrawShape = __esm({
9203
9274
  "radiusY",
9204
9275
  "width",
9205
9276
  "height",
9206
- "strokeWidth"
9277
+ "strokeWidth",
9278
+ "strokeDashOffset",
9279
+ "blur"
9207
9280
  ];
9208
9281
  lerp = (a, b, k) => a + (b - a) * k;
9209
9282
  paintShape = (painter, rawNode, dctx) => {
@@ -9213,6 +9286,16 @@ var init_DrawShape = __esm({
9213
9286
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9214
9287
  const origin = dctx.projector.project(node.position);
9215
9288
  const tileWidth = dctx.projector.tileWidth;
9289
+ const groupScale = dctx.groupScale ?? 1;
9290
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9291
+ if (node.blendMode) painter.setBlend(node.blendMode);
9292
+ if (node.strokeDash && node.strokeDash.length > 0) {
9293
+ painter.setLineDash(
9294
+ node.strokeDash.map((v) => v / groupScale),
9295
+ (node.strokeDashOffset ?? 0) / groupScale
9296
+ );
9297
+ }
9298
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9216
9299
  if (node.rotate) {
9217
9300
  const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9218
9301
  const px = origin.x + pivot.x * tileWidth;
@@ -9221,15 +9304,18 @@ var init_DrawShape = __esm({
9221
9304
  painter.rotate(node.rotate);
9222
9305
  painter.translate(-px, -py);
9223
9306
  }
9224
- if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9307
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9225
9308
  const fill = node.fill === "none" ? void 0 : node.fill;
9226
9309
  const stroke = node.stroke === "none" ? void 0 : node.stroke;
9227
9310
  const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9311
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9312
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9228
9313
  switch (node.shape) {
9229
9314
  case "cell": {
9230
9315
  const pts = dctx.projector.cellPath(node.position);
9231
9316
  if (pxFill) painter.fillPoly(pts, pxFill);
9232
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9317
+ if (patFill) painter.fillPoly(pts, patFill);
9318
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9233
9319
  break;
9234
9320
  }
9235
9321
  case "rect": {
@@ -9240,7 +9326,8 @@ var init_DrawShape = __esm({
9240
9326
  const w = (node.width ?? 0) * tw;
9241
9327
  const h = (node.height ?? 0) * tw;
9242
9328
  if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9243
- if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9329
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9330
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9244
9331
  break;
9245
9332
  }
9246
9333
  case "ellipse": {
@@ -9251,7 +9338,8 @@ var init_DrawShape = __esm({
9251
9338
  const rx = (node.radiusX ?? 0) * tw;
9252
9339
  const ry = (node.radiusY ?? rx) * tw;
9253
9340
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9254
- if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9341
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9342
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9255
9343
  break;
9256
9344
  }
9257
9345
  case "poly": {
@@ -9260,16 +9348,27 @@ var init_DrawShape = __esm({
9260
9348
  y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9261
9349
  }));
9262
9350
  if (pxFill) painter.fillPoly(pts, pxFill);
9263
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9351
+ if (patFill) painter.fillPoly(pts, patFill);
9352
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9264
9353
  break;
9265
9354
  }
9266
9355
  case "path": {
9267
9356
  if (!node.d) break;
9268
9357
  painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9269
9358
  painter.scale(tileWidth, tileWidth);
9359
+ if (node.strokeDash && node.strokeDash.length > 0) {
9360
+ painter.setLineDash(
9361
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9362
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9363
+ );
9364
+ }
9365
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9270
9366
  const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9367
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9368
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9271
9369
  if (localFill) painter.fillPath(node.d, localFill);
9272
- if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9370
+ if (localPat) painter.fillPath(node.d, localPat);
9371
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9273
9372
  break;
9274
9373
  }
9275
9374
  }
@@ -9350,8 +9449,6 @@ var init_DrawTextLayer = __esm({
9350
9449
  };
9351
9450
  }
9352
9451
  });
9353
-
9354
- // lib/drawable/paintDispatch.ts
9355
9452
  function paintDrawable(painter, node, dctx) {
9356
9453
  switch (node.type) {
9357
9454
  case "draw-sprite":
@@ -9372,10 +9469,20 @@ function paintDrawable(painter, node, dctx) {
9372
9469
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9373
9470
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9374
9471
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9375
- for (const item of node.items) paintDrawable(painter, item, dctx);
9472
+ if (node.clip) {
9473
+ const tw = dctx.projector.tileWidth;
9474
+ painter.scale(tw, tw);
9475
+ painter.clipPath(node.clip);
9476
+ painter.scale(1 / tw, 1 / tw);
9477
+ }
9478
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9479
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9376
9480
  painter.restore();
9377
9481
  break;
9378
9482
  }
9483
+ case "draw-mesh":
9484
+ warnUnsupported2d("draw-mesh");
9485
+ break;
9379
9486
  case "draw-sprite-layer":
9380
9487
  paintSpriteLayer(painter, node, dctx);
9381
9488
  break;
@@ -9387,6 +9494,7 @@ function paintDrawable(painter, node, dctx) {
9387
9494
  break;
9388
9495
  }
9389
9496
  }
9497
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9390
9498
  var init_paintDispatch = __esm({
9391
9499
  "lib/drawable/paintDispatch.ts"() {
9392
9500
  init_contract();
@@ -9396,6 +9504,13 @@ var init_paintDispatch = __esm({
9396
9504
  init_DrawSpriteLayer();
9397
9505
  init_DrawShapeLayer();
9398
9506
  init_DrawTextLayer();
9507
+ paint2dLog = createLogger("almadar:ui:drawable-2d");
9508
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9509
+ warnUnsupported2d = (kind) => {
9510
+ if (warnedUnsupported2d.has(kind)) return;
9511
+ warnedUnsupported2d.add(kind);
9512
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9513
+ };
9399
9514
  }
9400
9515
  });
9401
9516
 
@@ -9410,6 +9525,7 @@ function collectDrawnItems(nodes) {
9410
9525
  case "draw-shape":
9411
9526
  case "draw-text":
9412
9527
  case "draw-group":
9528
+ case "draw-mesh":
9413
9529
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9414
9530
  break;
9415
9531
  case "draw-sprite-layer":
@@ -10027,6 +10143,7 @@ function Canvas({
10027
10143
  isLoading,
10028
10144
  cameraMode: to3DCameraMode(camera?.mode),
10029
10145
  ...zoom !== void 0 ? { scale: zoom } : {},
10146
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10030
10147
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10031
10148
  unitScale,
10032
10149
  backgroundColor,
@@ -38854,14 +38971,26 @@ var init_DetailPanel = __esm({
38854
38971
  DetailPanel.displayName = "DetailPanel";
38855
38972
  }
38856
38973
  });
38857
-
38858
- // components/game/atoms/DrawGroup.tsx
38859
- function DrawGroup(_props) {
38974
+ function DrawGroup(props) {
38975
+ const register = useContext(DrawableRegistryContext);
38976
+ if (register) register({ ...props, type: "draw-group" });
38860
38977
  return null;
38861
38978
  }
38862
38979
  var init_DrawGroup = __esm({
38863
38980
  "components/game/atoms/DrawGroup.tsx"() {
38864
38981
  "use client";
38982
+ init_registry();
38983
+ }
38984
+ });
38985
+ function DrawMesh(props) {
38986
+ const register = useContext(DrawableRegistryContext);
38987
+ if (register) register({ ...props, type: "draw-mesh" });
38988
+ return null;
38989
+ }
38990
+ var init_DrawMesh = __esm({
38991
+ "components/game/atoms/DrawMesh.tsx"() {
38992
+ "use client";
38993
+ init_registry();
38865
38994
  }
38866
38995
  });
38867
38996
  function extractTitle(children) {
@@ -44568,6 +44697,7 @@ var init_component_registry_generated = __esm({
44568
44697
  init_DocTOC();
44569
44698
  init_DocumentViewer();
44570
44699
  init_DrawGroup();
44700
+ init_DrawMesh();
44571
44701
  init_DrawShape();
44572
44702
  init_DrawShapeLayer();
44573
44703
  init_DrawSprite();
@@ -44836,6 +44966,7 @@ var init_component_registry_generated = __esm({
44836
44966
  "DocTOC": DocTOC,
44837
44967
  "DocumentViewer": DocumentViewer,
44838
44968
  "DrawGroup": DrawGroup,
44969
+ "DrawMesh": DrawMesh,
44839
44970
  "DrawShape": DrawShape,
44840
44971
  "DrawShapeLayer": DrawShapeLayer,
44841
44972
  "DrawSprite": DrawSprite,