@layoutit/polycss-react 0.2.4 → 0.2.6

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
@@ -14,7 +14,11 @@ function useCameraContext() {
14
14
 
15
15
  // src/camera/useCamera.ts
16
16
  import { useRef as useRef2, useCallback as useCallback2, useEffect, useMemo } from "react";
17
- import { createIsometricCamera, BASE_TILE } from "@layoutit/polycss-core";
17
+ import {
18
+ buildPolyCameraSceneTransform,
19
+ capturePolyCameraSnapshot,
20
+ createIsometricCamera
21
+ } from "@layoutit/polycss-core";
18
22
 
19
23
  // src/store/sceneStore.ts
20
24
  import { useSyncExternalStore, useRef, useCallback } from "react";
@@ -60,6 +64,18 @@ function useStoreSelector(store, selector) {
60
64
  }
61
65
 
62
66
  // src/camera/useCamera.ts
67
+ function writeCameraSnapshotAttrs(el, camera, projection, perspectiveStyle) {
68
+ if (!el) return;
69
+ const snapshot = capturePolyCameraSnapshot(camera, { projection, perspectiveStyle });
70
+ el.dataset.polycssCameraProjection = snapshot.projection;
71
+ el.dataset.polycssCameraPerspective = snapshot.perspectiveStyle;
72
+ el.dataset.polycssCameraAppliedPerspective = snapshot.appliedPerspectiveStyle;
73
+ el.dataset.polycssCameraZoom = String(snapshot.state.zoom);
74
+ el.dataset.polycssCameraDistance = String(snapshot.state.distance);
75
+ el.dataset.polycssCameraRotX = String(snapshot.state.rotX);
76
+ el.dataset.polycssCameraRotY = String(snapshot.state.rotY);
77
+ el.dataset.polycssCameraTarget = snapshot.state.target.join(",");
78
+ }
63
79
  function usePolyCamera(options) {
64
80
  const handleRef = useRef2(null);
65
81
  if (!handleRef.current) {
@@ -89,40 +105,33 @@ function usePolyCamera(options) {
89
105
  handle.update(next);
90
106
  const el = sceneElRef.current;
91
107
  if (el) {
92
- const s = handle.state;
93
- const [ox, oy, oz] = store.getState().autoCenterOffset;
94
- const tileSize = BASE_TILE;
95
- const wx = s.target[0] + ox;
96
- const wy = s.target[1] + oy;
97
- const wz = s.target[2] + oz;
98
- const cssX = wy * tileSize;
99
- const cssY = wx * tileSize;
100
- const cssZ = wz * tileSize;
101
- const cssZoom = s.zoom / tileSize;
102
- const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
103
- el.style.transform = `${distancePart}scale(${cssZoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
108
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
109
+ autoCenterOffset: store.getState().autoCenterOffset
110
+ });
104
111
  }
105
112
  store.updateCameraFromRef(handle);
106
113
  store.notifyAll();
107
114
  }
108
- }, [options.zoom, options.target, options.rotX, options.rotY, options.distance, store]);
115
+ writeCameraSnapshotAttrs(cameraElRef.current, handle, options.projection, options.perspectiveStyle);
116
+ }, [
117
+ options.zoom,
118
+ options.target,
119
+ options.rotX,
120
+ options.rotY,
121
+ options.distance,
122
+ options.projection,
123
+ options.perspectiveStyle,
124
+ store
125
+ ]);
109
126
  const applyTransformDirect = useCallback2(() => {
110
127
  const el = sceneElRef.current;
111
128
  if (!el) return;
112
129
  const handle = handleRef.current;
113
- const s = handle.state;
114
- const [ox, oy, oz] = store.getState().autoCenterOffset;
115
- const tileSize = BASE_TILE;
116
- const wx = s.target[0] + ox;
117
- const wy = s.target[1] + oy;
118
- const wz = s.target[2] + oz;
119
- const cssX = wy * tileSize;
120
- const cssY = wx * tileSize;
121
- const cssZ = wz * tileSize;
122
- const cssZoom = s.zoom / tileSize;
123
- const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
124
- el.style.transform = `${distancePart}scale(${cssZoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
125
- }, [store]);
130
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
131
+ autoCenterOffset: store.getState().autoCenterOffset
132
+ });
133
+ writeCameraSnapshotAttrs(cameraElRef.current, handle, options.projection, options.perspectiveStyle);
134
+ }, [store, options.projection, options.perspectiveStyle]);
126
135
  return {
127
136
  store,
128
137
  cameraRef: handleRef,
@@ -146,18 +155,26 @@ function PolyPerspectiveCameraInner({
146
155
  className,
147
156
  style
148
157
  }) {
158
+ const perspectiveValue = `${typeof perspective === "number" ? perspective : DEFAULT_PERSPECTIVE}px`;
149
159
  const {
150
160
  store,
151
161
  cameraRef,
152
162
  sceneElRef,
153
163
  cameraElRef,
154
164
  applyTransformDirect
155
- } = usePolyCamera({ zoom, target, rotX, rotY, distance });
165
+ } = usePolyCamera({
166
+ zoom,
167
+ target,
168
+ rotX,
169
+ rotY,
170
+ distance,
171
+ projection: "perspective",
172
+ perspectiveStyle: perspectiveValue
173
+ });
156
174
  const contextValue = useMemo2(
157
175
  () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
158
176
  [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
159
177
  );
160
- const perspectiveValue = `${typeof perspective === "number" ? perspective : DEFAULT_PERSPECTIVE}px`;
161
178
  const cameraStyle = {
162
179
  ...style,
163
180
  perspective: perspectiveValue
@@ -168,6 +185,14 @@ function PolyPerspectiveCameraInner({
168
185
  ref: cameraElRef,
169
186
  className: `polycss-camera${className ? ` ${className}` : ""}`,
170
187
  style: cameraStyle,
188
+ "data-polycss-camera-projection": "perspective",
189
+ "data-polycss-camera-perspective": perspectiveValue,
190
+ "data-polycss-camera-applied-perspective": perspectiveValue,
191
+ "data-polycss-camera-zoom": cameraRef.current.state.zoom,
192
+ "data-polycss-camera-distance": cameraRef.current.state.distance,
193
+ "data-polycss-camera-rot-x": cameraRef.current.state.rotX,
194
+ "data-polycss-camera-rot-y": cameraRef.current.state.rotY,
195
+ "data-polycss-camera-target": cameraRef.current.state.target.join(","),
171
196
  children
172
197
  }
173
198
  ) });
@@ -193,7 +218,15 @@ function PolyOrthographicCameraInner({
193
218
  sceneElRef,
194
219
  cameraElRef,
195
220
  applyTransformDirect
196
- } = usePolyCamera({ zoom, target, rotX, rotY, distance });
221
+ } = usePolyCamera({
222
+ zoom,
223
+ target,
224
+ rotX,
225
+ rotY,
226
+ distance,
227
+ projection: "orthographic",
228
+ perspectiveStyle: "none"
229
+ });
197
230
  const contextValue = useMemo3(
198
231
  () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
199
232
  [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
@@ -213,6 +246,14 @@ function PolyOrthographicCameraInner({
213
246
  ref: cameraElRef,
214
247
  className: `polycss-camera${className ? ` ${className}` : ""}`,
215
248
  style: cameraStyle,
249
+ "data-polycss-camera-projection": "orthographic",
250
+ "data-polycss-camera-perspective": "none",
251
+ "data-polycss-camera-applied-perspective": "1000000px",
252
+ "data-polycss-camera-zoom": cameraRef.current.state.zoom,
253
+ "data-polycss-camera-distance": cameraRef.current.state.distance,
254
+ "data-polycss-camera-rot-x": cameraRef.current.state.rotX,
255
+ "data-polycss-camera-rot-y": cameraRef.current.state.rotY,
256
+ "data-polycss-camera-target": cameraRef.current.state.target.join(","),
216
257
  children
217
258
  }
218
259
  ) });
@@ -221,7 +262,13 @@ var PolyOrthographicCamera = memo2(PolyOrthographicCameraInner);
221
262
 
222
263
  // src/scene/PolyScene.tsx
223
264
  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";
265
+ import {
266
+ BASE_TILE,
267
+ DEFAULT_SEAM_BLEED,
268
+ parseHexColor,
269
+ resolvePolyTextureLeafGeometry,
270
+ worldDirectionToCss
271
+ } from "@layoutit/polycss-core";
225
272
 
226
273
  // src/scene/useSceneContext.ts
227
274
  import { useMemo as useMemo4 } from "react";
@@ -395,8 +442,8 @@ var CORE_BASE_STYLES = `
395
442
  }
396
443
 
397
444
  .polycss-scene s {
398
- width: var(--polycss-atlas-size, 64px);
399
- height: var(--polycss-atlas-size, 64px);
445
+ width: var(--polycss-atlas-width, var(--polycss-atlas-size, 64px));
446
+ height: var(--polycss-atlas-height, var(--polycss-atlas-size, 64px));
400
447
  }
401
448
 
402
449
  .polycss-scene u {
@@ -807,13 +854,16 @@ function isSolidTriangleSupported(doc) {
807
854
  import {
808
855
  filterAtlasPlans as filterAtlasPlansCore
809
856
  } from "@layoutit/polycss-core";
810
- function filterAtlasPlans(plans, textureLighting, disabled, doc) {
857
+ function filterAtlasPlans(plans, textureLighting, disabled, doc, textureBackend, textureImageRendering, textureProjection) {
811
858
  const d = doc ?? (typeof document !== "undefined" ? document : null);
812
859
  return filterAtlasPlansCore(plans, textureLighting, disabled, {
813
860
  solidTriangleSupported: isSolidTriangleSupported(doc),
814
861
  projectiveQuadSupported: doc ? projectiveQuadSupported(doc) : true,
815
862
  borderShapeSupported: isBorderShapeSupported(doc),
816
- cornerShapeSupported: d ? cornerShapeSupported(d) : false
863
+ cornerShapeSupported: d ? cornerShapeSupported(d) : false,
864
+ textureBackend,
865
+ textureImageRendering,
866
+ textureProjection
817
867
  });
818
868
  }
819
869
 
@@ -828,8 +878,13 @@ function isMobileDocument(doc) {
828
878
  if (!media) return false;
829
879
  return media("(pointer: coarse)").matches || media("(hover: none)").matches;
830
880
  }
831
- function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc) {
832
- return packTextureAtlasPlansWithScaleCore(plans, textureQualityInput, isMobileDocument(doc));
881
+ function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc, textureLeafSizing) {
882
+ return packTextureAtlasPlansWithScaleCore(
883
+ plans,
884
+ textureQualityInput,
885
+ isMobileDocument(doc),
886
+ textureLeafSizing
887
+ );
833
888
  }
834
889
 
835
890
  // src/scene/atlas/buildAtlasPages.ts
@@ -2047,7 +2102,7 @@ function deferRevoke(urls) {
2047
2102
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
2048
2103
  else setTimeout(run, 0);
2049
2104
  }
2050
- function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies, atomic = false) {
2105
+ function useTextureAtlas(plans, textureLighting, textureQualityInput, textureLeafSizing, textureBackend, textureImageRendering, textureProjection, strategies, atomic = false) {
2051
2106
  const disabled = useMemo5(
2052
2107
  () => new Set(strategies?.disable ?? []),
2053
2108
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -2058,17 +2113,21 @@ function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies
2058
2113
  plans,
2059
2114
  textureLighting,
2060
2115
  disabled,
2061
- typeof document !== "undefined" ? document : null
2116
+ typeof document !== "undefined" ? document : null,
2117
+ textureBackend,
2118
+ textureImageRendering,
2119
+ textureProjection
2062
2120
  ),
2063
- [plans, textureLighting, disabled]
2121
+ [plans, textureLighting, disabled, textureBackend, textureImageRendering, textureProjection]
2064
2122
  );
2065
2123
  const { packed, atlasScale } = useMemo5(
2066
2124
  () => packTextureAtlasPlansWithScale(
2067
2125
  atlasPlans,
2068
2126
  textureQualityInput,
2069
- typeof document !== "undefined" ? document : null
2127
+ typeof document !== "undefined" ? document : null,
2128
+ textureLeafSizing
2070
2129
  ),
2071
- [atlasPlans, textureQualityInput]
2130
+ [atlasPlans, textureQualityInput, textureLeafSizing]
2072
2131
  );
2073
2132
  const [pages, setPages] = useState(() => pageShells(packed.pages));
2074
2133
  const [frame, setFrame] = useState(() => ({
@@ -2421,12 +2480,13 @@ var TextureCornerShapeSolidPoly = memo6(function TextureCornerShapeSolidPoly2({
2421
2480
 
2422
2481
  // src/scene/atlas/atlasPoly.tsx
2423
2482
  import { memo as memo7 } from "react";
2424
- import { formatMatrix3d, formatCssLengthPx } from "@layoutit/polycss-core";
2483
+ import { formatMatrix3d, formatCssLengthPx, resolvePolyTextureImageRendering } from "@layoutit/polycss-core";
2425
2484
  import { jsx as jsx7 } from "react/jsx-runtime";
2426
2485
  var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2427
2486
  entry,
2428
2487
  page,
2429
2488
  textureLighting,
2489
+ textureImageRendering,
2430
2490
  solidPaintDefaults: _solidPaintDefaults,
2431
2491
  className,
2432
2492
  style: styleProp,
@@ -2436,26 +2496,26 @@ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2436
2496
  }) {
2437
2497
  const ATLAS_CANONICAL_SIZE_EXPLICIT = 64;
2438
2498
  const dynamic = textureLighting === "dynamic";
2499
+ const resolvedImageRendering = resolvePolyTextureImageRendering(entry.polygon, textureImageRendering);
2439
2500
  const atlasCanonicalSize = entry.atlasCanonicalSize ?? ATLAS_CANONICAL_SIZE_EXPLICIT;
2501
+ const atlasLeafWidth = entry.atlasLeafWidth ?? atlasCanonicalSize;
2502
+ const atlasLeafHeight = entry.atlasLeafHeight ?? atlasCanonicalSize;
2440
2503
  const atlasWidth = entry.canvasW || 1;
2441
2504
  const atlasHeight = entry.canvasH || 1;
2442
- const atlasPosition = page ? `${formatCssLengthPx(-entry.x / atlasWidth * atlasCanonicalSize)} ${formatCssLengthPx(-entry.y / atlasHeight * atlasCanonicalSize)}` : void 0;
2443
- const atlasSize = page ? `${formatCssLengthPx(page.width / atlasWidth * atlasCanonicalSize)} ${formatCssLengthPx(page.height / atlasHeight * atlasCanonicalSize)}` : void 0;
2505
+ const atlasPosition = page ? `${formatCssLengthPx(-entry.x / atlasWidth * atlasLeafWidth)} ${formatCssLengthPx(-entry.y / atlasHeight * atlasLeafHeight)}` : void 0;
2506
+ const atlasSize = page ? `${formatCssLengthPx(page.width / atlasWidth * atlasLeafWidth)} ${formatCssLengthPx(page.height / atlasHeight * atlasLeafHeight)}` : void 0;
2444
2507
  const dynamicMask = dynamic && page?.url ? `url(${page.url})` : void 0;
2445
- const background = !dynamic && page?.url ? `url(${page.url}) ${atlasPosition} / ${atlasSize} no-repeat` : void 0;
2446
2508
  const style = {
2447
2509
  transform: formatMatrix3d(entry.atlasMatrix),
2448
2510
  ["--polycss-atlas-size"]: `${atlasCanonicalSize}px`,
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 },
2511
+ ["--polycss-atlas-width"]: formatCssLengthPx(atlasLeafWidth),
2512
+ ["--polycss-atlas-height"]: formatCssLengthPx(atlasLeafHeight),
2513
+ ["--polycss-atlas-leaf-sizing"]: entry.atlasLeafSizing ?? "canonical",
2514
+ backgroundImage: page?.url ? `url(${page.url})` : void 0,
2515
+ backgroundPosition: atlasPosition,
2516
+ backgroundSize: atlasSize,
2517
+ backgroundRepeat: page?.url ? "no-repeat" : void 0,
2518
+ imageRendering: resolvedImageRendering === "pixelated" ? "pixelated" : void 0,
2459
2519
  ...dynamic ? {
2460
2520
  ["--pnx"]: entry.normal[0].toFixed(4),
2461
2521
  ["--pny"]: entry.normal[1].toFixed(4),
@@ -2486,6 +2546,74 @@ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2486
2546
  className: elementClassName,
2487
2547
  style,
2488
2548
  "data-poly-index": entry.index,
2549
+ "data-polycss-leaf": "polygon",
2550
+ "data-polycss-texture-backend": "atlas",
2551
+ "data-polycss-texture-leaf-sizing": entry.atlasLeafSizing ?? "canonical",
2552
+ "data-polycss-texture-ready": page?.url ? "true" : "false",
2553
+ "data-polycss-texture-image-rendering": resolvedImageRendering,
2554
+ "data-polycss-texture-projection": "affine",
2555
+ "data-polycss-texture-lighting": textureLighting,
2556
+ "data-polycss-texture-leaf-width": atlasLeafWidth,
2557
+ "data-polycss-texture-leaf-height": atlasLeafHeight,
2558
+ "data-polycss-double-sided": entry.polygon.doubleSided ? "true" : void 0,
2559
+ ...domEventHandlers,
2560
+ ...dataAttrs,
2561
+ ...domAttrs
2562
+ }
2563
+ );
2564
+ });
2565
+ var TextureImagePoly = memo7(function TextureImagePoly2({
2566
+ plan,
2567
+ geometry,
2568
+ className,
2569
+ style: styleProp,
2570
+ domAttrs,
2571
+ domEventHandlers,
2572
+ pointerEvents = "auto"
2573
+ }) {
2574
+ const style = {
2575
+ transform: formatMatrix3d(geometry.matrix),
2576
+ ["--polycss-atlas-width"]: formatCssLengthPx(geometry.leafWidth),
2577
+ ["--polycss-atlas-height"]: formatCssLengthPx(geometry.leafHeight),
2578
+ ["--polycss-atlas-leaf-sizing"]: "image",
2579
+ backgroundImage: `url(${geometry.url})`,
2580
+ backgroundPosition: `${formatCssLengthPx(geometry.backgroundPosition[0])} ${formatCssLengthPx(geometry.backgroundPosition[1])}`,
2581
+ backgroundSize: `${formatCssLengthPx(geometry.backgroundSize[0])} ${formatCssLengthPx(geometry.backgroundSize[1])}`,
2582
+ backgroundRepeat: "no-repeat",
2583
+ backgroundBlendMode: "normal",
2584
+ maskImage: "none",
2585
+ WebkitMaskImage: "none",
2586
+ imageRendering: geometry.imageRendering === "pixelated" ? "pixelated" : void 0,
2587
+ ["--pnx"]: plan.normal[0].toFixed(4),
2588
+ ["--pny"]: plan.normal[1].toFixed(4),
2589
+ ["--pnz"]: plan.normal[2].toFixed(4),
2590
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
2591
+ ...styleProp
2592
+ };
2593
+ const dataAttrs = plan.polygon.data ? Object.fromEntries(
2594
+ Object.entries(plan.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2595
+ ) : {};
2596
+ const elementClassName = className?.trim() || void 0;
2597
+ return /* @__PURE__ */ jsx7(
2598
+ "s",
2599
+ {
2600
+ className: elementClassName,
2601
+ style,
2602
+ "data-poly-index": plan.index,
2603
+ "data-polycss-leaf": "polygon",
2604
+ "data-polycss-texture-backend": "image",
2605
+ "data-polycss-texture-leaf-sizing": "image",
2606
+ "data-polycss-texture-ready": "true",
2607
+ "data-polycss-texture-image-rendering": geometry.imageRendering,
2608
+ "data-polycss-texture-projection": geometry.projection,
2609
+ "data-polycss-texture-lighting": geometry.lighting,
2610
+ "data-polycss-texture-leaf-width": geometry.leafWidth,
2611
+ "data-polycss-texture-leaf-height": geometry.leafHeight,
2612
+ "data-polycss-double-sided": plan.polygon.doubleSided ? "true" : void 0,
2613
+ "data-polycss-texture-source-x": geometry.sourceRect.x,
2614
+ "data-polycss-texture-source-y": geometry.sourceRect.y,
2615
+ "data-polycss-texture-source-width": geometry.sourceRect.width,
2616
+ "data-polycss-texture-source-height": geometry.sourceRect.height,
2489
2617
  ...domEventHandlers,
2490
2618
  ...dataAttrs,
2491
2619
  ...domAttrs
@@ -2513,6 +2641,10 @@ function PolySceneInner({
2513
2641
  ambientLight,
2514
2642
  textureLighting = "baked",
2515
2643
  textureQuality,
2644
+ textureLeafSizing,
2645
+ textureImageRendering,
2646
+ textureBackend,
2647
+ textureProjection,
2516
2648
  seamBleed = DEFAULT_SEAM_BLEED,
2517
2649
  strategies,
2518
2650
  autoCenter = false,
@@ -2608,7 +2740,16 @@ function PolySceneInner({
2608
2740
  },
2609
2741
  [polygons, polyContext, seamBleed, directionalForAtlas, ambientForAtlas]
2610
2742
  );
2611
- const textureAtlas = useTextureAtlas(textureAtlasPlans, textureLighting, textureQuality, strategies);
2743
+ const textureAtlas = useTextureAtlas(
2744
+ textureAtlasPlans,
2745
+ textureLighting,
2746
+ textureQuality,
2747
+ textureLeafSizing,
2748
+ textureBackend,
2749
+ textureImageRendering,
2750
+ textureProjection,
2751
+ strategies
2752
+ );
2612
2753
  const dynamicLightVars = useMemo6(() => {
2613
2754
  if (textureLighting !== "dynamic") return null;
2614
2755
  const userDir = directionalLight?.direction ?? [0.4, -0.7, 0.59];
@@ -2656,7 +2797,7 @@ function PolySceneInner({
2656
2797
  }
2657
2798
  if (!Number.isFinite(minWorldZ)) return null;
2658
2799
  const lift = shadow?.lift ?? 0.05;
2659
- return (minWorldZ + lift) * BASE_TILE2;
2800
+ return (minWorldZ + lift) * BASE_TILE;
2660
2801
  }, [shadow]);
2661
2802
  const registerShadowCaster = useCallback5((meshId, data) => {
2662
2803
  if (data === null) {
@@ -2706,12 +2847,28 @@ function PolySceneInner({
2706
2847
  {
2707
2848
  entry,
2708
2849
  page: textureAtlas.pages[entry.pageIndex],
2709
- textureLighting
2850
+ textureLighting,
2851
+ textureImageRendering
2710
2852
  },
2711
2853
  entry.index
2712
2854
  );
2713
2855
  }
2714
2856
  const plan = textureAtlasPlans[index];
2857
+ const imageGeometry = plan ? resolvePolyTextureLeafGeometry(plan, {
2858
+ imageRendering: textureImageRendering,
2859
+ backend: textureBackend,
2860
+ projection: textureProjection
2861
+ }) : null;
2862
+ if (plan && imageGeometry) {
2863
+ return /* @__PURE__ */ jsx8(
2864
+ TextureImagePoly,
2865
+ {
2866
+ plan,
2867
+ geometry: imageGeometry
2868
+ },
2869
+ plan.index
2870
+ );
2871
+ }
2715
2872
  if (!plan || plan.texture) return null;
2716
2873
  const useU = !disabledStrategies?.has("u");
2717
2874
  const useProjectiveSolid = !disabledStrategies?.has("b");
@@ -2737,6 +2894,10 @@ function PolySceneInner({
2737
2894
  ambientLight,
2738
2895
  strategies,
2739
2896
  seamBleed,
2897
+ textureLeafSizing,
2898
+ textureImageRendering,
2899
+ textureBackend,
2900
+ textureProjection,
2740
2901
  shadow,
2741
2902
  registerShadowCaster,
2742
2903
  registerShadowReceiver,
@@ -2746,7 +2907,7 @@ function PolySceneInner({
2746
2907
  groundCssZ,
2747
2908
  sceneEl
2748
2909
  }),
2749
- [textureLighting, directionalLight, ambientLight, strategies, seamBleed, shadow, registerShadowCaster, registerShadowReceiver, shadowCastersVersion, hasShadowReceiver, groundCssZ, sceneEl]
2910
+ [textureLighting, directionalLight, ambientLight, strategies, seamBleed, textureLeafSizing, textureImageRendering, textureBackend, textureProjection, shadow, registerShadowCaster, registerShadowReceiver, shadowCastersVersion, hasShadowReceiver, groundCssZ, sceneEl]
2750
2911
  );
2751
2912
  return /* @__PURE__ */ jsx8(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */ jsxs(
2752
2913
  "div",
@@ -2781,7 +2942,7 @@ import {
2781
2942
  useState as useState4
2782
2943
  } from "react";
2783
2944
  import {
2784
- BASE_TILE as BASE_TILE4,
2945
+ BASE_TILE as BASE_TILE3,
2785
2946
  buildPolyMeshTransform,
2786
2947
  buildSharedEdgeMap,
2787
2948
  computeReceiverShadowFaces,
@@ -2796,6 +2957,7 @@ import {
2796
2957
  prepareCasterPolyItems,
2797
2958
  prepareReceiverFacePlanes,
2798
2959
  projectCssVertexToGround,
2960
+ resolvePolyTextureLeafGeometry as resolvePolyTextureLeafGeometry2,
2799
2961
  worldDirectionToCss as worldDirectionToCss2
2800
2962
  } from "@layoutit/polycss-core";
2801
2963
 
@@ -2894,7 +3056,7 @@ import { createPortal } from "react-dom";
2894
3056
 
2895
3057
  // src/scene/voxelRenderer.ts
2896
3058
  import {
2897
- BASE_TILE as BASE_TILE3,
3059
+ BASE_TILE as BASE_TILE2,
2898
3060
  computeProjectiveQuadMatrix,
2899
3061
  normalFacesCamera,
2900
3062
  parsePureColor as parsePureColor2,
@@ -3003,11 +3165,11 @@ function polygonBrush(polygon) {
3003
3165
  return {
3004
3166
  axis: "z",
3005
3167
  face,
3006
- left: minY * BASE_TILE3,
3007
- top: minX * BASE_TILE3,
3008
- width: Math.max(0, (maxY - minY) * BASE_TILE3),
3009
- height: Math.max(0, (maxX - minX) * BASE_TILE3),
3010
- z: minZ * BASE_TILE3,
3168
+ left: minY * BASE_TILE2,
3169
+ top: minX * BASE_TILE2,
3170
+ width: Math.max(0, (maxY - minY) * BASE_TILE2),
3171
+ height: Math.max(0, (maxX - minX) * BASE_TILE2),
3172
+ z: minZ * BASE_TILE2,
3011
3173
  baseColor,
3012
3174
  bleed: zeroVoxelSeamBleed()
3013
3175
  };
@@ -3016,11 +3178,11 @@ function polygonBrush(polygon) {
3016
3178
  return {
3017
3179
  axis: "x",
3018
3180
  face,
3019
- left: minY * BASE_TILE3,
3020
- top: minZ * BASE_TILE3,
3021
- width: Math.max(0, (maxY - minY) * BASE_TILE3),
3022
- height: Math.max(0, (maxZ - minZ) * BASE_TILE3),
3023
- z: -minX * BASE_TILE3,
3181
+ left: minY * BASE_TILE2,
3182
+ top: minZ * BASE_TILE2,
3183
+ width: Math.max(0, (maxY - minY) * BASE_TILE2),
3184
+ height: Math.max(0, (maxZ - minZ) * BASE_TILE2),
3185
+ z: -minX * BASE_TILE2,
3024
3186
  baseColor,
3025
3187
  bleed: zeroVoxelSeamBleed()
3026
3188
  };
@@ -3029,11 +3191,11 @@ function polygonBrush(polygon) {
3029
3191
  return {
3030
3192
  axis: "y",
3031
3193
  face,
3032
- left: minZ * BASE_TILE3,
3033
- top: minX * BASE_TILE3,
3034
- width: Math.max(0, (maxZ - minZ) * BASE_TILE3),
3035
- height: Math.max(0, (maxX - minX) * BASE_TILE3),
3036
- z: -minY * BASE_TILE3,
3194
+ left: minZ * BASE_TILE2,
3195
+ top: minX * BASE_TILE2,
3196
+ width: Math.max(0, (maxZ - minZ) * BASE_TILE2),
3197
+ height: Math.max(0, (maxX - minX) * BASE_TILE2),
3198
+ z: -minY * BASE_TILE2,
3037
3199
  baseColor,
3038
3200
  bleed: zeroVoxelSeamBleed()
3039
3201
  };
@@ -3053,7 +3215,7 @@ function worldLineKey(segment) {
3053
3215
  return key;
3054
3216
  }
3055
3217
  function cssPointForVertex(v) {
3056
- return [v[1] * BASE_TILE3, v[0] * BASE_TILE3, v[2] * BASE_TILE3];
3218
+ return [v[1] * BASE_TILE2, v[0] * BASE_TILE2, v[2] * BASE_TILE2];
3057
3219
  }
3058
3220
  function localPointForItem(item, p) {
3059
3221
  if (item.axis === "x") return [p[0], p[2]];
@@ -3664,6 +3826,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3664
3826
  autoCenter,
3665
3827
  textureLighting,
3666
3828
  textureQuality,
3829
+ textureLeafSizing,
3830
+ textureImageRendering,
3831
+ textureBackend,
3832
+ textureProjection,
3667
3833
  seamBleed,
3668
3834
  atomicAtlas,
3669
3835
  onFrameReady,
@@ -3733,6 +3899,12 @@ var PolyMesh = forwardRef(function PolyMesh2({
3733
3899
  const stableTriangleColorFrameRef = useRef6(0);
3734
3900
  const setPolygonsImplRef = useRef6(() => {
3735
3901
  });
3902
+ const textureReadyRef = useRef6(true);
3903
+ const textureReadyWaitersRef = useRef6([]);
3904
+ const resolveTextureReadyWaiters = useCallback7(() => {
3905
+ const waiters = textureReadyWaitersRef.current.splice(0);
3906
+ for (const resolve of waiters) resolve();
3907
+ }, []);
3736
3908
  const handle = useMemo7(() => ({
3737
3909
  get element() {
3738
3910
  return wrapperRef.current;
@@ -3746,6 +3918,12 @@ var PolyMesh = forwardRef(function PolyMesh2({
3746
3918
  setPolygonsImplRef.current(nextPolygons);
3747
3919
  },
3748
3920
  rebakeAtlas: () => setBakedRotation(propsRef.current.rotation),
3921
+ whenTexturesReady() {
3922
+ if (textureReadyRef.current) return Promise.resolve();
3923
+ return new Promise((resolve) => {
3924
+ textureReadyWaitersRef.current.push(resolve);
3925
+ });
3926
+ },
3749
3927
  updatePolygon(target, partial) {
3750
3928
  const current = polygonsRef.current;
3751
3929
  const idx = typeof target === "number" ? target : current.indexOf(target);
@@ -3892,6 +4070,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3892
4070
  [effectiveStrategies]
3893
4071
  );
3894
4072
  const effectiveSeamBleed = seamBleed ?? sceneCtx?.seamBleed ?? DEFAULT_SEAM_BLEED2;
4073
+ const effectiveTextureLeafSizing = textureLeafSizing ?? sceneCtx?.textureLeafSizing;
4074
+ const effectiveTextureImageRendering = textureImageRendering ?? sceneCtx?.textureImageRendering;
4075
+ const effectiveTextureBackend = textureBackend ?? sceneCtx?.textureBackend;
4076
+ const effectiveTextureProjection = textureProjection ?? sceneCtx?.textureProjection;
3895
4077
  const effectiveDirectional = sceneCtx?.directionalLight;
3896
4078
  const effectiveAmbient = sceneCtx?.ambientLight;
3897
4079
  const directVoxelEnabled = Boolean(
@@ -3954,9 +4136,18 @@ var PolyMesh = forwardRef(function PolyMesh2({
3954
4136
  atlasPlans,
3955
4137
  effectiveTextureLighting,
3956
4138
  textureQuality,
4139
+ effectiveTextureLeafSizing,
4140
+ effectiveTextureBackend,
4141
+ effectiveTextureImageRendering,
4142
+ effectiveTextureProjection,
3957
4143
  effectiveStrategies,
3958
4144
  atomicAtlas
3959
4145
  );
4146
+ textureReadyRef.current = textureAtlas.ready;
4147
+ useEffect5(() => {
4148
+ if (textureAtlas.ready) resolveTextureReadyWaiters();
4149
+ }, [textureAtlas.ready, resolveTextureReadyWaiters]);
4150
+ useEffect5(() => resolveTextureReadyWaiters, [resolveTextureReadyWaiters]);
3960
4151
  const solidPaintDefaults = useMemo7(
3961
4152
  () => !renderPolygon ? getSolidPaintDefaults(textureAtlas.plans, effectiveTextureLighting, effectiveStrategies) : {},
3962
4153
  [renderPolygon, textureAtlas.plans, effectiveTextureLighting, effectiveStrategies]
@@ -4020,7 +4211,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
4020
4211
  const userGroundLightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
4021
4212
  const lightDir = worldDirectionToCss2(userGroundLightDir);
4022
4213
  const meshPosZ = position?.[2] ?? 0;
4023
- const localGroundCssZ = bakedShadowGroundCssZ - meshPosZ * BASE_TILE4;
4214
+ const localGroundCssZ = bakedShadowGroundCssZ - meshPosZ * BASE_TILE3;
4024
4215
  const shadowDedupDrop = findOverlappingPolygonDuplicates(polygons, {
4025
4216
  normalTolerance: 0.1,
4026
4217
  distanceTolerance: 0.5,
@@ -4038,9 +4229,9 @@ var PolyMesh = forwardRef(function PolyMesh2({
4038
4229
  const projected = [];
4039
4230
  for (const v of polygon.vertices) {
4040
4231
  const cssVertex = [
4041
- v[1] * BASE_TILE4,
4042
- v[0] * BASE_TILE4,
4043
- v[2] * BASE_TILE4
4232
+ v[1] * BASE_TILE3,
4233
+ v[0] * BASE_TILE3,
4234
+ v[2] * BASE_TILE3
4044
4235
  ];
4045
4236
  if (cssVertex[0] < fpMinX) fpMinX = cssVertex[0];
4046
4237
  if (cssVertex[1] < fpMinY) fpMinY = cssVertex[1];
@@ -4305,12 +4496,28 @@ var PolyMesh = forwardRef(function PolyMesh2({
4305
4496
  entry,
4306
4497
  page: textureAtlas.pages[entry.pageIndex],
4307
4498
  textureLighting: effectiveTextureLighting,
4499
+ textureImageRendering: effectiveTextureImageRendering,
4308
4500
  solidPaintDefaults
4309
4501
  },
4310
4502
  entry.index
4311
4503
  );
4312
4504
  }
4313
4505
  const plan = textureAtlas.plans[index];
4506
+ const imageGeometry = plan ? resolvePolyTextureLeafGeometry2(plan, {
4507
+ imageRendering: effectiveTextureImageRendering,
4508
+ backend: effectiveTextureBackend,
4509
+ projection: effectiveTextureProjection
4510
+ }) : null;
4511
+ if (plan && imageGeometry) {
4512
+ return /* @__PURE__ */ jsx9(
4513
+ TextureImagePoly,
4514
+ {
4515
+ plan,
4516
+ geometry: imageGeometry
4517
+ },
4518
+ plan.index
4519
+ );
4520
+ }
4314
4521
  if (!plan || plan.texture) return null;
4315
4522
  if (isProjectiveQuadPlan(plan)) {
4316
4523
  return /* @__PURE__ */ jsx9(
@@ -4412,7 +4619,7 @@ function RenderPropPolygon({
4412
4619
  }
4413
4620
 
4414
4621
  // src/scene/PolyIframe.tsx
4415
- import { BASE_TILE as BASE_TILE5 } from "@layoutit/polycss-core";
4622
+ import { BASE_TILE as BASE_TILE4 } from "@layoutit/polycss-core";
4416
4623
  import { jsx as jsx10 } from "react/jsx-runtime";
4417
4624
  function buildIframeTransform(position, rotation, scale, cssWidth, cssHeight) {
4418
4625
  const sx = typeof scale === "number" ? scale : scale?.[0] ?? 1;
@@ -4420,9 +4627,9 @@ function buildIframeTransform(position, rotation, scale, cssWidth, cssHeight) {
4420
4627
  const sz = typeof scale === "number" ? scale : scale?.[2] ?? 1;
4421
4628
  const hasScale = sx !== 1 || sy !== 1 || sz !== 1;
4422
4629
  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;
4630
+ const cssX = (position?.[1] ?? 0) * BASE_TILE4;
4631
+ const cssY = (position?.[0] ?? 0) * BASE_TILE4;
4632
+ const cssZ = (position?.[2] ?? 0) * BASE_TILE4;
4426
4633
  const parts = [];
4427
4634
  parts.push(`translate3d(${cssX}px, ${cssY}px, ${cssZ}px)`);
4428
4635
  if (hasRotation) {
@@ -4449,8 +4656,8 @@ function PolyIframe({
4449
4656
  className,
4450
4657
  style
4451
4658
  }) {
4452
- const cssW = width * BASE_TILE5;
4453
- const cssH = height * BASE_TILE5;
4659
+ const cssW = width * BASE_TILE4;
4660
+ const cssH = height * BASE_TILE4;
4454
4661
  const transform = buildIframeTransform(position, rotation, scale, cssW, cssH);
4455
4662
  const wrapperStyle = {
4456
4663
  position: "absolute",
@@ -4531,6 +4738,7 @@ function usePolyMaterial(options) {
4531
4738
 
4532
4739
  // src/shapes/Poly.tsx
4533
4740
  import { memo as memo9, useMemo as useMemo10 } from "react";
4741
+ import { resolvePolyTextureLeafGeometry as resolvePolyTextureLeafGeometry3 } from "@layoutit/polycss-core";
4534
4742
  import { jsx as jsx12 } from "react/jsx-runtime";
4535
4743
  var DIRECT_TEXTURE_CSS_DECIMALS = 4;
4536
4744
  function formatCssLength(value, decimals = DIRECT_TEXTURE_CSS_DECIMALS) {
@@ -4561,7 +4769,8 @@ function MaterialDirectPoly({
4561
4769
  style: styleProp,
4562
4770
  domAttrs,
4563
4771
  domEventHandlers,
4564
- pointerEvents = "auto"
4772
+ pointerEvents = "auto",
4773
+ imageRendering
4565
4774
  }) {
4566
4775
  const { u0, u1, v0, v1 } = uvRect;
4567
4776
  const du = u1 - u0;
@@ -4576,6 +4785,8 @@ function MaterialDirectPoly({
4576
4785
  backgroundImage: `url(${material.texture})`,
4577
4786
  backgroundSize: `${formatCssLength(sourceW)} ${formatCssLength(sourceH)}`,
4578
4787
  backgroundPosition: `${formatCssLength(-offsetX)} ${formatCssLength(-offsetY)}`,
4788
+ backgroundRepeat: "no-repeat",
4789
+ imageRendering: imageRendering === "pixelated" ? "pixelated" : void 0,
4579
4790
  pointerEvents: pointerEvents === "none" ? "none" : void 0,
4580
4791
  ...styleProp
4581
4792
  };
@@ -4588,6 +4799,14 @@ function MaterialDirectPoly({
4588
4799
  {
4589
4800
  className: elementClassName,
4590
4801
  style,
4802
+ "data-poly-index": plan.index,
4803
+ "data-polycss-leaf": "polygon",
4804
+ "data-polycss-texture-backend": "image",
4805
+ "data-polycss-texture-ready": "true",
4806
+ "data-polycss-texture-image-rendering": imageRendering ?? "auto",
4807
+ "data-polycss-texture-projection": "affine",
4808
+ "data-polycss-texture-lighting": "source",
4809
+ "data-polycss-double-sided": plan.polygon.doubleSided ? "true" : void 0,
4591
4810
  ...domEventHandlers,
4592
4811
  ...dataAttrs,
4593
4812
  ...domAttrs
@@ -4598,8 +4817,11 @@ function PolyInner({
4598
4817
  vertices,
4599
4818
  color,
4600
4819
  texture,
4820
+ textureImageSource,
4821
+ texturePresentation,
4601
4822
  uvs,
4602
4823
  data,
4824
+ doubleSided,
4603
4825
  material,
4604
4826
  position,
4605
4827
  scale,
@@ -4627,6 +4849,10 @@ function PolyInner({
4627
4849
  context,
4628
4850
  textureLighting: textureLightingProp,
4629
4851
  textureQuality: textureQualityProp,
4852
+ textureLeafSizing: textureLeafSizingProp,
4853
+ textureImageRendering: textureImageRenderingProp,
4854
+ textureBackend: textureBackendProp,
4855
+ textureProjection: textureProjectionProp,
4630
4856
  baseColor: baseColorProp,
4631
4857
  ...dataAttrs
4632
4858
  }) {
@@ -4634,11 +4860,25 @@ function PolyInner({
4634
4860
  const layerElevation = context?.layerElevation ?? tileSize;
4635
4861
  const textureLighting = textureLightingProp ?? context?.textureLighting ?? "baked";
4636
4862
  const textureQuality = textureQualityProp ?? context?.textureQuality;
4863
+ const textureLeafSizing = textureLeafSizingProp ?? context?.textureLeafSizing;
4864
+ const textureImageRendering = textureImageRenderingProp ?? context?.textureImageRendering;
4865
+ const textureBackend = textureBackendProp ?? context?.textureBackend;
4866
+ const textureProjection = textureProjectionProp ?? context?.textureProjection;
4637
4867
  const polygonColor = baseColorProp ?? color;
4638
4868
  const effectiveTexture = material?.texture ?? texture;
4639
4869
  const atlasPlan = useMemo10(
4640
4870
  () => computeTextureAtlasPlan(
4641
- { vertices, color: polygonColor, texture: effectiveTexture, uvs, data },
4871
+ {
4872
+ vertices,
4873
+ color: polygonColor,
4874
+ texture: effectiveTexture,
4875
+ textureImageSource,
4876
+ texturePresentation,
4877
+ uvs,
4878
+ data,
4879
+ doubleSided,
4880
+ material
4881
+ },
4642
4882
  0,
4643
4883
  {
4644
4884
  tileSize,
@@ -4650,8 +4890,12 @@ function PolyInner({
4650
4890
  vertices,
4651
4891
  polygonColor,
4652
4892
  effectiveTexture,
4893
+ textureImageSource,
4894
+ texturePresentation,
4653
4895
  uvs,
4654
4896
  data,
4897
+ doubleSided,
4898
+ material,
4655
4899
  tileSize,
4656
4900
  layerElevation,
4657
4901
  context?.directionalLight
@@ -4665,7 +4909,15 @@ function PolyInner({
4665
4909
  () => materialUvRect ? [] : [atlasPlan],
4666
4910
  [materialUvRect, atlasPlan]
4667
4911
  );
4668
- const textureAtlas = useTextureAtlas(atlasPlans, textureLighting, textureQuality);
4912
+ const textureAtlas = useTextureAtlas(
4913
+ atlasPlans,
4914
+ textureLighting,
4915
+ textureQuality,
4916
+ textureLeafSizing,
4917
+ textureBackend,
4918
+ textureImageRendering,
4919
+ textureProjection
4920
+ );
4669
4921
  const domEventHandlers = {
4670
4922
  onClick,
4671
4923
  onDoubleClick,
@@ -4724,7 +4976,8 @@ function PolyInner({
4724
4976
  style: styleProp,
4725
4977
  domAttrs,
4726
4978
  domEventHandlers,
4727
- pointerEvents: pointerEventsProp ?? "auto"
4979
+ pointerEvents: pointerEventsProp ?? "auto",
4980
+ imageRendering: textureImageRendering
4728
4981
  }
4729
4982
  );
4730
4983
  } else {
@@ -4736,6 +4989,7 @@ function PolyInner({
4736
4989
  entry: atlasEntry,
4737
4990
  page: textureAtlas.pages[atlasEntry.pageIndex],
4738
4991
  textureLighting,
4992
+ textureImageRendering,
4739
4993
  className,
4740
4994
  style: styleProp,
4741
4995
  domAttrs,
@@ -4743,7 +4997,28 @@ function PolyInner({
4743
4997
  pointerEvents: pointerEventsProp ?? "auto"
4744
4998
  }
4745
4999
  );
4746
- } else if (atlasPlan && !atlasPlan.texture) {
5000
+ } else {
5001
+ const imageGeometry = atlasPlan ? resolvePolyTextureLeafGeometry3(atlasPlan, {
5002
+ imageRendering: textureImageRendering,
5003
+ backend: textureBackend,
5004
+ projection: textureProjection
5005
+ }) : null;
5006
+ if (atlasPlan && imageGeometry) {
5007
+ front = /* @__PURE__ */ jsx12(
5008
+ TextureImagePoly,
5009
+ {
5010
+ plan: atlasPlan,
5011
+ geometry: imageGeometry,
5012
+ className,
5013
+ style: styleProp,
5014
+ domAttrs,
5015
+ domEventHandlers,
5016
+ pointerEvents: pointerEventsProp ?? "auto"
5017
+ }
5018
+ );
5019
+ }
5020
+ }
5021
+ if (!front && atlasPlan && !atlasPlan.texture) {
4747
5022
  front = isSolidTrianglePlan2(atlasPlan) ? /* @__PURE__ */ jsx12(
4748
5023
  TextureTrianglePoly,
4749
5024
  {
@@ -4972,7 +5247,7 @@ function PolyTorus({
4972
5247
 
4973
5248
  // src/controls/PolyFirstPersonControls.tsx
4974
5249
  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";
5250
+ import { BASE_TILE as BASE_TILE5 } from "@layoutit/polycss-core";
4976
5251
  var DEFAULTS = {
4977
5252
  enabled: true,
4978
5253
  lookEnabled: true,
@@ -5148,7 +5423,7 @@ var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(prop
5148
5423
  const host = cameraElRef.current;
5149
5424
  const perspStr = host ? getComputedStyle(host).perspective : "";
5150
5425
  const n = parseFloat(perspStr);
5151
- return (Number.isFinite(n) && n > 0 ? n : 32e3) / BASE_TILE6;
5426
+ return (Number.isFinite(n) && n > 0 ? n : 32e3) / BASE_TILE5;
5152
5427
  }
5153
5428
  function deriveTarget() {
5154
5429
  const s = cameraRef.current.state;
@@ -5389,7 +5664,7 @@ var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(prop
5389
5664
  import { useEffect as useEffect7, useRef as useRef8 } from "react";
5390
5665
 
5391
5666
  // src/controls/sharedControls.ts
5392
- import { BASE_TILE as BASE_TILE7 } from "@layoutit/polycss-core";
5667
+ import { BASE_TILE as BASE_TILE6 } from "@layoutit/polycss-core";
5393
5668
  var POINTER_DRAG_SPEED = 4;
5394
5669
  function invertFactor(invert) {
5395
5670
  if (invert === true) return -1;
@@ -5410,7 +5685,7 @@ function applyPan(dx, dy, s, handle, _invert) {
5410
5685
  const cosRotX = cosRotXRaw >= 0 ? Math.max(0.1, cosRotXRaw) : Math.min(-0.1, cosRotXRaw);
5411
5686
  const cZ = Math.cos(s.rotY * Math.PI / 180);
5412
5687
  const sZ = Math.sin(s.rotY * Math.PI / 180);
5413
- const k = z * BASE_TILE7;
5688
+ const k = z * BASE_TILE6;
5414
5689
  const targetD0 = (dx * sZ - dy * cZ / cosRotX) / k;
5415
5690
  const targetD1 = -(dx * cZ + dy * sZ / cosRotX) / k;
5416
5691
  const t = s.target;
@@ -7078,11 +7353,56 @@ var ZERO_SURFACE_LEAF_COUNTS = {
7078
7353
  atlas: 0,
7079
7354
  stableTriangle: 0
7080
7355
  };
7356
+ var READY_TEXTURE_READINESS = {
7357
+ ready: true,
7358
+ textureLeafCount: 0,
7359
+ readyTextureLeafCount: 0,
7360
+ pendingTextureLeafCount: 0,
7361
+ atlasTextureLeafCount: 0,
7362
+ imageTextureLeafCount: 0
7363
+ };
7364
+ function emptyTextureStats() {
7365
+ return {
7366
+ ready: true,
7367
+ leafCount: 0,
7368
+ atlasCount: 0,
7369
+ imageCount: 0,
7370
+ pendingCount: 0,
7371
+ sizingCounts: { canonical: 0, local: 0, raster: 0, image: 0, unknown: 0 },
7372
+ imageRenderingCounts: { auto: 0, pixelated: 0, unknown: 0 },
7373
+ projectionCounts: { affine: 0, projective: 0, fallback: 0, unknown: 0 },
7374
+ minLeafWidth: 0,
7375
+ maxLeafWidth: 0,
7376
+ minLeafHeight: 0,
7377
+ maxLeafHeight: 0,
7378
+ doubleSidedCount: 0
7379
+ };
7380
+ }
7381
+ function emptySnapshotStats() {
7382
+ return {
7383
+ runtimeAtlasUrlCount: 0,
7384
+ snapshotAtlasCount: 0,
7385
+ snapshotBackgroundCount: 0,
7386
+ selfContained: true
7387
+ };
7388
+ }
7389
+ function emptyCameraStats() {
7390
+ return {
7391
+ rootFound: false,
7392
+ projection: "unknown",
7393
+ perspectiveStyle: "",
7394
+ appliedPerspectiveStyle: ""
7395
+ };
7396
+ }
7081
7397
  var EMPTY_POLY_RENDER_STATS = {
7082
7398
  polygonCount: 0,
7083
7399
  mountedPolygonLeafCount: 0,
7084
7400
  shadowLeafCount: 0,
7085
7401
  surfaceLeafCounts: ZERO_SURFACE_LEAF_COUNTS,
7402
+ textureReadiness: READY_TEXTURE_READINESS,
7403
+ textureStats: emptyTextureStats(),
7404
+ snapshotStats: emptySnapshotStats(),
7405
+ cameraStats: emptyCameraStats(),
7086
7406
  bucketCount: 0
7087
7407
  };
7088
7408
  function asOptions(optionsOrPolygonCount) {
@@ -7094,6 +7414,28 @@ function asOptions(optionsOrPolygonCount) {
7094
7414
  function queryCount(scope, selector) {
7095
7415
  return scope.querySelectorAll(selector).length;
7096
7416
  }
7417
+ function matchingElementCount(scope, selector) {
7418
+ return (matchesSelector(scope, selector) ? 1 : 0) + queryCount(scope, selector);
7419
+ }
7420
+ function styleHasRuntimeUrl(styleText) {
7421
+ if (!styleText) return false;
7422
+ const urlPattern = /url\((?:"([^"]*)"|'([^']*)'|([^)]*))\)/g;
7423
+ let match;
7424
+ while (match = urlPattern.exec(styleText)) {
7425
+ const raw = (match[1] ?? match[2] ?? match[3] ?? "").trim();
7426
+ if (raw && !raw.startsWith("data:")) return true;
7427
+ }
7428
+ return false;
7429
+ }
7430
+ function runtimeUrlElementCount(scope) {
7431
+ let count = 0;
7432
+ const root = scope instanceof HTMLElement ? scope : null;
7433
+ if (root && styleHasRuntimeUrl(root.getAttribute("style"))) count++;
7434
+ for (const el of Array.from(scope.querySelectorAll("[style]"))) {
7435
+ if (el instanceof HTMLElement && styleHasRuntimeUrl(el.getAttribute("style"))) count++;
7436
+ }
7437
+ return count;
7438
+ }
7097
7439
  function matchesSelector(root, selector) {
7098
7440
  const candidate = root;
7099
7441
  return typeof candidate.matches === "function" && candidate.matches(selector);
@@ -7105,12 +7447,196 @@ function collectScopes(root, selector) {
7105
7447
  scopes.push(...Array.from(root.querySelectorAll(selector)));
7106
7448
  return scopes;
7107
7449
  }
7450
+ function leafStrategyForTag(tagName) {
7451
+ switch (tagName.toLowerCase()) {
7452
+ case "b":
7453
+ return "quad";
7454
+ case "i":
7455
+ return "clippedSolid";
7456
+ case "s":
7457
+ return "atlas";
7458
+ case "u":
7459
+ return "stableTriangle";
7460
+ default:
7461
+ return null;
7462
+ }
7463
+ }
7464
+ function parseOptionalInteger(value) {
7465
+ if (value == null || value === "") return void 0;
7466
+ const parsed = Number.parseInt(value, 10);
7467
+ return Number.isFinite(parsed) ? parsed : void 0;
7468
+ }
7469
+ function parseOptionalNumber(value) {
7470
+ if (value == null || value === "") return void 0;
7471
+ const parsed = Number(value);
7472
+ return Number.isFinite(parsed) ? parsed : void 0;
7473
+ }
7474
+ function parseOptionalVec3(value) {
7475
+ if (value == null || value === "") return void 0;
7476
+ const parts = value.split(",").map((part) => Number(part));
7477
+ if (parts.length !== 3 || parts.some((part) => !Number.isFinite(part))) return void 0;
7478
+ return parts;
7479
+ }
7480
+ function parseCameraProjection(value) {
7481
+ return value === "orthographic" || value === "perspective" ? value : "unknown";
7482
+ }
7483
+ function textureReadyForElement(el, textureBackend) {
7484
+ if (!textureBackend) return void 0;
7485
+ const attr = el.getAttribute("data-polycss-texture-ready");
7486
+ if (attr === "true") return true;
7487
+ if (attr === "false") return false;
7488
+ return el.style.opacity !== "0";
7489
+ }
7490
+ function queryPolyLeaves(root, optionsOrPolygonCount) {
7491
+ if (!root) return [];
7492
+ const options = asOptions(optionsOrPolygonCount);
7493
+ const out = [];
7494
+ for (const scope of collectScopes(root, options.scopeSelector)) {
7495
+ for (const el of Array.from(scope.querySelectorAll("b,i,s,u"))) {
7496
+ const htmlEl = el;
7497
+ const strategy = leafStrategyForTag(htmlEl.tagName);
7498
+ if (!strategy) continue;
7499
+ const textureBackend = htmlEl.getAttribute("data-polycss-texture-backend") ?? (strategy === "atlas" ? "atlas" : void 0);
7500
+ const textureReady = textureReadyForElement(htmlEl, textureBackend);
7501
+ out.push({
7502
+ element: htmlEl,
7503
+ strategy,
7504
+ polygonIndex: parseOptionalInteger(htmlEl.getAttribute("data-poly-index")),
7505
+ textureBackend,
7506
+ textureReady,
7507
+ textureLeafSizing: htmlEl.getAttribute("data-polycss-texture-leaf-sizing") ?? void 0,
7508
+ textureImageRendering: htmlEl.getAttribute("data-polycss-texture-image-rendering") ?? void 0,
7509
+ textureProjection: htmlEl.getAttribute("data-polycss-texture-projection") ?? void 0,
7510
+ textureLeafWidth: parseOptionalNumber(htmlEl.getAttribute("data-polycss-texture-leaf-width")),
7511
+ textureLeafHeight: parseOptionalNumber(htmlEl.getAttribute("data-polycss-texture-leaf-height")),
7512
+ doubleSided: htmlEl.getAttribute("data-polycss-double-sided") === "true"
7513
+ });
7514
+ }
7515
+ }
7516
+ return out;
7517
+ }
7518
+ function collectPolyTextureReadiness(root, optionsOrPolygonCount) {
7519
+ const readiness = { ...READY_TEXTURE_READINESS };
7520
+ for (const leaf of queryPolyLeaves(root, optionsOrPolygonCount)) {
7521
+ if (!leaf.textureBackend) continue;
7522
+ readiness.textureLeafCount++;
7523
+ if (leaf.textureBackend === "image") readiness.imageTextureLeafCount++;
7524
+ else readiness.atlasTextureLeafCount++;
7525
+ if (leaf.textureReady === false) readiness.pendingTextureLeafCount++;
7526
+ else readiness.readyTextureLeafCount++;
7527
+ }
7528
+ readiness.ready = readiness.pendingTextureLeafCount === 0;
7529
+ return readiness;
7530
+ }
7531
+ function collectPolyTextureStats(root, optionsOrPolygonCount) {
7532
+ const stats = emptyTextureStats();
7533
+ let hasWidth = false;
7534
+ let hasHeight = false;
7535
+ for (const leaf of queryPolyLeaves(root, optionsOrPolygonCount)) {
7536
+ if (!leaf.textureBackend) continue;
7537
+ stats.leafCount++;
7538
+ if (leaf.textureBackend === "image") stats.imageCount++;
7539
+ else stats.atlasCount++;
7540
+ if (leaf.textureReady === false) stats.pendingCount++;
7541
+ switch (leaf.textureLeafSizing) {
7542
+ case "canonical":
7543
+ case "local":
7544
+ case "raster":
7545
+ case "image":
7546
+ stats.sizingCounts[leaf.textureLeafSizing]++;
7547
+ break;
7548
+ default:
7549
+ stats.sizingCounts.unknown++;
7550
+ break;
7551
+ }
7552
+ switch (leaf.textureImageRendering) {
7553
+ case "auto":
7554
+ case "pixelated":
7555
+ stats.imageRenderingCounts[leaf.textureImageRendering]++;
7556
+ break;
7557
+ default:
7558
+ stats.imageRenderingCounts.unknown++;
7559
+ break;
7560
+ }
7561
+ switch (leaf.textureProjection) {
7562
+ case "affine":
7563
+ case "projective":
7564
+ case "fallback":
7565
+ stats.projectionCounts[leaf.textureProjection]++;
7566
+ break;
7567
+ default:
7568
+ stats.projectionCounts.unknown++;
7569
+ break;
7570
+ }
7571
+ if (typeof leaf.textureLeafWidth === "number") {
7572
+ stats.minLeafWidth = hasWidth ? Math.min(stats.minLeafWidth, leaf.textureLeafWidth) : leaf.textureLeafWidth;
7573
+ stats.maxLeafWidth = hasWidth ? Math.max(stats.maxLeafWidth, leaf.textureLeafWidth) : leaf.textureLeafWidth;
7574
+ hasWidth = true;
7575
+ }
7576
+ if (typeof leaf.textureLeafHeight === "number") {
7577
+ stats.minLeafHeight = hasHeight ? Math.min(stats.minLeafHeight, leaf.textureLeafHeight) : leaf.textureLeafHeight;
7578
+ stats.maxLeafHeight = hasHeight ? Math.max(stats.maxLeafHeight, leaf.textureLeafHeight) : leaf.textureLeafHeight;
7579
+ hasHeight = true;
7580
+ }
7581
+ if (leaf.doubleSided) stats.doubleSidedCount++;
7582
+ }
7583
+ stats.pendingCount = Math.min(stats.pendingCount, stats.leafCount);
7584
+ stats.ready = stats.pendingCount === 0;
7585
+ return stats;
7586
+ }
7587
+ function collectPolySnapshotStats(root, optionsOrPolygonCount) {
7588
+ const stats = emptySnapshotStats();
7589
+ if (!root) return stats;
7590
+ const options = asOptions(optionsOrPolygonCount);
7591
+ for (const scope of collectScopes(root, options.scopeSelector)) {
7592
+ stats.runtimeAtlasUrlCount += runtimeUrlElementCount(scope);
7593
+ stats.snapshotAtlasCount += matchingElementCount(scope, "[data-polycss-snapshot-atlas]");
7594
+ stats.snapshotBackgroundCount += matchingElementCount(scope, "[data-polycss-snapshot-bg]");
7595
+ }
7596
+ stats.selfContained = stats.runtimeAtlasUrlCount === 0;
7597
+ return stats;
7598
+ }
7599
+ function findCameraRoot(scope) {
7600
+ const element = scope instanceof HTMLElement ? scope : null;
7601
+ if (element) {
7602
+ if (element.classList.contains("polycss-camera")) return element;
7603
+ const closest = element.closest(".polycss-camera");
7604
+ if (closest instanceof HTMLElement) return closest;
7605
+ }
7606
+ const found = scope.querySelector(".polycss-camera");
7607
+ return found instanceof HTMLElement ? found : null;
7608
+ }
7609
+ function collectPolyCameraStats(root, optionsOrPolygonCount) {
7610
+ if (!root) return emptyCameraStats();
7611
+ const options = asOptions(optionsOrPolygonCount);
7612
+ let cameraRoot = null;
7613
+ for (const scope of collectScopes(root, options.scopeSelector)) {
7614
+ cameraRoot = findCameraRoot(scope);
7615
+ if (cameraRoot) break;
7616
+ }
7617
+ if (!cameraRoot) return emptyCameraStats();
7618
+ return {
7619
+ rootFound: true,
7620
+ projection: parseCameraProjection(cameraRoot.getAttribute("data-polycss-camera-projection")),
7621
+ perspectiveStyle: cameraRoot.getAttribute("data-polycss-camera-perspective") ?? "",
7622
+ appliedPerspectiveStyle: cameraRoot.getAttribute("data-polycss-camera-applied-perspective") ?? cameraRoot.style.perspective ?? "",
7623
+ zoom: parseOptionalNumber(cameraRoot.getAttribute("data-polycss-camera-zoom")),
7624
+ distance: parseOptionalNumber(cameraRoot.getAttribute("data-polycss-camera-distance")),
7625
+ rotX: parseOptionalNumber(cameraRoot.getAttribute("data-polycss-camera-rot-x")),
7626
+ rotY: parseOptionalNumber(cameraRoot.getAttribute("data-polycss-camera-rot-y")),
7627
+ target: parseOptionalVec3(cameraRoot.getAttribute("data-polycss-camera-target"))
7628
+ };
7629
+ }
7108
7630
  function collectPolyRenderStats(root, optionsOrPolygonCount) {
7109
7631
  const options = asOptions(optionsOrPolygonCount);
7110
7632
  if (!root) {
7111
7633
  return {
7112
7634
  ...EMPTY_POLY_RENDER_STATS,
7113
7635
  surfaceLeafCounts: { ...ZERO_SURFACE_LEAF_COUNTS },
7636
+ textureReadiness: { ...READY_TEXTURE_READINESS },
7637
+ textureStats: emptyTextureStats(),
7638
+ snapshotStats: emptySnapshotStats(),
7639
+ cameraStats: emptyCameraStats(),
7114
7640
  polygonCount: options.polygonCount ?? 0
7115
7641
  };
7116
7642
  }
@@ -7132,6 +7658,10 @@ function collectPolyRenderStats(root, optionsOrPolygonCount) {
7132
7658
  mountedPolygonLeafCount,
7133
7659
  shadowLeafCount,
7134
7660
  surfaceLeafCounts,
7661
+ textureReadiness: collectPolyTextureReadiness(root, options),
7662
+ textureStats: collectPolyTextureStats(root, options),
7663
+ snapshotStats: collectPolySnapshotStats(root, options),
7664
+ cameraStats: collectPolyCameraStats(root, options),
7135
7665
  bucketCount
7136
7666
  };
7137
7667
  }
@@ -7277,26 +7807,38 @@ import {
7277
7807
  planePolygons as planePolygons3,
7278
7808
  buildSceneContext as buildSceneContext2,
7279
7809
  buildPolyMeshTransform as buildPolyMeshTransform2,
7810
+ buildPolySceneTransform,
7280
7811
  computeSceneBbox as computeSceneBbox2,
7281
- BASE_TILE as BASE_TILE8,
7812
+ cssDistanceToWorld,
7813
+ cssPositionToWorld,
7814
+ polyCssDistanceToWorld,
7815
+ polyCssPositionToWorld,
7816
+ worldDirectionToCss as worldDirectionToCss3,
7817
+ worldDirectionToPolyCss,
7818
+ worldDirectionalLightToCss as worldDirectionalLightToCss2,
7819
+ worldDirectionalLightToPolyCss,
7820
+ worldDistanceToCss,
7821
+ worldDistanceToPolyCss,
7822
+ worldPositionToCss,
7823
+ worldPositionToPolyCss,
7824
+ BASE_TILE as BASE_TILE7,
7282
7825
  DEFAULT_CAMERA_STATE as DEFAULT_CAMERA_STATE2,
7283
7826
  DEFAULT_PROJECTION,
7284
7827
  normalizeInvertMultiplier,
7828
+ buildPolyCameraSceneTransform as buildPolyCameraSceneTransform2,
7829
+ capturePolyCameraSnapshot as capturePolyCameraSnapshot2,
7830
+ polyCameraTargetToCss,
7831
+ resolvePolyCameraAppliedPerspectiveStyle,
7285
7832
  createPolyAnimationMixer as createPolyAnimationMixer2,
7286
7833
  optimizeAnimatedMeshPolygons,
7287
7834
  DEFAULT_SEAM_FACET_SPLIT_OPTIONS,
7288
7835
  DEFAULT_SEAM_OVERLAP_OPTIONS,
7289
7836
  LoopOnce,
7290
7837
  LoopRepeat,
7291
- LoopPingPong,
7292
- cssDistanceToWorld,
7293
- cssPositionToWorld,
7294
- worldDistanceToCss,
7295
- worldDirectionToCss as worldDirectionToCss3,
7296
- worldPositionToCss
7838
+ LoopPingPong
7297
7839
  } from "@layoutit/polycss-core";
7298
7840
  export {
7299
- BASE_TILE8 as BASE_TILE,
7841
+ BASE_TILE7 as BASE_TILE,
7300
7842
  CAMERA_BACKFACE_CULL_EPS,
7301
7843
  DEFAULT_CAMERA_STATE2 as DEFAULT_CAMERA_STATE,
7302
7844
  DEFAULT_PROJECTION,
@@ -7339,15 +7881,19 @@ export {
7339
7881
  bakeSolidTextureSampledPolygons,
7340
7882
  bakeSolidTextureSamples,
7341
7883
  boxPolygons2 as boxPolygons,
7884
+ buildPolyCameraSceneTransform2 as buildPolyCameraSceneTransform,
7342
7885
  buildPolyMeshTransform2 as buildPolyMeshTransform,
7886
+ buildPolySceneTransform,
7343
7887
  buildSceneContext2 as buildSceneContext,
7344
7888
  cameraCullNormalGroups,
7345
7889
  cameraCullNormalGroupsFromPolygons,
7346
7890
  cameraCullNormalKey,
7347
7891
  cameraCullVisibleSignature,
7348
7892
  cameraFacingDepth,
7893
+ capturePolyCameraSnapshot2 as capturePolyCameraSnapshot,
7349
7894
  clampChannel2 as clampChannel,
7350
7895
  collectPolyRenderStats,
7896
+ collectPolyTextureReadiness,
7351
7897
  computeSceneBbox2 as computeSceneBbox,
7352
7898
  computeShapeLighting,
7353
7899
  computeTexturePaintMetrics,
@@ -7355,6 +7901,8 @@ export {
7355
7901
  coverPlanarPolygons,
7356
7902
  createIsometricCamera2 as createIsometricCamera,
7357
7903
  createPolyAnimationMixer2 as createPolyAnimationMixer,
7904
+ cssDistanceToWorld,
7905
+ cssPositionToWorld,
7358
7906
  cullInteriorPolygons,
7359
7907
  cylinderPolygons2 as cylinderPolygons,
7360
7908
  dodecahedronPolygons2 as dodecahedronPolygons,
@@ -7386,12 +7934,15 @@ export {
7386
7934
  parseVox,
7387
7935
  planePolygons3 as planePolygons,
7388
7936
  pointInMeshElement,
7389
- cssDistanceToWorld as polyCssDistanceToWorld,
7390
- cssPositionToWorld as polyCssPositionToWorld,
7937
+ polyCameraTargetToCss,
7938
+ polyCssDistanceToWorld,
7939
+ polyCssPositionToWorld,
7391
7940
  polygonCssSurfaceNormal,
7392
7941
  polygonFaces,
7393
7942
  polygonFacesCamera,
7943
+ queryPolyLeaves,
7394
7944
  repairMeshSeams,
7945
+ resolvePolyCameraAppliedPerspectiveStyle,
7395
7946
  ringPolygons2 as ringPolygons,
7396
7947
  rotateVec32 as rotateVec3,
7397
7948
  seamFacetSplitPolygons,
@@ -7411,7 +7962,12 @@ export {
7411
7962
  usePolySceneContext,
7412
7963
  usePolySelect,
7413
7964
  usePolySelectionApi,
7414
- worldDirectionToCss3 as worldDirectionToPolyCss,
7415
- worldDistanceToCss as worldDistanceToPolyCss,
7416
- worldPositionToCss as worldPositionToPolyCss
7965
+ worldDirectionToCss3 as worldDirectionToCss,
7966
+ worldDirectionToPolyCss,
7967
+ worldDirectionalLightToCss2 as worldDirectionalLightToCss,
7968
+ worldDirectionalLightToPolyCss,
7969
+ worldDistanceToCss,
7970
+ worldDistanceToPolyCss,
7971
+ worldPositionToCss,
7972
+ worldPositionToPolyCss
7417
7973
  };