@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.
@@ -9241,9 +9241,26 @@ var init_imageCache = __esm({
9241
9241
  });
9242
9242
 
9243
9243
  // lib/webPainter2d.ts
9244
+ function makeNoiseTile(alpha, color) {
9245
+ const tile = document.createElement("canvas");
9246
+ tile.width = NOISE_CELLS;
9247
+ tile.height = NOISE_CELLS;
9248
+ const t = tile.getContext("2d");
9249
+ if (t) {
9250
+ t.fillStyle = color;
9251
+ for (let y = 0; y < NOISE_CELLS; y++) {
9252
+ for (let x = 0; x < NOISE_CELLS; x++) {
9253
+ t.globalAlpha = noiseHash(x, y) * alpha;
9254
+ t.fillRect(x, y, 1, 1);
9255
+ }
9256
+ }
9257
+ }
9258
+ return tile;
9259
+ }
9244
9260
  function createWebPainter(ctx, onAssetLoad) {
9245
9261
  let vw = 0;
9246
9262
  let vh = 0;
9263
+ const patternCache = /* @__PURE__ */ new Map();
9247
9264
  const tracePoly = (points, closed) => {
9248
9265
  if (points.length === 0) return;
9249
9266
  ctx.beginPath();
@@ -9251,9 +9268,28 @@ function createWebPainter(ctx, onAssetLoad) {
9251
9268
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
9252
9269
  if (closed) ctx.closePath();
9253
9270
  };
9271
+ const toCanvasPattern = (style) => {
9272
+ const key = JSON.stringify(style);
9273
+ let pattern = patternCache.get(key);
9274
+ if (pattern === void 0) {
9275
+ if (style.kind === "noise") {
9276
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
9277
+ } else {
9278
+ const img = getOrLoadImage(style.url, onAssetLoad);
9279
+ if (!img) return "rgba(0,0,0,0)";
9280
+ pattern = ctx.createPattern(img, "repeat");
9281
+ }
9282
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
9283
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
9284
+ }
9285
+ patternCache.set(key, pattern);
9286
+ }
9287
+ return pattern ?? "rgba(0,0,0,0)";
9288
+ };
9254
9289
  const toCanvasStyle = (style) => {
9255
9290
  if (typeof style === "string") return style;
9256
- 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);
9291
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
9292
+ 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);
9257
9293
  for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
9258
9294
  return g;
9259
9295
  };
@@ -9288,6 +9324,19 @@ function createWebPainter(ctx, onAssetLoad) {
9288
9324
  ctx.shadowColor = shadow ? shadow.color : "transparent";
9289
9325
  ctx.shadowBlur = shadow ? shadow.blur : 0;
9290
9326
  },
9327
+ setBlend(mode) {
9328
+ ctx.globalCompositeOperation = mode ?? "source-over";
9329
+ },
9330
+ setLineDash(pattern, offset = 0) {
9331
+ ctx.setLineDash(pattern ? [...pattern] : []);
9332
+ ctx.lineDashOffset = offset;
9333
+ },
9334
+ setBlur(px) {
9335
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
9336
+ },
9337
+ clipPath(d) {
9338
+ ctx.clip(new Path2D(d));
9339
+ },
9291
9340
  resolveTexture(url) {
9292
9341
  const img = getOrLoadImage(url, onAssetLoad);
9293
9342
  if (!img) return null;
@@ -9363,13 +9412,18 @@ function createWebPainter(ctx, onAssetLoad) {
9363
9412
  }
9364
9413
  };
9365
9414
  }
9366
- var handleByImage, imageByHandle;
9415
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
9367
9416
  var init_webPainter2d = __esm({
9368
9417
  "lib/webPainter2d.ts"() {
9369
9418
  "use client";
9370
9419
  init_imageCache();
9371
9420
  handleByImage = /* @__PURE__ */ new WeakMap();
9372
9421
  imageByHandle = /* @__PURE__ */ new WeakMap();
9422
+ noiseHash = (x, y) => {
9423
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
9424
+ return s - Math.floor(s);
9425
+ };
9426
+ NOISE_CELLS = 32;
9373
9427
  }
9374
9428
  });
