@layoutit/polycss-react 0.2.1 → 0.2.3

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/index.js CHANGED
@@ -98,8 +98,9 @@ function usePolyCamera(options) {
98
98
  const cssX = wy * tileSize;
99
99
  const cssY = wx * tileSize;
100
100
  const cssZ = wz * tileSize;
101
+ const cssZoom = s.zoom / tileSize;
101
102
  const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
102
- el.style.transform = `${distancePart}scale(${s.zoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
103
+ el.style.transform = `${distancePart}scale(${cssZoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
103
104
  }
104
105
  store.updateCameraFromRef(handle);
105
106
  store.notifyAll();
@@ -118,8 +119,9 @@ function usePolyCamera(options) {
118
119
  const cssX = wy * tileSize;
119
120
  const cssY = wx * tileSize;
120
121
  const cssZ = wz * tileSize;
122
+ const cssZoom = s.zoom / tileSize;
121
123
  const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
122
- el.style.transform = `${distancePart}scale(${s.zoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
124
+ el.style.transform = `${distancePart}scale(${cssZoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
123
125
  }, [store]);
124
126
  return {
125
127
  store,
@@ -132,7 +134,7 @@ function usePolyCamera(options) {
132
134
 
133
135
  // src/camera/PolyPerspectiveCamera.tsx
134
136
  import { jsx } from "react/jsx-runtime";
135
- var DEFAULT_PERSPECTIVE = 8e3;
137
+ var DEFAULT_PERSPECTIVE = 32e3;
136
138
  function PolyPerspectiveCameraInner({
137
139
  zoom,
138
140
  target,
@@ -198,7 +200,12 @@ function PolyOrthographicCameraInner({
198
200
  );
199
201
  const cameraStyle = {
200
202
  ...style,
201
- perspective: "none"
203
+ // Vanilla emits 1000000px instead of "none" because true `perspective: none`
204
+ // sends Chrome down a compositor fast path that mis-rasterizes <u>
205
+ // border-triangle leaves. A very large finite value is visually orthographic
206
+ // but routes through the normal compositor path. Mirror that here so React
207
+ // produces byte-identical output to vanilla.
208
+ perspective: "1000000px"
202
209
  };
203
210
  return /* @__PURE__ */ jsx2(PolyCameraContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(
204
211
  "div",
@@ -213,8 +220,8 @@ function PolyOrthographicCameraInner({
213
220
  var PolyOrthographicCamera = memo2(PolyOrthographicCameraInner);
214
221
 
215
222
  // src/scene/PolyScene.tsx
216
- import { memo as memo7, useCallback as useCallback4, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo6, useRef as useRef3, useState as useState2 } from "react";
217
- import { BASE_TILE as BASE_TILE2, DEFAULT_SEAM_BLEED, parseHexColor } from "@layoutit/polycss-core";
223
+ import { memo as memo8, useCallback as useCallback5, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo6, useRef as useRef4, useState as useState2 } from "react";
224
+ import { BASE_TILE as BASE_TILE2, DEFAULT_SEAM_BLEED, parseHexColor, worldDirectionToCss } from "@layoutit/polycss-core";
218
225
 
219
226
  // src/scene/useSceneContext.ts
220
227
  import { useMemo as useMemo4 } from "react";
@@ -267,21 +274,24 @@ var CORE_BASE_STYLES = `
267
274
  will-change: transform;
268
275
  }
269
276
 
270
- /* \u2500\u2500 Camera wrapper (perspective + interactive drag) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
277
+ /* \u2500\u2500 Camera wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
271
278
 
279
+ /* Matches vanilla .polycss-camera: a simple positioned block that fills the
280
+ host. PolyPerspectiveCamera / PolyOrthographicCamera apply perspective
281
+ inline (driven by their perspective prop), so the CSS file does not bake a
282
+ default. Earlier React/Vue copies added flex centering, overflow:hidden,
283
+ contain:paint, isolation:isolate which shifted layout vs vanilla; removed
284
+ so cross-renderer iframes lay out identically. */
272
285
  .polycss-camera {
273
- display: flex;
286
+ position: relative;
287
+ display: block;
274
288
  width: 100%;
275
- justify-content: center;
276
- align-items: center;
277
- perspective: 8000px;
278
- min-height: inherit;
279
289
  height: 100%;
280
- position: relative;
281
- overflow: hidden;
282
- contain: paint;
283
- isolation: isolate;
284
290
  }
291
+ /* React-only descendant default: applies preserve-3d to controls + helpers
292
+ that ride on the camera; vanilla does not need this because every internal
293
+ element sets the property directly. Kept here so user-supplied wrapper
294
+ divs inside PolyCamera still participate in 3D layout. */
285
295
  .polycss-camera * {
286
296
  transform-style: preserve-3d;
287
297
  position: absolute;
@@ -303,6 +313,17 @@ var CORE_BASE_STYLES = `
303
313
  transform-style: preserve-3d !important;
304
314
  }
305
315
 
316
+ /* \u2500\u2500 Mesh wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
317
+
318
+ .polycss-mesh {
319
+ position: absolute;
320
+ transform-style: preserve-3d;
321
+ /* Pivot at wrapper local (0,0,0) for three.js mesh.position/rotation/scale
322
+ parity. Geometry is positioned by the parser (bbox-min-at-origin by
323
+ default, or pre-centered via loadMesh { center: true }). */
324
+ transform-origin: 0 0 0;
325
+ }
326
+
306
327
  /* \u2500\u2500 Polygon leaf element \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
307
328
 
308
329
  /*
@@ -388,19 +409,34 @@ var CORE_BASE_STYLES = `
388
409
  border-width: 0 16px 32px 16px;
389
410
  }
390
411
 
391
- .polycss-scene u.polycss-large-border-triangle {
392
- border-width: 0 48px 96px 48px;
393
- }
412
+ @supports (corner-top-left-shape: bevel) and (corner-top-right-shape: bevel) {
413
+ .polycss-scene > u,
414
+ .polycss-mesh > u,
415
+ .polycss-bucket > u {
416
+ border-width: 0;
417
+ width: 32px;
418
+ height: 32px;
419
+ background-color: currentColor;
420
+ border-top-left-radius: 50% 100%;
421
+ border-top-right-radius: 50% 100%;
422
+ corner-top-left-shape: bevel;
423
+ corner-top-right-shape: bevel;
424
+ }
394
425
 
395
- .polycss-scene u.polycss-corner-triangle {
396
- width: 32px;
397
- height: 32px;
398
- background: currentColor;
399
- border: 0;
400
- border-top-left-radius: 50% 100%;
401
- border-top-right-radius: 50% 100%;
402
- corner-top-left-shape: bevel;
403
- corner-top-right-shape: bevel;
426
+ .polycss-scene > u.polycss-corner-shape-solid,
427
+ .polycss-mesh > u.polycss-corner-shape-solid,
428
+ .polycss-bucket > u.polycss-corner-shape-solid {
429
+ width: 16px;
430
+ height: 16px;
431
+ box-sizing: border-box;
432
+ border: 0;
433
+ background: currentColor;
434
+ border-radius: 0;
435
+ corner-top-left-shape: initial;
436
+ corner-top-right-shape: initial;
437
+ corner-bottom-right-shape: initial;
438
+ corner-bottom-left-shape: initial;
439
+ }
404
440
  }
405
441
 
406
442
  /* \u2500\u2500 Gizmo override \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -505,22 +541,33 @@ var CORE_BASE_STYLES = `
505
541
  here (not inline per polygon) so each leaf only carries its tiny normal
506
542
  declarations \u2014 ~12\xD7 smaller per-polygon style payload on big meshes. */
507
543
  .polycss-scene[data-polycss-lighting="dynamic"] s {
544
+ /*
545
+ * Three.js MeshLambertMaterial parity for textured surfaces. See
546
+ * packages/polycss/src/styles/styles.ts for derivation and probe data.
547
+ * Kept in lockstep across renderer copies per cross-package discipline.
548
+ */
508
549
  background-color: rgb(
509
- calc(255 * (var(--par) * var(--pai)
510
- + var(--plr) * var(--pli) * max(0,
511
- var(--pnx) * var(--plx) +
512
- var(--pny) * var(--ply) +
513
- var(--pnz) * var(--plz))))
514
- calc(255 * (var(--pag) * var(--pai)
515
- + var(--plg) * var(--pli) * max(0,
516
- var(--pnx) * var(--plx) +
517
- var(--pny) * var(--ply) +
518
- var(--pnz) * var(--plz))))
519
- calc(255 * (var(--pab) * var(--pai)
520
- + var(--plb) * var(--pli) * max(0,
521
- var(--pnx) * var(--plx) +
522
- var(--pny) * var(--ply) +
523
- var(--pnz) * var(--plz))))
550
+ calc(255 * max(0, 1.055 * pow(min(1, (
551
+ pow((var(--par) + 0.055) / 1.055, 2.4) * var(--pai) +
552
+ pow((var(--plr) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
553
+ var(--pnx) * var(--plx) +
554
+ var(--pny) * var(--ply) +
555
+ var(--pnz) * var(--plz))
556
+ ) / 3.14159265), 0.4167) - 0.055))
557
+ calc(255 * max(0, 1.055 * pow(min(1, (
558
+ pow((var(--pag) + 0.055) / 1.055, 2.4) * var(--pai) +
559
+ pow((var(--plg) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
560
+ var(--pnx) * var(--plx) +
561
+ var(--pny) * var(--ply) +
562
+ var(--pnz) * var(--plz))
563
+ ) / 3.14159265), 0.4167) - 0.055))
564
+ calc(255 * max(0, 1.055 * pow(min(1, (
565
+ pow((var(--pab) + 0.055) / 1.055, 2.4) * var(--pai) +
566
+ pow((var(--plb) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
567
+ var(--pnx) * var(--plx) +
568
+ var(--pny) * var(--ply) +
569
+ var(--pnz) * var(--plz))
570
+ ) / 3.14159265), 0.4167) - 0.055))
524
571
  );
525
572
  background-blend-mode: multiply;
526
573
  background-image: var(--polycss-atlas-url);
@@ -539,35 +586,48 @@ var CORE_BASE_STYLES = `
539
586
  }
540
587
 
541
588
  .polycss-scene[data-polycss-lighting="dynamic"] b,
589
+ .polycss-scene[data-polycss-lighting="dynamic"] i,
542
590
  .polycss-scene[data-polycss-lighting="dynamic"] u {
591
+ /*
592
+ * Three.js MeshLambertMaterial parity (default useLegacyLights=false,
593
+ * physically-correct pipeline). See packages/polycss/src/styles/styles.ts
594
+ * for the derivation and probe data \u2014 kept in lockstep across the three
595
+ * renderer copies per cross-package discipline.
596
+ */
543
597
  color: rgb(
544
- calc(255 * var(--psr) * (var(--par) * var(--pai)
545
- + var(--plr) * var(--pli) * max(0,
546
- var(--pnx) * var(--plx) +
547
- var(--pny) * var(--ply) +
548
- var(--pnz) * var(--plz))))
549
- calc(255 * var(--psg) * (var(--pag) * var(--pai)
550
- + var(--plg) * var(--pli) * max(0,
551
- var(--pnx) * var(--plx) +
552
- var(--pny) * var(--ply) +
553
- var(--pnz) * var(--plz))))
554
- calc(255 * var(--psb) * (var(--pab) * var(--pai)
555
- + var(--plb) * var(--pli) * max(0,
556
- var(--pnx) * var(--plx) +
557
- var(--pny) * var(--ply) +
558
- var(--pnz) * var(--plz))))
598
+ calc(255 * max(0, 1.055 * pow(min(1,
599
+ pow((var(--psr) + 0.055) / 1.055, 2.4) * (
600
+ pow((var(--par) + 0.055) / 1.055, 2.4) * var(--pai) +
601
+ pow((var(--plr) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
602
+ var(--pnx) * var(--plx) +
603
+ var(--pny) * var(--ply) +
604
+ var(--pnz) * var(--plz))
605
+ ) / 3.14159265
606
+ ), 0.4167) - 0.055))
607
+ calc(255 * max(0, 1.055 * pow(min(1,
608
+ pow((var(--psg) + 0.055) / 1.055, 2.4) * (
609
+ pow((var(--pag) + 0.055) / 1.055, 2.4) * var(--pai) +
610
+ pow((var(--plg) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
611
+ var(--pnx) * var(--plx) +
612
+ var(--pny) * var(--ply) +
613
+ var(--pnz) * var(--plz))
614
+ ) / 3.14159265
615
+ ), 0.4167) - 0.055))
616
+ calc(255 * max(0, 1.055 * pow(min(1,
617
+ pow((var(--psb) + 0.055) / 1.055, 2.4) * (
618
+ pow((var(--pab) + 0.055) / 1.055, 2.4) * var(--pai) +
619
+ pow((var(--plb) + 0.055) / 1.055, 2.4) * var(--pli) * max(0,
620
+ var(--pnx) * var(--plx) +
621
+ var(--pny) * var(--ply) +
622
+ var(--pnz) * var(--plz))
623
+ ) / 3.14159265
624
+ ), 0.4167) - 0.055))
559
625
  );
560
626
  }
561
627
 
562
- /* <q> \u2014 dedicated shadow leaf. Same border-shape rendering trick as <i>
563
- (border-color: currentColor fills the polygon outline) but with its
564
- own tag so we don't have to thread :not(.polycss-shadow) exclusions
565
- through every dynamic-mode color rule. backface-visibility must be
566
- visible because the projection matrix is near-rank-deficient and the
567
- resulting plane's normal can read as back-facing under some camera
568
- angles; the leaf is intentionally always painted. Strip the UA's
569
- default ::before/::after open-/close-quote so the element is just a
570
- styled box. */
628
+ /* Reserved internal <q> shadow element rules. Current shadow emission uses SVG
629
+ surfaces; these rules keep any retained <q> markup styled as a plain
630
+ border-shape leaf instead of inheriting UA quote styling. */
571
631
  .polycss-scene q {
572
632
  position: absolute;
573
633
  display: block;
@@ -590,11 +650,12 @@ var CORE_BASE_STYLES = `
590
650
  content: none;
591
651
  }
592
652
 
593
- /* \u2500\u2500 Cast shadow projection (dynamic-mode CSS path) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
653
+ /* \u2500\u2500 Retained <q> shadow projection (dynamic-mode CSS path) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
594
654
 
595
655
  /*
596
- * Shadow projection matrix. Projects any 3D point P onto the horizontal
597
- * ground plane (cssZ \u2248 G) along the CSS-space light direction (--clx/y/z).
656
+ * Projection matrix for retained internal <q> shadow leaves. Projects any
657
+ * 3D point P onto the horizontal ground plane (cssZ \u2248 G) along the CSS-space
658
+ * light direction (--clx/y/z).
598
659
  *
599
660
  * In PolyCSS's world convention world Z is up (red-green plane is the
600
661
  * floor in the axes helper). After the world\u2192CSS swap (Y\u2194X), world Z stays
@@ -607,7 +668,7 @@ var CORE_BASE_STYLES = `
607
668
  * has a valid layout box. The fix: collapse along z by a near-zero
608
669
  * scale (Z_SQUASH = 0.01) instead of exactly zero \u2014 output.z is then
609
670
  * approximately G with ~1% drift from the input, full-rank and renderable.
610
- * The shadow still looks flat to the eye (the drift is sub-pixel for
671
+ * The result still looks flat to the eye (the drift is sub-pixel for
611
672
  * any realistic scene size).
612
673
  *
613
674
  * out.cssX = P.cssX - (--clx/--clz) * (P.cssZ - G)
@@ -635,17 +696,9 @@ var CORE_BASE_STYLES = `
635
696
  );
636
697
  }
637
698
 
638
- /* <q> shadow leaf \u2014 Lambert-gated opacity. Polygons facing the light cast
639
- full shadow; polygons facing away cast zero shadow (their projection
640
- would stack inside the silhouette and produce ugly overdraw). The
641
- * 10 multiplier sharpens the cutoff so small positive Lambert values
642
- jump quickly to 1, giving a near-binary visibility decision with a
643
- smooth edge transition. Pure CSS calc \u2014 no JS at light-change time.
644
-
645
- Scoped to dynamic mode: baked-mode shadow leaves are dropped up-front
646
- by isBakedShadowCaster() and don't carry --pnx/--pny/--pnz, so an
647
- unscoped gate would silently zero them via the @property initial
648
- values. */
699
+ /* Retained <q> opacity gate. Polygons facing the light cast full shadow;
700
+ polygons facing away cast zero shadow. The * 10 multiplier sharpens the
701
+ cutoff so small positive Lambert values jump quickly to 1. */
649
702
  .polycss-scene[data-polycss-lighting="dynamic"] q {
650
703
  opacity: clamp(0, calc((var(--pnx) * var(--clx) + var(--pny) * var(--cly) + var(--pnz) * var(--clz)) * 10), 1);
651
704
  }
@@ -681,6 +734,7 @@ function borderShapeSupported(doc) {
681
734
  return media("(pointer: fine)").matches && media("(hover: hover)").matches;
682
735
  }
683
736
  function solidTriangleSupported(doc) {
737
+ if (cornerTriangleSupported(doc)) return true;
684
738
  const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
685
739
  const userAgent = win?.navigator?.userAgent ?? "";
686
740
  if (!userAgent) return true;
@@ -690,6 +744,10 @@ function cornerShapeSupported(doc) {
690
744
  const css = doc.defaultView?.CSS ?? (typeof CSS !== "undefined" ? CSS : void 0);
691
745
  return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel") && !!css.supports("corner-bottom-right-shape", "bevel") && !!css.supports("corner-bottom-left-shape", "bevel");
692
746
  }
747
+ function cornerTriangleSupported(doc) {
748
+ const css = doc.defaultView?.CSS ?? (typeof CSS !== "undefined" ? CSS : void 0);
749
+ return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel");
750
+ }
693
751
  function projectiveQuadSupported(doc) {
694
752
  const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
695
753
  const userAgent = win?.navigator?.userAgent ?? "";
@@ -734,6 +792,8 @@ function isBorderShapeSupported(doc) {
734
792
  function isSolidTriangleSupported(doc) {
735
793
  const d = doc ?? (typeof document !== "undefined" ? document : null);
736
794
  if (!d) {
795
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
796
+ if (!!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel")) return true;
737
797
  const userAgent = (typeof navigator !== "undefined" ? navigator : globalThis.navigator)?.userAgent ?? "";
738
798
  if (!userAgent) return true;
739
799
  const isChromiumFamily = /\b(?:Chrome|HeadlessChrome|Chromium|Edg|OPR)\//.test(userAgent);
@@ -748,9 +808,12 @@ import {
748
808
  filterAtlasPlans as filterAtlasPlansCore
749
809
  } from "@layoutit/polycss-core";
750
810
  function filterAtlasPlans(plans, textureLighting, disabled, doc) {
811
+ const d = doc ?? (typeof document !== "undefined" ? document : null);
751
812
  return filterAtlasPlansCore(plans, textureLighting, disabled, {
752
813
  solidTriangleSupported: isSolidTriangleSupported(doc),
753
- borderShapeSupported: isBorderShapeSupported(doc)
814
+ projectiveQuadSupported: doc ? projectiveQuadSupported(doc) : true,
815
+ borderShapeSupported: isBorderShapeSupported(doc),
816
+ cornerShapeSupported: d ? cornerShapeSupported(d) : false
754
817
  });
755
818
  }
756
819
 
@@ -772,7 +835,6 @@ function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc) {
772
835
  // src/scene/atlas/buildAtlasPages.ts
773
836
  import {
774
837
  expandClipPoints,
775
- tintToCss,
776
838
  TEXTURE_TRIANGLE_BLEED,
777
839
  TEXTURE_EDGE_REPAIR_ALPHA_MIN,
778
840
  TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN,
@@ -812,16 +874,29 @@ function setCssTransform(ctx, atlasScale, a = 1, b = 0, c = 0, d = 1, e = 0, f =
812
874
  f * atlasScale
813
875
  );
814
876
  }
877
+ function srgbByteToLinear(c) {
878
+ const u = c / 255;
879
+ return u <= 0.04045 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4);
880
+ }
881
+ function linearToSrgbByte(c) {
882
+ const s = c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
883
+ return Math.max(0, Math.min(255, Math.round(s * 255)));
884
+ }
815
885
  function applyTextureTint(ctx, x, y, width, height, tint, atlasScale) {
816
- if (Math.abs(tint.r - 1) < 1e-3 && Math.abs(tint.g - 1) < 1e-3 && Math.abs(tint.b - 1) < 1e-3) {
817
- return;
886
+ const px = Math.round(x * atlasScale);
887
+ const py = Math.round(y * atlasScale);
888
+ const pw = Math.max(1, Math.round(width * atlasScale));
889
+ const ph = Math.max(1, Math.round(height * atlasScale));
890
+ const img = ctx.getImageData(px, py, pw, ph);
891
+ const data = img.data;
892
+ const tr = tint.r, tg = tint.g, tb = tint.b;
893
+ for (let i = 0; i < data.length; i += 4) {
894
+ if (data[i + 3] === 0) continue;
895
+ data[i] = linearToSrgbByte(srgbByteToLinear(data[i]) * tr);
896
+ data[i + 1] = linearToSrgbByte(srgbByteToLinear(data[i + 1]) * tg);
897
+ data[i + 2] = linearToSrgbByte(srgbByteToLinear(data[i + 2]) * tb);
818
898
  }
819
- ctx.save();
820
- setCssTransform(ctx, atlasScale);
821
- ctx.globalCompositeOperation = "multiply";
822
- ctx.fillStyle = tintToCss(tint);
823
- ctx.fillRect(x, y, width, height);
824
- ctx.restore();
899
+ ctx.putImageData(img, px, py);
825
900
  }
826
901
  function drawImageCover(ctx, img, x, y, width, height, atlasScale) {
827
902
  const srcW = img.naturalWidth || img.width || 1;
@@ -1235,6 +1310,15 @@ var SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96;
1235
1310
  var SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 48px 96px 48px";
1236
1311
  var cachedSolidTriangleUserAgent;
1237
1312
  var cachedSolidTriangleCanonicalSize = SOLID_TRIANGLE_CANONICAL_SIZE;
1313
+ function cornerTriangleSupported2() {
1314
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
1315
+ return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel");
1316
+ }
1317
+ function solidTrianglePrimitive() {
1318
+ if (cornerTriangleSupported2()) return "corner-bevel";
1319
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
1320
+ return /\bFirefox\//.test(ua) ? "border-large" : "border";
1321
+ }
1238
1322
  function solidTriangleCanonicalSize() {
1239
1323
  const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
1240
1324
  if (ua !== cachedSolidTriangleUserAgent) {
@@ -1244,7 +1328,35 @@ function solidTriangleCanonicalSize() {
1244
1328
  return cachedSolidTriangleCanonicalSize;
1245
1329
  }
1246
1330
  function solidTriangleBorderWidth() {
1247
- return solidTriangleCanonicalSize() === SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : void 0;
1331
+ return solidTrianglePrimitive() === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : void 0;
1332
+ }
1333
+ function solidTrianglePaintStyle() {
1334
+ const primitive = solidTrianglePrimitive();
1335
+ if (primitive === "corner-bevel") return void 0;
1336
+ const borderWidth = primitive === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : void 0;
1337
+ return borderWidth ? { borderWidth } : void 0;
1338
+ }
1339
+ function applySolidTrianglePaintStyle(el) {
1340
+ const primitive = solidTrianglePrimitive();
1341
+ if (primitive === "corner-bevel") {
1342
+ el.style.width = "";
1343
+ el.style.height = "";
1344
+ el.style.backgroundColor = "";
1345
+ el.style.borderWidth = "";
1346
+ el.style.borderTopLeftRadius = "";
1347
+ el.style.borderTopRightRadius = "";
1348
+ el.style.removeProperty("corner-top-left-shape");
1349
+ el.style.removeProperty("corner-top-right-shape");
1350
+ } else {
1351
+ el.style.width = "";
1352
+ el.style.height = "";
1353
+ el.style.backgroundColor = "";
1354
+ el.style.borderTopLeftRadius = "";
1355
+ el.style.borderTopRightRadius = "";
1356
+ el.style.removeProperty("corner-top-left-shape");
1357
+ el.style.removeProperty("corner-top-right-shape");
1358
+ el.style.borderWidth = primitive === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : "";
1359
+ }
1248
1360
  }
1249
1361
  function parseHex2(hex) {
1250
1362
  const parsed = parsePureColor(hex);
@@ -1659,9 +1771,10 @@ function solidTriangleStyle(entry, textureLighting, pointerEvents, solidPaintDef
1659
1771
  txCol[2],
1660
1772
  1
1661
1773
  ].map((v) => (Math.round(v * 1e3) / 1e3 || 0).toString()).join(",");
1774
+ const primitiveStyle = solidTrianglePaintStyle();
1662
1775
  return {
1663
1776
  transform: `matrix3d(${canonicalMatrix})`,
1664
- borderWidth: solidTriangleBorderWidth(),
1777
+ ...primitiveStyle ?? {},
1665
1778
  ...sharedStyle
1666
1779
  };
1667
1780
  }
@@ -1890,6 +2003,7 @@ function updateStableTriangleDom(root, polygons, options = {}) {
1890
2003
  const el = leaves[i];
1891
2004
  if (el.style.visibility) el.style.visibility = "";
1892
2005
  el.__polycssStableTriangleBasis = style.basis;
2006
+ applySolidTrianglePaintStyle(el);
1893
2007
  el.style.transform = style.transform;
1894
2008
  if (style.borderWidth !== void 0) el.style.borderWidth = style.borderWidth;
1895
2009
  if (style.color !== void 0) applyStableTriangleColor(el, i, style.color, options);
@@ -1899,8 +2013,8 @@ function updateStableTriangleDom(root, polygons, options = {}) {
1899
2013
 
1900
2014
  // src/scene/atlas/paintDefaults.ts
1901
2015
  import { computeTextureAtlasPlanPublic } from "@layoutit/polycss-core";
1902
- function computeTextureAtlasPlan(polygon, index, options = {}) {
1903
- return computeTextureAtlasPlanPublic(polygon, index, options);
2016
+ function computeTextureAtlasPlan(polygon, index, options = {}, basisHint) {
2017
+ return computeTextureAtlasPlanPublic(polygon, index, options, void 0, basisHint);
1904
2018
  }
1905
2019
  function getSolidPaintDefaults(plans, textureLighting, strategies) {
1906
2020
  const disabled = new Set(strategies?.disable ?? []);
@@ -1908,24 +2022,44 @@ function getSolidPaintDefaults(plans, textureLighting, strategies) {
1908
2022
  }
1909
2023
 
1910
2024
  // src/scene/atlas/useTextureAtlas.ts
1911
- import { useEffect as useEffect2, useMemo as useMemo5, useState } from "react";
2025
+ import { useEffect as useEffect2, useMemo as useMemo5, useRef as useRef3, useState } from "react";
1912
2026
  function pageShells(pages) {
1913
2027
  return pages.map((page) => ({ width: page.width, height: page.height, url: null }));
1914
2028
  }
1915
- function textureAtlasPagesEqual(a, b) {
1916
- return a.length === b.length && a.every((page, index) => {
1917
- const other = b[index];
1918
- return page.width === other.width && page.height === other.height && page.url === other.url;
1919
- });
2029
+ function blobUrlsOf(pages) {
2030
+ return pages.flatMap((page) => page.url?.startsWith("blob:") ? [page.url] : []);
2031
+ }
2032
+ function decodeBlobUrls(urls) {
2033
+ if (urls.length === 0 || typeof Image === "undefined") return Promise.resolve();
2034
+ return Promise.all(urls.map((url) => {
2035
+ const img = new Image();
2036
+ img.src = url;
2037
+ const decoded = img.decode?.();
2038
+ return decoded ? decoded.catch(() => {
2039
+ }) : Promise.resolve();
2040
+ })).then(() => void 0);
2041
+ }
2042
+ function deferRevoke(urls) {
2043
+ if (urls.length === 0) return;
2044
+ const run = () => {
2045
+ for (const url of urls) URL.revokeObjectURL(url);
2046
+ };
2047
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
2048
+ else setTimeout(run, 0);
1920
2049
  }
1921
- function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies) {
2050
+ function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies, atomic = false) {
1922
2051
  const disabled = useMemo5(
1923
2052
  () => new Set(strategies?.disable ?? []),
1924
2053
  // eslint-disable-next-line react-hooks/exhaustive-deps
1925
2054
  [strategies?.disable?.join(",")]
1926
2055
  );
1927
2056
  const atlasPlans = useMemo5(
1928
- () => filterAtlasPlans(plans, textureLighting, disabled),
2057
+ () => filterAtlasPlans(
2058
+ plans,
2059
+ textureLighting,
2060
+ disabled,
2061
+ typeof document !== "undefined" ? document : null
2062
+ ),
1929
2063
  [plans, textureLighting, disabled]
1930
2064
  );
1931
2065
  const { packed, atlasScale } = useMemo5(
@@ -1936,38 +2070,93 @@ function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies
1936
2070
  ),
1937
2071
  [atlasPlans, textureQualityInput]
1938
2072
  );
1939
- const [pages, setPages] = useState(
1940
- () => pageShells(packed.pages)
1941
- );
2073
+ const [pages, setPages] = useState(() => pageShells(packed.pages));
2074
+ const [frame, setFrame] = useState(() => ({
2075
+ plans,
2076
+ entries: packed.entries,
2077
+ pages: pageShells(packed.pages)
2078
+ }));
2079
+ const shownUrls = useRef3([]);
2080
+ const seqRef = useRef3(0);
2081
+ const mountedRef = useRef3(true);
1942
2082
  useEffect2(() => {
2083
+ mountedRef.current = true;
2084
+ return () => {
2085
+ mountedRef.current = false;
2086
+ for (const url of shownUrls.current) URL.revokeObjectURL(url);
2087
+ shownUrls.current = [];
2088
+ };
2089
+ }, []);
2090
+ useEffect2(() => {
2091
+ if (atomic) {
2092
+ const seq = ++seqRef.current;
2093
+ const snapPlans = plans;
2094
+ const snapEntries = packed.entries;
2095
+ if (packed.pages.length === 0 || typeof document === "undefined") {
2096
+ deferRevoke(shownUrls.current);
2097
+ shownUrls.current = [];
2098
+ setFrame({ plans: snapPlans, entries: snapEntries, pages: pageShells(packed.pages) });
2099
+ return;
2100
+ }
2101
+ const stale = () => seq !== seqRef.current;
2102
+ let built2 = [];
2103
+ buildAtlasPages(packed.pages, textureLighting, document, atlasScale, stale).then(async (nextPages) => {
2104
+ built2 = blobUrlsOf(nextPages);
2105
+ await decodeBlobUrls(built2);
2106
+ if (!mountedRef.current || stale()) {
2107
+ deferRevoke(built2);
2108
+ return;
2109
+ }
2110
+ const prev = shownUrls.current;
2111
+ shownUrls.current = built2;
2112
+ built2 = [];
2113
+ deferRevoke(prev);
2114
+ setFrame({ plans: snapPlans, entries: snapEntries, pages: nextPages });
2115
+ }).catch(() => {
2116
+ });
2117
+ return;
2118
+ }
1943
2119
  let cancelled = false;
1944
- let urls = [];
1945
- const nextPageShells = pageShells(packed.pages);
1946
- setPages((prev) => textureAtlasPagesEqual(prev, nextPageShells) ? prev : nextPageShells);
1947
- if (packed.pages.length === 0 || typeof document === "undefined") {
2120
+ if (packed.pages.length === 0) {
2121
+ deferRevoke(shownUrls.current);
2122
+ shownUrls.current = [];
2123
+ setPages((prev) => prev.length === 0 ? prev : []);
1948
2124
  return () => {
1949
2125
  };
1950
2126
  }
1951
- buildAtlasPages(packed.pages, textureLighting, document, atlasScale, () => cancelled).then((nextPages) => {
2127
+ if (typeof document === "undefined") return () => {
2128
+ };
2129
+ setPages((prev) => prev.some((page) => page.url) ? prev : pageShells(packed.pages));
2130
+ let built = [];
2131
+ buildAtlasPages(packed.pages, textureLighting, document, atlasScale, () => cancelled).then(async (nextPages) => {
2132
+ built = blobUrlsOf(nextPages);
2133
+ await decodeBlobUrls(built);
1952
2134
  if (cancelled) {
1953
- for (const page of nextPages) {
1954
- if (page.url?.startsWith("blob:")) URL.revokeObjectURL(page.url);
1955
- }
2135
+ deferRevoke(built);
1956
2136
  return;
1957
2137
  }
1958
- urls = nextPages.flatMap((page) => page.url?.startsWith("blob:") ? [page.url] : []);
1959
- setPages((prev) => textureAtlasPagesEqual(prev, nextPages) ? prev : nextPages);
2138
+ const stale = shownUrls.current;
2139
+ shownUrls.current = built;
2140
+ built = [];
2141
+ deferRevoke(stale);
2142
+ setPages(nextPages);
1960
2143
  }).catch(() => {
1961
- if (!cancelled) {
1962
- setPages((prev) => textureAtlasPagesEqual(prev, nextPageShells) ? prev : nextPageShells);
1963
- }
1964
2144
  });
1965
2145
  return () => {
1966
2146
  cancelled = true;
1967
- for (const url of urls) URL.revokeObjectURL(url);
2147
+ deferRevoke(built);
1968
2148
  };
1969
- }, [packed, textureLighting, atlasScale]);
2149
+ }, [packed, textureLighting, atlasScale, atomic]);
2150
+ if (atomic) {
2151
+ return {
2152
+ plans: frame.plans,
2153
+ entries: frame.entries,
2154
+ pages: frame.pages,
2155
+ ready: frame.pages.length === 0 || frame.pages.every((page) => !!page.url)
2156
+ };
2157
+ }
1970
2158
  return {
2159
+ plans,
1971
2160
  entries: packed.entries,
1972
2161
  pages,
1973
2162
  ready: pages.length === 0 || pages.every((page) => !!page.url)
@@ -1998,6 +2187,7 @@ var TextureTrianglePoly = memo3(function TextureTrianglePoly2({
1998
2187
  {
1999
2188
  className: elementClassName,
2000
2189
  style: { ...triangleStyle, ...styleProp },
2190
+ "data-poly-index": entry.index,
2001
2191
  ...domEventHandlers,
2002
2192
  ...dataAttrs,
2003
2193
  ...domAttrs
@@ -2034,6 +2224,7 @@ function orderBrushInlineStyle(el) {
2034
2224
  }
2035
2225
  var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2036
2226
  entry,
2227
+ textureLighting,
2037
2228
  solidPaintDefaults,
2038
2229
  className,
2039
2230
  style: styleProp,
@@ -2046,7 +2237,9 @@ var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2046
2237
  const bDisabled = disabledStrategies?.has("b") ?? false;
2047
2238
  const useIForFullRect = bDisabled && isBorderShapeSupported();
2048
2239
  const borderShape = !fullRect || useIForFullRect ? cssBorderShapeForPlan(entry) : null;
2049
- const useDefaultPaint = entry.shadedColor === solidPaintDefaults?.paintColor;
2240
+ const dynamic = textureLighting === "dynamic";
2241
+ const base = parseHex2(entry.polygon.color ?? "#cccccc");
2242
+ const useDefaultDynamicColor = dynamic && rgbKey2(base) === solidPaintDefaults?.dynamicColorKey;
2050
2243
  const setElementRef = useCallback3((el) => {
2051
2244
  if (!el) return;
2052
2245
  if (borderShape) el.style.setProperty("border-shape", borderShape);
@@ -2056,8 +2249,25 @@ var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2056
2249
  const transform = borderShape ? formatBorderShapeEntryMatrix(entry) : formatSolidQuadEntryMatrix(entry);
2057
2250
  const style = {
2058
2251
  transform,
2059
- color: useDefaultPaint ? void 0 : entry.shadedColor,
2252
+ // Baked mode: always emit per-leaf shaded color when known so the
2253
+ // first render matches the post-light-nudge commit path. Vanilla
2254
+ // dropped the `=== solidPaintDefaults.paintColor` shortcut in
2255
+ // commit 0423777 — leaves with undefined shadedColor would inherit
2256
+ // the (often-wrong) wrapper --polycss-paint default, producing
2257
+ // facets that look dimmer than they should until a light change
2258
+ // forced an explicit re-bake.
2259
+ color: dynamic ? void 0 : entry.shadedColor,
2060
2260
  pointerEvents: pointerEvents === "none" ? "none" : void 0,
2261
+ ...dynamic ? {
2262
+ ["--pnx"]: entry.normal[0].toFixed(4),
2263
+ ["--pny"]: entry.normal[1].toFixed(4),
2264
+ ["--pnz"]: entry.normal[2].toFixed(4),
2265
+ ...useDefaultDynamicColor ? null : {
2266
+ ["--psr"]: (base.r / 255).toFixed(4),
2267
+ ["--psg"]: (base.g / 255).toFixed(4),
2268
+ ["--psb"]: (base.b / 255).toFixed(4)
2269
+ }
2270
+ } : null,
2061
2271
  ...styleProp
2062
2272
  };
2063
2273
  const dataAttrs = entry.polygon.data ? Object.fromEntries(
@@ -2070,6 +2280,7 @@ var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2070
2280
  {
2071
2281
  className: elementClassName,
2072
2282
  style,
2283
+ "data-poly-index": entry.index,
2073
2284
  ...domEventHandlers,
2074
2285
  ...dataAttrs,
2075
2286
  ...domAttrs
@@ -2082,6 +2293,7 @@ var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2082
2293
  ref: setElementRef,
2083
2294
  className: elementClassName,
2084
2295
  style,
2296
+ "data-poly-index": entry.index,
2085
2297
  ...domEventHandlers,
2086
2298
  ...dataAttrs,
2087
2299
  ...domAttrs
@@ -2111,7 +2323,8 @@ var TextureProjectiveSolidPoly = memo5(function TextureProjectiveSolidPoly2({
2111
2323
  // would drop it to 3 decimals and leave visible seam gaps between
2112
2324
  // adjacent projective quads at zoom-out (matches vanilla scene.add).
2113
2325
  transform: `matrix3d(${entry.projectiveMatrix})`,
2114
- color: dynamic || entry.shadedColor === solidPaintDefaults?.paintColor ? void 0 : entry.shadedColor,
2326
+ // Baked: always emit per-leaf shaded color (vanilla commit 0423777).
2327
+ color: dynamic ? void 0 : entry.shadedColor,
2115
2328
  pointerEvents: pointerEvents === "none" ? "none" : void 0,
2116
2329
  ...dynamic && !useDefaultDynamicColor ? {
2117
2330
  ["--pnx"]: entry.normal[0].toFixed(4),
@@ -2136,6 +2349,69 @@ var TextureProjectiveSolidPoly = memo5(function TextureProjectiveSolidPoly2({
2136
2349
  {
2137
2350
  className: elementClassName,
2138
2351
  style,
2352
+ "data-poly-index": entry.index,
2353
+ ...domEventHandlers,
2354
+ ...dataAttrs,
2355
+ ...domAttrs
2356
+ }
2357
+ );
2358
+ });
2359
+
2360
+ // src/scene/atlas/cornerShapeSolid.tsx
2361
+ import { memo as memo6, useCallback as useCallback4 } from "react";
2362
+ import {
2363
+ formatCornerShapeElementStyle,
2364
+ parseHex as parseHex3,
2365
+ rgbKey as rgbKey4
2366
+ } from "@layoutit/polycss-core";
2367
+ import { jsx as jsx6 } from "react/jsx-runtime";
2368
+ function formatPaintCss(entry, textureLighting, solidPaintDefaults) {
2369
+ if (textureLighting === "dynamic") {
2370
+ const base = parseHex3(entry.polygon.color ?? "#cccccc");
2371
+ let style = `;--pnx:${entry.normal[0].toFixed(4)};--pny:${entry.normal[1].toFixed(4)};--pnz:${entry.normal[2].toFixed(4)}`;
2372
+ if (rgbKey4(base) !== solidPaintDefaults?.dynamicColorKey) {
2373
+ style += `;--psr:${(base.r / 255).toFixed(4)};--psg:${(base.g / 255).toFixed(4)};--psb:${(base.b / 255).toFixed(4)}`;
2374
+ }
2375
+ return style;
2376
+ }
2377
+ return entry.shadedColor ? `;color:${entry.shadedColor}` : "";
2378
+ }
2379
+ var TextureCornerShapeSolidPoly = memo6(function TextureCornerShapeSolidPoly2({
2380
+ entry,
2381
+ geometry,
2382
+ textureLighting,
2383
+ solidPaintDefaults,
2384
+ className,
2385
+ style: styleProp,
2386
+ domAttrs,
2387
+ domEventHandlers,
2388
+ pointerEvents = "auto"
2389
+ }) {
2390
+ const cornerShapeCss = formatCornerShapeElementStyle(entry, geometry);
2391
+ const paintCss = formatPaintCss(entry, textureLighting, solidPaintDefaults);
2392
+ const pointerCss = pointerEvents === "none" ? ";pointer-events:none" : "";
2393
+ const fullCss = cornerShapeCss + paintCss + pointerCss;
2394
+ const setRef = useCallback4((el) => {
2395
+ if (!el) return;
2396
+ el.setAttribute("style", fullCss);
2397
+ if (styleProp) {
2398
+ for (const [k, v] of Object.entries(styleProp)) {
2399
+ if (v === void 0 || v === null) continue;
2400
+ if (k.startsWith("--")) el.style.setProperty(k, String(v));
2401
+ else el.style[k] = String(v);
2402
+ }
2403
+ }
2404
+ }, [fullCss, styleProp]);
2405
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
2406
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2407
+ ) : {};
2408
+ const elementClassName = className?.trim() || void 0;
2409
+ return /* @__PURE__ */ jsx6(
2410
+ "u",
2411
+ {
2412
+ ref: setRef,
2413
+ className: elementClassName,
2414
+ "data-poly-index": entry.index,
2139
2415
  ...domEventHandlers,
2140
2416
  ...dataAttrs,
2141
2417
  ...domAttrs
@@ -2144,10 +2420,10 @@ var TextureProjectiveSolidPoly = memo5(function TextureProjectiveSolidPoly2({
2144
2420
  });
2145
2421
 
2146
2422
  // src/scene/atlas/atlasPoly.tsx
2147
- import { memo as memo6 } from "react";
2423
+ import { memo as memo7 } from "react";
2148
2424
  import { formatMatrix3d, formatCssLengthPx } from "@layoutit/polycss-core";
2149
- import { jsx as jsx6 } from "react/jsx-runtime";
2150
- var TextureAtlasPoly = memo6(function TextureAtlasPoly2({
2425
+ import { jsx as jsx7 } from "react/jsx-runtime";
2426
+ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2151
2427
  entry,
2152
2428
  page,
2153
2429
  textureLighting,
@@ -2170,10 +2446,16 @@ var TextureAtlasPoly = memo6(function TextureAtlasPoly2({
2170
2446
  const style = {
2171
2447
  transform: formatMatrix3d(entry.atlasMatrix),
2172
2448
  ["--polycss-atlas-size"]: `${atlasCanonicalSize}px`,
2173
- background,
2174
- backgroundImage: dynamic && page?.url ? `url(${page.url})` : void 0,
2175
- backgroundPosition: dynamic ? atlasPosition : void 0,
2176
- backgroundSize: dynamic ? atlasSize : void 0,
2449
+ // Listing the `background` shorthand alongside the `background-*` longhands
2450
+ // in one inline style object makes React warn on every update (mixing
2451
+ // shorthand and non-shorthand for the same property). Branch so only the
2452
+ // current mode's keys are assigned — baked gets `background`, dynamic gets
2453
+ // the longhands.
2454
+ ...dynamic ? {
2455
+ backgroundImage: page?.url ? `url(${page.url})` : void 0,
2456
+ backgroundPosition: atlasPosition,
2457
+ backgroundSize: atlasSize
2458
+ } : { background },
2177
2459
  ...dynamic ? {
2178
2460
  ["--pnx"]: entry.normal[0].toFixed(4),
2179
2461
  ["--pny"]: entry.normal[1].toFixed(4),
@@ -2198,11 +2480,12 @@ var TextureAtlasPoly = memo6(function TextureAtlasPoly2({
2198
2480
  Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2199
2481
  ) : {};
2200
2482
  const elementClassName = className?.trim() || void 0;
2201
- return /* @__PURE__ */ jsx6(
2483
+ return /* @__PURE__ */ jsx7(
2202
2484
  "s",
2203
2485
  {
2204
2486
  className: elementClassName,
2205
2487
  style,
2488
+ "data-poly-index": entry.index,
2206
2489
  ...domEventHandlers,
2207
2490
  ...dataAttrs,
2208
2491
  ...domAttrs
@@ -2218,7 +2501,7 @@ function usePolySceneContext2() {
2218
2501
  }
2219
2502
 
2220
2503
  // src/scene/PolyScene.tsx
2221
- import { jsx as jsx7, jsxs } from "react/jsx-runtime";
2504
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
2222
2505
  function PolySceneInner({
2223
2506
  polygons: polygonsProp,
2224
2507
  centerPolygons: centerPolygonsProp,
@@ -2244,9 +2527,11 @@ function PolySceneInner({
2244
2527
  debugShowBackfaces
2245
2528
  }) {
2246
2529
  const { store, sceneElRef, applyTransformDirect } = useCameraContext();
2247
- const localSceneRef = useCallback4(
2530
+ const [sceneEl, setSceneEl] = useState2(null);
2531
+ const localSceneRef = useCallback5(
2248
2532
  (el) => {
2249
2533
  sceneElRef.current = el;
2534
+ setSceneEl(el);
2250
2535
  },
2251
2536
  [sceneElRef]
2252
2537
  );
@@ -2255,7 +2540,7 @@ function PolySceneInner({
2255
2540
  if (!el) return;
2256
2541
  el.classList.toggle("polycss-debug-show-backfaces", !!debugShowBackfaces);
2257
2542
  }, [debugShowBackfaces, sceneElRef]);
2258
- const injectedRef = useRef3(false);
2543
+ const injectedRef = useRef4(false);
2259
2544
  useEffect3(() => {
2260
2545
  if (injectedRef.current) return;
2261
2546
  if (typeof document !== "undefined") {
@@ -2326,7 +2611,8 @@ function PolySceneInner({
2326
2611
  const textureAtlas = useTextureAtlas(textureAtlasPlans, textureLighting, textureQuality, strategies);
2327
2612
  const dynamicLightVars = useMemo6(() => {
2328
2613
  if (textureLighting !== "dynamic") return null;
2329
- const dir = directionalLight?.direction ?? [0.4, -0.7, 0.59];
2614
+ const userDir = directionalLight?.direction ?? [0.4, -0.7, 0.59];
2615
+ const dir = worldDirectionToCss(userDir);
2330
2616
  const len = Math.hypot(dir[0], dir[1], dir[2]) || 1;
2331
2617
  const lx = dir[0] / len, ly = dir[1] / len, lz = dir[2] / len;
2332
2618
  const lightRgb = parseHexColor(directionalLight?.color ?? "#ffffff")?.rgb ?? [255, 255, 255];
@@ -2337,9 +2623,9 @@ function PolySceneInner({
2337
2623
  const rawClz = lz;
2338
2624
  const clz = Math.sign(rawClz || 1) * Math.max(Math.abs(rawClz), 0.01);
2339
2625
  return {
2340
- ["--plx"]: lx.toFixed(4),
2341
- ["--ply"]: ly.toFixed(4),
2342
- ["--plz"]: lz.toFixed(4),
2626
+ ["--plx"]: lx.toFixed(2),
2627
+ ["--ply"]: ly.toFixed(2),
2628
+ ["--plz"]: lz.toFixed(2),
2343
2629
  ["--plr"]: ch(lightRgb[0]),
2344
2630
  ["--plg"]: ch(lightRgb[1]),
2345
2631
  ["--plb"]: ch(lightRgb[2]),
@@ -2348,19 +2634,23 @@ function PolySceneInner({
2348
2634
  ["--pag"]: ch(ambRgb[1]),
2349
2635
  ["--pab"]: ch(ambRgb[2]),
2350
2636
  ["--pai"]: ambientIntensity.toFixed(4),
2351
- ["--clx"]: lx.toFixed(4),
2352
- ["--cly"]: ly.toFixed(4),
2353
- ["--clz"]: clz.toFixed(4)
2637
+ ["--clx"]: lx.toFixed(2),
2638
+ ["--cly"]: ly.toFixed(2),
2639
+ ["--clz"]: clz.toFixed(2)
2354
2640
  };
2355
2641
  }, [textureLighting, directionalLight, ambientLight]);
2356
- const shadowCastersRef = useRef3(/* @__PURE__ */ new Map());
2642
+ const shadowCastersRef = useRef4(/* @__PURE__ */ new Map());
2643
+ const shadowReceiversRef = useRef4(/* @__PURE__ */ new Set());
2357
2644
  const [groundCssZ, setGroundCssZ] = useState2(null);
2358
- const recomputeGroundCssZ = useCallback4(() => {
2645
+ const [shadowCastersVersion, setShadowCastersVersion] = useState2(0);
2646
+ const [hasShadowReceiver, setHasShadowReceiver] = useState2(false);
2647
+ const recomputeGroundCssZ = useCallback5(() => {
2359
2648
  let minWorldZ = Infinity;
2360
- for (const polys of shadowCastersRef.current.values()) {
2361
- for (const poly of polys) {
2649
+ for (const data of shadowCastersRef.current.values()) {
2650
+ for (const poly of data.polygons) {
2362
2651
  for (const v of poly.vertices) {
2363
- if (v[2] < minWorldZ) minWorldZ = v[2];
2652
+ const z = v[2] + (data.position[2] ?? 0);
2653
+ if (z < minWorldZ) minWorldZ = z;
2364
2654
  }
2365
2655
  }
2366
2656
  }
@@ -2368,12 +2658,13 @@ function PolySceneInner({
2368
2658
  const lift = shadow?.lift ?? 0.05;
2369
2659
  return (minWorldZ + lift) * BASE_TILE2;
2370
2660
  }, [shadow]);
2371
- const registerShadowCaster = useCallback4((meshId, meshPolygons) => {
2372
- if (meshPolygons === null) {
2661
+ const registerShadowCaster = useCallback5((meshId, data) => {
2662
+ if (data === null) {
2373
2663
  shadowCastersRef.current.delete(meshId);
2374
2664
  } else {
2375
- shadowCastersRef.current.set(meshId, meshPolygons);
2665
+ shadowCastersRef.current.set(meshId, data);
2376
2666
  }
2667
+ setShadowCastersVersion((v) => v + 1);
2377
2668
  const next = recomputeGroundCssZ();
2378
2669
  setGroundCssZ((prev) => prev === next ? prev : next);
2379
2670
  const el = sceneElRef.current;
@@ -2384,6 +2675,11 @@ function PolySceneInner({
2384
2675
  el.style.removeProperty("--shadow-ground-cssz");
2385
2676
  }
2386
2677
  }, [sceneElRef, textureLighting, recomputeGroundCssZ]);
2678
+ const registerShadowReceiver = useCallback5((meshId, registered) => {
2679
+ if (registered) shadowReceiversRef.current.add(meshId);
2680
+ else shadowReceiversRef.current.delete(meshId);
2681
+ setHasShadowReceiver(shadowReceiversRef.current.size > 0);
2682
+ }, []);
2387
2683
  useEffect3(() => {
2388
2684
  const el = sceneElRef.current;
2389
2685
  if (!el) return;
@@ -2405,7 +2701,7 @@ function PolySceneInner({
2405
2701
  );
2406
2702
  const polyChildren = textureAtlas.entries.map((entry, index) => {
2407
2703
  if (entry) {
2408
- return /* @__PURE__ */ jsx7(
2704
+ return /* @__PURE__ */ jsx8(
2409
2705
  TextureAtlasPoly,
2410
2706
  {
2411
2707
  entry,
@@ -2420,7 +2716,7 @@ function PolySceneInner({
2420
2716
  const useU = !disabledStrategies?.has("u");
2421
2717
  const useProjectiveSolid = !disabledStrategies?.has("b");
2422
2718
  if (useU && isSolidTrianglePlan2(plan)) {
2423
- return /* @__PURE__ */ jsx7(
2719
+ return /* @__PURE__ */ jsx8(
2424
2720
  TextureTrianglePoly,
2425
2721
  {
2426
2722
  entry: plan,
@@ -2430,9 +2726,9 @@ function PolySceneInner({
2430
2726
  );
2431
2727
  }
2432
2728
  if (useProjectiveSolid && isProjectiveQuadPlan(plan)) {
2433
- return /* @__PURE__ */ jsx7(TextureProjectiveSolidPoly, { entry: plan, textureLighting }, plan.index);
2729
+ return /* @__PURE__ */ jsx8(TextureProjectiveSolidPoly, { entry: plan, textureLighting }, plan.index);
2434
2730
  }
2435
- return /* @__PURE__ */ jsx7(TextureBorderShapePoly, { entry: plan, disabledStrategies }, plan.index);
2731
+ return /* @__PURE__ */ jsx8(TextureBorderShapePoly, { entry: plan, textureLighting, disabledStrategies }, plan.index);
2436
2732
  });
2437
2733
  const sceneCtxValue = useMemo6(
2438
2734
  () => ({
@@ -2443,11 +2739,16 @@ function PolySceneInner({
2443
2739
  seamBleed,
2444
2740
  shadow,
2445
2741
  registerShadowCaster,
2446
- groundCssZ
2742
+ registerShadowReceiver,
2743
+ shadowCasters: shadowCastersRef.current,
2744
+ shadowCastersVersion,
2745
+ hasShadowReceiver,
2746
+ groundCssZ,
2747
+ sceneEl
2447
2748
  }),
2448
- [textureLighting, directionalLight, ambientLight, strategies, seamBleed, shadow, registerShadowCaster, groundCssZ]
2749
+ [textureLighting, directionalLight, ambientLight, strategies, seamBleed, shadow, registerShadowCaster, registerShadowReceiver, shadowCastersVersion, hasShadowReceiver, groundCssZ, sceneEl]
2449
2750
  );
2450
- return /* @__PURE__ */ jsx7(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */ jsxs(
2751
+ return /* @__PURE__ */ jsx8(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */ jsxs(
2451
2752
  "div",
2452
2753
  {
2453
2754
  ref: localSceneRef,
@@ -2465,33 +2766,41 @@ function PolySceneInner({
2465
2766
  }
2466
2767
  ) });
2467
2768
  }
2468
- var PolyScene = memo7(PolySceneInner);
2769
+ var PolyScene = memo8(PolySceneInner);
2469
2770
 
2470
2771
  // src/scene/PolyMesh.tsx
2471
2772
  import {
2472
2773
  forwardRef,
2473
- useCallback as useCallback6,
2774
+ useCallback as useCallback7,
2474
2775
  useContext as useContext3,
2475
2776
  useEffect as useEffect5,
2476
2777
  useImperativeHandle,
2477
2778
  useLayoutEffect as useLayoutEffect2,
2478
2779
  useMemo as useMemo7,
2479
- useRef as useRef5,
2780
+ useRef as useRef6,
2480
2781
  useState as useState4
2481
2782
  } from "react";
2482
2783
  import {
2483
2784
  BASE_TILE as BASE_TILE4,
2785
+ buildPolyMeshTransform,
2786
+ buildSharedEdgeMap,
2787
+ computeReceiverShadowFaces,
2484
2788
  computeSceneBbox,
2485
2789
  DEFAULT_SEAM_BLEED as DEFAULT_SEAM_BLEED2,
2486
2790
  ensureCcw2D,
2487
2791
  findOverlappingPolygonDuplicates,
2488
2792
  inverseRotateVec3,
2793
+ optimizeMeshPolygons,
2489
2794
  parseHexColor as parseHexColor2,
2490
- projectCssVertexToGround
2795
+ prepareCasterEdgeOwners,
2796
+ prepareCasterPolyItems,
2797
+ prepareReceiverFacePlanes,
2798
+ projectCssVertexToGround,
2799
+ worldDirectionToCss as worldDirectionToCss2
2491
2800
  } from "@layoutit/polycss-core";
2492
2801
 
2493
2802
  // src/scene/useMesh.ts
2494
- import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
2803
+ import { useCallback as useCallback6, useEffect as useEffect4, useRef as useRef5, useState as useState3 } from "react";
2495
2804
  import { loadMesh } from "@layoutit/polycss-core";
2496
2805
  var EMPTY_POLYGONS = [];
2497
2806
  var EMPTY_WARNINGS = [];
@@ -2503,8 +2812,8 @@ function usePolyMesh(src, options) {
2503
2812
  error: null,
2504
2813
  warnings: EMPTY_WARNINGS
2505
2814
  });
2506
- const activeResultRef = useRef4(null);
2507
- const dispose = useCallback5(() => {
2815
+ const activeResultRef = useRef5(null);
2816
+ const dispose = useCallback6(() => {
2508
2817
  const r = activeResultRef.current;
2509
2818
  if (r) {
2510
2819
  try {
@@ -2579,6 +2888,10 @@ function usePolyMesh(src, options) {
2579
2888
  };
2580
2889
  }
2581
2890
 
2891
+ // src/scene/PolyMesh.tsx
2892
+ import { buildBasisHints, cornerShapeGeometryForPlan, worldDirectionalLightToCss } from "@layoutit/polycss-core";
2893
+ import { createPortal } from "react-dom";
2894
+
2582
2895
  // src/scene/voxelRenderer.ts
2583
2896
  import {
2584
2897
  BASE_TILE as BASE_TILE3,
@@ -2588,6 +2901,7 @@ import {
2588
2901
  PROJECTIVE_QUAD_BLEED,
2589
2902
  resolveProjectiveQuadGuards,
2590
2903
  rotateVec3,
2904
+ shadePolygon as shadePolygon2,
2591
2905
  SOLID_QUAD_CANONICAL_SIZE
2592
2906
  } from "@layoutit/polycss-core";
2593
2907
  var DEFAULT_LIGHT_DIR2 = [0.4, -0.7, 0.59];
@@ -2848,9 +3162,6 @@ function clampChannel(value) {
2848
3162
  return Math.round(Math.max(0, Math.min(255, value)));
2849
3163
  }
2850
3164
  function shadeBrushColor(normal, baseColor, directionalLight, ambientLight) {
2851
- const base = parseColor(baseColor);
2852
- const light = parseColor(directionalLight?.color ?? DEFAULT_LIGHT_COLOR2);
2853
- const ambient = parseColor(ambientLight?.color ?? DEFAULT_AMBIENT_COLOR2);
2854
3165
  const lightDir = directionalLight?.direction ?? DEFAULT_LIGHT_DIR2;
2855
3166
  const lightLen = Math.hypot(lightDir[0], lightDir[1], lightDir[2]) || 1;
2856
3167
  const lx = lightDir[0] / lightLen;
@@ -2858,16 +3169,15 @@ function shadeBrushColor(normal, baseColor, directionalLight, ambientLight) {
2858
3169
  const lz = lightDir[2] / lightLen;
2859
3170
  const directScale = Math.max(0, directionalLight?.intensity ?? DEFAULT_LIGHT_INTENSITY2) * Math.max(0, normal[0] * lx + normal[1] * ly + normal[2] * lz);
2860
3171
  const ambientIntensity = Math.max(0, ambientLight?.intensity ?? DEFAULT_AMBIENT_INTENSITY2);
2861
- const tintR = ambient.r / 255 * ambientIntensity + light.r / 255 * directScale;
2862
- const tintG = ambient.g / 255 * ambientIntensity + light.g / 255 * directScale;
2863
- const tintB = ambient.b / 255 * ambientIntensity + light.b / 255 * directScale;
2864
- const shaded = {
2865
- r: base.r * tintR,
2866
- g: base.g * tintG,
2867
- b: base.b * tintB,
2868
- alpha: base.alpha
2869
- };
2870
- return shaded.alpha < 1 ? `rgba(${clampChannel(shaded.r)}, ${clampChannel(shaded.g)}, ${clampChannel(shaded.b)}, ${shaded.alpha})` : rgbToHex2(shaded);
3172
+ const lightColor = directionalLight?.color ?? DEFAULT_LIGHT_COLOR2;
3173
+ const ambientColor = ambientLight?.color ?? DEFAULT_AMBIENT_COLOR2;
3174
+ const shaded = shadePolygon2(baseColor, directScale, lightColor, ambientColor, ambientIntensity);
3175
+ const base = parseColor(baseColor);
3176
+ if (base.alpha >= 1) return shaded;
3177
+ const r = parseInt(shaded.slice(1, 3), 16);
3178
+ const g = parseInt(shaded.slice(3, 5), 16);
3179
+ const b = parseInt(shaded.slice(5, 7), 16);
3180
+ return `rgba(${r}, ${g}, ${b}, ${base.alpha})`;
2871
3181
  }
2872
3182
  function buildDirectMatrixItems(polygons) {
2873
3183
  if (!polygons?.length) return [];
@@ -3313,7 +3623,9 @@ function findMeshUnderPoint(clientX, clientY, filter) {
3313
3623
  }
3314
3624
 
3315
3625
  // src/scene/PolyMesh.tsx
3316
- import { Fragment, jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
3626
+ import { Fragment, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
3627
+ var reactEdgeOwnersCache = /* @__PURE__ */ new WeakMap();
3628
+ var reactEdgeOwnersCacheKey = /* @__PURE__ */ new WeakMap();
3317
3629
  function solidPaintVars(defaults) {
3318
3630
  const out = {};
3319
3631
  if (defaults.paintColor) out["--polycss-paint"] = defaults.paintColor;
@@ -3324,25 +3636,6 @@ function solidPaintVars(defaults) {
3324
3636
  }
3325
3637
  return Object.keys(out).length > 0 ? out : null;
3326
3638
  }
3327
- function buildTransform(position, scale, rotation) {
3328
- const parts = [];
3329
- if (position) {
3330
- parts.push(`translate3d(${position[0]}px, ${position[1]}px, ${position[2]}px)`);
3331
- }
3332
- if (scale !== void 0) {
3333
- if (typeof scale === "number") {
3334
- if (scale !== 1) parts.push(`scale3d(${scale}, ${scale}, ${scale})`);
3335
- } else {
3336
- parts.push(`scale3d(${scale[0]}, ${scale[1]}, ${scale[2]})`);
3337
- }
3338
- }
3339
- if (rotation) {
3340
- if (rotation[0]) parts.push(`rotateX(${rotation[0]}deg)`);
3341
- if (rotation[1]) parts.push(`rotateY(${rotation[1]}deg)`);
3342
- if (rotation[2]) parts.push(`rotateZ(${rotation[2]}deg)`);
3343
- }
3344
- return parts.length > 0 ? parts.join(" ") : void 0;
3345
- }
3346
3639
  function recenterPolygons(polygons) {
3347
3640
  if (polygons.length === 0) return polygons;
3348
3641
  const bbox = computeSceneBbox(polygons);
@@ -3367,11 +3660,16 @@ var PolyMesh = forwardRef(function PolyMesh2({
3367
3660
  src,
3368
3661
  mtl,
3369
3662
  polygons: polygonsProp,
3663
+ voxelSource: voxelSourceProp,
3370
3664
  autoCenter,
3371
3665
  textureLighting,
3372
3666
  textureQuality,
3373
3667
  seamBleed,
3668
+ atomicAtlas,
3669
+ onFrameReady,
3374
3670
  castShadow,
3671
+ receiveShadow,
3672
+ merge = true,
3375
3673
  children,
3376
3674
  fallback,
3377
3675
  errorFallback,
@@ -3405,14 +3703,18 @@ var PolyMesh = forwardRef(function PolyMesh2({
3405
3703
  }, [mtl, parseOptions, meshResolution]);
3406
3704
  const fetched = usePolyMesh(src ?? "", mergedOptions);
3407
3705
  const externalPolygons = src ? fetched.polygons : polygonsProp ?? [];
3408
- const externalVoxelSource = src ? fetched.voxelSource : void 0;
3706
+ const externalVoxelSource = src ? fetched.voxelSource : voxelSourceProp;
3409
3707
  const [localPolygons, setLocalPolygons] = useState4(null);
3410
- const prevExternalRef = useRef5(externalPolygons);
3708
+ const prevExternalRef = useRef6(externalPolygons);
3411
3709
  if (prevExternalRef.current !== externalPolygons) {
3412
3710
  prevExternalRef.current = externalPolygons;
3413
3711
  if (localPolygons !== null) setLocalPolygons(null);
3414
3712
  }
3415
- const sourcePolygons = localPolygons ?? externalPolygons;
3713
+ const rawSourcePolygons = localPolygons ?? externalPolygons;
3714
+ const sourcePolygons = useMemo7(
3715
+ () => merge ? optimizeMeshPolygons(rawSourcePolygons, meshResolution !== void 0 ? { meshResolution } : void 0) : rawSourcePolygons,
3716
+ [rawSourcePolygons, merge, meshResolution]
3717
+ );
3416
3718
  const hasRenderProp = typeof children === "function";
3417
3719
  const renderPolygon = hasRenderProp ? children : null;
3418
3720
  const staticChildren = hasRenderProp ? null : children;
@@ -3421,36 +3723,15 @@ var PolyMesh = forwardRef(function PolyMesh2({
3421
3723
  () => autoCenter ? recenterPolygons(sourcePolygons) : sourcePolygons,
3422
3724
  [sourcePolygons, autoCenter]
3423
3725
  );
3424
- const transform = buildTransform(position, scale, rotation);
3425
- const transformOrigin = useMemo7(() => {
3426
- if (polygons.length === 0) return void 0;
3427
- let minX = Infinity, minY = Infinity, minZ = Infinity;
3428
- let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
3429
- for (const poly of polygons) {
3430
- for (const v of poly.vertices) {
3431
- if (v[0] < minX) minX = v[0];
3432
- if (v[0] > maxX) maxX = v[0];
3433
- if (v[1] < minY) minY = v[1];
3434
- if (v[1] > maxY) maxY = v[1];
3435
- if (v[2] < minZ) minZ = v[2];
3436
- if (v[2] > maxZ) maxZ = v[2];
3437
- }
3438
- }
3439
- if (!Number.isFinite(minX)) return void 0;
3440
- const tile = 50;
3441
- const x = (minY + maxY) / 2 * tile;
3442
- const y = (minX + maxX) / 2 * tile;
3443
- const z = (minZ + maxZ) / 2 * tile;
3444
- return `${x}px ${y}px ${z}px`;
3445
- }, [polygons]);
3446
- const wrapperRef = useRef5(null);
3447
- const propsRef = useRef5({ position, scale, rotation });
3726
+ const transform = buildPolyMeshTransform({ position, scale, rotation });
3727
+ const wrapperRef = useRef6(null);
3728
+ const propsRef = useRef6({ position, scale, rotation });
3448
3729
  propsRef.current = { position, scale, rotation };
3449
- const polygonsRef = useRef5(polygons);
3730
+ const polygonsRef = useRef6(polygons);
3450
3731
  polygonsRef.current = polygons;
3451
3732
  const [bakedRotation, setBakedRotation] = useState4(rotation);
3452
- const stableTriangleColorFrameRef = useRef5(0);
3453
- const setPolygonsImplRef = useRef5(() => {
3733
+ const stableTriangleColorFrameRef = useRef6(0);
3734
+ const setPolygonsImplRef = useRef6(() => {
3454
3735
  });
3455
3736
  const handle = useMemo7(() => ({
3456
3737
  get element() {
@@ -3482,8 +3763,8 @@ var PolyMesh = forwardRef(function PolyMesh2({
3482
3763
  }, [handle]);
3483
3764
  const cameraCtx = useContext3(PolyCameraContext);
3484
3765
  const cameraElRef = cameraCtx?.cameraElRef ?? null;
3485
- const pointerDownAtRef = useRef5(null);
3486
- const makeEvent = useCallback6(
3766
+ const pointerDownAtRef = useRef6(null);
3767
+ const makeEvent = useCallback7(
3487
3768
  function makeEvent2(nativeEvent, clientX, clientY) {
3488
3769
  const intersections = [];
3489
3770
  if (typeof document !== "undefined" && typeof document.elementsFromPoint === "function") {
@@ -3606,9 +3887,13 @@ var PolyMesh = forwardRef(function PolyMesh2({
3606
3887
  const sceneCtx = usePolySceneContext2();
3607
3888
  const effectiveTextureLighting = textureLighting ?? sceneCtx?.textureLighting ?? "baked";
3608
3889
  const effectiveStrategies = sceneCtx?.strategies;
3890
+ const disabledStrategies = useMemo7(
3891
+ () => effectiveStrategies?.disable?.length ? new Set(effectiveStrategies.disable) : void 0,
3892
+ [effectiveStrategies]
3893
+ );
3609
3894
  const effectiveSeamBleed = seamBleed ?? sceneCtx?.seamBleed ?? DEFAULT_SEAM_BLEED2;
3610
- const effectiveDirectional = effectiveTextureLighting === "dynamic" ? void 0 : sceneCtx?.directionalLight;
3611
- const effectiveAmbient = effectiveTextureLighting === "dynamic" ? void 0 : sceneCtx?.ambientLight;
3895
+ const effectiveDirectional = sceneCtx?.directionalLight;
3896
+ const effectiveAmbient = sceneCtx?.ambientLight;
3612
3897
  const directVoxelEnabled = Boolean(
3613
3898
  externalVoxelSource && localPolygons === null && !renderPolygon && !hasStaticChildren && effectiveTextureLighting === "baked" && !castShadow
3614
3899
  );
@@ -3621,20 +3906,22 @@ var PolyMesh = forwardRef(function PolyMesh2({
3621
3906
  const localDir = inverseRotateVec3(dir, rotation);
3622
3907
  const len = Math.hypot(localDir[0], localDir[1], localDir[2]) || 1;
3623
3908
  return {
3624
- ["--plx"]: (localDir[0] / len).toFixed(4),
3625
- ["--ply"]: (localDir[1] / len).toFixed(4),
3626
- ["--plz"]: (localDir[2] / len).toFixed(4)
3909
+ ["--plx"]: (localDir[0] / len).toFixed(2),
3910
+ ["--ply"]: (localDir[1] / len).toFixed(2),
3911
+ ["--plz"]: (localDir[2] / len).toFixed(2)
3627
3912
  };
3628
3913
  }, [effectiveTextureLighting, rotation, sceneDirectionalLight]);
3629
3914
  const bakedDirectional = useMemo7(() => {
3630
3915
  if (!effectiveDirectional) return effectiveDirectional;
3631
3916
  const rot = bakedRotation ?? [0, 0, 0];
3632
- if (rot[0] === 0 && rot[1] === 0 && rot[2] === 0) return effectiveDirectional;
3917
+ const cssLight = worldDirectionalLightToCss(effectiveDirectional);
3918
+ if (rot[0] === 0 && rot[1] === 0 && rot[2] === 0) return cssLight;
3633
3919
  return {
3634
- ...effectiveDirectional,
3635
- direction: inverseRotateVec3(effectiveDirectional.direction, rot)
3920
+ ...cssLight,
3921
+ direction: inverseRotateVec3(cssLight.direction, rot)
3636
3922
  };
3637
3923
  }, [effectiveDirectional, bakedRotation]);
3924
+ const lightOccludedPolyIndices = void 0;
3638
3925
  const atlasPlans = useMemo7(
3639
3926
  () => {
3640
3927
  if (renderPolygon || directVoxelEnabled) return [];
@@ -3643,53 +3930,101 @@ var PolyMesh = forwardRef(function PolyMesh2({
3643
3930
  directionalLight: bakedDirectional,
3644
3931
  ambientLight: effectiveAmbient
3645
3932
  }) : null;
3646
- return polygons.map((p, i) => computeTextureAtlasPlan(p, i, {
3933
+ const basisHints = buildBasisHints(polygons, {
3647
3934
  directionalLight: bakedDirectional,
3648
- ambientLight: effectiveAmbient,
3649
- seamBleed: seamBleedEdges?.has(i) ? effectiveSeamBleed : void 0,
3650
- seamEdges: seamBleedEdges?.get(i),
3651
- textureEdgeRepairEdges: repairEdges[i]
3652
- }));
3935
+ ambientLight: effectiveAmbient
3936
+ });
3937
+ return polygons.map((p, i) => computeTextureAtlasPlan(
3938
+ p,
3939
+ i,
3940
+ {
3941
+ directionalLight: bakedDirectional,
3942
+ ambientLight: effectiveAmbient,
3943
+ seamBleed: seamBleedEdges?.has(i) ? effectiveSeamBleed : void 0,
3944
+ seamEdges: seamBleedEdges?.get(i),
3945
+ textureEdgeRepairEdges: repairEdges[i],
3946
+ lightOccludedPolyIndices
3947
+ },
3948
+ basisHints[i]
3949
+ ));
3653
3950
  },
3654
- [renderPolygon, directVoxelEnabled, polygons, bakedDirectional, effectiveAmbient, effectiveSeamBleed]
3951
+ [renderPolygon, directVoxelEnabled, polygons, bakedDirectional, effectiveAmbient, effectiveSeamBleed, lightOccludedPolyIndices]
3655
3952
  );
3656
3953
  const textureAtlas = useTextureAtlas(
3657
3954
  atlasPlans,
3658
3955
  effectiveTextureLighting,
3659
3956
  textureQuality,
3660
- effectiveStrategies
3957
+ effectiveStrategies,
3958
+ atomicAtlas
3661
3959
  );
3662
3960
  const solidPaintDefaults = useMemo7(
3663
- () => !renderPolygon ? getSolidPaintDefaults(atlasPlans, effectiveTextureLighting, effectiveStrategies) : {},
3664
- [renderPolygon, atlasPlans, effectiveTextureLighting, effectiveStrategies]
3961
+ () => !renderPolygon ? getSolidPaintDefaults(textureAtlas.plans, effectiveTextureLighting, effectiveStrategies) : {},
3962
+ [renderPolygon, textureAtlas.plans, effectiveTextureLighting, effectiveStrategies]
3665
3963
  );
3964
+ const onFrameReadyRef = useRef6(onFrameReady);
3965
+ onFrameReadyRef.current = onFrameReady;
3966
+ useLayoutEffect2(() => {
3967
+ if (atomicAtlas && textureAtlas.ready) onFrameReadyRef.current?.();
3968
+ }, [textureAtlas.entries]);
3666
3969
  const defaultPaintVars = useMemo7(
3667
3970
  () => solidPaintVars(solidPaintDefaults),
3668
3971
  [solidPaintDefaults]
3669
3972
  );
3670
- const meshIdRef = useRef5(/* @__PURE__ */ Symbol());
3973
+ const meshIdRef = useRef6(/* @__PURE__ */ Symbol());
3671
3974
  const sceneRegisterShadowCaster = sceneCtx?.registerShadowCaster;
3975
+ const renderedPolygonIndices = useMemo7(() => {
3976
+ const dedupDrop = findOverlappingPolygonDuplicates(polygons, {
3977
+ normalTolerance: 0.1,
3978
+ distanceTolerance: 0.5,
3979
+ overlapFraction: 0.95,
3980
+ preserveDoubleSidedBackfaces: false
3981
+ });
3982
+ const s = /* @__PURE__ */ new Set();
3983
+ for (let i = 0; i < atlasPlans.length; i++) {
3984
+ if (atlasPlans[i] && !dedupDrop.has(i)) s.add(i);
3985
+ }
3986
+ return s;
3987
+ }, [atlasPlans, polygons]);
3672
3988
  useEffect5(() => {
3673
3989
  if (!sceneRegisterShadowCaster) return;
3674
3990
  if (castShadow) {
3675
- sceneRegisterShadowCaster(meshIdRef.current, polygons);
3991
+ sceneRegisterShadowCaster(meshIdRef.current, {
3992
+ polygons,
3993
+ position: position ?? [0, 0, 0],
3994
+ scale,
3995
+ rotation,
3996
+ renderedPolygonIndices
3997
+ });
3676
3998
  } else {
3677
3999
  sceneRegisterShadowCaster(meshIdRef.current, null);
3678
4000
  }
3679
4001
  return () => {
3680
4002
  sceneRegisterShadowCaster(meshIdRef.current, null);
3681
4003
  };
3682
- }, [sceneRegisterShadowCaster, castShadow, polygons]);
4004
+ }, [sceneRegisterShadowCaster, castShadow, polygons, position, scale, rotation, renderedPolygonIndices]);
4005
+ const sceneRegisterShadowReceiver = sceneCtx?.registerShadowReceiver;
4006
+ useEffect5(() => {
4007
+ if (!sceneRegisterShadowReceiver) return;
4008
+ sceneRegisterShadowReceiver(meshIdRef.current, !!receiveShadow);
4009
+ return () => {
4010
+ sceneRegisterShadowReceiver(meshIdRef.current, false);
4011
+ };
4012
+ }, [sceneRegisterShadowReceiver, receiveShadow]);
3683
4013
  const bakedShadowGroundCssZ = sceneCtx?.groundCssZ ?? null;
3684
4014
  const sceneShadow = sceneCtx?.shadow;
4015
+ const sceneHasReceiver = sceneCtx?.hasShadowReceiver ?? false;
3685
4016
  const shadowSvgNode = useMemo7(() => {
3686
4017
  if (!castShadow || renderPolygon) return null;
4018
+ if (sceneHasReceiver) return null;
3687
4019
  if (bakedShadowGroundCssZ === null) return null;
3688
- const lightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
4020
+ const userGroundLightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
4021
+ const lightDir = worldDirectionToCss2(userGroundLightDir);
4022
+ const meshPosZ = position?.[2] ?? 0;
4023
+ const localGroundCssZ = bakedShadowGroundCssZ - meshPosZ * BASE_TILE4;
3689
4024
  const shadowDedupDrop = findOverlappingPolygonDuplicates(polygons, {
3690
4025
  normalTolerance: 0.1,
3691
4026
  distanceTolerance: 0.5,
3692
- overlapFraction: 0.4,
4027
+ overlapFraction: 0.95,
3693
4028
  preserveDoubleSidedBackfaces: false
3694
4029
  });
3695
4030
  const projections = [];
@@ -3711,7 +4046,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
3711
4046
  if (cssVertex[1] < fpMinY) fpMinY = cssVertex[1];
3712
4047
  if (cssVertex[0] > fpMaxX) fpMaxX = cssVertex[0];
3713
4048
  if (cssVertex[1] > fpMaxY) fpMaxY = cssVertex[1];
3714
- const p = projectCssVertexToGround(cssVertex, lightDir, bakedShadowGroundCssZ);
4049
+ const p = projectCssVertexToGround(cssVertex, lightDir, localGroundCssZ);
3715
4050
  projected.push(p);
3716
4051
  if (p[0] < minX) minX = p[0];
3717
4052
  if (p[1] < minY) minY = p[1];
@@ -3741,7 +4076,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
3741
4076
  }
3742
4077
  d += "Z";
3743
4078
  }
3744
- return /* @__PURE__ */ jsx8(
4079
+ return /* @__PURE__ */ jsx9(
3745
4080
  "svg",
3746
4081
  {
3747
4082
  className: "polycss-shadow polycss-shadow-svg",
@@ -3757,16 +4092,16 @@ var PolyMesh = forwardRef(function PolyMesh2({
3757
4092
  transformOrigin: "0 0",
3758
4093
  pointerEvents: "none",
3759
4094
  willChange: "transform",
3760
- transform: `translate3d(${bx0.toFixed(3)}px,${by0.toFixed(3)}px,${bakedShadowGroundCssZ.toFixed(3)}px)`
4095
+ transform: `translate3d(${bx0.toFixed(3)}px,${by0.toFixed(3)}px,${localGroundCssZ.toFixed(3)}px)`
3761
4096
  },
3762
- children: /* @__PURE__ */ jsx8(
4097
+ children: /* @__PURE__ */ jsx9(
3763
4098
  "path",
3764
4099
  {
3765
4100
  d,
3766
4101
  fill: `rgb(${parsed[0]},${parsed[1]},${parsed[2]})`,
3767
4102
  fillRule: "nonzero",
3768
4103
  stroke: `rgb(${parsed[0]},${parsed[1]},${parsed[2]})`,
3769
- strokeWidth: "2",
4104
+ strokeWidth: "3",
3770
4105
  strokeLinejoin: "round",
3771
4106
  opacity: shadowOpacity.toFixed(4)
3772
4107
  }
@@ -3774,7 +4109,125 @@ var PolyMesh = forwardRef(function PolyMesh2({
3774
4109
  },
3775
4110
  "shadow-svg"
3776
4111
  );
3777
- }, [castShadow, renderPolygon, polygons, atlasPlans, sceneDirectionalLight, bakedShadowGroundCssZ, sceneShadow]);
4112
+ }, [castShadow, renderPolygon, polygons, atlasPlans, sceneDirectionalLight, bakedShadowGroundCssZ, sceneShadow, sceneHasReceiver]);
4113
+ const shadowCasters = sceneCtx?.shadowCasters;
4114
+ const shadowCastersVersion = sceneCtx?.shadowCastersVersion ?? 0;
4115
+ const [cameraTick, setCameraTick] = useState4(0);
4116
+ useEffect5(() => {
4117
+ if (!receiveShadow) return;
4118
+ return cameraCtx?.store.subscribe(() => setCameraTick((n) => n + 1));
4119
+ }, [receiveShadow, cameraCtx?.store]);
4120
+ void cameraTick;
4121
+ const selfShadowEdgeMap = useMemo7(
4122
+ () => receiveShadow ? buildSharedEdgeMap(polygons) : void 0,
4123
+ [polygons, receiveShadow]
4124
+ );
4125
+ const receiverShadowSvgs = useMemo7(() => {
4126
+ if (!receiveShadow) return null;
4127
+ if (!shadowCasters || shadowCasters.size === 0) return null;
4128
+ const userLightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
4129
+ const lightDir = worldDirectionToCss2(userLightDir);
4130
+ const shadowLift = sceneShadow?.lift ?? 1e-3;
4131
+ const planes = prepareReceiverFacePlanes(
4132
+ polygons,
4133
+ position ?? [0, 0, 0],
4134
+ scale,
4135
+ /* @__PURE__ */ new Set(),
4136
+ shadowLift,
4137
+ rotation
4138
+ );
4139
+ if (planes.length === 0) return null;
4140
+ const casterInputs = [];
4141
+ for (const [casterId, data] of shadowCasters) {
4142
+ const rendered = data.renderedPolygonIndices;
4143
+ const items = prepareCasterPolyItems(
4144
+ data.polygons,
4145
+ data.position,
4146
+ data.scale,
4147
+ rendered ? (idx) => rendered.has(idx) : () => true,
4148
+ data.rotation ?? null
4149
+ );
4150
+ const isSelf = data.polygons === polygons;
4151
+ const selfMap = isSelf ? selfShadowEdgeMap : void 0;
4152
+ let edgeOwners;
4153
+ if (!isSelf && data.polygons.length >= 40) {
4154
+ const dposArr = data.position;
4155
+ const drot = data.rotation ?? null;
4156
+ const dsKey = JSON.stringify(data.scale ?? null);
4157
+ const eoKey = `${dposArr[0]},${dposArr[1]},${dposArr[2]}|${drot ? drot.join(",") : "n"}|${dsKey}`;
4158
+ let cachedOwners = reactEdgeOwnersCache.get(data.polygons);
4159
+ if (cachedOwners === void 0 || reactEdgeOwnersCacheKey.get(data.polygons) !== eoKey) {
4160
+ cachedOwners = prepareCasterEdgeOwners(data.polygons, dposArr, data.scale, drot);
4161
+ reactEdgeOwnersCache.set(data.polygons, cachedOwners);
4162
+ reactEdgeOwnersCacheKey.set(data.polygons, eoKey);
4163
+ }
4164
+ edgeOwners = cachedOwners;
4165
+ }
4166
+ casterInputs.push({
4167
+ id: casterId,
4168
+ items,
4169
+ selfShadowEdgeMap: selfMap,
4170
+ edgeOwners,
4171
+ casterPolygonCount: data.polygons.length
4172
+ });
4173
+ }
4174
+ const cameraState = cameraCtx?.store.getState().cameraState;
4175
+ const cameraRot = {
4176
+ rotX: cameraState?.rotX ?? 65,
4177
+ rotY: cameraState?.rotY ?? 45,
4178
+ meshRotation: rotation
4179
+ };
4180
+ const specs = computeReceiverShadowFaces({
4181
+ receiverPlanes: planes,
4182
+ receiverPolygons: polygons,
4183
+ receiverHasTexture: polygons.some((p) => p.texture !== void 0),
4184
+ casters: casterInputs,
4185
+ lightDir,
4186
+ cameraRot,
4187
+ ambientLight: sceneCtx?.ambientLight,
4188
+ directionalLight: sceneDirectionalLight,
4189
+ shadow: { color: sceneShadow?.color, opacity: sceneShadow?.opacity ?? 0.25, maxExtend: sceneShadow?.maxExtend }
4190
+ });
4191
+ return /* @__PURE__ */ jsx9(Fragment, { children: specs.map((spec) => /* @__PURE__ */ jsx9(
4192
+ "svg",
4193
+ {
4194
+ className: "polycss-shadow polycss-shadow-svg polycss-shadow-receiver",
4195
+ "data-poly-shadow-type": "receiver",
4196
+ "data-poly-shadow-receiver-face": spec.faceIndex,
4197
+ "data-poly-shadow-receiver-polys": JSON.stringify(spec.memberPolyIndices),
4198
+ width: spec.width,
4199
+ height: spec.height,
4200
+ viewBox: `0 0 ${spec.width} ${spec.height}`,
4201
+ style: {
4202
+ position: "absolute",
4203
+ top: 0,
4204
+ left: 0,
4205
+ display: "block",
4206
+ overflow: "hidden",
4207
+ transformOrigin: "0 0",
4208
+ pointerEvents: "none",
4209
+ willChange: "transform",
4210
+ transform: spec.matrixCss
4211
+ },
4212
+ children: spec.paths.map((p, i) => /* @__PURE__ */ jsx9(
4213
+ "path",
4214
+ {
4215
+ d: p.d,
4216
+ fill: spec.fill,
4217
+ stroke: spec.fill,
4218
+ strokeWidth: "3",
4219
+ strokeLinejoin: "round",
4220
+ opacity: spec.opacity.toFixed(4),
4221
+ "data-poly-shadow-caster-polys": JSON.stringify(p.casterPolygonIndices)
4222
+ },
4223
+ i
4224
+ ))
4225
+ },
4226
+ `receiver-${spec.faceIndex}`
4227
+ )) });
4228
+ }, [receiveShadow, shadowCasters, shadowCastersVersion, polygons, position, scale, rotation, sceneDirectionalLight, sceneShadow, sceneCtx?.ambientLight, cameraCtx?.store, cameraTick, selfShadowEdgeMap]);
4229
+ const portalSceneEl = sceneCtx?.sceneEl ?? null;
4230
+ const portaledReceiverShadowSvgs = portalSceneEl && receiverShadowSvgs ? createPortal(receiverShadowSvgs, portalSceneEl) : null;
3778
4231
  setPolygonsImplRef.current = (nextPolygons) => {
3779
4232
  const nextRenderedPolygons = autoCenter ? recenterPolygons(nextPolygons) : nextPolygons;
3780
4233
  polygonsRef.current = nextRenderedPolygons;
@@ -3794,7 +4247,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
3794
4247
  }
3795
4248
  setLocalPolygons([...nextPolygons]);
3796
4249
  };
3797
- const voxelRendererRef = useRef5(null);
4250
+ const voxelRendererRef = useRef6(null);
3798
4251
  useLayoutEffect2(() => {
3799
4252
  const root = wrapperRef.current;
3800
4253
  voxelRendererRef.current?.dispose();
@@ -3836,7 +4289,6 @@ var PolyMesh = forwardRef(function PolyMesh2({
3836
4289
  ]);
3837
4290
  const wrapperStyle = {
3838
4291
  transform,
3839
- ...transformOrigin ? { transformOrigin } : null,
3840
4292
  ...dynamicLightOverride,
3841
4293
  ...style,
3842
4294
  ...defaultPaintVars
@@ -3844,10 +4296,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3844
4296
  const renderedPolygons = renderPolygon ? polygons.map((p, i) => (
3845
4297
  // Render-prop: caller controls how each polygon renders. We still
3846
4298
  // wrap in a fragment with key so React reconciliation works.
3847
- /* @__PURE__ */ jsx8(RenderPropPolygon, { polygon: p, index: i, children: renderPolygon }, i)
4299
+ /* @__PURE__ */ jsx9(RenderPropPolygon, { polygon: p, index: i, children: renderPolygon }, i)
3848
4300
  )) : textureAtlas.entries.map((entry, index) => {
3849
4301
  if (entry) {
3850
- return /* @__PURE__ */ jsx8(
4302
+ return /* @__PURE__ */ jsx9(
3851
4303
  TextureAtlasPoly,
3852
4304
  {
3853
4305
  entry,
@@ -3858,10 +4310,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3858
4310
  entry.index
3859
4311
  );
3860
4312
  }
3861
- const plan = atlasPlans[index];
4313
+ const plan = textureAtlas.plans[index];
3862
4314
  if (!plan || plan.texture) return null;
3863
4315
  if (isProjectiveQuadPlan(plan)) {
3864
- return /* @__PURE__ */ jsx8(
4316
+ return /* @__PURE__ */ jsx9(
3865
4317
  TextureProjectiveSolidPoly,
3866
4318
  {
3867
4319
  entry: plan,
@@ -3871,26 +4323,44 @@ var PolyMesh = forwardRef(function PolyMesh2({
3871
4323
  plan.index
3872
4324
  );
3873
4325
  }
3874
- return isSolidTrianglePlan2(plan) ? /* @__PURE__ */ jsx8(
3875
- TextureTrianglePoly,
3876
- {
3877
- entry: plan,
3878
- textureLighting: effectiveTextureLighting,
3879
- solidPaintDefaults
3880
- },
3881
- plan.index
3882
- ) : /* @__PURE__ */ jsx8(
4326
+ if (isSolidTrianglePlan2(plan)) {
4327
+ return /* @__PURE__ */ jsx9(
4328
+ TextureTrianglePoly,
4329
+ {
4330
+ entry: plan,
4331
+ textureLighting: effectiveTextureLighting,
4332
+ solidPaintDefaults
4333
+ },
4334
+ plan.index
4335
+ );
4336
+ }
4337
+ const cornerGeo = !disabledStrategies?.has("i") ? cornerShapeGeometryForPlan(plan) : null;
4338
+ if (cornerGeo) {
4339
+ return /* @__PURE__ */ jsx9(
4340
+ TextureCornerShapeSolidPoly,
4341
+ {
4342
+ entry: plan,
4343
+ geometry: cornerGeo,
4344
+ textureLighting: effectiveTextureLighting,
4345
+ solidPaintDefaults
4346
+ },
4347
+ plan.index
4348
+ );
4349
+ }
4350
+ return /* @__PURE__ */ jsx9(
3883
4351
  TextureBorderShapePoly,
3884
4352
  {
3885
4353
  entry: plan,
3886
- solidPaintDefaults
4354
+ textureLighting: effectiveTextureLighting,
4355
+ solidPaintDefaults,
4356
+ disabledStrategies
3887
4357
  },
3888
4358
  plan.index
3889
4359
  );
3890
4360
  });
3891
4361
  if (src) {
3892
4362
  if (fetched.loading && fetched.polygons.length === 0) {
3893
- return /* @__PURE__ */ jsx8(
4363
+ return /* @__PURE__ */ jsx9(
3894
4364
  "div",
3895
4365
  {
3896
4366
  ref: wrapperRef,
@@ -3903,7 +4373,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
3903
4373
  );
3904
4374
  }
3905
4375
  if (fetched.error && fetched.polygons.length === 0) {
3906
- return /* @__PURE__ */ jsx8(
4376
+ return /* @__PURE__ */ jsx9(
3907
4377
  "div",
3908
4378
  {
3909
4379
  ref: wrapperRef,
@@ -3926,6 +4396,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
3926
4396
  ...wrapperHandlers,
3927
4397
  children: [
3928
4398
  shadowSvgNode,
4399
+ portaledReceiverShadowSvgs,
3929
4400
  renderedPolygons,
3930
4401
  staticChildren
3931
4402
  ]
@@ -3937,12 +4408,89 @@ function RenderPropPolygon({
3937
4408
  index,
3938
4409
  children
3939
4410
  }) {
3940
- return /* @__PURE__ */ jsx8(Fragment, { children: children(polygon, index) });
4411
+ return /* @__PURE__ */ jsx9(Fragment, { children: children(polygon, index) });
4412
+ }
4413
+
4414
+ // src/scene/PolyIframe.tsx
4415
+ import { BASE_TILE as BASE_TILE5 } from "@layoutit/polycss-core";
4416
+ import { jsx as jsx10 } from "react/jsx-runtime";
4417
+ function buildIframeTransform(position, rotation, scale, cssWidth, cssHeight) {
4418
+ const sx = typeof scale === "number" ? scale : scale?.[0] ?? 1;
4419
+ const sy = typeof scale === "number" ? scale : scale?.[1] ?? 1;
4420
+ const sz = typeof scale === "number" ? scale : scale?.[2] ?? 1;
4421
+ const hasScale = sx !== 1 || sy !== 1 || sz !== 1;
4422
+ const hasRotation = !!rotation && (!!rotation[0] || !!rotation[1] || !!rotation[2]);
4423
+ const cssX = (position?.[1] ?? 0) * BASE_TILE5;
4424
+ const cssY = (position?.[0] ?? 0) * BASE_TILE5;
4425
+ const cssZ = (position?.[2] ?? 0) * BASE_TILE5;
4426
+ const parts = [];
4427
+ parts.push(`translate3d(${cssX}px, ${cssY}px, ${cssZ}px)`);
4428
+ if (hasRotation) {
4429
+ if (rotation[0]) parts.push(`rotateY(${-rotation[0]}deg)`);
4430
+ if (rotation[1]) parts.push(`rotateX(${-rotation[1]}deg)`);
4431
+ if (rotation[2]) parts.push(`rotateZ(${-rotation[2]}deg)`);
4432
+ }
4433
+ if (hasScale) parts.push(`scale3d(${sx}, ${sy}, ${sz})`);
4434
+ parts.push(`translate(${-cssWidth / 2}px, ${-cssHeight / 2}px)`);
4435
+ return parts.join(" ");
4436
+ }
4437
+ function PolyIframe({
4438
+ src,
4439
+ width,
4440
+ height,
4441
+ position,
4442
+ rotation,
4443
+ scale,
4444
+ allow,
4445
+ sandbox,
4446
+ loading,
4447
+ referrerPolicy,
4448
+ title,
4449
+ className,
4450
+ style
4451
+ }) {
4452
+ const cssW = width * BASE_TILE5;
4453
+ const cssH = height * BASE_TILE5;
4454
+ const transform = buildIframeTransform(position, rotation, scale, cssW, cssH);
4455
+ const wrapperStyle = {
4456
+ position: "absolute",
4457
+ left: 0,
4458
+ top: 0,
4459
+ transformOrigin: "0 0",
4460
+ transformStyle: "preserve-3d",
4461
+ transform,
4462
+ ...style
4463
+ };
4464
+ return /* @__PURE__ */ jsx10(
4465
+ "div",
4466
+ {
4467
+ className: `polycss-iframe${className ? ` ${className}` : ""}`,
4468
+ style: wrapperStyle,
4469
+ children: /* @__PURE__ */ jsx10(
4470
+ "iframe",
4471
+ {
4472
+ src,
4473
+ allow,
4474
+ sandbox,
4475
+ loading,
4476
+ referrerPolicy,
4477
+ title,
4478
+ style: {
4479
+ width: cssW,
4480
+ height: cssH,
4481
+ border: 0,
4482
+ display: "block",
4483
+ background: "#000"
4484
+ }
4485
+ }
4486
+ )
4487
+ }
4488
+ );
3941
4489
  }
3942
4490
 
3943
4491
  // src/scene/PolyGround.tsx
3944
4492
  import { useMemo as useMemo8 } from "react";
3945
- import { jsx as jsx9 } from "react/jsx-runtime";
4493
+ import { jsx as jsx11 } from "react/jsx-runtime";
3946
4494
  function PolyGround({
3947
4495
  size = 6,
3948
4496
  z = 0,
@@ -3961,7 +4509,7 @@ function PolyGround({
3961
4509
  ];
3962
4510
  return [{ vertices, color }];
3963
4511
  }, [size, z, center, color]);
3964
- return /* @__PURE__ */ jsx9(
4512
+ return /* @__PURE__ */ jsx11(
3965
4513
  PolyMesh,
3966
4514
  {
3967
4515
  polygons,
@@ -3982,8 +4530,8 @@ function usePolyMaterial(options) {
3982
4530
  }
3983
4531
 
3984
4532
  // src/shapes/Poly.tsx
3985
- import { memo as memo8, useMemo as useMemo10 } from "react";
3986
- import { jsx as jsx10 } from "react/jsx-runtime";
4533
+ import { memo as memo9, useMemo as useMemo10 } from "react";
4534
+ import { jsx as jsx12 } from "react/jsx-runtime";
3987
4535
  var DIRECT_TEXTURE_CSS_DECIMALS = 4;
3988
4536
  function formatCssLength(value, decimals = DIRECT_TEXTURE_CSS_DECIMALS) {
3989
4537
  const next = value.toFixed(decimals).replace(/\.?0+$/, "");
@@ -4035,7 +4583,7 @@ function MaterialDirectPoly({
4035
4583
  Object.entries(plan.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
4036
4584
  ) : {};
4037
4585
  const elementClassName = className?.trim() || void 0;
4038
- return /* @__PURE__ */ jsx10(
4586
+ return /* @__PURE__ */ jsx12(
4039
4587
  "i",
4040
4588
  {
4041
4589
  className: elementClassName,
@@ -4166,7 +4714,7 @@ function PolyInner({
4166
4714
  const wrapperTransform = transformParts.length > 0 ? transformParts.join(" ") : void 0;
4167
4715
  let front = null;
4168
4716
  if (materialUvRect && material && atlasPlan) {
4169
- front = /* @__PURE__ */ jsx10(
4717
+ front = /* @__PURE__ */ jsx12(
4170
4718
  MaterialDirectPoly,
4171
4719
  {
4172
4720
  plan: atlasPlan,
@@ -4182,7 +4730,7 @@ function PolyInner({
4182
4730
  } else {
4183
4731
  const atlasEntry = textureAtlas.entries[0];
4184
4732
  if (atlasEntry) {
4185
- front = /* @__PURE__ */ jsx10(
4733
+ front = /* @__PURE__ */ jsx12(
4186
4734
  TextureAtlasPoly,
4187
4735
  {
4188
4736
  entry: atlasEntry,
@@ -4196,7 +4744,7 @@ function PolyInner({
4196
4744
  }
4197
4745
  );
4198
4746
  } else if (atlasPlan && !atlasPlan.texture) {
4199
- front = isSolidTrianglePlan2(atlasPlan) ? /* @__PURE__ */ jsx10(
4747
+ front = isSolidTrianglePlan2(atlasPlan) ? /* @__PURE__ */ jsx12(
4200
4748
  TextureTrianglePoly,
4201
4749
  {
4202
4750
  entry: atlasPlan,
@@ -4207,7 +4755,7 @@ function PolyInner({
4207
4755
  domEventHandlers,
4208
4756
  pointerEvents: pointerEventsProp ?? "auto"
4209
4757
  }
4210
- ) : isProjectiveQuadPlan(atlasPlan) ? /* @__PURE__ */ jsx10(
4758
+ ) : isProjectiveQuadPlan(atlasPlan) ? /* @__PURE__ */ jsx12(
4211
4759
  TextureProjectiveSolidPoly,
4212
4760
  {
4213
4761
  entry: atlasPlan,
@@ -4218,10 +4766,11 @@ function PolyInner({
4218
4766
  domEventHandlers,
4219
4767
  pointerEvents: pointerEventsProp ?? "auto"
4220
4768
  }
4221
- ) : /* @__PURE__ */ jsx10(
4769
+ ) : /* @__PURE__ */ jsx12(
4222
4770
  TextureBorderShapePoly,
4223
4771
  {
4224
4772
  entry: atlasPlan,
4773
+ textureLighting,
4225
4774
  className,
4226
4775
  style: styleProp,
4227
4776
  domAttrs,
@@ -4233,7 +4782,7 @@ function PolyInner({
4233
4782
  }
4234
4783
  if (!front) return null;
4235
4784
  if (!wrapperTransform) return front;
4236
- return /* @__PURE__ */ jsx10(
4785
+ return /* @__PURE__ */ jsx12(
4237
4786
  "div",
4238
4787
  {
4239
4788
  className: "polycss-poly-wrapper",
@@ -4246,7 +4795,7 @@ function PolyInner({
4246
4795
  }
4247
4796
  );
4248
4797
  }
4249
- var Poly = memo8(PolyInner);
4798
+ var Poly = memo9(PolyInner);
4250
4799
 
4251
4800
  // src/shapes/PolyShapes.tsx
4252
4801
  import { useMemo as useMemo11 } from "react";
@@ -4263,7 +4812,7 @@ import {
4263
4812
  conePolygons,
4264
4813
  torusPolygons
4265
4814
  } from "@layoutit/polycss-core";
4266
- import { jsx as jsx11 } from "react/jsx-runtime";
4815
+ import { jsx as jsx13 } from "react/jsx-runtime";
4267
4816
  function PolyBox({
4268
4817
  size,
4269
4818
  center,
@@ -4282,7 +4831,7 @@ function PolyBox({
4282
4831
  // eslint-disable-next-line react-hooks/exhaustive-deps
4283
4832
  [size, center, min, max, color, texture, material, uvs, data, faces]
4284
4833
  );
4285
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4834
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4286
4835
  }
4287
4836
  function PolyPlane({
4288
4837
  axis,
@@ -4297,7 +4846,7 @@ function PolyPlane({
4297
4846
  // eslint-disable-next-line react-hooks/exhaustive-deps
4298
4847
  [axis, size, offset, along, color]
4299
4848
  );
4300
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4849
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4301
4850
  }
4302
4851
  function PolyRing({
4303
4852
  axis,
@@ -4312,7 +4861,7 @@ function PolyRing({
4312
4861
  // eslint-disable-next-line react-hooks/exhaustive-deps
4313
4862
  [axis, radius, halfThickness, segments, color]
4314
4863
  );
4315
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4864
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4316
4865
  }
4317
4866
  function PolyOctahedron({
4318
4867
  center,
@@ -4325,7 +4874,7 @@ function PolyOctahedron({
4325
4874
  // eslint-disable-next-line react-hooks/exhaustive-deps
4326
4875
  [center, size, color]
4327
4876
  );
4328
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4877
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4329
4878
  }
4330
4879
  function PolyTetrahedron({
4331
4880
  size,
@@ -4337,7 +4886,7 @@ function PolyTetrahedron({
4337
4886
  // eslint-disable-next-line react-hooks/exhaustive-deps
4338
4887
  [size, color]
4339
4888
  );
4340
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4889
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4341
4890
  }
4342
4891
  function PolyIcosahedron({
4343
4892
  size,
@@ -4349,7 +4898,7 @@ function PolyIcosahedron({
4349
4898
  // eslint-disable-next-line react-hooks/exhaustive-deps
4350
4899
  [size, color]
4351
4900
  );
4352
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4901
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4353
4902
  }
4354
4903
  function PolyDodecahedron({
4355
4904
  size,
@@ -4361,7 +4910,7 @@ function PolyDodecahedron({
4361
4910
  // eslint-disable-next-line react-hooks/exhaustive-deps
4362
4911
  [size, color]
4363
4912
  );
4364
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4913
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4365
4914
  }
4366
4915
  function PolySphere({
4367
4916
  radius,
@@ -4374,7 +4923,7 @@ function PolySphere({
4374
4923
  // eslint-disable-next-line react-hooks/exhaustive-deps
4375
4924
  [radius, subdivisions, color]
4376
4925
  );
4377
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4926
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4378
4927
  }
4379
4928
  function PolyCylinder({
4380
4929
  radius,
@@ -4389,7 +4938,7 @@ function PolyCylinder({
4389
4938
  // eslint-disable-next-line react-hooks/exhaustive-deps
4390
4939
  [radius, radiusTop, height, radialSegments, color]
4391
4940
  );
4392
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4941
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4393
4942
  }
4394
4943
  function PolyCone({
4395
4944
  radius,
@@ -4403,7 +4952,7 @@ function PolyCone({
4403
4952
  // eslint-disable-next-line react-hooks/exhaustive-deps
4404
4953
  [radius, height, radialSegments, color]
4405
4954
  );
4406
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4955
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4407
4956
  }
4408
4957
  function PolyTorus({
4409
4958
  radius,
@@ -4418,12 +4967,12 @@ function PolyTorus({
4418
4967
  // eslint-disable-next-line react-hooks/exhaustive-deps
4419
4968
  [radius, tube, radialSegments, tubularSegments, color]
4420
4969
  );
4421
- return /* @__PURE__ */ jsx11(PolyMesh, { polygons, ...meshProps });
4970
+ return /* @__PURE__ */ jsx13(PolyMesh, { polygons, ...meshProps });
4422
4971
  }
4423
4972
 
4424
4973
  // src/controls/PolyFirstPersonControls.tsx
4425
- import { useEffect as useEffect6, useRef as useRef6, useImperativeHandle as useImperativeHandle2, forwardRef as forwardRef2 } from "react";
4426
- import { BASE_TILE as BASE_TILE5 } from "@layoutit/polycss-core";
4974
+ import { useEffect as useEffect6, useRef as useRef7, useImperativeHandle as useImperativeHandle2, forwardRef as forwardRef2 } from "react";
4975
+ import { BASE_TILE as BASE_TILE6 } from "@layoutit/polycss-core";
4427
4976
  var DEFAULTS = {
4428
4977
  enabled: true,
4429
4978
  lookEnabled: true,
@@ -4483,25 +5032,25 @@ function emitEvent(registry, type) {
4483
5032
  }
4484
5033
  var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(props, ref) {
4485
5034
  const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
4486
- const onChangeRef = useRef6(props.onChange);
4487
- const onInteractionStartRef = useRef6(props.onInteractionStart);
4488
- const onInteractionEndRef = useRef6(props.onInteractionEnd);
5035
+ const onChangeRef = useRef7(props.onChange);
5036
+ const onInteractionStartRef = useRef7(props.onInteractionStart);
5037
+ const onInteractionEndRef = useRef7(props.onInteractionEnd);
4489
5038
  useEffect6(() => {
4490
5039
  onChangeRef.current = props.onChange;
4491
5040
  onInteractionStartRef.current = props.onInteractionStart;
4492
5041
  onInteractionEndRef.current = props.onInteractionEnd;
4493
5042
  });
4494
- const optsRef = useRef6(resolveOptions(DEFAULTS, props));
4495
- const cameraOriginRef = useRef6([0, 0, 0]);
4496
- const rafIdRef = useRef6(null);
4497
- const lastTimeRef = useRef6(0);
4498
- const stoppedRef = useRef6(false);
4499
- const pointerLockedRef = useRef6(false);
4500
- const interactingRef = useRef6(false);
4501
- const keysHeldRef = useRef6(/* @__PURE__ */ new Set());
4502
- const verticalVelRef = useRef6(0);
4503
- const jumpOffsetRef = useRef6(0);
4504
- const registryRef = useRef6(makeRegistry());
5043
+ const optsRef = useRef7(resolveOptions(DEFAULTS, props));
5044
+ const cameraOriginRef = useRef7([0, 0, 0]);
5045
+ const rafIdRef = useRef7(null);
5046
+ const lastTimeRef = useRef7(0);
5047
+ const stoppedRef = useRef7(false);
5048
+ const pointerLockedRef = useRef7(false);
5049
+ const interactingRef = useRef7(false);
5050
+ const keysHeldRef = useRef7(/* @__PURE__ */ new Set());
5051
+ const verticalVelRef = useRef7(0);
5052
+ const jumpOffsetRef = useRef7(0);
5053
+ const registryRef = useRef7(makeRegistry());
4505
5054
  useImperativeHandle2(ref, () => ({
4506
5055
  update(partial) {
4507
5056
  const prev = optsRef.current;
@@ -4599,7 +5148,7 @@ var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(prop
4599
5148
  const host = cameraElRef.current;
4600
5149
  const perspStr = host ? getComputedStyle(host).perspective : "";
4601
5150
  const n = parseFloat(perspStr);
4602
- return (Number.isFinite(n) && n > 0 ? n : 8e3) / BASE_TILE5;
5151
+ return (Number.isFinite(n) && n > 0 ? n : 32e3) / BASE_TILE6;
4603
5152
  }
4604
5153
  function deriveTarget() {
4605
5154
  const s = cameraRef.current.state;
@@ -4837,10 +5386,10 @@ var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(prop
4837
5386
  });
4838
5387
 
4839
5388
  // src/controls/PolyMapControls.tsx
4840
- import { useEffect as useEffect7, useRef as useRef7 } from "react";
5389
+ import { useEffect as useEffect7, useRef as useRef8 } from "react";
4841
5390
 
4842
5391
  // src/controls/sharedControls.ts
4843
- import { BASE_TILE as BASE_TILE6 } from "@layoutit/polycss-core";
5392
+ import { BASE_TILE as BASE_TILE7 } from "@layoutit/polycss-core";
4844
5393
  var POINTER_DRAG_SPEED = 4;
4845
5394
  function invertFactor(invert) {
4846
5395
  if (invert === true) return -1;
@@ -4851,7 +5400,7 @@ function applyOrbit(dx, dy, s, handle, invert) {
4851
5400
  const f = invertFactor(invert);
4852
5401
  const dX = dx / POINTER_DRAG_SPEED * f;
4853
5402
  const dY = dy / POINTER_DRAG_SPEED * f;
4854
- const rotX = Math.max(0, Math.min(100, s.rotX - dY));
5403
+ const rotX = s.rotX - dY;
4855
5404
  const rotY = ((s.rotY - dX) % 360 + 360) % 360;
4856
5405
  handle.update({ rotX, rotY });
4857
5406
  }
@@ -4861,7 +5410,7 @@ function applyPan(dx, dy, s, handle, _invert) {
4861
5410
  const cosRotX = cosRotXRaw >= 0 ? Math.max(0.1, cosRotXRaw) : Math.min(-0.1, cosRotXRaw);
4862
5411
  const cZ = Math.cos(s.rotY * Math.PI / 180);
4863
5412
  const sZ = Math.sin(s.rotY * Math.PI / 180);
4864
- const k = z * BASE_TILE6;
5413
+ const k = z * BASE_TILE7;
4865
5414
  const targetD0 = (dx * sZ - dy * cZ / cosRotX) / k;
4866
5415
  const targetD1 = -(dx * cZ + dy * sZ / cosRotX) / k;
4867
5416
  const t = s.target;
@@ -5010,18 +5559,18 @@ function PolyMapControls({
5010
5559
  onInteractionEnd
5011
5560
  }) {
5012
5561
  const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
5013
- const dragRef = useRef7(drag);
5014
- const wheelRef = useRef7(wheel);
5015
- const dollyRef = useRef7(dolly);
5016
- const invertRef = useRef7(invert);
5017
- const zoomMinRef = useRef7(minZoom);
5018
- const zoomMaxRef = useRef7(maxZoom);
5019
- const distanceMinRef = useRef7(minDistance);
5020
- const distanceMaxRef = useRef7(maxDistance);
5021
- const animateRef = useRef7(animate);
5022
- const onChangeRef = useRef7(onChange);
5023
- const onInteractionStartRef = useRef7(onInteractionStart);
5024
- const onInteractionEndRef = useRef7(onInteractionEnd);
5562
+ const dragRef = useRef8(drag);
5563
+ const wheelRef = useRef8(wheel);
5564
+ const dollyRef = useRef8(dolly);
5565
+ const invertRef = useRef8(invert);
5566
+ const zoomMinRef = useRef8(minZoom);
5567
+ const zoomMaxRef = useRef8(maxZoom);
5568
+ const distanceMinRef = useRef8(minDistance);
5569
+ const distanceMaxRef = useRef8(maxDistance);
5570
+ const animateRef = useRef8(animate);
5571
+ const onChangeRef = useRef8(onChange);
5572
+ const onInteractionStartRef = useRef8(onInteractionStart);
5573
+ const onInteractionEndRef = useRef8(onInteractionEnd);
5025
5574
  useEffect7(() => {
5026
5575
  dragRef.current = drag;
5027
5576
  wheelRef.current = wheel;
@@ -5067,7 +5616,7 @@ function PolyMapControls({
5067
5616
  console.error("[polycss/react] PolyMapControls onInteractionEnd threw:", err);
5068
5617
  }
5069
5618
  };
5070
- const animationPausedShared = useRef7({ value: false }).current;
5619
+ const animationPausedShared = useRef8({ value: false }).current;
5071
5620
  useEffect7(() => {
5072
5621
  if (!drag) return;
5073
5622
  const el = cameraElRef.current;
@@ -5192,7 +5741,7 @@ function PolyMapControls({
5192
5741
  }
5193
5742
 
5194
5743
  // src/controls/PolyOrbitControls.tsx
5195
- import { useEffect as useEffect8, useRef as useRef8 } from "react";
5744
+ import { useEffect as useEffect8, useRef as useRef9 } from "react";
5196
5745
  function PolyOrbitControls({
5197
5746
  drag = true,
5198
5747
  wheel = true,
@@ -5208,18 +5757,18 @@ function PolyOrbitControls({
5208
5757
  onInteractionEnd
5209
5758
  }) {
5210
5759
  const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
5211
- const dragRef = useRef8(drag);
5212
- const wheelRef = useRef8(wheel);
5213
- const dollyRef = useRef8(dolly);
5214
- const invertRef = useRef8(invert);
5215
- const zoomMinRef = useRef8(minZoom);
5216
- const zoomMaxRef = useRef8(maxZoom);
5217
- const distanceMinRef = useRef8(minDistance);
5218
- const distanceMaxRef = useRef8(maxDistance);
5219
- const animateRef = useRef8(animate);
5220
- const onChangeRef = useRef8(onChange);
5221
- const onInteractionStartRef = useRef8(onInteractionStart);
5222
- const onInteractionEndRef = useRef8(onInteractionEnd);
5760
+ const dragRef = useRef9(drag);
5761
+ const wheelRef = useRef9(wheel);
5762
+ const dollyRef = useRef9(dolly);
5763
+ const invertRef = useRef9(invert);
5764
+ const zoomMinRef = useRef9(minZoom);
5765
+ const zoomMaxRef = useRef9(maxZoom);
5766
+ const distanceMinRef = useRef9(minDistance);
5767
+ const distanceMaxRef = useRef9(maxDistance);
5768
+ const animateRef = useRef9(animate);
5769
+ const onChangeRef = useRef9(onChange);
5770
+ const onInteractionStartRef = useRef9(onInteractionStart);
5771
+ const onInteractionEndRef = useRef9(onInteractionEnd);
5223
5772
  useEffect8(() => {
5224
5773
  dragRef.current = drag;
5225
5774
  wheelRef.current = wheel;
@@ -5265,7 +5814,7 @@ function PolyOrbitControls({
5265
5814
  console.error("[polycss/react] PolyOrbitControls onInteractionEnd threw:", err);
5266
5815
  }
5267
5816
  };
5268
- const animationPausedShared = useRef8({ value: false }).current;
5817
+ const animationPausedShared = useRef9({ value: false }).current;
5269
5818
  useEffect8(() => {
5270
5819
  if (!drag) return;
5271
5820
  const el = cameraElRef.current;
@@ -5391,11 +5940,11 @@ function PolyOrbitControls({
5391
5940
 
5392
5941
  // src/controls/TransformControls.tsx
5393
5942
  import {
5394
- useCallback as useCallback7,
5943
+ useCallback as useCallback8,
5395
5944
  useContext as useContext4,
5396
5945
  useEffect as useEffect9,
5397
5946
  useMemo as useMemo12,
5398
- useRef as useRef9,
5947
+ useRef as useRef10,
5399
5948
  useState as useState5
5400
5949
  } from "react";
5401
5950
  import {
@@ -5408,7 +5957,7 @@ import {
5408
5957
  quatMultiply,
5409
5958
  ringQuadPolygons
5410
5959
  } from "@layoutit/polycss-core";
5411
- import { jsx as jsx12, jsxs as jsxs3 } from "react/jsx-runtime";
5960
+ import { jsx as jsx14, jsxs as jsxs3 } from "react/jsx-runtime";
5412
5961
  var FALLBACK_CAMERA_STORE = createSceneStore(DEFAULT_CAMERA_STATE);
5413
5962
  var COLOR_X = "#ff3653";
5414
5963
  var COLOR_Y = "#8adb00";
@@ -5755,7 +6304,7 @@ function PolyTransformControls({
5755
6304
  const [draggingKey, setDraggingKey] = useState5(null);
5756
6305
  const cameraCtx = useContext4(PolyCameraContext);
5757
6306
  const cameraElRef = cameraCtx?.cameraElRef;
5758
- const dragRef = useRef9({
6307
+ const dragRef = useRef10({
5759
6308
  target: null,
5760
6309
  mode: "translate",
5761
6310
  shaftLengthCss: 0,
@@ -5886,10 +6435,13 @@ function PolyTransformControls({
5886
6435
  const position = target.getPosition() ?? [0, 0, 0];
5887
6436
  const polygons = target.getPolygons();
5888
6437
  const bboxCenter = gizmoCenterForMesh(polygons);
6438
+ const cssPosX = position[1] * SCENE_TILE_SIZE;
6439
+ const cssPosY = position[0] * SCENE_TILE_SIZE;
6440
+ const cssPosZ = position[2] * SCENE_TILE_SIZE;
5889
6441
  const wrapperPos = [
5890
- position[0] + bboxCenter[0],
5891
- position[1] + bboxCenter[1],
5892
- position[2] + bboxCenter[2]
6442
+ cssPosX + bboxCenter[0],
6443
+ cssPosY + bboxCenter[1],
6444
+ cssPosZ + bboxCenter[2]
5893
6445
  ];
5894
6446
  const baseLength = gizmoLengthForMesh(polygons);
5895
6447
  const shaftLengthCss = baseLength * size;
@@ -5934,7 +6486,7 @@ function PolyTransformControls({
5934
6486
  const dragging = draggingKey === spec.key;
5935
6487
  const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
5936
6488
  const backFacing = isAxisBackFacing(spec.cssAxis, spec.sign, rotX, rotY);
5937
- return /* @__PURE__ */ jsx12(
6489
+ return /* @__PURE__ */ jsx14(
5938
6490
  TranslateArrow,
5939
6491
  {
5940
6492
  cssAxis: spec.cssAxis,
@@ -5966,7 +6518,7 @@ function PolyTransformControls({
5966
6518
  const hovered = hoveredKey === spec.key;
5967
6519
  const dragging = draggingKey === spec.key;
5968
6520
  const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
5969
- return /* @__PURE__ */ jsx12(
6521
+ return /* @__PURE__ */ jsx14(
5970
6522
  TranslatePlane,
5971
6523
  {
5972
6524
  axisA: spec.axisA,
@@ -5998,7 +6550,7 @@ function PolyTransformControls({
5998
6550
  const hovered = hoveredKey === spec.key;
5999
6551
  const dragging = draggingKey === spec.key;
6000
6552
  const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
6001
- return /* @__PURE__ */ jsx12(
6553
+ return /* @__PURE__ */ jsx14(
6002
6554
  RotateRing,
6003
6555
  {
6004
6556
  cssAxis: spec.cssAxis,
@@ -6043,7 +6595,7 @@ function TranslateArrow({
6043
6595
  onDraggingStart,
6044
6596
  onDraggingStop
6045
6597
  }) {
6046
- const cbRef = useRef9({
6598
+ const cbRef = useRef10({
6047
6599
  onChange,
6048
6600
  onObjectChange,
6049
6601
  onMouseDown,
@@ -6078,7 +6630,7 @@ function TranslateArrow({
6078
6630
  shaft: includeShaft
6079
6631
  });
6080
6632
  }, [cssAxis, sign, color, shaftLengthCss, includeShaft]);
6081
- const onPointerDown = useCallback7(
6633
+ const onPointerDown = useCallback8(
6082
6634
  (e) => {
6083
6635
  if (!cbRef.current.enabled) return;
6084
6636
  e.stopPropagation();
@@ -6107,7 +6659,7 @@ function TranslateArrow({
6107
6659
  },
6108
6660
  [cssAxis, sign, target, shaftLengthCss]
6109
6661
  );
6110
- return /* @__PURE__ */ jsx12(
6662
+ return /* @__PURE__ */ jsx14(
6111
6663
  PolyMesh,
6112
6664
  {
6113
6665
  polygons,
@@ -6140,7 +6692,7 @@ function TranslatePlane({
6140
6692
  onDraggingStart,
6141
6693
  onDraggingStop
6142
6694
  }) {
6143
- const cbRef = useRef9({
6695
+ const cbRef = useRef10({
6144
6696
  onChange,
6145
6697
  onObjectChange,
6146
6698
  onMouseDown,
@@ -6179,7 +6731,7 @@ function TranslatePlane({
6179
6731
  color
6180
6732
  });
6181
6733
  }, [perpAxis, color, shaftLengthCss, rotX, rotY]);
6182
- const onPointerDown = useCallback7(
6734
+ const onPointerDown = useCallback8(
6183
6735
  (e) => {
6184
6736
  if (!cbRef.current.enabled) return;
6185
6737
  e.stopPropagation();
@@ -6208,7 +6760,7 @@ function TranslatePlane({
6208
6760
  },
6209
6761
  [axisA, axisB, target, shaftLengthCss]
6210
6762
  );
6211
- return /* @__PURE__ */ jsx12(
6763
+ return /* @__PURE__ */ jsx14(
6212
6764
  PolyMesh,
6213
6765
  {
6214
6766
  polygons,
@@ -6237,7 +6789,7 @@ function RotateRing({
6237
6789
  onDraggingStart,
6238
6790
  onDraggingStop
6239
6791
  }) {
6240
- const cbRef = useRef9({
6792
+ const cbRef = useRef10({
6241
6793
  onChange,
6242
6794
  onObjectChange,
6243
6795
  onMouseDown,
@@ -6270,7 +6822,7 @@ function RotateRing({
6270
6822
  }, [cssAxis, color, radiusCss]);
6271
6823
  const ringInnerRatio = (1 - RING_HALF_THICKNESS_RATIO) / RING_QUAD_OUTER_RATIO;
6272
6824
  const ringOuterRatio = (1 + RING_HALF_THICKNESS_RATIO) / RING_QUAD_OUTER_RATIO;
6273
- const onPointerDown = useCallback7(
6825
+ const onPointerDown = useCallback8(
6274
6826
  (e) => {
6275
6827
  if (!cbRef.current.enabled) return;
6276
6828
  e.stopPropagation();
@@ -6297,7 +6849,7 @@ function RotateRing({
6297
6849
  },
6298
6850
  [cssAxis, target]
6299
6851
  );
6300
- return /* @__PURE__ */ jsx12(
6852
+ return /* @__PURE__ */ jsx14(
6301
6853
  PolyMesh,
6302
6854
  {
6303
6855
  polygons,
@@ -6317,14 +6869,14 @@ function RotateRing({
6317
6869
  // src/select/Select.tsx
6318
6870
  import {
6319
6871
  createContext as createContext3,
6320
- useCallback as useCallback8,
6872
+ useCallback as useCallback9,
6321
6873
  useContext as useContext5,
6322
6874
  useEffect as useEffect10,
6323
6875
  useMemo as useMemo13,
6324
- useRef as useRef10,
6876
+ useRef as useRef11,
6325
6877
  useState as useState6
6326
6878
  } from "react";
6327
- import { jsx as jsx13 } from "react/jsx-runtime";
6879
+ import { jsx as jsx15 } from "react/jsx-runtime";
6328
6880
  var SelectContext = createContext3(null);
6329
6881
  function PolySelect({
6330
6882
  multiple = false,
@@ -6337,13 +6889,13 @@ function PolySelect({
6337
6889
  style
6338
6890
  }) {
6339
6891
  const [selected, setSelectedState] = useState6([]);
6340
- const filterRef = useRef10(filter);
6892
+ const filterRef = useRef11(filter);
6341
6893
  filterRef.current = filter;
6342
- const onChangeRef = useRef10(onChange);
6894
+ const onChangeRef = useRef11(onChange);
6343
6895
  onChangeRef.current = onChange;
6344
- const multipleRef = useRef10(multiple);
6896
+ const multipleRef = useRef11(multiple);
6345
6897
  multipleRef.current = multiple;
6346
- const apply = useCallback8((next) => {
6898
+ const apply = useCallback9((next) => {
6347
6899
  const filtered = filterRef.current ? filterRef.current(next) : next;
6348
6900
  setSelectedState(filtered);
6349
6901
  if (onChangeRef.current) onChangeRef.current(filtered);
@@ -6381,14 +6933,14 @@ function PolySelect({
6381
6933
  has: (h) => selected.includes(h)
6382
6934
  };
6383
6935
  }, [selected, apply]);
6384
- const wrapperRef = useRef10(null);
6936
+ const wrapperRef = useRef11(null);
6385
6937
  const cameraCtx = useContext5(PolyCameraContext);
6386
6938
  const cameraElRef = cameraCtx?.cameraElRef;
6387
- const clearOnMissRef = useRef10(clearOnMiss);
6939
+ const clearOnMissRef = useRef11(clearOnMiss);
6388
6940
  clearOnMissRef.current = clearOnMiss;
6389
- const onPointerMissedRef = useRef10(onPointerMissed);
6941
+ const onPointerMissedRef = useRef11(onPointerMissed);
6390
6942
  onPointerMissedRef.current = onPointerMissed;
6391
- const findMeshUnderPoint2 = useCallback8(
6943
+ const findMeshUnderPoint2 = useCallback9(
6392
6944
  (clientX, clientY) => findMeshUnderPoint(
6393
6945
  clientX,
6394
6946
  clientY,
@@ -6443,7 +6995,7 @@ function PolySelect({
6443
6995
  }
6444
6996
  };
6445
6997
  const wrapperStyle = { display: "contents", ...style };
6446
- return /* @__PURE__ */ jsx13(SelectContext.Provider, { value: api, children: /* @__PURE__ */ jsx13(
6998
+ return /* @__PURE__ */ jsx15(SelectContext.Provider, { value: api, children: /* @__PURE__ */ jsx15(
6447
6999
  "div",
6448
7000
  {
6449
7001
  ref: wrapperRef,
@@ -6469,7 +7021,7 @@ function usePolySelectionApi() {
6469
7021
  // src/helpers/PolyAxesHelper.tsx
6470
7022
  import { useMemo as useMemo14 } from "react";
6471
7023
  import { axesHelperPolygons } from "@layoutit/polycss-core";
6472
- import { jsx as jsx14 } from "react/jsx-runtime";
7024
+ import { jsx as jsx16 } from "react/jsx-runtime";
6473
7025
  function PolyAxesHelper({
6474
7026
  size,
6475
7027
  thickness,
@@ -6482,14 +7034,13 @@ function PolyAxesHelper({
6482
7034
  () => axesHelperPolygons({ size, thickness, negative, xColor, yColor, zColor }),
6483
7035
  [size, thickness, negative, xColor, yColor, zColor]
6484
7036
  );
6485
- return /* @__PURE__ */ jsx14(PolyMesh, { polygons });
7037
+ return /* @__PURE__ */ jsx16(PolyMesh, { polygons, merge: false });
6486
7038
  }
6487
7039
 
6488
7040
  // src/helpers/PolyDirectionalLightHelper.tsx
6489
7041
  import { useMemo as useMemo15 } from "react";
6490
7042
  import { octahedronPolygons as octahedronPolygons2 } from "@layoutit/polycss-core";
6491
- import { jsx as jsx15 } from "react/jsx-runtime";
6492
- var TILE = 50;
7043
+ import { jsx as jsx17 } from "react/jsx-runtime";
6493
7044
  function PolyDirectionalLightHelper({
6494
7045
  light,
6495
7046
  target,
@@ -6508,12 +7059,13 @@ function PolyDirectionalLightHelper({
6508
7059
  const tx = target?.[0] ?? 0;
6509
7060
  const ty = target?.[1] ?? 0;
6510
7061
  const tz = target?.[2] ?? 0;
6511
- const worldX = tx + dy / len * distance;
6512
- const worldY = ty + dx / len * distance;
6513
- const worldZ = tz + dz / len * distance;
6514
- return [worldY * TILE, worldX * TILE, worldZ * TILE];
7062
+ return [
7063
+ tx + dx / len * distance,
7064
+ ty + dy / len * distance,
7065
+ tz + dz / len * distance
7066
+ ];
6515
7067
  }, [light.direction, target, distance]);
6516
- return /* @__PURE__ */ jsx15(PolyMesh, { polygons, position: meshPosition });
7068
+ return /* @__PURE__ */ jsx17(PolyMesh, { polygons, position: meshPosition, merge: false });
6517
7069
  }
6518
7070
 
6519
7071
  // src/styles/colorResolver.ts
@@ -6571,7 +7123,7 @@ function collectPolyRenderStats(root, optionsOrPolygonCount) {
6571
7123
  surfaceLeafCounts.clippedSolid += queryCount(scope, "i");
6572
7124
  surfaceLeafCounts.atlas += queryCount(scope, "s");
6573
7125
  surfaceLeafCounts.stableTriangle += queryCount(scope, "u");
6574
- shadowLeafCount += queryCount(scope, "q");
7126
+ shadowLeafCount += queryCount(scope, "q, .polycss-shadow-svg");
6575
7127
  bucketCount += queryCount(scope, ".polycss-bucket");
6576
7128
  }
6577
7129
  const mountedPolygonLeafCount = surfaceLeafCounts.quad + surfaceLeafCounts.clippedSolid + surfaceLeafCounts.atlas + surfaceLeafCounts.stableTriangle;
@@ -6585,7 +7137,7 @@ function collectPolyRenderStats(root, optionsOrPolygonCount) {
6585
7137
  }
6586
7138
 
6587
7139
  // src/animation/usePolyAnimation.ts
6588
- import { useEffect as useEffect11, useRef as useRef11, useMemo as useMemo16 } from "react";
7140
+ import { useEffect as useEffect11, useRef as useRef12, useMemo as useMemo16 } from "react";
6589
7141
  import { createPolyAnimationMixer } from "@layoutit/polycss-core";
6590
7142
  function resolveRoot(rootArg) {
6591
7143
  if (!rootArg) return null;
@@ -6593,8 +7145,8 @@ function resolveRoot(rootArg) {
6593
7145
  return rootArg;
6594
7146
  }
6595
7147
  function usePolyAnimation(clips, controller, root) {
6596
- const internalRef = useRef11(null);
6597
- const mixerRef = useRef11(null);
7148
+ const internalRef = useRef12(null);
7149
+ const mixerRef = useRef12(null);
6598
7150
  useEffect11(() => {
6599
7151
  if (!clips || clips.length === 0 || !controller) {
6600
7152
  mixerRef.current = null;
@@ -6670,7 +7222,9 @@ import {
6670
7222
  normalizePolygons,
6671
7223
  mergePolygons as mergePolygons2,
6672
7224
  coverPlanarPolygons,
6673
- optimizeMeshPolygons,
7225
+ optimizeMeshPolygons as optimizeMeshPolygons2,
7226
+ optimizeMeshParseResult,
7227
+ simplifyTriangleMeshPolygons,
6674
7228
  repairMeshSeams,
6675
7229
  seamFacetSplitPolygons,
6676
7230
  seamFacetSplitReport,
@@ -6691,6 +7245,7 @@ import {
6691
7245
  parseObj,
6692
7246
  parseMtl,
6693
7247
  parseGltf,
7248
+ parseStl,
6694
7249
  bakeSolidTextureSamples,
6695
7250
  bakeSolidTextureSampledPolygons,
6696
7251
  loadMesh as loadMesh2,
@@ -6721,8 +7276,9 @@ import {
6721
7276
  torusPolygons as torusPolygons2,
6722
7277
  planePolygons as planePolygons3,
6723
7278
  buildSceneContext as buildSceneContext2,
7279
+ buildPolyMeshTransform as buildPolyMeshTransform2,
6724
7280
  computeSceneBbox as computeSceneBbox2,
6725
- BASE_TILE as BASE_TILE7,
7281
+ BASE_TILE as BASE_TILE8,
6726
7282
  DEFAULT_CAMERA_STATE as DEFAULT_CAMERA_STATE2,
6727
7283
  DEFAULT_PROJECTION,
6728
7284
  normalizeInvertMultiplier,
@@ -6732,10 +7288,12 @@ import {
6732
7288
  DEFAULT_SEAM_OVERLAP_OPTIONS,
6733
7289
  LoopOnce,
6734
7290
  LoopRepeat,
6735
- LoopPingPong
7291
+ LoopPingPong,
7292
+ worldDirectionToCss as worldDirectionToCss3,
7293
+ worldPositionToCss
6736
7294
  } from "@layoutit/polycss-core";
6737
7295
  export {
6738
- BASE_TILE7 as BASE_TILE,
7296
+ BASE_TILE8 as BASE_TILE,
6739
7297
  CAMERA_BACKFACE_CULL_EPS,
6740
7298
  DEFAULT_CAMERA_STATE2 as DEFAULT_CAMERA_STATE,
6741
7299
  DEFAULT_PROJECTION,
@@ -6756,6 +7314,7 @@ export {
6756
7314
  PolyFirstPersonControls,
6757
7315
  PolyGround,
6758
7316
  PolyIcosahedron,
7317
+ PolyIframe,
6759
7318
  PolyMapControls,
6760
7319
  PolyMesh,
6761
7320
  PolyOctahedron,
@@ -6777,6 +7336,7 @@ export {
6777
7336
  bakeSolidTextureSampledPolygons,
6778
7337
  bakeSolidTextureSamples,
6779
7338
  boxPolygons2 as boxPolygons,
7339
+ buildPolyMeshTransform2 as buildPolyMeshTransform,
6780
7340
  buildSceneContext2 as buildSceneContext,
6781
7341
  cameraCullNormalGroups,
6782
7342
  cameraCullNormalGroupsFromPolygons,
@@ -6810,7 +7370,8 @@ export {
6810
7370
  normalizePolygons,
6811
7371
  octahedronPolygons3 as octahedronPolygons,
6812
7372
  optimizeAnimatedMeshPolygons,
6813
- optimizeMeshPolygons,
7373
+ optimizeMeshParseResult,
7374
+ optimizeMeshPolygons2 as optimizeMeshPolygons,
6814
7375
  parseColor2 as parseColor,
6815
7376
  parseGltf,
6816
7377
  parseHexColor3 as parseHexColor,
@@ -6818,6 +7379,7 @@ export {
6818
7379
  parseObj,
6819
7380
  parsePureColor4 as parsePureColor,
6820
7381
  parseRgbColor,
7382
+ parseStl,
6821
7383
  parseVox,
6822
7384
  planePolygons3 as planePolygons,
6823
7385
  pointInMeshElement,
@@ -6833,6 +7395,7 @@ export {
6833
7395
  seamOverlapPolygons,
6834
7396
  seamOverlapReport,
6835
7397
  shadeColor,
7398
+ simplifyTriangleMeshPolygons,
6836
7399
  tetrahedronPolygons2 as tetrahedronPolygons,
6837
7400
  torusPolygons2 as torusPolygons,
6838
7401
  useCameraContext,
@@ -6842,5 +7405,7 @@ export {
6842
7405
  usePolyMesh,
6843
7406
  usePolySceneContext,
6844
7407
  usePolySelect,
6845
- usePolySelectionApi
7408
+ usePolySelectionApi,
7409
+ worldDirectionToCss3 as worldDirectionToPolyCss,
7410
+ worldPositionToCss as worldPositionToPolyCss
6846
7411
  };