@layoutit/polycss-react 0.2.5 → 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 { buildPolySceneTransform, createIsometricCamera } 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,24 +105,33 @@ function usePolyCamera(options) {
89
105
  handle.update(next);
90
106
  const el = sceneElRef.current;
91
107
  if (el) {
92
- el.style.transform = buildPolySceneTransform({
93
- ...handle.state,
108
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
94
109
  autoCenterOffset: store.getState().autoCenterOffset
95
110
  });
96
111
  }
97
112
  store.updateCameraFromRef(handle);
98
113
  store.notifyAll();
99
114
  }
100
- }, [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
+ ]);
101
126
  const applyTransformDirect = useCallback2(() => {
102
127
  const el = sceneElRef.current;
103
128
  if (!el) return;
104
129
  const handle = handleRef.current;
105
- el.style.transform = buildPolySceneTransform({
106
- ...handle.state,
130
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
107
131
  autoCenterOffset: store.getState().autoCenterOffset
108
132
  });
109
- }, [store]);
133
+ writeCameraSnapshotAttrs(cameraElRef.current, handle, options.projection, options.perspectiveStyle);
134
+ }, [store, options.projection, options.perspectiveStyle]);
110
135
  return {
111
136
  store,
112
137
  cameraRef: handleRef,
@@ -130,18 +155,26 @@ function PolyPerspectiveCameraInner({
130
155
  className,
131
156
  style
132
157
  }) {
158
+ const perspectiveValue = `${typeof perspective === "number" ? perspective : DEFAULT_PERSPECTIVE}px`;
133
159
  const {
134
160
  store,
135
161
  cameraRef,
136
162
  sceneElRef,
137
163
  cameraElRef,
138
164
  applyTransformDirect
139
- } = 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
+ });
140
174
  const contextValue = useMemo2(
141
175
  () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
142
176
  [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
143
177
  );
144
- const perspectiveValue = `${typeof perspective === "number" ? perspective : DEFAULT_PERSPECTIVE}px`;
145
178
  const cameraStyle = {
146
179
  ...style,
147
180
  perspective: perspectiveValue
@@ -152,6 +185,14 @@ function PolyPerspectiveCameraInner({
152
185
  ref: cameraElRef,
153
186
  className: `polycss-camera${className ? ` ${className}` : ""}`,
154
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(","),
155
196
  children
156
197
  }
157
198
  ) });
@@ -177,7 +218,15 @@ function PolyOrthographicCameraInner({
177
218
  sceneElRef,
178
219
  cameraElRef,
179
220
  applyTransformDirect
180
- } = 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
+ });
181
230
  const contextValue = useMemo3(
182
231
  () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
183
232
  [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
@@ -197,6 +246,14 @@ function PolyOrthographicCameraInner({
197
246
  ref: cameraElRef,
198
247
  className: `polycss-camera${className ? ` ${className}` : ""}`,
199
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(","),
200
257
  children
201
258
  }
202
259
  ) });
@@ -205,7 +262,13 @@ var PolyOrthographicCamera = memo2(PolyOrthographicCameraInner);
205
262
 
206
263
  // src/scene/PolyScene.tsx
207
264
  import { memo as memo8, useCallback as useCallback5, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo6, useRef as useRef4, useState as useState2 } from "react";