9375
9429
 
@@ -9594,6 +9648,16 @@ function gradientStyle(g, ox, oy, scale) {
9594
9648
  stops: g.stops
9595
9649
  };
9596
9650
  }
9651
+ if (g.kind === "conic") {
9652
+ if (!g.center) return void 0;
9653
+ return {
9654
+ kind: "conic",
9655
+ cx: ox + g.center.x * scale,
9656
+ cy: oy + g.center.y * scale,
9657
+ angle: g.angle ?? 0,
9658
+ stops: g.stops
9659
+ };
9660
+ }
9597
9661
  if (!g.center || g.radius === void 0) return void 0;
9598
9662
  return {
9599
9663
  kind: "radial",
@@ -9603,6 +9667,13 @@ function gradientStyle(g, ox, oy, scale) {
9603
9667
  stops: g.stops
9604
9668
  };
9605
9669
  }
9670
+ function patternStyle(p, unit) {
9671
+ if (p.kind === "image") {
9672
+ if (!p.url) return void 0;
9673
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9674
+ }
9675
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9676
+ }
9606
9677
  function DrawShape(props) {
9607
9678
  const register = React84.useContext(DrawableRegistryContext);
9608
9679
  if (register) {
@@ -9631,7 +9702,9 @@ var init_DrawShape = __esm({
9631
9702
  "radiusY",
9632
9703
  "width",
9633
9704
  "height",
9634
- "strokeWidth"
9705
+ "strokeWidth",
9706
+ "strokeDashOffset",
9707
+ "blur"
9635
9708
  ];
9636
9709
  lerp = (a, b, k) => a + (b - a) * k;
9637
9710
  paintShape = (painter, rawNode, dctx) => {
@@ -9641,6 +9714,16 @@ var init_DrawShape = __esm({
9641
9714
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9642
9715
  const origin = dctx.projector.project(node.position);
9643
9716
  const tileWidth = dctx.projector.tileWidth;
9717
+ const groupScale = dctx.groupScale ?? 1;
9718
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9719
+ if (node.blendMode) painter.setBlend(node.blendMode);
9720
+ if (node.strokeDash && node.strokeDash.length > 0) {
9721
+ painter.setLineDash(
9722
+ node.strokeDash.map((v) => v / groupScale),
9723
+ (node.strokeDashOffset ?? 0) / groupScale
9724
+ );
9725
+ }
9726
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9644
9727
  if (node.rotate) {
9645
9728
  const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9646
9729
  const px = origin.x + pivot.x * tileWidth;
@@ -9649,15 +9732,18 @@ var init_DrawShape = __esm({
9649
9732
  painter.rotate(node.rotate);
9650
9733
  painter.translate(-px, -py);
9651
9734
  }
9652
- if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9735
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9653
9736
  const fill = node.fill === "none" ? void 0 : node.fill;
9654
9737
  const stroke = node.stroke === "none" ? void 0 : node.stroke;
9655
9738
  const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9739
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9740
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9656
9741
  switch (node.shape) {
9657
9742
  case "cell": {
9658
9743
  const pts = dctx.projector.cellPath(node.position);
9659
9744
  if (pxFill) painter.fillPoly(pts, pxFill);
9660
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9745
+ if (patFill) painter.fillPoly(pts, patFill);
9746
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9661
9747
  break;
9662
9748
  }
9663
9749
  case "rect": {
@@ -9668,7 +9754,8 @@ var init_DrawShape = __esm({
9668
9754
  const w = (node.width ?? 0) * tw;
9669
9755
  const h = (node.height ?? 0) * tw;
9670
9756
  if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9671
- if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9757
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9758
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9672
9759
  break;
9673
9760
  }
9674
9761
  case "ellipse": {
@@ -9679,7 +9766,8 @@ var init_DrawShape = __esm({
9679
9766
  const rx = (node.radiusX ?? 0) * tw;
9680
9767
  const ry = (node.radiusY ?? rx) * tw;
9681
9768
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9682
- if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9769
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9770
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9683
9771
  break;
9684
9772
  }
9685
9773
  case "poly": {
@@ -9688,16 +9776,27 @@ var init_DrawShape = __esm({
9688
9776
  y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9689
9777
  }));
9690
9778
  if (pxFill) painter.fillPoly(pts, pxFill);
9691
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9779
+ if (patFill) painter.fillPoly(pts, patFill);
9780
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9692
9781
  break;
9693
9782
  }
9694
9783
  case "path": {
9695
9784
  if (!node.d) break;
9696
9785
  painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9697
9786
  painter.scale(tileWidth, tileWidth);
9787
+ if (node.strokeDash && node.strokeDash.length > 0) {
9788
+ painter.setLineDash(
9789
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9790
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9791
+ );
9792
+ }
9793
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9698
9794
  const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9795
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9796
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9699
9797
  if (localFill) painter.fillPath(node.d, localFill);
9700
- if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9798
+ if (localPat) painter.fillPath(node.d, localPat);
9799
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9701
9800
  break;
9702
9801
  }
9703
9802
  }
@@ -9778,8 +9877,6 @@ var init_DrawTextLayer = __esm({
9778
9877
  };
9779
9878
  }
9780
9879
  });
9781
-
9782
- // lib/drawable/paintDispatch.ts
9783
9880
  function paintDrawable(painter, node, dctx) {
9784
9881
  switch (node.type) {
9785
9882
  case "draw-sprite":
@@ -9800,10 +9897,20 @@ function paintDrawable(painter, node, dctx) {
9800
9897
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9801
9898
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9802
9899
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9803
- for (const item of node.items) paintDrawable(painter, item, dctx);
9900
+ if (node.clip) {
9901
+ const tw = dctx.projector.tileWidth;
9902
+ painter.scale(tw, tw);
9903
+ painter.clipPath(node.clip);
9904
+ painter.scale(1 / tw, 1 / tw);
9905
+ }
9906
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9907
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9804
9908
  painter.restore();
9805
9909
  break;
9806
9910
  }
9911
+ case "draw-mesh":
9912
+ warnUnsupported2d("draw-mesh");
9913
+ break;
9807
9914
  case "draw-sprite-layer":
9808
9915
  paintSpriteLayer(painter, node, dctx);
9809
9916
  break;
@@ -9815,6 +9922,7 @@ function paintDrawable(painter, node, dctx) {
9815
9922
  break;
9816
9923
  }
9817
9924
  }
9925
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9818
9926
  var init_paintDispatch = __esm({
9819
9927
  "lib/drawable/paintDispatch.ts"() {
9820
9928
  init_contract();
@@ -9824,6 +9932,13 @@ var init_paintDispatch = __esm({
9824
9932
  init_DrawSpriteLayer();
9825
9933
  init_DrawShapeLayer();
9826
9934
  init_DrawTextLayer();
9935
+ paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
9936
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9937
+ warnUnsupported2d = (kind) => {
9938
+ if (warnedUnsupported2d.has(kind)) return;
9939
+ warnedUnsupported2d.add(kind);
9940
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9941
+ };
9827
9942
  }
9828
9943
  });
9829
9944
 
@@ -9838,6 +9953,7 @@ function collectDrawnItems(nodes) {
9838
9953
  case "draw-shape":
9839
9954
  case "draw-text":
9840
9955
  case "draw-group":
9956
+ case "draw-mesh":
9841
9957
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9842
9958
  break;
9843
9959
  case "draw-sprite-layer":
@@ -10455,6 +10571,7 @@ function Canvas({
10455
10571
  isLoading,
10456
10572
  cameraMode: to3DCameraMode(camera?.mode),
10457
10573
  ...zoom !== void 0 ? { scale: zoom } : {},
10574
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10458
10575
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10459
10576
  unitScale,
10460
10577
  backgroundColor,
@@ -38279,14 +38396,26 @@ var init_DetailPanel = __esm({
38279
38396
  DetailPanel.displayName = "DetailPanel";
38280
38397
  }
38281
38398
  });
38282
-
38283
- // components/game/atoms/DrawGroup.tsx
38284
- function DrawGroup(_props) {
38399
+ function DrawGroup(props) {
38400
+ const register = React84.useContext(DrawableRegistryContext);
38401
+ if (register) register({ ...props, type: "draw-group" });
38285
38402
  return null;
38286
38403
  }
38287
38404
  var init_DrawGroup = __esm({
38288
38405
  "components/game/atoms/DrawGroup.tsx"() {
38289
38406
  "use client";
38407
+ init_registry();
38408
+ }
38409
+ });
38410
+ function DrawMesh(props) {
38411
+ const register = React84.useContext(DrawableRegistryContext);
38412
+ if (register) register({ ...props, type: "draw-mesh" });
38413
+ return null;
38414
+ }
38415
+ var init_DrawMesh = __esm({
38416
+ "components/game/atoms/DrawMesh.tsx"() {
38417
+ "use client";
38418
+ init_registry();
38290
38419
  }
38291
38420
  });
38292
38421
  function extractTitle(children) {
@@ -44012,6 +44141,7 @@ var init_component_registry_generated = __esm({
44012
44141
  init_DocTOC();
44013
44142
  init_DocumentViewer();
44014
44143
  init_DrawGroup();
44144
+ init_DrawMesh();
44015
44145
  init_DrawShape();
44016
44146
  init_DrawShapeLayer();
44017
44147
  init_DrawSprite();
@@ -44280,6 +44410,7 @@ var init_component_registry_generated = __esm({
44280
44410
  "DocTOC": DocTOC,
44281
44411
  "DocumentViewer": DocumentViewer,
44282
44412
  "DrawGroup": DrawGroup,
44413
+ "DrawMesh": DrawMesh,
44283
44414
  "DrawShape": DrawShape,
44284
44415
  "DrawShapeLayer": DrawShapeLayer,
44285
44416
  "DrawSprite": DrawSprite,
@@ -9167,9 +9167,26 @@ var init_imageCache = __esm({
9167
9167
  });
9168
9168
 
9169
9169
  // lib/webPainter2d.ts
9170
+ function makeNoiseTile(alpha, color) {
9171
+ const tile = document.createElement("canvas");
9172
+ tile.width = NOISE_CELLS;
9173
+ tile.height = NOISE_CELLS;
9174
+ const t = tile.getContext("2d");
9175
+ if (t) {
9176
+ t.fillStyle = color;
9177
+ for (let y = 0; y < NOISE_CELLS; y++) {
9178
+ for (let x = 0; x < NOISE_CELLS; x++) {
9179
+ t.globalAlpha = noiseHash(x, y) * alpha;
9180
+ t.fillRect(x, y, 1, 1);
9181
+ }
9182
+ }
9183
+ }
9184
+ return tile;
9185
+ }
9170
9186
  function createWebPainter(ctx, onAssetLoad) {
9171
9187
  let vw = 0;
9172
9188
  let vh = 0;
9189
+ const patternCache = /* @__PURE__ */ new Map();
9173
9190
  const tracePoly = (points, closed) => {
9174
9191
  if (points.length === 0) return;
9175
9192
  ctx.beginPath();
@@ -9177,9 +9194,28 @@ function createWebPainter(ctx, onAssetLoad) {
9177
9194
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
9178
9195
  if (closed) ctx.closePath();
9179
9196
  };
9197
+ const toCanvasPattern = (style) => {
9198
+ const key = JSON.stringify(style);
9199
+ let pattern = patternCache.get(key);
9200
+ if (pattern === void 0) {
9201
+ if (style.kind === "noise") {
9202
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
9203
+ } else {
9204
+ const img = getOrLoadImage(style.url, onAssetLoad);
9205
+ if (!img) return "rgba(0,0,0,0)";
9206
+ pattern = ctx.createPattern(img, "repeat");
9207
+ }
9208
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
9209
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
9210
+ }
9211
+ patternCache.set(key, pattern);
9212
+ }
9213
+ return pattern ?? "rgba(0,0,0,0)";
9214
+ };
9180
9215
  const toCanvasStyle = (style) => {
9181
9216
  if (typeof style === "string") return style;
9182
- 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);
9217
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
9218
+ 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);
9183
9219
  for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
9184
9220
  return g;
9185
9221
  };
@@ -9214,6 +9250,19 @@ function createWebPainter(ctx, onAssetLoad) {
9214
9250
  ctx.shadowColor = shadow ? shadow.color : "transparent";
9215
9251
  ctx.shadowBlur = shadow ? shadow.blur : 0;
9216
9252
  },
9253
+ setBlend(mode) {
9254
+ ctx.globalCompositeOperation = mode ?? "source-over";
9255
+ },
9256
+ setLineDash(pattern, offset = 0) {
9257
+ ctx.setLineDash(pattern ? [...pattern] : []);
9258
+ ctx.lineDashOffset = offset;
9259
+ },
9260
+ setBlur(px) {
9261
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
9262
+ },
9263
+ clipPath(d) {
9264
+ ctx.clip(new Path2D(d));
9265
+ },
9217
9266
  resolveTexture(url) {
9218
9267
  const img = getOrLoadImage(url, onAssetLoad);
9219
9268
  if (!img) return null;
@@ -9289,13 +9338,18 @@ function createWebPainter(ctx, onAssetLoad) {
9289
9338
  }
9290
9339
  };
9291
9340
  }
9292
- var handleByImage, imageByHandle;
9341
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
9293
9342
  var init_webPainter2d = __esm({
9294
9343
  "lib/webPainter2d.ts"() {
9295
9344
  "use client";
9296
9345
  init_imageCache();
9297
9346
  handleByImage = /* @__PURE__ */ new WeakMap();
9298
9347
  imageByHandle = /* @__PURE__ */ new WeakMap();
9348
+ noiseHash = (x, y) => {
9349
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
9350
+ return s - Math.floor(s);
9351
+ };
9352
+ NOISE_CELLS = 32;
9299
9353
  }
9300
9354
  });
9301
9355
 
@@ -9520,6 +9574,16 @@ function gradientStyle(g, ox, oy, scale) {
9520
9574
  stops: g.stops
9521
9575
  };
9522
9576
  }
9577
+ if (g.kind === "conic") {
9578
+ if (!g.center) return void 0;
9579
+ return {
9580
+ kind: "conic",
9581
+ cx: ox + g.center.x * scale,
9582
+ cy: oy + g.center.y * scale,
9583
+ angle: g.angle ?? 0,
9584
+ stops: g.stops
9585
+ };
9586
+ }
9523
9587
  if (!g.center || g.radius === void 0) return void 0;
9524
9588
  return {
9525
9589
  kind: "radial",
@@ -9529,6 +9593,13 @@ function gradientStyle(g, ox, oy, scale) {
9529
9593
  stops: g.stops
9530
9594
  };
9531
9595
  }
9596
+ function patternStyle(p, unit) {
9597
+ if (p.kind === "image") {
9598
+ if (!p.url) return void 0;
9599
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9600
+ }
9601
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9602
+ }
9532
9603
  function DrawShape(props) {
9533
9604
  const register = useContext(DrawableRegistryContext);
9534
9605
  if (register) {
@@ -9557,7 +9628,9 @@ var init_DrawShape = __esm({
9557
9628
  "radiusY",
9558
9629
  "width",
9559
9630
  "height",
9560
- "strokeWidth"
9631
+ "strokeWidth",
9632
+ "strokeDashOffset",
9633
+ "blur"
9561
9634
  ];
9562
9635
  lerp = (a, b, k) => a + (b - a) * k;
9563
9636
  paintShape = (painter, rawNode, dctx) => {
@@ -9567,6 +9640,16 @@ var init_DrawShape = __esm({
9567
9640
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9568
9641
  const origin = dctx.projector.project(node.position);
9569
9642
  const tileWidth = dctx.projector.tileWidth;
9643
+ const groupScale = dctx.groupScale ?? 1;
9644
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9645
+ if (node.blendMode) painter.setBlend(node.blendMode);
9646
+ if (node.strokeDash && node.strokeDash.length > 0) {
9647
+ painter.setLineDash(
9648
+ node.strokeDash.map((v) => v / groupScale),
9649
+ (node.strokeDashOffset ?? 0) / groupScale
9650
+ );
9651
+ }
9652
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9570
9653
  if (node.rotate) {
9571
9654
  const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9572
9655
  const px = origin.x + pivot.x * tileWidth;
@@ -9575,15 +9658,18 @@ var init_DrawShape = __esm({
9575
9658
  painter.rotate(node.rotate);
9576
9659
  painter.translate(-px, -py);
9577
9660
  }
9578
- if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth });
9661
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9579
9662
  const fill = node.fill === "none" ? void 0 : node.fill;
9580
9663
  const stroke = node.stroke === "none" ? void 0 : node.stroke;
9581
9664
  const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9665
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9666
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9582
9667
  switch (node.shape) {
9583
9668
  case "cell": {
9584
9669
  const pts = dctx.projector.cellPath(node.position);
9585
9670
  if (pxFill) painter.fillPoly(pts, pxFill);
9586
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9671
+ if (patFill) painter.fillPoly(pts, patFill);
9672
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9587
9673
  break;
9588
9674
  }
9589
9675
  case "rect": {
@@ -9594,7 +9680,8 @@ var init_DrawShape = __esm({
9594
9680
  const w = (node.width ?? 0) * tw;
9595
9681
  const h = (node.height ?? 0) * tw;
9596
9682
  if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9597
- if (stroke) painter.strokeRect(x, y, w, h, stroke, node.strokeWidth ?? 1);
9683
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9684
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9598
9685
  break;
9599
9686
  }
9600
9687
  case "ellipse": {
@@ -9605,7 +9692,8 @@ var init_DrawShape = __esm({
9605
9692
  const rx = (node.radiusX ?? 0) * tw;
9606
9693
  const ry = (node.radiusY ?? rx) * tw;
9607
9694
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9608
- if (stroke) painter.strokeEllipse(cx, cy, rx, ry, stroke, node.strokeWidth ?? 1);
9695
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9696
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9609
9697
  break;
9610
9698
  }
9611
9699
  case "poly": {
@@ -9614,16 +9702,27 @@ var init_DrawShape = __esm({
9614
9702
  y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9615
9703
  }));
9616
9704
  if (pxFill) painter.fillPoly(pts, pxFill);
9617
- if (stroke) painter.strokePoly(pts, stroke, node.strokeWidth ?? 1, true);
9705
+ if (patFill) painter.fillPoly(pts, patFill);
9706
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9618
9707
  break;
9619
9708
  }
9620
9709
  case "path": {
9621
9710
  if (!node.d) break;
9622
9711
  painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9623
9712
  painter.scale(tileWidth, tileWidth);
9713
+ if (node.strokeDash && node.strokeDash.length > 0) {
9714
+ painter.setLineDash(
9715
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9716
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9717
+ );
9718
+ }
9719
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9624
9720
  const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9721
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9722
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9625
9723
  if (localFill) painter.fillPath(node.d, localFill);
9626
- if (stroke) painter.strokePath(node.d, stroke, (node.strokeWidth ?? 1) / tileWidth);
9724
+ if (localPat) painter.fillPath(node.d, localPat);
9725
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9627
9726
  break;
9628
9727
  }
9629
9728
  }
@@ -9704,8 +9803,6 @@ var init_DrawTextLayer = __esm({
9704
9803
  };
9705
9804
  }
9706
9805
  });
9707
-
9708
- // lib/drawable/paintDispatch.ts
9709
9806
  function paintDrawable(painter, node, dctx) {
9710
9807
  switch (node.type) {
9711
9808
  case "draw-sprite":
@@ -9726,10 +9823,20 @@ function paintDrawable(painter, node, dctx) {
9726
9823
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9727
9824
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9728
9825
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9729
- for (const item of node.items) paintDrawable(painter, item, dctx);
9826
+ if (node.clip) {
9827
+ const tw = dctx.projector.tileWidth;
9828
+ painter.scale(tw, tw);
9829
+ painter.clipPath(node.clip);
9830
+ painter.scale(1 / tw, 1 / tw);
9831
+ }
9832
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9833
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9730
9834
  painter.restore();
9731
9835
  break;
9732
9836
  }
9837
+ case "draw-mesh":
9838
+ warnUnsupported2d("draw-mesh");
9839
+ break;
9733
9840
  case "draw-sprite-layer":
9734
9841
  paintSpriteLayer(painter, node, dctx);
9735
9842
  break;
@@ -9741,6 +9848,7 @@ function paintDrawable(painter, node, dctx) {
9741
9848
  break;
9742
9849
  }
9743
9850
  }
9851
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9744
9852
  var init_paintDispatch = __esm({
9745
9853
  "lib/drawable/paintDispatch.ts"() {
9746
9854
  init_contract();
@@ -9750,6 +9858,13 @@ var init_paintDispatch = __esm({
9750
9858
  init_DrawSpriteLayer();
9751
9859
  init_DrawShapeLayer();
9752
9860
  init_DrawTextLayer();
9861
+ paint2dLog = createLogger("almadar:ui:drawable-2d");
9862
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9863
+ warnUnsupported2d = (kind) => {
9864
+ if (warnedUnsupported2d.has(kind)) return;
9865
+ warnedUnsupported2d.add(kind);
9866
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9867
+ };
9753
9868
  }
9754
9869
  });
9755
9870
 
@@ -9764,6 +9879,7 @@ function collectDrawnItems(nodes) {
9764
9879
  case "draw-shape":
9765
9880
  case "draw-text":
9766
9881
  case "draw-group":
9882
+ case "draw-mesh":
9767
9883
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9768
9884
  break;
9769
9885
  case "draw-sprite-layer":
@@ -10381,6 +10497,7 @@ function Canvas({
10381
10497
  isLoading,
10382
10498
  cameraMode: to3DCameraMode(camera?.mode),
10383
10499
  ...zoom !== void 0 ? { scale: zoom } : {},
10500
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10384
10501
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10385
10502
  unitScale,
10386
10503
  backgroundColor,
@@ -38205,14 +38322,26 @@ var init_DetailPanel = __esm({
38205
38322
  DetailPanel.displayName = "DetailPanel";
38206
38323
  }
38207
38324
  });
38208
-
38209
- // components/game/atoms/DrawGroup.tsx
38210
- function DrawGroup(_props) {
38325
+ function DrawGroup(props) {
38326
+ const register = useContext(DrawableRegistryContext);
38327
+ if (register) register({ ...props, type: "draw-group" });
38211
38328
  return null;
38212
38329
  }
38213
38330
  var init_DrawGroup = __esm({
38214
38331
  "components/game/atoms/DrawGroup.tsx"() {
38215
38332
  "use client";
38333
+ init_registry();
38334
+ }
38335
+ });
38336
+ function DrawMesh(props) {
38337
+ const register = useContext(DrawableRegistryContext);
38338
+ if (register) register({ ...props, type: "draw-mesh" });
38339
+ return null;
38340
+ }
38341
+ var init_DrawMesh = __esm({
38342
+ "components/game/atoms/DrawMesh.tsx"() {
38343
+ "use client";
38344
+ init_registry();
38216
38345
  }
38217
38346
  });
38218
38347
  function extractTitle(children) {
@@ -43938,6 +44067,7 @@ var init_component_registry_generated = __esm({
43938
44067
  init_DocTOC();
43939
44068
  init_DocumentViewer();
43940
44069
  init_DrawGroup();
44070
+ init_DrawMesh();
43941
44071
  init_DrawShape();
43942
44072
  init_DrawShapeLayer();
43943
44073
  init_DrawSprite();
@@ -44206,6 +44336,7 @@ var init_component_registry_generated = __esm({
44206
44336
  "DocTOC": DocTOC,
44207
44337
  "DocumentViewer": DocumentViewer,
44208
44338
  "DrawGroup": DrawGroup,
44339
+ "DrawMesh": DrawMesh,
44209
44340
  "DrawShape": DrawShape,
44210
44341
  "DrawShapeLayer": DrawShapeLayer,
44211
44342
  "DrawSprite": DrawSprite,