208
- import { BASE_TILE, 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";
209
272
 
210
273
  // src/scene/useSceneContext.ts
211
274
  import { useMemo as useMemo4 } from "react";
@@ -379,8 +442,8 @@ var CORE_BASE_STYLES = `
379
442
  }
380
443
 
381
444
  .polycss-scene s {
382
- width: var(--polycss-atlas-size, 64px);
383
- 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));
384
447
  }
385
448
 
386
449
  .polycss-scene u {
@@ -791,13 +854,16 @@ function isSolidTriangleSupported(doc) {
791
854
  import {
792
855
  filterAtlasPlans as filterAtlasPlansCore
793
856
  } from "@layoutit/polycss-core";
794
- function filterAtlasPlans(plans, textureLighting, disabled, doc) {
857
+ function filterAtlasPlans(plans, textureLighting, disabled, doc, textureBackend, textureImageRendering, textureProjection) {
795
858
  const d = doc ?? (typeof document !== "undefined" ? document : null);
796
859
  return filterAtlasPlansCore(plans, textureLighting, disabled, {
797
860
  solidTriangleSupported: isSolidTriangleSupported(doc),
798
861
  projectiveQuadSupported: doc ? projectiveQuadSupported(doc) : true,
799
862
  borderShapeSupported: isBorderShapeSupported(doc),
800
- cornerShapeSupported: d ? cornerShapeSupported(d) : false
863
+ cornerShapeSupported: d ? cornerShapeSupported(d) : false,
864
+ textureBackend,
865
+ textureImageRendering,
866
+ textureProjection
801
867
  });
802
868
  }
803
869
 
@@ -812,8 +878,13 @@ function isMobileDocument(doc) {
812
878
  if (!media) return false;
813
879
  return media("(pointer: coarse)").matches || media("(hover: none)").matches;
814
880
  }
815
- function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc) {
816
- 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
+ );
817
888
  }
818
889
 
819
890
  // src/scene/atlas/buildAtlasPages.ts
@@ -2031,7 +2102,7 @@ function deferRevoke(urls) {
2031
2102
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
2032
2103
  else setTimeout(run, 0);
2033
2104
  }
2034
- function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies, atomic = false) {
2105
+ function useTextureAtlas(plans, textureLighting, textureQualityInput, textureLeafSizing, textureBackend, textureImageRendering, textureProjection, strategies, atomic = false) {
2035
2106
  const disabled = useMemo5(
2036
2107
  () => new Set(strategies?.disable ?? []),
2037
2108
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -2042,17 +2113,21 @@ function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies
2042
2113
  plans,
2043
2114
  textureLighting,
2044
2115
  disabled,
2045
- typeof document !== "undefined" ? document : null
2116
+ typeof document !== "undefined" ? document : null,
2117
+ textureBackend,
2118
+ textureImageRendering,
2119
+ textureProjection
2046
2120
  ),
2047
- [plans, textureLighting, disabled]
2121
+ [plans, textureLighting, disabled, textureBackend, textureImageRendering, textureProjection]
2048
2122
  );
2049
2123
  const { packed, atlasScale } = useMemo5(
2050
2124
  () => packTextureAtlasPlansWithScale(
2051
2125
  atlasPlans,
2052
2126
  textureQualityInput,
2053
- typeof document !== "undefined" ? document : null
2127
+ typeof document !== "undefined" ? document : null,
2128
+ textureLeafSizing
2054
2129
  ),
2055
- [atlasPlans, textureQualityInput]
2130
+ [atlasPlans, textureQualityInput, textureLeafSizing]
2056
2131
  );
2057
2132
  const [pages, setPages] = useState(() => pageShells(packed.pages));
2058
2133
  const [frame, setFrame] = useState(() => ({
@@ -2405,12 +2480,13 @@ var TextureCornerShapeSolidPoly = memo6(function TextureCornerShapeSolidPoly2({
2405
2480
 
2406
2481
  // src/scene/atlas/atlasPoly.tsx
2407
2482
  import { memo as memo7 } from "react";
2408
- import { formatMatrix3d, formatCssLengthPx } from "@layoutit/polycss-core";
2483
+ import { formatMatrix3d, formatCssLengthPx, resolvePolyTextureImageRendering } from "@layoutit/polycss-core";
2409
2484
  import { jsx as jsx7 } from "react/jsx-runtime";
2410
2485
  var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2411
2486
  entry,
2412
2487
  page,
2413
2488
  textureLighting,
2489
+ textureImageRendering,
2414
2490
  solidPaintDefaults: _solidPaintDefaults,
2415
2491
  className,
2416
2492
  style: styleProp,
@@ -2420,26 +2496,26 @@ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2420
2496
  }) {
2421
2497
  const ATLAS_CANONICAL_SIZE_EXPLICIT = 64;
2422
2498
  const dynamic = textureLighting === "dynamic";
2499
+ const resolvedImageRendering = resolvePolyTextureImageRendering(entry.polygon, textureImageRendering);
2423
2500
  const atlasCanonicalSize = entry.atlasCanonicalSize ?? ATLAS_CANONICAL_SIZE_EXPLICIT;
2501
+ const atlasLeafWidth = entry.atlasLeafWidth ?? atlasCanonicalSize;
2502
+ const atlasLeafHeight = entry.atlasLeafHeight ?? atlasCanonicalSize;
2424
2503
  const atlasWidth = entry.canvasW || 1;
2425
2504
  const atlasHeight = entry.canvasH || 1;
2426
- const atlasPosition = page ? `${formatCssLengthPx(-entry.x / atlasWidth * atlasCanonicalSize)} ${formatCssLengthPx(-entry.y / atlasHeight * atlasCanonicalSize)}` : void 0;
2427
- 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;
2428
2507
  const dynamicMask = dynamic && page?.url ? `url(${page.url})` : void 0;
2429
- const background = !dynamic && page?.url ? `url(${page.url}) ${atlasPosition} / ${atlasSize} no-repeat` : void 0;
2430
2508
  const style = {
2431
2509
  transform: formatMatrix3d(entry.atlasMatrix),
2432
2510
  ["--polycss-atlas-size"]: `${atlasCanonicalSize}px`,
2433
- // Listing the `background` shorthand alongside the `background-*` longhands
2434
- // in one inline style object makes React warn on every update (mixing
2435
- // shorthand and non-shorthand for the same property). Branch so only the
2436
- // current mode's keys are assigned — baked gets `background`, dynamic gets
2437
- // the longhands.
2438
- ...dynamic ? {
2439
- backgroundImage: page?.url ? `url(${page.url})` : void 0,
2440
- backgroundPosition: atlasPosition,
2441
- backgroundSize: atlasSize
2442
- } : { 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,
2443
2519
  ...dynamic ? {
2444
2520
  ["--pnx"]: entry.normal[0].toFixed(4),
2445
2521
  ["--pny"]: entry.normal[1].toFixed(4),
@@ -2470,6 +2546,74 @@ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2470
2546
  className: elementClassName,
2471
2547
  style,
2472
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,
2473
2617
  ...domEventHandlers,
2474
2618
  ...dataAttrs,
2475
2619
  ...domAttrs
@@ -2497,6 +2641,10 @@ function PolySceneInner({
2497
2641
  ambientLight,
2498
2642
  textureLighting = "baked",
2499
2643
  textureQuality,
2644
+ textureLeafSizing,
2645
+ textureImageRendering,
2646
+ textureBackend,
2647
+ textureProjection,
2500
2648
  seamBleed = DEFAULT_SEAM_BLEED,
2501
2649
  strategies,
2502
2650
  autoCenter = false,
@@ -2592,7 +2740,16 @@ function PolySceneInner({
2592
2740
  },
2593
2741
  [polygons, polyContext, seamBleed, directionalForAtlas, ambientForAtlas]
2594
2742
  );
2595
- 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
+ );
2596
2753
  const dynamicLightVars = useMemo6(() => {
2597
2754
  if (textureLighting !== "dynamic") return null;
2598
2755
  const userDir = directionalLight?.direction ?? [0.4, -0.7, 0.59];
@@ -2690,12 +2847,28 @@ function PolySceneInner({
2690
2847
  {
2691
2848
  entry,
2692
2849
  page: textureAtlas.pages[entry.pageIndex],
2693
- textureLighting
2850
+ textureLighting,
2851
+ textureImageRendering
2694
2852
  },
2695
2853
  entry.index
2696
2854
  );
2697
2855
  }
2698
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
+ }
2699
2872
  if (!plan || plan.texture) return null;
2700
2873
  const useU = !disabledStrategies?.has("u");
2701
2874
  const useProjectiveSolid = !disabledStrategies?.has("b");
@@ -2721,6 +2894,10 @@ function PolySceneInner({
2721
2894
  ambientLight,
2722
2895
  strategies,
2723
2896
  seamBleed,
2897
+ textureLeafSizing,
2898
+ textureImageRendering,
2899
+ textureBackend,
2900
+ textureProjection,
2724
2901
  shadow,
2725
2902
  registerShadowCaster,
2726
2903
  registerShadowReceiver,
@@ -2730,7 +2907,7 @@ function PolySceneInner({
2730
2907
  groundCssZ,
2731
2908
  sceneEl
2732
2909
  }),
2733
- [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]
2734
2911
  );
2735
2912
  return /* @__PURE__ */ jsx8(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */ jsxs(
2736
2913
  "div",
@@ -2780,6 +2957,7 @@ import {
2780
2957
  prepareCasterPolyItems,
2781
2958
  prepareReceiverFacePlanes,
2782
2959
  projectCssVertexToGround,
2960
+ resolvePolyTextureLeafGeometry as resolvePolyTextureLeafGeometry2,
2783
2961
  worldDirectionToCss as worldDirectionToCss2
2784
2962
  } from "@layoutit/polycss-core";
2785
2963
 
@@ -3648,6 +3826,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3648
3826
  autoCenter,
3649
3827
  textureLighting,
3650
3828
  textureQuality,
3829
+ textureLeafSizing,
3830
+ textureImageRendering,
3831
+ textureBackend,
3832
+ textureProjection,
3651
3833
  seamBleed,
3652
3834
  atomicAtlas,
3653
3835
  onFrameReady,
@@ -3717,6 +3899,12 @@ var PolyMesh = forwardRef(function PolyMesh2({
3717
3899
  const stableTriangleColorFrameRef = useRef6(0);
3718
3900
  const setPolygonsImplRef = useRef6(() => {
3719
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
+ }, []);
3720
3908
  const handle = useMemo7(() => ({
3721
3909
  get element() {
3722
3910
  return wrapperRef.current;
@@ -3730,6 +3918,12 @@ var PolyMesh = forwardRef(function PolyMesh2({
3730
3918
  setPolygonsImplRef.current(nextPolygons);
3731
3919
  },
3732
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
+ },
3733
3927
  updatePolygon(target, partial) {
3734
3928
  const current = polygonsRef.current;
3735
3929
  const idx = typeof target === "number" ? target : current.indexOf(target);
@@ -3876,6 +4070,10 @@ var PolyMesh = forwardRef(function PolyMesh2({
3876
4070
  [effectiveStrategies]
3877
4071
  );
3878
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;
3879
4077
  const effectiveDirectional = sceneCtx?.directionalLight;
3880
4078
  const effectiveAmbient = sceneCtx?.ambientLight;
3881
4079
  const directVoxelEnabled = Boolean(
@@ -3938,9 +4136,18 @@ var PolyMesh = forwardRef(function PolyMesh2({
3938
4136
  atlasPlans,
3939
4137
  effectiveTextureLighting,
3940
4138
  textureQuality,
4139
+ effectiveTextureLeafSizing,
4140
+ effectiveTextureBackend,
4141
+ effectiveTextureImageRendering,
4142
+ effectiveTextureProjection,
3941
4143
  effectiveStrategies,
3942
4144
  atomicAtlas
3943
4145
  );
4146
+ textureReadyRef.current = textureAtlas.ready;
4147
+ useEffect5(() => {
4148
+ if (textureAtlas.ready) resolveTextureReadyWaiters();
4149
+ }, [textureAtlas.ready, resolveTextureReadyWaiters]);
4150
+ useEffect5(() => resolveTextureReadyWaiters, [resolveTextureReadyWaiters]);
3944
4151
  const solidPaintDefaults = useMemo7(
3945
4152
  () => !renderPolygon ? getSolidPaintDefaults(textureAtlas.plans, effectiveTextureLighting, effectiveStrategies) : {},
3946
4153
  [renderPolygon, textureAtlas.plans, effectiveTextureLighting, effectiveStrategies]
@@ -4289,12 +4496,28 @@ var PolyMesh = forwardRef(function PolyMesh2({
4289
4496
  entry,
4290
4497
  page: textureAtlas.pages[entry.pageIndex],
4291
4498
  textureLighting: effectiveTextureLighting,
4499
+ textureImageRendering: effectiveTextureImageRendering,
4292
4500
  solidPaintDefaults
4293
4501
  },
4294
4502
  entry.index
4295
4503
  );
4296
4504
  }
4297
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
+ }
4298
4521
  if (!plan || plan.texture) return null;
4299
4522
  if (isProjectiveQuadPlan(plan)) {
4300
4523
  return /* @__PURE__ */ jsx9(
@@ -4515,6 +4738,7 @@ function usePolyMaterial(options) {
4515
4738
 
4516
4739
  // src/shapes/Poly.tsx
4517
4740
  import { memo as memo9, useMemo as useMemo10 } from "react";
4741
+ import { resolvePolyTextureLeafGeometry as resolvePolyTextureLeafGeometry3 } from "@layoutit/polycss-core";
4518
4742
  import { jsx as jsx12 } from "react/jsx-runtime";
4519
4743
  var DIRECT_TEXTURE_CSS_DECIMALS = 4;
4520
4744
  function formatCssLength(value, decimals = DIRECT_TEXTURE_CSS_DECIMALS) {
@@ -4545,7 +4769,8 @@ function MaterialDirectPoly({
4545
4769
  style: styleProp,
4546
4770
  domAttrs,
4547
4771
  domEventHandlers,
4548
- pointerEvents = "auto"
4772
+ pointerEvents = "auto",
4773
+ imageRendering
4549
4774
  }) {
4550
4775
  const { u0, u1, v0, v1 } = uvRect;
4551
4776
  const du = u1 - u0;
@@ -4560,6 +4785,8 @@ function MaterialDirectPoly({
4560
4785
  backgroundImage: `url(${material.texture})`,
4561
4786
  backgroundSize: `${formatCssLength(sourceW)} ${formatCssLength(sourceH)}`,
4562
4787
  backgroundPosition: `${formatCssLength(-offsetX)} ${formatCssLength(-offsetY)}`,
4788
+ backgroundRepeat: "no-repeat",
4789
+ imageRendering: imageRendering === "pixelated" ? "pixelated" : void 0,
4563
4790
  pointerEvents: pointerEvents === "none" ? "none" : void 0,
4564
4791
  ...styleProp
4565
4792
  };
@@ -4572,6 +4799,14 @@ function MaterialDirectPoly({
4572
4799
  {
4573
4800
  className: elementClassName,
4574
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,
4575
4810
  ...domEventHandlers,
4576
4811
  ...dataAttrs,
4577
4812
  ...domAttrs
@@ -4582,8 +4817,11 @@ function PolyInner({
4582
4817
  vertices,
4583
4818
  color,
4584
4819
  texture,
4820
+ textureImageSource,
4821
+ texturePresentation,
4585
4822
  uvs,
4586
4823
  data,
4824
+ doubleSided,
4587
4825
  material,
4588
4826
  position,
4589
4827
  scale,
@@ -4611,6 +4849,10 @@ function PolyInner({
4611
4849
  context,
4612
4850
  textureLighting: textureLightingProp,
4613
4851
  textureQuality: textureQualityProp,
4852
+ textureLeafSizing: textureLeafSizingProp,
4853
+ textureImageRendering: textureImageRenderingProp,
4854
+ textureBackend: textureBackendProp,
4855
+ textureProjection: textureProjectionProp,
4614
4856
  baseColor: baseColorProp,
4615
4857
  ...dataAttrs
4616
4858
  }) {
@@ -4618,11 +4860,25 @@ function PolyInner({
4618
4860
  const layerElevation = context?.layerElevation ?? tileSize;
4619
4861
  const textureLighting = textureLightingProp ?? context?.textureLighting ?? "baked";
4620
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;
4621
4867
  const polygonColor = baseColorProp ?? color;
4622
4868
  const effectiveTexture = material?.texture ?? texture;
4623
4869
  const atlasPlan = useMemo10(
4624
4870
  () => computeTextureAtlasPlan(
4625
- { 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
+ },
4626
4882
  0,
4627
4883
  {
4628
4884
  tileSize,
@@ -4634,8 +4890,12 @@ function PolyInner({
4634
4890
  vertices,
4635
4891
  polygonColor,
4636
4892
  effectiveTexture,
4893
+ textureImageSource,
4894
+ texturePresentation,
4637
4895
  uvs,
4638
4896
  data,
4897
+ doubleSided,
4898
+ material,
4639
4899
  tileSize,
4640
4900
  layerElevation,
4641
4901
  context?.directionalLight
@@ -4649,7 +4909,15 @@ function PolyInner({
4649
4909
  () => materialUvRect ? [] : [atlasPlan],
4650
4910
  [materialUvRect, atlasPlan]
4651
4911
  );
4652
- 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
+ );
4653
4921
  const domEventHandlers = {
4654
4922
  onClick,
4655
4923
  onDoubleClick,
@@ -4708,7 +4976,8 @@ function PolyInner({
4708
4976
  style: styleProp,
4709
4977
  domAttrs,
4710
4978
  domEventHandlers,
4711
- pointerEvents: pointerEventsProp ?? "auto"
4979
+ pointerEvents: pointerEventsProp ?? "auto",
4980
+ imageRendering: textureImageRendering
4712
4981
  }
4713
4982
  );
4714
4983
  } else {
@@ -4720,6 +4989,7 @@ function PolyInner({
4720
4989
  entry: atlasEntry,
4721
4990
  page: textureAtlas.pages[atlasEntry.pageIndex],
4722
4991
  textureLighting,
4992
+ textureImageRendering,
4723
4993
  className,
4724
4994
  style: styleProp,
4725
4995
  domAttrs,
@@ -4727,7 +4997,28 @@ function PolyInner({
4727
4997
  pointerEvents: pointerEventsProp ?? "auto"
4728
4998
  }
4729
4999
  );
4730
- } 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) {
4731
5022
  front = isSolidTrianglePlan2(atlasPlan) ? /* @__PURE__ */ jsx12(
4732
5023
  TextureTrianglePoly,
4733
5024
  {
@@ -7062,11 +7353,56 @@ var ZERO_SURFACE_LEAF_COUNTS = {
7062
7353
  atlas: 0,
7063
7354
  stableTriangle: 0
7064
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
+ }
7065
7397
  var EMPTY_POLY_RENDER_STATS = {
7066
7398
  polygonCount: 0,
7067
7399
  mountedPolygonLeafCount: 0,
7068
7400
  shadowLeafCount: 0,
7069
7401
  surfaceLeafCounts: ZERO_SURFACE_LEAF_COUNTS,
7402
+ textureReadiness: READY_TEXTURE_READINESS,
7403
+ textureStats: emptyTextureStats(),
7404
+ snapshotStats: emptySnapshotStats(),
7405
+ cameraStats: emptyCameraStats(),
7070
7406
  bucketCount: 0
7071
7407
  };
7072
7408
  function asOptions(optionsOrPolygonCount) {
@@ -7078,6 +7414,28 @@ function asOptions(optionsOrPolygonCount) {
7078
7414
  function queryCount(scope, selector) {
7079
7415
  return scope.querySelectorAll(selector).length;
7080
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
+ }
7081
7439
  function matchesSelector(root, selector) {
7082
7440
  const candidate = root;
7083
7441
  return typeof candidate.matches === "function" && candidate.matches(selector);
@@ -7089,12 +7447,196 @@ function collectScopes(root, selector) {
7089
7447
  scopes.push(...Array.from(root.querySelectorAll(selector)));
7090
7448
  return scopes;
7091
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
+ }
7092
7630
  function collectPolyRenderStats(root, optionsOrPolygonCount) {
7093
7631
  const options = asOptions(optionsOrPolygonCount);
7094
7632
  if (!root) {
7095
7633
  return {
7096
7634
  ...EMPTY_POLY_RENDER_STATS,
7097
7635
  surfaceLeafCounts: { ...ZERO_SURFACE_LEAF_COUNTS },
7636
+ textureReadiness: { ...READY_TEXTURE_READINESS },
7637
+ textureStats: emptyTextureStats(),
7638
+ snapshotStats: emptySnapshotStats(),
7639
+ cameraStats: emptyCameraStats(),
7098
7640
  polygonCount: options.polygonCount ?? 0
7099
7641
  };
7100
7642
  }
@@ -7116,6 +7658,10 @@ function collectPolyRenderStats(root, optionsOrPolygonCount) {
7116
7658
  mountedPolygonLeafCount,
7117
7659
  shadowLeafCount,
7118
7660
  surfaceLeafCounts,
7661
+ textureReadiness: collectPolyTextureReadiness(root, options),
7662
+ textureStats: collectPolyTextureStats(root, options),
7663
+ snapshotStats: collectPolySnapshotStats(root, options),
7664
+ cameraStats: collectPolyCameraStats(root, options),
7119
7665
  bucketCount
7120
7666
  };
7121
7667
  }
@@ -7261,25 +7807,35 @@ import {
7261
7807
  planePolygons as planePolygons3,
7262
7808
  buildSceneContext as buildSceneContext2,
7263
7809
  buildPolyMeshTransform as buildPolyMeshTransform2,
7264
- buildPolySceneTransform as buildPolySceneTransform2,
7810
+ buildPolySceneTransform,
7265
7811
  computeSceneBbox as computeSceneBbox2,
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,
7266
7824
  BASE_TILE as BASE_TILE7,
7267
7825
  DEFAULT_CAMERA_STATE as DEFAULT_CAMERA_STATE2,
7268
7826
  DEFAULT_PROJECTION,
7269
7827
  normalizeInvertMultiplier,
7828
+ buildPolyCameraSceneTransform as buildPolyCameraSceneTransform2,
7829
+ capturePolyCameraSnapshot as capturePolyCameraSnapshot2,
7830
+ polyCameraTargetToCss,
7831
+ resolvePolyCameraAppliedPerspectiveStyle,
7270
7832
  createPolyAnimationMixer as createPolyAnimationMixer2,
7271
7833
  optimizeAnimatedMeshPolygons,
7272
7834
  DEFAULT_SEAM_FACET_SPLIT_OPTIONS,
7273
7835
  DEFAULT_SEAM_OVERLAP_OPTIONS,
7274
7836
  LoopOnce,
7275
7837
  LoopRepeat,
7276
- LoopPingPong,
7277
- cssDistanceToWorld,
7278
- cssPositionToWorld,
7279
- worldDistanceToCss,
7280
- worldDirectionToCss as worldDirectionToCss3,
7281
- worldDirectionalLightToCss as worldDirectionalLightToCss2,
7282
- worldPositionToCss
7838
+ LoopPingPong
7283
7839
  } from "@layoutit/polycss-core";
7284
7840
  export {
7285
7841
  BASE_TILE7 as BASE_TILE,
@@ -7325,16 +7881,19 @@ export {
7325
7881
  bakeSolidTextureSampledPolygons,
7326
7882
  bakeSolidTextureSamples,
7327
7883
  boxPolygons2 as boxPolygons,
7884
+ buildPolyCameraSceneTransform2 as buildPolyCameraSceneTransform,
7328
7885
  buildPolyMeshTransform2 as buildPolyMeshTransform,
7329
- buildPolySceneTransform2 as buildPolySceneTransform,
7886
+ buildPolySceneTransform,
7330
7887
  buildSceneContext2 as buildSceneContext,
7331
7888
  cameraCullNormalGroups,
7332
7889
  cameraCullNormalGroupsFromPolygons,
7333
7890
  cameraCullNormalKey,
7334
7891
  cameraCullVisibleSignature,
7335
7892
  cameraFacingDepth,
7893
+ capturePolyCameraSnapshot2 as capturePolyCameraSnapshot,
7336
7894
  clampChannel2 as clampChannel,
7337
7895
  collectPolyRenderStats,
7896
+ collectPolyTextureReadiness,
7338
7897
  computeSceneBbox2 as computeSceneBbox,
7339
7898
  computeShapeLighting,
7340
7899
  computeTexturePaintMetrics,
@@ -7342,6 +7901,8 @@ export {
7342
7901
  coverPlanarPolygons,
7343
7902
  createIsometricCamera2 as createIsometricCamera,
7344
7903
  createPolyAnimationMixer2 as createPolyAnimationMixer,
7904
+ cssDistanceToWorld,
7905
+ cssPositionToWorld,
7345
7906
  cullInteriorPolygons,
7346
7907
  cylinderPolygons2 as cylinderPolygons,
7347
7908
  dodecahedronPolygons2 as dodecahedronPolygons,
@@ -7373,12 +7934,15 @@ export {
7373
7934
  parseVox,
7374
7935
  planePolygons3 as planePolygons,
7375
7936
  pointInMeshElement,
7376
- cssDistanceToWorld as polyCssDistanceToWorld,
7377
- cssPositionToWorld as polyCssPositionToWorld,
7937
+ polyCameraTargetToCss,
7938
+ polyCssDistanceToWorld,
7939
+ polyCssPositionToWorld,
7378
7940
  polygonCssSurfaceNormal,
7379
7941
  polygonFaces,
7380
7942
  polygonFacesCamera,
7943
+ queryPolyLeaves,
7381
7944
  repairMeshSeams,
7945
+ resolvePolyCameraAppliedPerspectiveStyle,
7382
7946
  ringPolygons2 as ringPolygons,
7383
7947
  rotateVec32 as rotateVec3,
7384
7948
  seamFacetSplitPolygons,
@@ -7398,8 +7962,12 @@ export {
7398
7962
  usePolySceneContext,
7399
7963
  usePolySelect,
7400
7964
  usePolySelectionApi,
7401
- worldDirectionToCss3 as worldDirectionToPolyCss,
7402
- worldDirectionalLightToCss2 as worldDirectionalLightToPolyCss,
7403
- worldDistanceToCss as worldDistanceToPolyCss,
7404
- worldPositionToCss as worldPositionToPolyCss
7965
+ worldDirectionToCss3 as worldDirectionToCss,
7966
+ worldDirectionToPolyCss,
7967
+ worldDirectionalLightToCss2 as worldDirectionalLightToCss,
7968
+ worldDirectionalLightToPolyCss,
7969
+ worldDistanceToCss,
7970
+ worldDistanceToPolyCss,
7971
+ worldPositionToCss,
7972
+ worldPositionToPolyCss
7405
7973
  };