@layoutit/polycss-react 0.2.6 → 0.2.8

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/three.js ADDED
@@ -0,0 +1,4080 @@
1
+ // src/three/index.ts
2
+ import {
3
+ AmbientLight,
4
+ DirectionalLight,
5
+ Euler,
6
+ Object3D as Object3D2,
7
+ OrthographicCamera as OrthographicCamera2,
8
+ PerspectiveCamera as PerspectiveCamera2,
9
+ PointLight,
10
+ Vector3,
11
+ polyToThreeDirection,
12
+ polyToThreePoint,
13
+ threeToPolyDirection,
14
+ threeToPolyPoint,
15
+ transformPointToPoly,
16
+ transformPolygonsToPoly as transformPolygonsToPoly2
17
+ } from "@layoutit/polycss-core/three";
18
+
19
+ // src/three/PolyThreePerspectiveCamera.tsx
20
+ import { memo, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3, useState } from "react";
21
+ import { PerspectiveCamera } from "@layoutit/polycss-core/three";
22
+
23
+ // src/camera/context.ts
24
+ import { createContext, useContext } from "react";
25
+ var PolyCameraContext = createContext(null);
26
+
27
+ // src/camera/useCamera.ts
28
+ import { useRef as useRef2, useCallback as useCallback2, useEffect, useMemo } from "react";
29
+ import {
30
+ buildPolyCameraSceneTransform,
31
+ capturePolyCameraSnapshot,
32
+ createIsometricCamera
33
+ } from "@layoutit/polycss-core";
34
+
35
+ // src/store/sceneStore.ts
36
+ import { useSyncExternalStore, useRef, useCallback } from "react";
37
+ function createSceneStore(initial) {
38
+ let state = {
39
+ cameraState: { ...initial },
40
+ autoCenterOffset: [0, 0, 0]
41
+ };
42
+ const listeners = /* @__PURE__ */ new Set();
43
+ function notify() {
44
+ for (const listener of listeners) listener();
45
+ }
46
+ return {
47
+ getState() {
48
+ return state;
49
+ },
50
+ setState(partial) {
51
+ state = { ...state, ...partial };
52
+ notify();
53
+ },
54
+ subscribe(listener) {
55
+ listeners.add(listener);
56
+ return () => listeners.delete(listener);
57
+ },
58
+ updateCameraFromRef(handle) {
59
+ state = { ...state, cameraState: { ...handle.state } };
60
+ notify();
61
+ return true;
62
+ },
63
+ notifyAll() {
64
+ notify();
65
+ },
66
+ setAutoCenterOffset(offset) {
67
+ state = { ...state, autoCenterOffset: offset };
68
+ }
69
+ };
70
+ }
71
+
72
+ // src/camera/useCamera.ts
73
+ function writeCameraSnapshotAttrs(el, camera, projection, perspectiveStyle) {
74
+ if (!el) return;
75
+ const snapshot = capturePolyCameraSnapshot(camera, { projection, perspectiveStyle });
76
+ el.dataset.polycssCameraProjection = snapshot.projection;
77
+ el.dataset.polycssCameraPerspective = snapshot.perspectiveStyle;
78
+ el.dataset.polycssCameraAppliedPerspective = snapshot.appliedPerspectiveStyle;
79
+ el.dataset.polycssCameraZoom = String(snapshot.state.zoom);
80
+ el.dataset.polycssCameraDistance = String(snapshot.state.distance);
81
+ el.dataset.polycssCameraRotX = String(snapshot.state.rotX);
82
+ el.dataset.polycssCameraRotY = String(snapshot.state.rotY);
83
+ el.dataset.polycssCameraTarget = snapshot.state.target.join(",");
84
+ }
85
+ function usePolyCamera(options) {
86
+ const handleRef = useRef2(null);
87
+ if (!handleRef.current) {
88
+ handleRef.current = createIsometricCamera({
89
+ zoom: options.zoom,
90
+ target: options.target,
91
+ rotX: options.rotX,
92
+ rotY: options.rotY,
93
+ distance: options.distance
94
+ });
95
+ }
96
+ const sceneElRef = useRef2(null);
97
+ const cameraElRef = useRef2(null);
98
+ const store = useMemo(
99
+ () => createSceneStore(handleRef.current.state),
100
+ []
101
+ );
102
+ useEffect(() => {
103
+ const handle = handleRef.current;
104
+ const next = {};
105
+ if (options.zoom !== void 0) next.zoom = options.zoom;
106
+ if (options.target !== void 0) next.target = options.target;
107
+ if (options.rotX !== void 0) next.rotX = options.rotX;
108
+ if (options.rotY !== void 0) next.rotY = options.rotY;
109
+ if (options.distance !== void 0) next.distance = options.distance;
110
+ if (Object.keys(next).length > 0) {
111
+ handle.update(next);
112
+ const el = sceneElRef.current;
113
+ if (el) {
114
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
115
+ autoCenterOffset: store.getState().autoCenterOffset
116
+ });
117
+ }
118
+ store.updateCameraFromRef(handle);
119
+ store.notifyAll();
120
+ }
121
+ writeCameraSnapshotAttrs(cameraElRef.current, handle, options.projection, options.perspectiveStyle);
122
+ }, [
123
+ options.zoom,
124
+ options.target,
125
+ options.rotX,
126
+ options.rotY,
127
+ options.distance,
128
+ options.projection,
129
+ options.perspectiveStyle,
130
+ store
131
+ ]);
132
+ const applyTransformDirect = useCallback2(() => {
133
+ const el = sceneElRef.current;
134
+ if (!el) return;
135
+ const handle = handleRef.current;
136
+ el.style.transform = buildPolyCameraSceneTransform(handle.state, {
137
+ autoCenterOffset: store.getState().autoCenterOffset
138
+ });
139
+ writeCameraSnapshotAttrs(cameraElRef.current, handle, options.projection, options.perspectiveStyle);
140
+ }, [store, options.projection, options.perspectiveStyle]);
141
+ return {
142
+ store,
143
+ cameraRef: handleRef,
144
+ sceneElRef,
145
+ cameraElRef,
146
+ applyTransformDirect
147
+ };
148
+ }
149
+
150
+ // src/three/PolyThreePerspectiveCamera.tsx
151
+ import { jsx } from "react/jsx-runtime";
152
+ function applyCameraProps(camera, props) {
153
+ if (props.fov !== void 0) camera.fov = props.fov;
154
+ if (props.aspect !== void 0) camera.aspect = props.aspect;
155
+ if (props.near !== void 0) camera.near = props.near;
156
+ if (props.far !== void 0) camera.far = props.far;
157
+ if (props.zoom !== void 0) camera.zoom = props.zoom;
158
+ if (props.position !== void 0) camera.position.set(props.position[0], props.position[1], props.position[2]);
159
+ if (props.up !== void 0) camera.up.set(props.up[0], props.up[1], props.up[2]);
160
+ if (props.lookAt !== void 0) camera.lookAt(props.lookAt[0], props.lookAt[1], props.lookAt[2]);
161
+ return camera;
162
+ }
163
+ function PolyThreePerspectiveCameraInner({
164
+ camera: cameraProp,
165
+ fov = 50,
166
+ aspect = 1,
167
+ near = 0.1,
168
+ far = 2e3,
169
+ zoom,
170
+ polyZoom,
171
+ perspective,
172
+ viewportHeight,
173
+ position,
174
+ lookAt,
175
+ up,
176
+ className,
177
+ style,
178
+ children
179
+ }) {
180
+ const localCameraRef = useRef3(null);
181
+ if (!localCameraRef.current) {
182
+ localCameraRef.current = new PerspectiveCamera(fov, aspect, near, far);
183
+ }
184
+ const [measuredHeight, setMeasuredHeight] = useState(viewportHeight ?? 420);
185
+ const camera = applyCameraProps(cameraProp ?? localCameraRef.current, {
186
+ fov,
187
+ aspect,
188
+ near,
189
+ far,
190
+ zoom,
191
+ position,
192
+ lookAt,
193
+ up
194
+ });
195
+ const polyCamera = camera.toPolyCameraState({
196
+ zoom: polyZoom,
197
+ perspective,
198
+ viewportHeight: viewportHeight ?? measuredHeight
199
+ });
200
+ const perspectiveStyle = `${polyCamera.perspective}px`;
201
+ const {
202
+ store,
203
+ cameraRef,
204
+ sceneElRef,
205
+ cameraElRef,
206
+ applyTransformDirect
207
+ } = usePolyCamera({
208
+ zoom: polyCamera.zoom,
209
+ target: polyCamera.target,
210
+ rotX: polyCamera.rotX,
211
+ rotY: polyCamera.rotY,
212
+ distance: polyCamera.distance,
213
+ projection: "perspective",
214
+ perspectiveStyle
215
+ });
216
+ useEffect2(() => {
217
+ if (viewportHeight !== void 0) return;
218
+ const el = cameraElRef.current;
219
+ if (!el) return;
220
+ const measure = () => {
221
+ const next = el.getBoundingClientRect().height || el.clientHeight || 420;
222
+ setMeasuredHeight(next);
223
+ };
224
+ measure();
225
+ if (typeof ResizeObserver === "undefined") return;
226
+ const observer = new ResizeObserver(measure);
227
+ observer.observe(el);
228
+ return () => observer.disconnect();
229
+ }, [cameraElRef, viewportHeight]);
230
+ const contextValue = useMemo2(
231
+ () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
232
+ [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
233
+ );
234
+ return /* @__PURE__ */ jsx(PolyCameraContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
235
+ "div",
236
+ {
237
+ ref: cameraElRef,
238
+ className: `polycss-camera${className ? ` ${className}` : ""}`,
239
+ style: { ...style, perspective: perspectiveStyle },
240
+ "data-polycss-camera-projection": "perspective",
241
+ "data-polycss-camera-perspective": perspectiveStyle,
242
+ "data-polycss-camera-applied-perspective": perspectiveStyle,
243
+ "data-polycss-camera-zoom": cameraRef.current.state.zoom,
244
+ "data-polycss-camera-distance": cameraRef.current.state.distance,
245
+ "data-polycss-camera-rot-x": cameraRef.current.state.rotX,
246
+ "data-polycss-camera-rot-y": cameraRef.current.state.rotY,
247
+ "data-polycss-camera-target": cameraRef.current.state.target.join(","),
248
+ children
249
+ }
250
+ ) });
251
+ }
252
+ var PolyThreePerspectiveCamera = memo(PolyThreePerspectiveCameraInner);
253
+
254
+ // src/three/PolyThreeOrthographicCamera.tsx
255
+ import { memo as memo2, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef4, useState as useState2 } from "react";
256
+ import { OrthographicCamera } from "@layoutit/polycss-core/three";
257
+ import { jsx as jsx2 } from "react/jsx-runtime";
258
+ function applyCameraProps2(camera, props) {
259
+ if (props.left !== void 0) camera.left = props.left;
260
+ if (props.right !== void 0) camera.right = props.right;
261
+ if (props.top !== void 0) camera.top = props.top;
262
+ if (props.bottom !== void 0) camera.bottom = props.bottom;
263
+ if (props.near !== void 0) camera.near = props.near;
264
+ if (props.far !== void 0) camera.far = props.far;
265
+ if (props.zoom !== void 0) camera.zoom = props.zoom;
266
+ if (props.position !== void 0) camera.position.set(props.position[0], props.position[1], props.position[2]);
267
+ if (props.up !== void 0) camera.up.set(props.up[0], props.up[1], props.up[2]);
268
+ if (props.lookAt !== void 0) camera.lookAt(props.lookAt[0], props.lookAt[1], props.lookAt[2]);
269
+ return camera;
270
+ }
271
+ function PolyThreeOrthographicCameraInner({
272
+ camera: cameraProp,
273
+ left = -1,
274
+ right = 1,
275
+ top = 1,
276
+ bottom = -1,
277
+ near = 0.1,
278
+ far = 2e3,
279
+ zoom,
280
+ polyZoom,
281
+ viewportHeight,
282
+ position,
283
+ lookAt,
284
+ up,
285
+ className,
286
+ style,
287
+ children
288
+ }) {
289
+ const localCameraRef = useRef4(null);
290
+ if (!localCameraRef.current) {
291
+ localCameraRef.current = new OrthographicCamera(left, right, top, bottom, near, far);
292
+ }
293
+ const [measuredHeight, setMeasuredHeight] = useState2(viewportHeight ?? 420);
294
+ const camera = applyCameraProps2(cameraProp ?? localCameraRef.current, {
295
+ left,
296
+ right,
297
+ top,
298
+ bottom,
299
+ near,
300
+ far,
301
+ zoom,
302
+ position,
303
+ lookAt,
304
+ up
305
+ });
306
+ const polyCamera = camera.toPolyCameraState({
307
+ zoom: polyZoom,
308
+ viewportHeight: viewportHeight ?? measuredHeight
309
+ });
310
+ const {
311
+ store,
312
+ cameraRef,
313
+ sceneElRef,
314
+ cameraElRef,
315
+ applyTransformDirect
316
+ } = usePolyCamera({
317
+ zoom: polyCamera.zoom,
318
+ target: polyCamera.target,
319
+ rotX: polyCamera.rotX,
320
+ rotY: polyCamera.rotY,
321
+ distance: polyCamera.distance,
322
+ projection: "orthographic",
323
+ perspectiveStyle: "none"
324
+ });
325
+ useEffect3(() => {
326
+ if (viewportHeight !== void 0) return;
327
+ const el = cameraElRef.current;
328
+ if (!el) return;
329
+ const measure = () => {
330
+ const next = el.getBoundingClientRect().height || el.clientHeight || 420;
331
+ setMeasuredHeight(next);
332
+ };
333
+ measure();
334
+ if (typeof ResizeObserver === "undefined") return;
335
+ const observer = new ResizeObserver(measure);
336
+ observer.observe(el);
337
+ return () => observer.disconnect();
338
+ }, [cameraElRef, viewportHeight]);
339
+ const contextValue = useMemo3(
340
+ () => ({ store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect }),
341
+ [store, cameraRef, sceneElRef, cameraElRef, applyTransformDirect]
342
+ );
343
+ return /* @__PURE__ */ jsx2(PolyCameraContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(
344
+ "div",
345
+ {
346
+ ref: cameraElRef,
347
+ className: `polycss-camera${className ? ` ${className}` : ""}`,
348
+ style: { ...style, perspective: "none" },
349
+ "data-polycss-camera-projection": "orthographic",
350
+ "data-polycss-camera-perspective": "none",
351
+ "data-polycss-camera-applied-perspective": "1000000px",
352
+ "data-polycss-camera-zoom": cameraRef.current.state.zoom,
353
+ "data-polycss-camera-distance": cameraRef.current.state.distance,
354
+ "data-polycss-camera-rot-x": cameraRef.current.state.rotX,
355
+ "data-polycss-camera-rot-y": cameraRef.current.state.rotY,
356
+ "data-polycss-camera-target": cameraRef.current.state.target.join(","),
357
+ children
358
+ }
359
+ ) });
360
+ }
361
+ var PolyThreeOrthographicCamera = memo2(PolyThreeOrthographicCameraInner);
362
+
363
+ // src/three/PolyThreeMesh.tsx
364
+ import { memo as memo8, useMemo as useMemo6, useRef as useRef8 } from "react";
365
+ import {
366
+ computeSceneBbox as computeSceneBbox2
367
+ } from "@layoutit/polycss-core";
368
+ import {
369
+ Object3D,
370
+ transformPolygonsToPoly
371
+ } from "@layoutit/polycss-core/three";
372
+
373
+ // src/scene/PolyMesh.tsx
374
+ import {
375
+ forwardRef,
376
+ useCallback as useCallback6,
377
+ useContext as useContext3,
378
+ useEffect as useEffect6,
379
+ useImperativeHandle,
380
+ useLayoutEffect,
381
+ useMemo as useMemo5,
382
+ useRef as useRef7,
383
+ useState as useState5
384
+ } from "react";
385
+ import {
386
+ BASE_TILE as BASE_TILE2,
387
+ buildParametricCasterOverride,
388
+ buildPolyMeshTransform,
389
+ buildSharedEdgeMap,
390
+ computeMergedReceiverShadows,
391
+ computeSceneBbox,
392
+ DEFAULT_SEAM_BLEED,
393
+ ensureCcw2D,
394
+ findOverlappingPolygonDuplicates,
395
+ inverseRotateVec3,
396
+ optimizeMeshPolygons,
397
+ parseHexColor,
398
+ prepareCasterEdgeOwners,
399
+ prepareCasterPolyItems,
400
+ prepareReceiverFacePlanes,
401
+ projectCssVertexToGround,
402
+ resolvePolyTextureLeafGeometry,
403
+ worldDirectionToCss,
404
+ worldPositionToCss
405
+ } from "@layoutit/polycss-core";
406
+
407
+ // src/scene/useMesh.ts
408
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef5, useState as useState3 } from "react";
409
+ import { loadMesh } from "@layoutit/polycss-core";
410
+ var EMPTY_POLYGONS = [];
411
+ var EMPTY_WARNINGS = [];
412
+ function usePolyMesh(src, options) {
413
+ const [state, setState] = useState3({
414
+ polygons: EMPTY_POLYGONS,
415
+ voxelSource: void 0,
416
+ loading: !!src,
417
+ error: null,
418
+ warnings: EMPTY_WARNINGS
419
+ });
420
+ const activeResultRef = useRef5(null);
421
+ const dispose = useCallback3(() => {
422
+ const r = activeResultRef.current;
423
+ if (r) {
424
+ try {
425
+ r.dispose();
426
+ } catch {
427
+ }
428
+ activeResultRef.current = null;
429
+ }
430
+ }, []);
431
+ useEffect4(() => {
432
+ if (!src) {
433
+ dispose();
434
+ setState({
435
+ polygons: EMPTY_POLYGONS,
436
+ voxelSource: void 0,
437
+ loading: false,
438
+ error: null,
439
+ warnings: EMPTY_WARNINGS
440
+ });
441
+ return;
442
+ }
443
+ let cancelled = false;
444
+ setState((prev) => ({ ...prev, loading: true, error: null }));
445
+ const prevResult = activeResultRef.current;
446
+ loadMesh(src, options).then((result) => {
447
+ if (cancelled) {
448
+ try {
449
+ result.dispose();
450
+ } catch {
451
+ }
452
+ return;
453
+ }
454
+ if (prevResult) {
455
+ try {
456
+ prevResult.dispose();
457
+ } catch {
458
+ }
459
+ }
460
+ activeResultRef.current = result;
461
+ setState({
462
+ polygons: result.polygons,
463
+ voxelSource: result.voxelSource,
464
+ loading: false,
465
+ error: null,
466
+ warnings: result.warnings ?? EMPTY_WARNINGS
467
+ });
468
+ }).catch((err) => {
469
+ if (cancelled) return;
470
+ const error = err instanceof Error ? err : new Error(String(err));
471
+ setState((prev) => ({
472
+ polygons: prev.polygons,
473
+ voxelSource: prev.voxelSource,
474
+ loading: false,
475
+ error,
476
+ warnings: prev.warnings
477
+ }));
478
+ });
479
+ return () => {
480
+ cancelled = true;
481
+ };
482
+ }, [src, dispose]);
483
+ useEffect4(() => {
484
+ return () => dispose();
485
+ }, [dispose]);
486
+ return {
487
+ polygons: state.polygons,
488
+ voxelSource: state.voxelSource,
489
+ loading: state.loading,
490
+ error: state.error,
491
+ warnings: state.warnings,
492
+ dispose
493
+ };
494
+ }
495
+
496
+ // src/scene/PolyMesh.tsx
497
+ import { buildBasisHints, cornerShapeGeometryForPlan, worldDirectionalLightToCss } from "@layoutit/polycss-core";
498
+
499
+ // src/scene/atlas/index.tsx
500
+ import {
501
+ isSolidTrianglePlan as isSolidTrianglePlan2,
502
+ isProjectiveQuadPlan,
503
+ buildTextureEdgeRepairSets,
504
+ buildSeamBleedPolygonEdges,
505
+ buildSeamBleedPolygonSet,
506
+ cssBorderShapeForPlan as cssBorderShapeForPlan2
507
+ } from "@layoutit/polycss-core";
508
+
509
+ // src/scene/atlas/detection.ts
510
+ import {
511
+ getSolidPaintDefaultsForPlansCore,
512
+ safariCssProjectiveUnsupported,
513
+ parseHex,
514
+ rgbKey
515
+ } from "@layoutit/polycss-core";
516
+ function borderShapeSupported(doc) {
517
+ const css = doc.defaultView?.CSS ?? (typeof CSS !== "undefined" ? CSS : void 0);
518
+ const supportsBorderShape = !!css?.supports?.(
519
+ "border-shape",
520
+ "polygon(0 0, 100% 0, 0 100%) circle(0)"
521
+ );
522
+ if (!supportsBorderShape) return false;
523
+ const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
524
+ const media = win?.matchMedia;
525
+ if (!media) return true;
526
+ return media("(pointer: fine)").matches && media("(hover: hover)").matches;
527
+ }
528
+ function solidTriangleSupported(doc) {
529
+ if (cornerTriangleSupported(doc)) return true;
530
+ const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
531
+ const userAgent = win?.navigator?.userAgent ?? "";
532
+ if (!userAgent) return true;
533
+ return !safariCssProjectiveUnsupported(userAgent);
534
+ }
535
+ function cornerShapeSupported(doc) {
536
+ const css = doc.defaultView?.CSS ?? (typeof CSS !== "undefined" ? CSS : void 0);
537
+ return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel") && !!css.supports("corner-bottom-right-shape", "bevel") && !!css.supports("corner-bottom-left-shape", "bevel");
538
+ }
539
+ function cornerTriangleSupported(doc) {
540
+ const css = doc.defaultView?.CSS ?? (typeof CSS !== "undefined" ? CSS : void 0);
541
+ return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel");
542
+ }
543
+ function projectiveQuadSupported(doc) {
544
+ const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
545
+ const userAgent = win?.navigator?.userAgent ?? "";
546
+ if (!userAgent) return true;
547
+ return !safariCssProjectiveUnsupported(userAgent);
548
+ }
549
+ function getSolidPaintDefaultsForPlans(plans, textureLighting, doc, strategies, cornerShapeGeometryForPlanFn) {
550
+ const disabled = new Set(strategies?.disable ?? []);
551
+ return getSolidPaintDefaultsForPlansCore(
552
+ plans,
553
+ textureLighting,
554
+ disabled,
555
+ {
556
+ solidTriangleSupported: solidTriangleSupported(doc),
557
+ projectiveQuadSupported: projectiveQuadSupported(doc),
558
+ cornerShapeSupported: cornerShapeSupported(doc),
559
+ borderShapeSupported: borderShapeSupported(doc)
560
+ },
561
+ parseHex,
562
+ rgbKey,
563
+ cornerShapeGeometryForPlanFn
564
+ );
565
+ }
566
+ function getSolidPaintDefaultsFromPlans(plans, textureLighting, disabled = /* @__PURE__ */ new Set(), doc) {
567
+ const resolvedDoc = doc ?? (typeof document !== "undefined" ? document : null);
568
+ if (!resolvedDoc) return {};
569
+ const strategies = disabled.size > 0 ? { disable: Array.from(disabled) } : void 0;
570
+ return getSolidPaintDefaultsForPlans(plans, textureLighting, resolvedDoc, strategies);
571
+ }
572
+ function isBorderShapeSupported(doc) {
573
+ const d = doc ?? (typeof document !== "undefined" ? document : null);
574
+ if (!d) {
575
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
576
+ const supportsBorderShape = !!css?.supports?.("border-shape", "polygon(0 0, 100% 0, 0 100%) circle(0)");
577
+ if (!supportsBorderShape) return false;
578
+ const media = typeof matchMedia !== "undefined" ? matchMedia : void 0;
579
+ if (!media) return true;
580
+ return media("(pointer: fine)").matches && media("(hover: hover)").matches;
581
+ }
582
+ return borderShapeSupported(d);
583
+ }
584
+ function isSolidTriangleSupported(doc) {
585
+ const d = doc ?? (typeof document !== "undefined" ? document : null);
586
+ if (!d) {
587
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
588
+ if (!!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel")) return true;
589
+ const userAgent = (typeof navigator !== "undefined" ? navigator : globalThis.navigator)?.userAgent ?? "";
590
+ if (!userAgent) return true;
591
+ const isChromiumFamily = /\b(?:Chrome|HeadlessChrome|Chromium|Edg|OPR)\//.test(userAgent);
592
+ const isSafariFamily = /\bVersion\/[\d.]+.*\bSafari\//.test(userAgent);
593
+ return !isSafariFamily || isChromiumFamily;
594
+ }
595
+ return solidTriangleSupported(d);
596
+ }
597
+
598
+ // src/scene/atlas/filterPlans.ts
599
+ import {
600
+ filterAtlasPlans as filterAtlasPlansCore
601
+ } from "@layoutit/polycss-core";
602
+ function filterAtlasPlans(plans, textureLighting, disabled, doc, textureBackend, textureImageRendering, textureProjection) {
603
+ const d = doc ?? (typeof document !== "undefined" ? document : null);
604
+ return filterAtlasPlansCore(plans, textureLighting, disabled, {
605
+ solidTriangleSupported: isSolidTriangleSupported(doc),
606
+ projectiveQuadSupported: doc ? projectiveQuadSupported(doc) : true,
607
+ borderShapeSupported: isBorderShapeSupported(doc),
608
+ cornerShapeSupported: d ? cornerShapeSupported(d) : false,
609
+ textureBackend,
610
+ textureImageRendering,
611
+ textureProjection
612
+ });
613
+ }
614
+
615
+ // src/scene/atlas/packing.ts
616
+ import {
617
+ packTextureAtlasPlansWithScaleCore
618
+ } from "@layoutit/polycss-core";
619
+ function isMobileDocument(doc) {
620
+ if (!doc) return false;
621
+ const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
622
+ const media = win?.matchMedia;
623
+ if (!media) return false;
624
+ return media("(pointer: coarse)").matches || media("(hover: none)").matches;
625
+ }
626
+ function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc, textureLeafSizing) {
627
+ return packTextureAtlasPlansWithScaleCore(
628
+ plans,
629
+ textureQualityInput,
630
+ isMobileDocument(doc),
631
+ textureLeafSizing
632
+ );
633
+ }
634
+
635
+ // src/scene/atlas/buildAtlasPages.ts
636
+ import {
637
+ expandClipPoints,
638
+ TEXTURE_TRIANGLE_BLEED,
639
+ TEXTURE_EDGE_REPAIR_ALPHA_MIN,
640
+ TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN,
641
+ TEXTURE_EDGE_REPAIR_RADIUS
642
+ } from "@layoutit/polycss-core";
643
+ var TEXTURE_IMAGE_CACHE = /* @__PURE__ */ new Map();
644
+ function loadTextureImage(url) {
645
+ let p = TEXTURE_IMAGE_CACHE.get(url);
646
+ if (!p) {
647
+ p = new Promise((resolve, reject) => {
648
+ const img = new Image();
649
+ img.decoding = "async";
650
+ img.crossOrigin = "anonymous";
651
+ img.onload = () => resolve(img);
652
+ img.onerror = () => reject(new Error(`texture load failed: ${url}`));
653
+ img.src = url;
654
+ });
655
+ TEXTURE_IMAGE_CACHE.set(url, p);
656
+ p.then(
657
+ () => {
658
+ if (TEXTURE_IMAGE_CACHE.get(url) === p) TEXTURE_IMAGE_CACHE.delete(url);
659
+ },
660
+ () => {
661
+ if (TEXTURE_IMAGE_CACHE.get(url) === p) TEXTURE_IMAGE_CACHE.delete(url);
662
+ }
663
+ );
664
+ }
665
+ return p;
666
+ }
667
+ function setCssTransform(ctx, atlasScale, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) {
668
+ ctx.setTransform(
669
+ a * atlasScale,
670
+ b * atlasScale,
671
+ c * atlasScale,
672
+ d * atlasScale,
673
+ e * atlasScale,
674
+ f * atlasScale
675
+ );
676
+ }
677
+ function srgbByteToLinear(c) {
678
+ const u = c / 255;
679
+ return u <= 0.04045 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4);
680
+ }
681
+ function linearToSrgbByte(c) {
682
+ const s = c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
683
+ return Math.max(0, Math.min(255, Math.round(s * 255)));
684
+ }
685
+ function applyTextureTint(ctx, x, y, width, height, tint, atlasScale) {
686
+ const px = Math.round(x * atlasScale);
687
+ const py = Math.round(y * atlasScale);
688
+ const pw = Math.max(1, Math.round(width * atlasScale));
689
+ const ph = Math.max(1, Math.round(height * atlasScale));
690
+ const img = ctx.getImageData(px, py, pw, ph);
691
+ const data = img.data;
692
+ const tr = tint.r, tg = tint.g, tb = tint.b;
693
+ for (let i = 0; i < data.length; i += 4) {
694
+ if (data[i + 3] === 0) continue;
695
+ data[i] = linearToSrgbByte(srgbByteToLinear(data[i]) * tr);
696
+ data[i + 1] = linearToSrgbByte(srgbByteToLinear(data[i + 1]) * tg);
697
+ data[i + 2] = linearToSrgbByte(srgbByteToLinear(data[i + 2]) * tb);
698
+ }
699
+ ctx.putImageData(img, px, py);
700
+ }
701
+ function drawImageCover(ctx, img, x, y, width, height, atlasScale) {
702
+ const srcW = img.naturalWidth || img.width || 1;
703
+ const srcH = img.naturalHeight || img.height || 1;
704
+ const scale = Math.max(width / srcW, height / srcH);
705
+ const drawW = srcW * scale;
706
+ const drawH = srcH * scale;
707
+ setCssTransform(ctx, atlasScale);
708
+ ctx.drawImage(img, x + (width - drawW) / 2, y + (height - drawH) / 2, drawW, drawH);
709
+ }
710
+ function clampSourceCoord(value, max) {
711
+ return Math.max(0, Math.min(max, value));
712
+ }
713
+ function drawImageUvSample(ctx, img, rect, x, y, width, height, atlasScale) {
714
+ const imgW = img.naturalWidth || img.width || 1;
715
+ const imgH = img.naturalHeight || img.height || 1;
716
+ const rawX0 = clampSourceCoord(Math.min(rect.minU, rect.maxU) * imgW, imgW);
717
+ const rawX1 = clampSourceCoord(Math.max(rect.minU, rect.maxU) * imgW, imgW);
718
+ const rawY0 = clampSourceCoord(Math.min(rect.minV, rect.maxV) * imgH, imgH);
719
+ const rawY1 = clampSourceCoord(Math.max(rect.minV, rect.maxV) * imgH, imgH);
720
+ let sx = Math.floor(rawX0);
721
+ let sy = Math.floor(rawY0);
722
+ let sw = Math.ceil(rawX1) - sx;
723
+ let sh = Math.ceil(rawY1) - sy;
724
+ if (sw < 1) {
725
+ sx = Math.floor(clampSourceCoord((rect.minU + rect.maxU) / 2 * imgW, imgW - 1));
726
+ sw = 1;
727
+ }
728
+ if (sh < 1) {
729
+ sy = Math.floor(clampSourceCoord((rect.minV + rect.maxV) / 2 * imgH, imgH - 1));
730
+ sh = 1;
731
+ }
732
+ sx = Math.max(0, Math.min(imgW - 1, sx));
733
+ sy = Math.max(0, Math.min(imgH - 1, sy));
734
+ sw = Math.max(1, Math.min(imgW - sx, sw));
735
+ sh = Math.max(1, Math.min(imgH - sy, sh));
736
+ setCssTransform(ctx, atlasScale);
737
+ ctx.drawImage(img, sx, sy, sw, sh, x, y, width, height);
738
+ }
739
+ function isTiledWrapMode(mode) {
740
+ return mode === "repeat" || mode === "mirrored-repeat";
741
+ }
742
+ function tiledRangeStart(mode, min) {
743
+ return isTiledWrapMode(mode) ? Math.floor(min) : 0;
744
+ }
745
+ function tiledRangeEnd(mode, max) {
746
+ return isTiledWrapMode(mode) ? Math.ceil(max) - 1 : 0;
747
+ }
748
+ function isMirroredTile(mode, tile) {
749
+ return mode === "mirrored-repeat" && Math.abs(tile % 2) === 1;
750
+ }
751
+ function drawWrappedImageTiles(ctx, img, imgW, imgH, uvSampleRect, wrapS, wrapT) {
752
+ const startS = uvSampleRect ? tiledRangeStart(wrapS, uvSampleRect.minU) : 0;
753
+ const endS = uvSampleRect ? tiledRangeEnd(wrapS, uvSampleRect.maxU) : 0;
754
+ const startT = uvSampleRect ? tiledRangeStart(wrapT, uvSampleRect.minV) : 0;
755
+ const endT = uvSampleRect ? tiledRangeEnd(wrapT, uvSampleRect.maxV) : 0;
756
+ for (let s = startS; s <= endS; s++) {
757
+ for (let t = startT; t <= endT; t++) {
758
+ const flipS = isMirroredTile(wrapS, s);
759
+ const flipT = isMirroredTile(wrapT, t);
760
+ if (!flipS && !flipT) {
761
+ ctx.drawImage(img, s * imgW, t * imgH);
762
+ continue;
763
+ }
764
+ ctx.save();
765
+ ctx.translate((flipS ? s + 1 : s) * imgW, (flipT ? t + 1 : t) * imgH);
766
+ ctx.scale(flipS ? -1 : 1, flipT ? -1 : 1);
767
+ ctx.drawImage(img, 0, 0);
768
+ ctx.restore();
769
+ }
770
+ }
771
+ }
772
+ function tracePolygonPath(ctx, x, y, points) {
773
+ for (let i = 0; i < points.length; i += 2) {
774
+ const px = x + points[i];
775
+ const py = y + points[i + 1];
776
+ if (i === 0) ctx.moveTo(px, py);
777
+ else ctx.lineTo(px, py);
778
+ }
779
+ ctx.closePath();
780
+ }
781
+ function traceOffsetPolygonPath(ctx, x, y, points, offsetX, offsetY) {
782
+ for (let i = 0; i < points.length; i += 2) {
783
+ const px = x + points[i] + offsetX;
784
+ const py = y + points[i + 1] + offsetY;
785
+ if (i === 0) ctx.moveTo(px, py);
786
+ else ctx.lineTo(px, py);
787
+ }
788
+ ctx.closePath();
789
+ }
790
+ function paintSolidAtlasEntry(ctx, entry, textureLighting, atlasScale) {
791
+ setCssTransform(ctx, atlasScale);
792
+ ctx.beginPath();
793
+ tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
794
+ ctx.clip();
795
+ setCssTransform(ctx, atlasScale);
796
+ ctx.fillStyle = textureLighting === "dynamic" ? entry.polygon.color ?? "#cccccc" : entry.shadedColor;
797
+ ctx.fillRect(entry.x, entry.y, entry.canvasW, entry.canvasH);
798
+ }
799
+ function paintOpaqueTextureBase(ctx, entry, atlasScale, offsetX, offsetY) {
800
+ if (entry.polygon.textureAlphaMode !== "opaque") return;
801
+ setCssTransform(ctx, atlasScale);
802
+ ctx.fillStyle = entry.polygon.color ?? "#cccccc";
803
+ ctx.fillRect(entry.x + offsetX, entry.y + offsetY, entry.canvasW, entry.canvasH);
804
+ }
805
+ function drawTexturedAtlasEntry(ctx, entry, srcImg, atlasScale, offsetX = 0, offsetY = 0) {
806
+ if (entry.textureTriangles?.length) {
807
+ const imgW = srcImg.naturalWidth || srcImg.width || 1;
808
+ const imgH = srcImg.naturalHeight || srcImg.height || 1;
809
+ for (const triangle of entry.textureTriangles) {
810
+ const clipPts = expandClipPoints(triangle.screenPts, TEXTURE_TRIANGLE_BLEED);
811
+ ctx.save();
812
+ setCssTransform(ctx, atlasScale);
813
+ ctx.beginPath();
814
+ traceOffsetPolygonPath(ctx, entry.x, entry.y, clipPts, offsetX, offsetY);
815
+ ctx.clip();
816
+ paintOpaqueTextureBase(ctx, entry, atlasScale, offsetX, offsetY);
817
+ if (triangle.uvAffine) {
818
+ setCssTransform(
819
+ ctx,
820
+ atlasScale,
821
+ triangle.uvAffine.a / imgW,
822
+ triangle.uvAffine.c / imgW,
823
+ triangle.uvAffine.b / imgH,
824
+ triangle.uvAffine.d / imgH,
825
+ entry.x + triangle.uvAffine.e + offsetX,
826
+ entry.y + triangle.uvAffine.f + offsetY
827
+ );
828
+ drawWrappedImageTiles(
829
+ ctx,
830
+ srcImg,
831
+ imgW,
832
+ imgH,
833
+ triangle.uvSampleRect,
834
+ entry.polygon.textureWrap?.s,
835
+ entry.polygon.textureWrap?.t
836
+ );
837
+ } else if (triangle.uvSampleRect) {
838
+ drawImageUvSample(
839
+ ctx,
840
+ srcImg,
841
+ triangle.uvSampleRect,
842
+ entry.x + offsetX,
843
+ entry.y + offsetY,
844
+ entry.canvasW,
845
+ entry.canvasH,
846
+ atlasScale
847
+ );
848
+ }
849
+ ctx.restore();
850
+ }
851
+ } else if (entry.uvAffine) {
852
+ const imgW = srcImg.naturalWidth || srcImg.width || 1;
853
+ const imgH = srcImg.naturalHeight || srcImg.height || 1;
854
+ const clipOpaque = entry.polygon.textureAlphaMode === "opaque";
855
+ if (clipOpaque) {
856
+ ctx.save();
857
+ setCssTransform(ctx, atlasScale);
858
+ ctx.beginPath();
859
+ traceOffsetPolygonPath(ctx, entry.x, entry.y, entry.screenPts, offsetX, offsetY);
860
+ ctx.clip();
861
+ paintOpaqueTextureBase(ctx, entry, atlasScale, offsetX, offsetY);
862
+ }
863
+ setCssTransform(
864
+ ctx,
865
+ atlasScale,
866
+ entry.uvAffine.a / imgW,
867
+ entry.uvAffine.c / imgW,
868
+ entry.uvAffine.b / imgH,
869
+ entry.uvAffine.d / imgH,
870
+ entry.x + entry.uvAffine.e + offsetX,
871
+ entry.y + entry.uvAffine.f + offsetY
872
+ );
873
+ drawWrappedImageTiles(
874
+ ctx,
875
+ srcImg,
876
+ imgW,
877
+ imgH,
878
+ entry.uvSampleRect,
879
+ entry.polygon.textureWrap?.s,
880
+ entry.polygon.textureWrap?.t
881
+ );
882
+ if (clipOpaque) ctx.restore();
883
+ } else if (entry.uvSampleRect) {
884
+ const clipOpaque = entry.polygon.textureAlphaMode === "opaque";
885
+ if (clipOpaque) {
886
+ ctx.save();
887
+ setCssTransform(ctx, atlasScale);
888
+ ctx.beginPath();
889
+ traceOffsetPolygonPath(ctx, entry.x, entry.y, entry.screenPts, offsetX, offsetY);
890
+ ctx.clip();
891
+ paintOpaqueTextureBase(ctx, entry, atlasScale, offsetX, offsetY);
892
+ }
893
+ drawImageUvSample(
894
+ ctx,
895
+ srcImg,
896
+ entry.uvSampleRect,
897
+ entry.x + offsetX,
898
+ entry.y + offsetY,
899
+ entry.canvasW,
900
+ entry.canvasH,
901
+ atlasScale
902
+ );
903
+ if (clipOpaque) ctx.restore();
904
+ } else {
905
+ const clipOpaque = entry.polygon.textureAlphaMode === "opaque";
906
+ if (clipOpaque) {
907
+ ctx.save();
908
+ setCssTransform(ctx, atlasScale);
909
+ ctx.beginPath();
910
+ traceOffsetPolygonPath(ctx, entry.x, entry.y, entry.screenPts, offsetX, offsetY);
911
+ ctx.clip();
912
+ paintOpaqueTextureBase(ctx, entry, atlasScale, offsetX, offsetY);
913
+ }
914
+ drawImageCover(
915
+ ctx,
916
+ srcImg,
917
+ entry.x + offsetX,
918
+ entry.y + offsetY,
919
+ entry.canvasW,
920
+ entry.canvasH,
921
+ atlasScale
922
+ );
923
+ if (clipOpaque) ctx.restore();
924
+ }
925
+ }
926
+ function distanceToSegment(px, py, ax, ay, bx, by) {
927
+ const dx = bx - ax;
928
+ const dy = by - ay;
929
+ const lenSq = dx * dx + dy * dy;
930
+ if (lenSq <= 1e-9) return Math.hypot(px - ax, py - ay);
931
+ const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));
932
+ return Math.hypot(px - (ax + dx * t), py - (ay + dy * t));
933
+ }
934
+ function distanceToPolygonEdges(px, py, points, edgeIndices) {
935
+ let best = Infinity;
936
+ const count = points.length / 2;
937
+ for (const edgeIndex of edgeIndices) {
938
+ if (edgeIndex < 0 || edgeIndex >= count) continue;
939
+ const i = edgeIndex * 2;
940
+ const next = (edgeIndex + 1) % count * 2;
941
+ best = Math.min(
942
+ best,
943
+ distanceToSegment(px, py, points[i], points[i + 1], points[next], points[next + 1])
944
+ );
945
+ }
946
+ return best;
947
+ }
948
+ function nearestOpaquePixelOffset(data, width, height, x, y, radius) {
949
+ const minX = Math.max(0, x - radius);
950
+ const maxX = Math.min(width - 1, x + radius);
951
+ const minY = Math.max(0, y - radius);
952
+ const maxY = Math.min(height - 1, y + radius);
953
+ let bestOffset = null;
954
+ let bestDistanceSq = Infinity;
955
+ for (let yy = minY; yy <= maxY; yy++) {
956
+ for (let xx = minX; xx <= maxX; xx++) {
957
+ if (xx === x && yy === y) continue;
958
+ const dx = xx - x;
959
+ const dy = yy - y;
960
+ const distanceSq = dx * dx + dy * dy;
961
+ if (distanceSq > radius * radius || distanceSq >= bestDistanceSq) continue;
962
+ const offset = (yy * width + xx) * 4;
963
+ if (data[offset + 3] < TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN) continue;
964
+ bestOffset = offset;
965
+ bestDistanceSq = distanceSq;
966
+ }
967
+ }
968
+ return bestOffset;
969
+ }
970
+ function repairTextureEdgeAlpha(ctx, entry, atlasScale) {
971
+ if (!entry.textureEdgeRepair || !entry.texture) return;
972
+ if (!entry.textureEdgeRepairEdges || entry.textureEdgeRepairEdges.size === 0) return;
973
+ const canvas = ctx.canvas;
974
+ if (!canvas) return;
975
+ const pixelX = Math.max(0, Math.floor(entry.x * atlasScale));
976
+ const pixelY = Math.max(0, Math.floor(entry.y * atlasScale));
977
+ const pixelW = Math.max(1, Math.min(canvas.width - pixelX, Math.ceil(entry.canvasW * atlasScale)));
978
+ const pixelH = Math.max(1, Math.min(canvas.height - pixelY, Math.ceil(entry.canvasH * atlasScale)));
979
+ if (pixelW <= 0 || pixelH <= 0) return;
980
+ let imageData;
981
+ try {
982
+ imageData = ctx.getImageData(pixelX, pixelY, pixelW, pixelH);
983
+ } catch {
984
+ return;
985
+ }
986
+ const data = imageData.data;
987
+ const source = new Uint8ClampedArray(data);
988
+ const radius = Math.max(TEXTURE_EDGE_REPAIR_RADIUS, TEXTURE_EDGE_REPAIR_RADIUS / atlasScale);
989
+ const sourceRadius = Math.max(2, Math.ceil(radius * atlasScale) + 1);
990
+ let changed = false;
991
+ for (let y = 0; y < pixelH; y++) {
992
+ for (let x = 0; x < pixelW; x++) {
993
+ const offset = (y * pixelW + x) * 4;
994
+ const alpha = data[offset + 3];
995
+ if (alpha < TEXTURE_EDGE_REPAIR_ALPHA_MIN || alpha === 255) continue;
996
+ const localX = (pixelX + x + 0.5) / atlasScale - entry.x;
997
+ const localY = (pixelY + y + 0.5) / atlasScale - entry.y;
998
+ if (distanceToPolygonEdges(localX, localY, entry.screenPts, entry.textureEdgeRepairEdges) > radius) {
999
+ continue;
1000
+ }
1001
+ const sourceOffset = nearestOpaquePixelOffset(source, pixelW, pixelH, x, y, sourceRadius);
1002
+ if (sourceOffset === null) continue;
1003
+ data[offset] = source[sourceOffset];
1004
+ data[offset + 1] = source[sourceOffset + 1];
1005
+ data[offset + 2] = source[sourceOffset + 2];
1006
+ data[offset + 3] = 255;
1007
+ changed = true;
1008
+ }
1009
+ }
1010
+ if (!changed) return;
1011
+ ctx.putImageData(imageData, pixelX, pixelY);
1012
+ }
1013
+ function canvasToUrl(canvas) {
1014
+ if (typeof canvas.toBlob === "function") {
1015
+ return new Promise((resolve) => {
1016
+ canvas.toBlob((blob) => {
1017
+ resolve(blob ? URL.createObjectURL(blob) : null);
1018
+ }, "image/png");
1019
+ });
1020
+ }
1021
+ try {
1022
+ return Promise.resolve(canvas.toDataURL("image/png"));
1023
+ } catch {
1024
+ return Promise.resolve(null);
1025
+ }
1026
+ }
1027
+ async function buildAtlasPage(page, textureLighting, doc, atlasScale) {
1028
+ const canvas = doc.createElement("canvas");
1029
+ canvas.width = Math.max(1, Math.ceil(page.width * atlasScale));
1030
+ canvas.height = Math.max(1, Math.ceil(page.height * atlasScale));
1031
+ const needsReadback = page.entries.some(
1032
+ (entry) => entry.textureEdgeRepair && entry.texture && entry.textureEdgeRepairEdges && entry.textureEdgeRepairEdges.size > 0
1033
+ );
1034
+ const ctx = canvas.getContext("2d", needsReadback ? { willReadFrequently: true } : void 0);
1035
+ if (!ctx) return { width: page.width, height: page.height, url: null };
1036
+ const uniqueTextures = Array.from(new Set(
1037
+ page.entries.flatMap((entry) => entry.texture ? [entry.texture] : [])
1038
+ ));
1039
+ const loaded = /* @__PURE__ */ new Map();
1040
+ await Promise.all(uniqueTextures.map(async (url2) => {
1041
+ loaded.set(url2, await loadTextureImage(url2));
1042
+ }));
1043
+ for (const entry of page.entries) {
1044
+ const srcImg = entry.texture ? loaded.get(entry.texture) : null;
1045
+ if (!entry.texture) {
1046
+ ctx.save();
1047
+ paintSolidAtlasEntry(ctx, entry, textureLighting, atlasScale);
1048
+ ctx.restore();
1049
+ continue;
1050
+ }
1051
+ if (srcImg) {
1052
+ ctx.save();
1053
+ setCssTransform(
1054
+ ctx,
1055
+ atlasScale
1056
+ );
1057
+ ctx.beginPath();
1058
+ tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
1059
+ ctx.clip();
1060
+ drawTexturedAtlasEntry(ctx, entry, srcImg, atlasScale);
1061
+ ctx.restore();
1062
+ }
1063
+ if (entry.texture && textureLighting === "baked") {
1064
+ ctx.save();
1065
+ setCssTransform(ctx, atlasScale);
1066
+ ctx.beginPath();
1067
+ tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
1068
+ ctx.clip();
1069
+ applyTextureTint(ctx, entry.x, entry.y, entry.canvasW, entry.canvasH, entry.textureTint, atlasScale);
1070
+ ctx.restore();
1071
+ }
1072
+ repairTextureEdgeAlpha(ctx, entry, atlasScale);
1073
+ }
1074
+ const url = await canvasToUrl(canvas);
1075
+ canvas.width = 1;
1076
+ canvas.height = 1;
1077
+ return {
1078
+ width: page.width,
1079
+ height: page.height,
1080
+ url
1081
+ };
1082
+ }
1083
+ async function buildAtlasPages(pages, textureLighting, doc, atlasScale, isCancelled) {
1084
+ const built = [];
1085
+ for (const page of pages) {
1086
+ if (isCancelled()) break;
1087
+ built.push(await buildAtlasPage(page, textureLighting, doc, atlasScale));
1088
+ }
1089
+ return built;
1090
+ }
1091
+
1092
+ // src/scene/atlas/solidTriangleStyle.ts
1093
+ import {
1094
+ isSolidTrianglePlan,
1095
+ offsetConvexPolygonPointsByEdgeAmounts,
1096
+ parsePureColor,
1097
+ resolveSeamBleed,
1098
+ safePlanSeamBleedAmount
1099
+ } from "@layoutit/polycss-core";
1100
+ var DEFAULT_TILE = 50;
1101
+ var DEFAULT_LIGHT_DIR = [0.4, -0.7, 0.59];
1102
+ var DEFAULT_LIGHT_COLOR = "#ffffff";
1103
+ var DEFAULT_LIGHT_INTENSITY = 1;
1104
+ var DEFAULT_AMBIENT_COLOR = "#ffffff";
1105
+ var DEFAULT_AMBIENT_INTENSITY = 0.4;
1106
+ var BASIS_EPS = 1e-9;
1107
+ var SOLID_TRIANGLE_BLEED = 0.75;
1108
+ var SOLID_TRIANGLE_CANONICAL_SIZE = 32;
1109
+ var SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96;
1110
+ var SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 48px 96px 48px";
1111
+ var cachedSolidTriangleUserAgent;
1112
+ var cachedSolidTriangleCanonicalSize = SOLID_TRIANGLE_CANONICAL_SIZE;
1113
+ function cornerTriangleSupported2() {
1114
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
1115
+ return !!css?.supports?.("corner-top-left-shape", "bevel") && !!css.supports("corner-top-right-shape", "bevel");
1116
+ }
1117
+ function solidTrianglePrimitive() {
1118
+ if (cornerTriangleSupported2()) return "corner-bevel";
1119
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
1120
+ return /\bFirefox\//.test(ua) ? "border-large" : "border";
1121
+ }
1122
+ function solidTriangleCanonicalSize() {
1123
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
1124
+ if (ua !== cachedSolidTriangleUserAgent) {
1125
+ cachedSolidTriangleUserAgent = ua;
1126
+ cachedSolidTriangleCanonicalSize = /\bFirefox\//.test(ua) ? SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE : SOLID_TRIANGLE_CANONICAL_SIZE;
1127
+ }
1128
+ return cachedSolidTriangleCanonicalSize;
1129
+ }
1130
+ function solidTriangleBorderWidth() {
1131
+ return solidTrianglePrimitive() === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : void 0;
1132
+ }
1133
+ function solidTrianglePaintStyle() {
1134
+ const primitive = solidTrianglePrimitive();
1135
+ if (primitive === "corner-bevel") return void 0;
1136
+ const borderWidth = primitive === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : void 0;
1137
+ return borderWidth ? { borderWidth } : void 0;
1138
+ }
1139
+ function applySolidTrianglePaintStyle(el) {
1140
+ const primitive = solidTrianglePrimitive();
1141
+ if (primitive === "corner-bevel") {
1142
+ el.style.width = "";
1143
+ el.style.height = "";
1144
+ el.style.backgroundColor = "";
1145
+ el.style.borderWidth = "";
1146
+ el.style.borderTopLeftRadius = "";
1147
+ el.style.borderTopRightRadius = "";
1148
+ el.style.removeProperty("corner-top-left-shape");
1149
+ el.style.removeProperty("corner-top-right-shape");
1150
+ } else {
1151
+ el.style.width = "";
1152
+ el.style.height = "";
1153
+ el.style.backgroundColor = "";
1154
+ el.style.borderTopLeftRadius = "";
1155
+ el.style.borderTopRightRadius = "";
1156
+ el.style.removeProperty("corner-top-left-shape");
1157
+ el.style.removeProperty("corner-top-right-shape");
1158
+ el.style.borderWidth = primitive === "border-large" ? SOLID_TRIANGLE_LARGE_BORDER_WIDTH : "";
1159
+ }
1160
+ }
1161
+ function parseHex2(hex) {
1162
+ const parsed = parsePureColor(hex);
1163
+ if (!parsed) return { r: 255, g: 255, b: 255 };
1164
+ return { r: parsed.rgb[0], g: parsed.rgb[1], b: parsed.rgb[2] };
1165
+ }
1166
+ function rgbKey2({ r, g, b }) {
1167
+ return `${r},${g},${b}`;
1168
+ }
1169
+ function parseAlpha(input) {
1170
+ return parsePureColor(input)?.alpha ?? 1;
1171
+ }
1172
+ function rgbToHex({ r, g, b }) {
1173
+ const f = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
1174
+ return `#${f(r)}${f(g)}${f(b)}`;
1175
+ }
1176
+ function shadePolygon(baseColor, directScale, lightColor, ambientColor, ambientIntensity) {
1177
+ const base = parseHex2(baseColor);
1178
+ const light = parseHex2(lightColor);
1179
+ const amb = parseHex2(ambientColor);
1180
+ const tintR = amb.r / 255 * ambientIntensity + light.r / 255 * directScale;
1181
+ const tintG = amb.g / 255 * ambientIntensity + light.g / 255 * directScale;
1182
+ const tintB = amb.b / 255 * ambientIntensity + light.b / 255 * directScale;
1183
+ const r = Math.max(0, Math.min(255, Math.round(base.r * tintR)));
1184
+ const g = Math.max(0, Math.min(255, Math.round(base.g * tintG)));
1185
+ const b = Math.max(0, Math.min(255, Math.round(base.b * tintB)));
1186
+ const alpha = parseAlpha(baseColor);
1187
+ return alpha < 1 ? `rgba(${r}, ${g}, ${b}, ${alpha})` : rgbToHex({ r, g, b });
1188
+ }
1189
+ function quantizeCssColor(input, steps) {
1190
+ if (!Number.isFinite(steps) || steps <= 1) return input;
1191
+ const parsed = parsePureColor(input);
1192
+ if (!parsed) return input;
1193
+ const channelStep = 255 / Math.max(1, Math.round(steps) - 1);
1194
+ const quantize = (value) => Math.max(0, Math.min(255, Math.round(Math.round(value / channelStep) * channelStep)));
1195
+ const rgb = {
1196
+ r: quantize(parsed.rgb[0]),
1197
+ g: quantize(parsed.rgb[1]),
1198
+ b: quantize(parsed.rgb[2])
1199
+ };
1200
+ return parsed.alpha < 1 ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${parsed.alpha})` : rgbToHex(rgb);
1201
+ }
1202
+ function stepRgbToward(current, target, maxStep) {
1203
+ const step = (from, to) => {
1204
+ if (from === to) return from;
1205
+ const delta = to - from;
1206
+ return from + Math.sign(delta) * Math.min(Math.abs(delta), maxStep);
1207
+ };
1208
+ return {
1209
+ r: step(current.r, target.r),
1210
+ g: step(current.g, target.g),
1211
+ b: step(current.b, target.b)
1212
+ };
1213
+ }
1214
+ function dotVec(a, b) {
1215
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
1216
+ }
1217
+ function crossVec(a, b) {
1218
+ return [
1219
+ a[1] * b[2] - a[2] * b[1],
1220
+ a[2] * b[0] - a[0] * b[2],
1221
+ a[0] * b[1] - a[1] * b[0]
1222
+ ];
1223
+ }
1224
+ function computeSurfaceNormal(pts) {
1225
+ if (pts.length < 3) return null;
1226
+ const p0 = pts[0];
1227
+ const normal = [0, 0, 0];
1228
+ for (let i = 1; i + 1 < pts.length; i++) {
1229
+ const p1 = pts[i];
1230
+ const p2 = pts[i + 1];
1231
+ const e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
1232
+ const e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
1233
+ normal[0] -= e1[1] * e2[2] - e1[2] * e2[1];
1234
+ normal[1] -= e1[2] * e2[0] - e1[0] * e2[2];
1235
+ normal[2] -= e1[0] * e2[1] - e1[1] * e2[0];
1236
+ }
1237
+ const len = Math.hypot(normal[0], normal[1], normal[2]);
1238
+ if (len <= BASIS_EPS) return null;
1239
+ return [normal[0] / len, normal[1] / len, normal[2] / len];
1240
+ }
1241
+ function isConvexPolygonPoints(points) {
1242
+ if (points.length < 3) return false;
1243
+ let sign = 0;
1244
+ for (let i = 0; i < points.length; i++) {
1245
+ const a = points[i];
1246
+ const b = points[(i + 1) % points.length];
1247
+ const c = points[(i + 2) % points.length];
1248
+ const cross = (b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0]);
1249
+ if (Math.abs(cross) <= BASIS_EPS) return false;
1250
+ const nextSign = Math.sign(cross);
1251
+ if (sign === 0) sign = nextSign;
1252
+ else if (nextSign !== sign) return false;
1253
+ }
1254
+ return true;
1255
+ }
1256
+ function signedArea2D(points) {
1257
+ let area = 0;
1258
+ for (let i = 0; i < points.length; i++) {
1259
+ const a = points[i];
1260
+ const b = points[(i + 1) % points.length];
1261
+ area += a[0] * b[1] - a[1] * b[0];
1262
+ }
1263
+ return area / 2;
1264
+ }
1265
+ function intersect2DLines(a0, a1, b0, b1) {
1266
+ const rx = a1[0] - a0[0];
1267
+ const ry = a1[1] - a0[1];
1268
+ const sx = b1[0] - b0[0];
1269
+ const sy = b1[1] - b0[1];
1270
+ const det = rx * sy - ry * sx;
1271
+ if (Math.abs(det) <= BASIS_EPS) return null;
1272
+ const qpx = b0[0] - a0[0];
1273
+ const qpy = b0[1] - a0[1];
1274
+ const t = (qpx * sy - qpy * sx) / det;
1275
+ return [a0[0] + t * rx, a0[1] + t * ry];
1276
+ }
1277
+ function expandClipPoints2(points, amount) {
1278
+ if (points.length < 6 || amount <= 0) return points;
1279
+ let cx = 0;
1280
+ let cy = 0;
1281
+ const count = points.length / 2;
1282
+ for (let i = 0; i < points.length; i += 2) {
1283
+ cx += points[i];
1284
+ cy += points[i + 1];
1285
+ }
1286
+ cx /= count;
1287
+ cy /= count;
1288
+ const expanded = points.slice();
1289
+ for (let i = 0; i < expanded.length; i += 2) {
1290
+ const dx = expanded[i] - cx;
1291
+ const dy = expanded[i + 1] - cy;
1292
+ const len = Math.hypot(dx, dy);
1293
+ if (len <= BASIS_EPS) continue;
1294
+ expanded[i] += dx / len * amount;
1295
+ expanded[i + 1] += dy / len * amount;
1296
+ }
1297
+ return expanded;
1298
+ }
1299
+ function offsetConvexPolygonPoints(points, amount) {
1300
+ if (points.length < 6 || points.length % 2 !== 0 || amount <= 0) return points;
1301
+ const q = [];
1302
+ for (let i = 0; i < points.length; i += 2) q.push([points[i], points[i + 1]]);
1303
+ if (!isConvexPolygonPoints(q)) return expandClipPoints2(points, amount);
1304
+ const area = signedArea2D(q);
1305
+ if (Math.abs(area) <= BASIS_EPS) return expandClipPoints2(points, amount);
1306
+ const outwardSign = area > 0 ? 1 : -1;
1307
+ const offsetLines = [];
1308
+ for (let i = 0; i < q.length; i++) {
1309
+ const a = q[i];
1310
+ const b = q[(i + 1) % q.length];
1311
+ const dx = b[0] - a[0];
1312
+ const dy = b[1] - a[1];
1313
+ const length = Math.hypot(dx, dy);
1314
+ if (length <= BASIS_EPS) return expandClipPoints2(points, amount);
1315
+ const ox = outwardSign * (dy / length) * amount;
1316
+ const oy = outwardSign * (-dx / length) * amount;
1317
+ offsetLines.push({ a: [a[0] + ox, a[1] + oy], b: [b[0] + ox, b[1] + oy] });
1318
+ }
1319
+ const expanded = [];
1320
+ const maxMiter = Math.max(2, amount * 4);
1321
+ for (let i = 0; i < q.length; i++) {
1322
+ const prev = offsetLines[(i + q.length - 1) % q.length];
1323
+ const next = offsetLines[i];
1324
+ const intersection = intersect2DLines(prev.a, prev.b, next.a, next.b);
1325
+ if (!intersection) return expandClipPoints2(points, amount);
1326
+ const original = q[i];
1327
+ const dx = intersection[0] - original[0];
1328
+ const dy = intersection[1] - original[1];
1329
+ const miter = Math.hypot(dx, dy);
1330
+ if (miter > maxMiter) {
1331
+ expanded.push(original[0] + dx / miter * maxMiter, original[1] + dy / miter * maxMiter);
1332
+ } else {
1333
+ expanded.push(intersection[0], intersection[1]);
1334
+ }
1335
+ }
1336
+ return expanded;
1337
+ }
1338
+ function offsetStableTrianglePoints(left, right, height, amount) {
1339
+ const baseWidth = left + right;
1340
+ if (amount <= 0 || height <= BASIS_EPS || baseWidth <= BASIS_EPS || !Number.isFinite(left + right + height + amount)) {
1341
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1342
+ }
1343
+ const leftLen = Math.sqrt(left * left + height * height);
1344
+ const rightLen = Math.sqrt(right * right + height * height);
1345
+ if (leftLen <= BASIS_EPS || rightLen <= BASIS_EPS) {
1346
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1347
+ }
1348
+ const leftOffsetX = -amount * height / leftLen;
1349
+ const leftOffsetY = -amount * left / leftLen;
1350
+ const rightOffsetX = amount * height / rightLen;
1351
+ const rightOffsetY = -amount * right / rightLen;
1352
+ const apexLineLeftX = left + leftOffsetX;
1353
+ const apexLineLeftY = leftOffsetY;
1354
+ const apexLineRightX = baseWidth + rightOffsetX;
1355
+ const apexLineRightY = height + rightOffsetY;
1356
+ const det = -height * baseWidth;
1357
+ if (Math.abs(det) <= BASIS_EPS) {
1358
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1359
+ }
1360
+ const qx = apexLineLeftX - apexLineRightX;
1361
+ const qy = apexLineLeftY - apexLineRightY;
1362
+ const t = (qx * height + qy * left) / det;
1363
+ let apexX = apexLineRightX - t * right;
1364
+ let apexY = apexLineRightY - t * height;
1365
+ let baseLeftX = -amount * (left + leftLen) / height;
1366
+ let baseLeftY = height + amount;
1367
+ let baseRightX = baseWidth + amount * (right + rightLen) / height;
1368
+ let baseRightY = baseLeftY;
1369
+ const maxMiter = Math.max(2, amount * 4);
1370
+ const apexDx = apexX - left;
1371
+ const apexDy = apexY;
1372
+ const apexMiter = Math.sqrt(apexDx * apexDx + apexDy * apexDy);
1373
+ if (apexMiter > maxMiter) {
1374
+ apexX = left + apexDx / apexMiter * maxMiter;
1375
+ apexY = apexDy / apexMiter * maxMiter;
1376
+ }
1377
+ const leftMiter = Math.sqrt(baseLeftX * baseLeftX + amount * amount);
1378
+ if (leftMiter > maxMiter) {
1379
+ baseLeftX = baseLeftX / leftMiter * maxMiter;
1380
+ baseLeftY = height + amount / leftMiter * maxMiter;
1381
+ }
1382
+ const rightDx = baseRightX - baseWidth;
1383
+ const rightMiter = Math.sqrt(rightDx * rightDx + amount * amount);
1384
+ if (rightMiter > maxMiter) {
1385
+ baseRightX = baseWidth + rightDx / rightMiter * maxMiter;
1386
+ baseRightY = height + amount / rightMiter * maxMiter;
1387
+ }
1388
+ return [apexX, apexY, baseLeftX, baseLeftY, baseRightX, baseRightY];
1389
+ }
1390
+ function triangleEdgeIndexForPair(a, b) {
1391
+ if ((a + 1) % 3 === b) return a;
1392
+ if ((b + 1) % 3 === a) return b;
1393
+ return void 0;
1394
+ }
1395
+ function stableTriangleEdgeAmounts(entry, a, b, c, screenPts) {
1396
+ const seamEdges = entry.seamBleedEdges;
1397
+ if (!seamEdges?.size) return null;
1398
+ const seamAmount = entry.seamBleed === void 0 ? SOLID_TRIANGLE_BLEED : entry.seamBleed;
1399
+ const edgePairs = [[c, a], [a, b], [b, c]];
1400
+ return edgePairs.map(([from, to], localEdgeIndex) => {
1401
+ const edgeIndex = triangleEdgeIndexForPair(from, to);
1402
+ const requested = edgeIndex !== void 0 && seamEdges.has(edgeIndex) ? entry.seamBleedEdgeAmounts?.get(edgeIndex) ?? resolveSeamBleed(seamAmount, SOLID_TRIANGLE_BLEED) : 0;
1403
+ return safePlanSeamBleedAmount(screenPts, localEdgeIndex, requested);
1404
+ });
1405
+ }
1406
+ function formatStableTriangleTransformScalars(x0, x1, x2, y0, y1, y2, z0, z1, z2, tx0, tx1, tx2) {
1407
+ const rx0 = Math.round(x0 * 1e3) / 1e3 || 0;
1408
+ const rx1 = Math.round(x1 * 1e3) / 1e3 || 0;
1409
+ const rx2 = Math.round(x2 * 1e3) / 1e3 || 0;
1410
+ const ry0 = Math.round(y0 * 1e3) / 1e3 || 0;
1411
+ const ry1 = Math.round(y1 * 1e3) / 1e3 || 0;
1412
+ const ry2 = Math.round(y2 * 1e3) / 1e3 || 0;
1413
+ const rz0 = Math.round(z0 * 1e3) / 1e3 || 0;
1414
+ const rz1 = Math.round(z1 * 1e3) / 1e3 || 0;
1415
+ const rz2 = Math.round(z2 * 1e3) / 1e3 || 0;
1416
+ const rtx0 = Math.round(tx0 * 1e3) / 1e3 || 0;
1417
+ const rtx1 = Math.round(tx1 * 1e3) / 1e3 || 0;
1418
+ const rtx2 = Math.round(tx2 * 1e3) / 1e3 || 0;
1419
+ return `matrix3d(${rx0},${rx1},${rx2},0,${ry0},${ry1},${ry2},0,${rz0},${rz1},${rz2},0,${rtx0},${rtx1},${rtx2},1)`;
1420
+ }
1421
+ function cssPoints(vertices, tile, elev) {
1422
+ return vertices.map((v) => [v[1] * tile, v[0] * tile, v[2] * elev]);
1423
+ }
1424
+ function solidTriangleStyle(entry, textureLighting, pointerEvents, solidPaintDefaults) {
1425
+ if (!isSolidTrianglePlan(entry)) return null;
1426
+ const tile = entry.tileSize;
1427
+ const elev = entry.layerElevation;
1428
+ const pts = cssPoints(entry.polygon.vertices, tile, elev);
1429
+ const normal = computeSurfaceNormal(pts);
1430
+ if (!normal) return null;
1431
+ const edges = [
1432
+ { a: 0, b: 1, c: 2 },
1433
+ { a: 1, b: 2, c: 0 },
1434
+ { a: 2, b: 0, c: 1 }
1435
+ ].map((edge) => {
1436
+ const av2 = pts[edge.a];
1437
+ const bv2 = pts[edge.b];
1438
+ return {
1439
+ ...edge,
1440
+ length: Math.hypot(bv2[0] - av2[0], bv2[1] - av2[1], bv2[2] - av2[2])
1441
+ };
1442
+ }).sort((a2, b2) => b2.length - a2.length);
1443
+ let a = edges[0].a;
1444
+ let b = edges[0].b;
1445
+ const c = edges[0].c;
1446
+ let av = pts[a];
1447
+ let bv = pts[b];
1448
+ const cv = pts[c];
1449
+ let baseLength = edges[0].length;
1450
+ if (baseLength <= BASIS_EPS) return null;
1451
+ let xAxis = [
1452
+ (bv[0] - av[0]) / baseLength,
1453
+ (bv[1] - av[1]) / baseLength,
1454
+ (bv[2] - av[2]) / baseLength
1455
+ ];
1456
+ const ac = [cv[0] - av[0], cv[1] - av[1], cv[2] - av[2]];
1457
+ let apexX = dotVec(ac, xAxis);
1458
+ let foot = [
1459
+ av[0] + xAxis[0] * apexX,
1460
+ av[1] + xAxis[1] * apexX,
1461
+ av[2] + xAxis[2] * apexX
1462
+ ];
1463
+ let yAxisRaw = [foot[0] - cv[0], foot[1] - cv[1], foot[2] - cv[2]];
1464
+ const height = Math.hypot(yAxisRaw[0], yAxisRaw[1], yAxisRaw[2]);
1465
+ if (height <= BASIS_EPS) return null;
1466
+ let yAxis = [yAxisRaw[0] / height, yAxisRaw[1] / height, yAxisRaw[2] / height];
1467
+ if (dotVec(crossVec(xAxis, yAxis), normal) < 0) {
1468
+ const nextA = b;
1469
+ b = a;
1470
+ a = nextA;
1471
+ av = pts[a];
1472
+ bv = pts[b];
1473
+ baseLength = Math.hypot(bv[0] - av[0], bv[1] - av[1], bv[2] - av[2]);
1474
+ if (baseLength <= BASIS_EPS) return null;
1475
+ xAxis = [
1476
+ (bv[0] - av[0]) / baseLength,
1477
+ (bv[1] - av[1]) / baseLength,
1478
+ (bv[2] - av[2]) / baseLength
1479
+ ];
1480
+ const nextAc = [cv[0] - av[0], cv[1] - av[1], cv[2] - av[2]];
1481
+ apexX = dotVec(nextAc, xAxis);
1482
+ foot = [
1483
+ av[0] + xAxis[0] * apexX,
1484
+ av[1] + xAxis[1] * apexX,
1485
+ av[2] + xAxis[2] * apexX
1486
+ ];
1487
+ yAxisRaw = [foot[0] - cv[0], foot[1] - cv[1], foot[2] - cv[2]];
1488
+ const nextHeight = Math.hypot(yAxisRaw[0], yAxisRaw[1], yAxisRaw[2]);
1489
+ if (nextHeight <= BASIS_EPS) return null;
1490
+ yAxis = [yAxisRaw[0] / nextHeight, yAxisRaw[1] / nextHeight, yAxisRaw[2] / nextHeight];
1491
+ }
1492
+ const canonicalSize = solidTriangleCanonicalSize();
1493
+ const left = Math.max(0, Math.min(baseLength, apexX));
1494
+ const right = Math.max(0, baseLength - left);
1495
+ const screenPts = [left, 0, 0, height, left + right, height];
1496
+ const edgeAmounts = stableTriangleEdgeAmounts(entry, a, b, c, screenPts);
1497
+ const expanded = edgeAmounts ? offsetConvexPolygonPointsByEdgeAmounts(screenPts, edgeAmounts) : offsetStableTrianglePoints(
1498
+ left,
1499
+ right,
1500
+ height,
1501
+ resolveSeamBleed(entry.seamBleed, SOLID_TRIANGLE_BLEED)
1502
+ );
1503
+ const apex2 = [expanded[0], expanded[1]];
1504
+ const baseLeft2 = [expanded[2], expanded[3]];
1505
+ const baseRight2 = [expanded[4], expanded[5]];
1506
+ const baseY = (baseLeft2[1] + baseRight2[1]) / 2;
1507
+ const leftPx = apex2[0] - baseLeft2[0];
1508
+ const rightPx = baseRight2[0] - apex2[0];
1509
+ const heightPx = baseY - apex2[1];
1510
+ if (leftPx <= BASIS_EPS || rightPx <= BASIS_EPS || heightPx <= BASIS_EPS || !Number.isFinite(leftPx + rightPx + heightPx)) {
1511
+ return null;
1512
+ }
1513
+ const dynamic = textureLighting === "dynamic";
1514
+ const base = parseHex2(entry.polygon.color ?? "#cccccc");
1515
+ const useDefaultDynamicColor = dynamic && rgbKey2(base) === solidPaintDefaults?.dynamicColorKey;
1516
+ const sharedStyle = {
1517
+ color: dynamic || entry.shadedColor === solidPaintDefaults?.paintColor ? void 0 : entry.shadedColor,
1518
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
1519
+ ...dynamic && !useDefaultDynamicColor ? {
1520
+ ["--pnx"]: normal[0].toFixed(4),
1521
+ ["--pny"]: normal[1].toFixed(4),
1522
+ ["--pnz"]: normal[2].toFixed(4),
1523
+ ["--psr"]: (base.r / 255).toFixed(4),
1524
+ ["--psg"]: (base.g / 255).toFixed(4),
1525
+ ["--psb"]: (base.b / 255).toFixed(4)
1526
+ } : dynamic ? {
1527
+ ["--pnx"]: normal[0].toFixed(4),
1528
+ ["--pny"]: normal[1].toFixed(4),
1529
+ ["--pnz"]: normal[2].toFixed(4)
1530
+ } : null
1531
+ };
1532
+ const worldPoint = ([x, y]) => [
1533
+ cv[0] + (x - left) * xAxis[0] + y * yAxis[0],
1534
+ cv[1] + (x - left) * xAxis[1] + y * yAxis[1],
1535
+ cv[2] + (x - left) * xAxis[2] + y * yAxis[2]
1536
+ ];
1537
+ const apex = worldPoint(apex2);
1538
+ const baseLeft = worldPoint([baseLeft2[0], baseY]);
1539
+ const baseRight = worldPoint([baseRight2[0], baseY]);
1540
+ const halfBase = canonicalSize / 2;
1541
+ const xCol = [
1542
+ (baseRight[0] - baseLeft[0]) / canonicalSize,
1543
+ (baseRight[1] - baseLeft[1]) / canonicalSize,
1544
+ (baseRight[2] - baseLeft[2]) / canonicalSize
1545
+ ];
1546
+ const txCol = [
1547
+ apex[0] - xCol[0] * halfBase,
1548
+ apex[1] - xCol[1] * halfBase,
1549
+ apex[2] - xCol[2] * halfBase
1550
+ ];
1551
+ const yCol = [
1552
+ (baseLeft[0] - txCol[0]) / canonicalSize,
1553
+ (baseLeft[1] - txCol[1]) / canonicalSize,
1554
+ (baseLeft[2] - txCol[2]) / canonicalSize
1555
+ ];
1556
+ const canonicalMatrix = [
1557
+ xCol[0],
1558
+ xCol[1],
1559
+ xCol[2],
1560
+ 0,
1561
+ yCol[0],
1562
+ yCol[1],
1563
+ yCol[2],
1564
+ 0,
1565
+ normal[0],
1566
+ normal[1],
1567
+ normal[2],
1568
+ 0,
1569
+ txCol[0],
1570
+ txCol[1],
1571
+ txCol[2],
1572
+ 1
1573
+ ].map((v) => (Math.round(v * 1e3) / 1e3 || 0).toString()).join(",");
1574
+ const primitiveStyle = solidTrianglePaintStyle();
1575
+ return {
1576
+ transform: `matrix3d(${canonicalMatrix})`,
1577
+ ...primitiveStyle ?? {},
1578
+ ...sharedStyle
1579
+ };
1580
+ }
1581
+
1582
+ // src/scene/atlas/stableTriangleDom.ts
1583
+ function isStableTriangleBasis(value) {
1584
+ if (!value) return false;
1585
+ const { a, b, c } = value;
1586
+ return a === 0 && b === 1 && c === 2 || a === 1 && b === 2 && c === 0 || a === 2 && b === 0 && c === 1;
1587
+ }
1588
+ function offsetStableTrianglePoints2(left, right, height, amount) {
1589
+ const baseWidth = left + right;
1590
+ if (amount <= 0 || height <= BASIS_EPS || baseWidth <= BASIS_EPS || !Number.isFinite(left + right + height + amount)) {
1591
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1592
+ }
1593
+ const leftLen = Math.sqrt(left * left + height * height);
1594
+ const rightLen = Math.sqrt(right * right + height * height);
1595
+ if (leftLen <= BASIS_EPS || rightLen <= BASIS_EPS) {
1596
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1597
+ }
1598
+ const leftOffsetX = -amount * height / leftLen;
1599
+ const leftOffsetY = -amount * left / leftLen;
1600
+ const rightOffsetX = amount * height / rightLen;
1601
+ const rightOffsetY = -amount * right / rightLen;
1602
+ const apexLineLeftX = left + leftOffsetX;
1603
+ const apexLineLeftY = leftOffsetY;
1604
+ const apexLineRightX = baseWidth + rightOffsetX;
1605
+ const apexLineRightY = height + rightOffsetY;
1606
+ const det = -height * baseWidth;
1607
+ if (Math.abs(det) <= BASIS_EPS) {
1608
+ return offsetConvexPolygonPoints([left, 0, 0, height, baseWidth, height], amount);
1609
+ }
1610
+ const qx = apexLineLeftX - apexLineRightX;
1611
+ const qy = apexLineLeftY - apexLineRightY;
1612
+ const t = (qx * height + qy * left) / det;
1613
+ let apexX = apexLineRightX - t * right;
1614
+ let apexY = apexLineRightY - t * height;
1615
+ let baseLeftX = -amount * (left + leftLen) / height;
1616
+ let baseLeftY = height + amount;
1617
+ let baseRightX = baseWidth + amount * (right + rightLen) / height;
1618
+ let baseRightY = baseLeftY;
1619
+ const maxMiter = Math.max(2, amount * 4);
1620
+ const apexDx = apexX - left;
1621
+ const apexDy = apexY;
1622
+ const apexMiter = Math.sqrt(apexDx * apexDx + apexDy * apexDy);
1623
+ if (apexMiter > maxMiter) {
1624
+ apexX = left + apexDx / apexMiter * maxMiter;
1625
+ apexY = apexDy / apexMiter * maxMiter;
1626
+ }
1627
+ const leftMiter = Math.sqrt(baseLeftX * baseLeftX + amount * amount);
1628
+ if (leftMiter > maxMiter) {
1629
+ baseLeftX = baseLeftX / leftMiter * maxMiter;
1630
+ baseLeftY = height + amount / leftMiter * maxMiter;
1631
+ }
1632
+ const rightDx = baseRightX - baseWidth;
1633
+ const rightMiter = Math.sqrt(rightDx * rightDx + amount * amount);
1634
+ if (rightMiter > maxMiter) {
1635
+ baseRightX = baseWidth + rightDx / rightMiter * maxMiter;
1636
+ baseRightY = height + amount / rightMiter * maxMiter;
1637
+ }
1638
+ return [apexX, apexY, baseLeftX, baseLeftY, baseRightX, baseRightY];
1639
+ }
1640
+ function computeStableTriangleDomStyle(polygon, options, basisHint) {
1641
+ if (polygon.texture || polygon.vertices.length !== 3) return null;
1642
+ const tile = DEFAULT_TILE;
1643
+ const elev = tile;
1644
+ const v0 = polygon.vertices[0];
1645
+ const v1 = polygon.vertices[1];
1646
+ const v2 = polygon.vertices[2];
1647
+ const p0x = v0[1] * tile, p0y = v0[0] * tile, p0z = v0[2] * elev;
1648
+ const p1x = v1[1] * tile, p1y = v1[0] * tile, p1z = v1[2] * elev;
1649
+ const p2x = v2[1] * tile, p2y = v2[0] * tile, p2z = v2[2] * elev;
1650
+ const e10x = p1x - p0x, e10y = p1y - p0y, e10z = p1z - p0z;
1651
+ const e20x = p2x - p0x, e20y = p2y - p0y, e20z = p2z - p0z;
1652
+ let nx = -(e10y * e20z - e10z * e20y);
1653
+ let ny = -(e10z * e20x - e10x * e20z);
1654
+ let nz = -(e10x * e20y - e10y * e20x);
1655
+ const nLen = Math.sqrt(nx * nx + ny * ny + nz * nz);
1656
+ if (nLen <= BASIS_EPS) return null;
1657
+ nx /= nLen;
1658
+ ny /= nLen;
1659
+ nz /= nLen;
1660
+ const len01Sq = e10x * e10x + e10y * e10y + e10z * e10z;
1661
+ const e21x = p2x - p1x, e21y = p2y - p1y, e21z = p2z - p1z;
1662
+ const e02x = p0x - p2x, e02y = p0y - p2y, e02z = p0z - p2z;
1663
+ const len12Sq = e21x * e21x + e21y * e21y + e21z * e21z;
1664
+ const len20Sq = e02x * e02x + e02y * e02y + e02z * e02z;
1665
+ let a = isStableTriangleBasis(basisHint) ? basisHint.a : 0;
1666
+ let b = isStableTriangleBasis(basisHint) ? basisHint.b : 1;
1667
+ let c = isStableTriangleBasis(basisHint) ? basisHint.c : 2;
1668
+ const retryWithoutBasis = () => basisHint ? computeStableTriangleDomStyle(polygon, options) : null;
1669
+ if (!isStableTriangleBasis(basisHint)) {
1670
+ let baseLengthSq = len01Sq;
1671
+ if (len12Sq > baseLengthSq) {
1672
+ a = 1;
1673
+ b = 2;
1674
+ c = 0;
1675
+ baseLengthSq = len12Sq;
1676
+ }
1677
+ if (len20Sq > baseLengthSq) {
1678
+ a = 2;
1679
+ b = 0;
1680
+ c = 1;
1681
+ }
1682
+ }
1683
+ const cvx = c === 0 ? p0x : c === 1 ? p1x : p2x;
1684
+ const cvy = c === 0 ? p0y : c === 1 ? p1y : p2y;
1685
+ const cvz = c === 0 ? p0z : c === 1 ? p1z : p2z;
1686
+ const avx = a === 0 ? p0x : a === 1 ? p1x : p2x;
1687
+ const avy = a === 0 ? p0y : a === 1 ? p1y : p2y;
1688
+ const avz = a === 0 ? p0z : a === 1 ? p1z : p2z;
1689
+ const bvx = b === 0 ? p0x : b === 1 ? p1x : p2x;
1690
+ const bvy = b === 0 ? p0y : b === 1 ? p1y : p2y;
1691
+ const bvz = b === 0 ? p0z : b === 1 ? p1z : p2z;
1692
+ const baseDx = bvx - avx, baseDy = bvy - avy, baseDz = bvz - avz;
1693
+ const baseLength = Math.sqrt(baseDx * baseDx + baseDy * baseDy + baseDz * baseDz);
1694
+ if (baseLength <= BASIS_EPS) return retryWithoutBasis();
1695
+ const x0 = baseDx / baseLength, x1 = baseDy / baseLength, x2 = baseDz / baseLength;
1696
+ const apexXproj = (cvx - avx) * x0 + (cvy - avy) * x1 + (cvz - avz) * x2;
1697
+ let y0 = avx + x0 * apexXproj - cvx;
1698
+ let y1 = avy + x1 * apexXproj - cvy;
1699
+ let y2 = avz + x2 * apexXproj - cvz;
1700
+ const height = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
1701
+ if (height <= BASIS_EPS) return retryWithoutBasis();
1702
+ y0 /= height;
1703
+ y1 /= height;
1704
+ y2 /= height;
1705
+ const leftExtent = Math.max(0, Math.min(baseLength, apexXproj));
1706
+ const rightExtent = Math.max(0, baseLength - leftExtent);
1707
+ const expanded = offsetStableTrianglePoints2(leftExtent, rightExtent, height, SOLID_TRIANGLE_BLEED);
1708
+ const apex2x = expanded[0], apex2y = expanded[1];
1709
+ const baseLeft2x = expanded[2], baseLeft2y = expanded[3];
1710
+ const baseRight2x = expanded[4], baseRight2y = expanded[5];
1711
+ const baseY = (baseLeft2y + baseRight2y) / 2;
1712
+ const leftPx = apex2x - baseLeft2x;
1713
+ const rightPx = baseRight2x - apex2x;
1714
+ const heightPx = baseY - apex2y;
1715
+ if (leftPx <= BASIS_EPS || rightPx <= BASIS_EPS || heightPx <= BASIS_EPS || !Number.isFinite(leftPx + rightPx + heightPx)) {
1716
+ return retryWithoutBasis();
1717
+ }
1718
+ const canonicalSize = solidTriangleCanonicalSize();
1719
+ const invCanonicalSize = 1 / canonicalSize;
1720
+ const baseWidthPx = leftPx + rightPx;
1721
+ const xScale = baseWidthPx * invCanonicalSize;
1722
+ const yXScale = (rightPx - leftPx) * 0.5 * invCanonicalSize;
1723
+ const yYScale = heightPx * invCanonicalSize;
1724
+ const txXOffset = apex2x - leftExtent - baseWidthPx * 0.5;
1725
+ const txYOffset = apex2y;
1726
+ const transform = formatStableTriangleTransformScalars(
1727
+ x0 * xScale,
1728
+ x1 * xScale,
1729
+ x2 * xScale,
1730
+ x0 * yXScale + y0 * yYScale,
1731
+ x1 * yXScale + y1 * yYScale,
1732
+ x2 * yXScale + y2 * yYScale,
1733
+ nx,
1734
+ ny,
1735
+ nz,
1736
+ cvx + x0 * txXOffset + y0 * txYOffset,
1737
+ cvy + x1 * txXOffset + y1 * txYOffset,
1738
+ cvz + x2 * txXOffset + y2 * txYOffset
1739
+ );
1740
+ let color;
1741
+ if (Math.floor(options.colorFreezeFrames ?? 1) !== 0) {
1742
+ const directionalCfg = options.directionalLight;
1743
+ const ambientCfg = options.ambientLight;
1744
+ const lightDir = directionalCfg?.direction ?? DEFAULT_LIGHT_DIR;
1745
+ const lightColor = directionalCfg?.color ?? DEFAULT_LIGHT_COLOR;
1746
+ const lightIntensity = Math.max(0, directionalCfg?.intensity ?? DEFAULT_LIGHT_INTENSITY);
1747
+ const ambientColor = ambientCfg?.color ?? DEFAULT_AMBIENT_COLOR;
1748
+ const ambientIntensity = Math.max(0, ambientCfg?.intensity ?? DEFAULT_AMBIENT_INTENSITY);
1749
+ const lLen = Math.sqrt(
1750
+ lightDir[0] * lightDir[0] + lightDir[1] * lightDir[1] + lightDir[2] * lightDir[2]
1751
+ ) || 1;
1752
+ const lx = lightDir[0] / lLen, ly = lightDir[1] / lLen, lz = lightDir[2] / lLen;
1753
+ const directScale = lightIntensity * Math.max(0, nx * lx + ny * ly + nz * lz);
1754
+ const shadedColor = shadePolygon(
1755
+ polygon.color ?? "#cccccc",
1756
+ directScale,
1757
+ lightColor,
1758
+ ambientColor,
1759
+ ambientIntensity
1760
+ );
1761
+ color = options.colorSteps ? quantizeCssColor(shadedColor, options.colorSteps) : shadedColor;
1762
+ }
1763
+ return { transform, borderWidth: solidTriangleBorderWidth(), color, basis: { a, b, c } };
1764
+ }
1765
+ function stableTriangleColorAllowed(index, colorFrame, freezeFrames) {
1766
+ return freezeFrames > 0 && (freezeFrames <= 1 || (colorFrame + index) % freezeFrames === 0);
1767
+ }
1768
+ function applyStableTriangleColor(el, index, nextColor, options) {
1769
+ const freezeFrames = Math.floor(options.colorFreezeFrames ?? 1);
1770
+ if (freezeFrames === 0) return;
1771
+ const currentColor = el.__polycssStableTriangleColor;
1772
+ const shouldWrite = currentColor === void 0 || stableTriangleColorAllowed(
1773
+ index,
1774
+ Math.max(0, Math.floor(options.colorFrame ?? 0)),
1775
+ Math.max(1, freezeFrames)
1776
+ );
1777
+ if (!shouldWrite || currentColor === nextColor) return;
1778
+ let writeColor = nextColor;
1779
+ let writeRgb = nextColor ? parseHex2(nextColor) : void 0;
1780
+ const currentRgb = el.__polycssStableTriangleColorRgb;
1781
+ const maxStep = Math.max(0, Math.floor(options.colorMaxStep ?? 0));
1782
+ if (maxStep > 0 && currentRgb && writeRgb && nextColor) {
1783
+ writeRgb = stepRgbToward(currentRgb, writeRgb, maxStep);
1784
+ writeColor = rgbToHex(writeRgb);
1785
+ }
1786
+ el.style.color = writeColor;
1787
+ el.__polycssStableTriangleColor = writeColor;
1788
+ el.__polycssStableTriangleColorRgb = writeRgb;
1789
+ }
1790
+ function updateStableTriangleDom(root, polygons, options = {}) {
1791
+ if ((options.textureLighting ?? "baked") !== "baked") return false;
1792
+ if (!isSolidTriangleSupported()) return false;
1793
+ const leaves = Array.from(root.children).filter(
1794
+ (child) => child instanceof HTMLElement && child.localName === "u"
1795
+ );
1796
+ if (leaves.length !== polygons.length) return false;
1797
+ const styles = polygons.map(
1798
+ (polygon, index) => computeStableTriangleDomStyle(polygon, options, leaves[index].__polycssStableTriangleBasis)
1799
+ );
1800
+ if (styles.some((style) => !style)) return false;
1801
+ for (let i = 0; i < leaves.length; i += 1) {
1802
+ const style = styles[i];
1803
+ const el = leaves[i];
1804
+ if (el.style.visibility) el.style.visibility = "";
1805
+ el.__polycssStableTriangleBasis = style.basis;
1806
+ applySolidTrianglePaintStyle(el);
1807
+ el.style.transform = style.transform;
1808
+ if (style.borderWidth !== void 0) el.style.borderWidth = style.borderWidth;
1809
+ if (style.color !== void 0) applyStableTriangleColor(el, i, style.color, options);
1810
+ }
1811
+ return true;
1812
+ }
1813
+
1814
+ // src/scene/atlas/paintDefaults.ts
1815
+ import { computeTextureAtlasPlanPublic } from "@layoutit/polycss-core";
1816
+ function computeTextureAtlasPlan(polygon, index, options = {}, basisHint) {
1817
+ return computeTextureAtlasPlanPublic(polygon, index, options, void 0, basisHint);
1818
+ }
1819
+ function getSolidPaintDefaults(plans, textureLighting, strategies) {
1820
+ const disabled = new Set(strategies?.disable ?? []);
1821
+ return getSolidPaintDefaultsFromPlans(plans, textureLighting, disabled);
1822
+ }
1823
+
1824
+ // src/scene/atlas/useTextureAtlas.ts
1825
+ import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef6, useState as useState4 } from "react";
1826
+ function pageShells(pages) {
1827
+ return pages.map((page) => ({ width: page.width, height: page.height, url: null }));
1828
+ }
1829
+ function blobUrlsOf(pages) {
1830
+ return pages.flatMap((page) => page.url?.startsWith("blob:") ? [page.url] : []);
1831
+ }
1832
+ function decodeBlobUrls(urls) {
1833
+ if (urls.length === 0 || typeof Image === "undefined") return Promise.resolve();
1834
+ return Promise.all(urls.map((url) => {
1835
+ const img = new Image();
1836
+ img.src = url;
1837
+ const decoded = img.decode?.();
1838
+ return decoded ? decoded.catch(() => {
1839
+ }) : Promise.resolve();
1840
+ })).then(() => void 0);
1841
+ }
1842
+ function deferRevoke(urls) {
1843
+ if (urls.length === 0) return;
1844
+ const run = () => {
1845
+ for (const url of urls) URL.revokeObjectURL(url);
1846
+ };
1847
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
1848
+ else setTimeout(run, 0);
1849
+ }
1850
+ function useTextureAtlas(plans, textureLighting, textureQualityInput, textureLeafSizing, textureBackend, textureImageRendering, textureProjection, strategies, atomic = false) {
1851
+ const disabled = useMemo4(
1852
+ () => new Set(strategies?.disable ?? []),
1853
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1854
+ [strategies?.disable?.join(",")]
1855
+ );
1856
+ const atlasPlans = useMemo4(
1857
+ () => filterAtlasPlans(
1858
+ plans,
1859
+ textureLighting,
1860
+ disabled,
1861
+ typeof document !== "undefined" ? document : null,
1862
+ textureBackend,
1863
+ textureImageRendering,
1864
+ textureProjection
1865
+ ),
1866
+ [plans, textureLighting, disabled, textureBackend, textureImageRendering, textureProjection]
1867
+ );
1868
+ const { packed, atlasScale } = useMemo4(
1869
+ () => packTextureAtlasPlansWithScale(
1870
+ atlasPlans,
1871
+ textureQualityInput,
1872
+ typeof document !== "undefined" ? document : null,
1873
+ textureLeafSizing
1874
+ ),
1875
+ [atlasPlans, textureQualityInput, textureLeafSizing]
1876
+ );
1877
+ const [pages, setPages] = useState4(() => pageShells(packed.pages));
1878
+ const [frame, setFrame] = useState4(() => ({
1879
+ plans,
1880
+ entries: packed.entries,
1881
+ pages: pageShells(packed.pages)
1882
+ }));
1883
+ const shownUrls = useRef6([]);
1884
+ const seqRef = useRef6(0);
1885
+ const mountedRef = useRef6(true);
1886
+ useEffect5(() => {
1887
+ mountedRef.current = true;
1888
+ return () => {
1889
+ mountedRef.current = false;
1890
+ for (const url of shownUrls.current) URL.revokeObjectURL(url);
1891
+ shownUrls.current = [];
1892
+ };
1893
+ }, []);
1894
+ useEffect5(() => {
1895
+ if (atomic) {
1896
+ const seq = ++seqRef.current;
1897
+ const snapPlans = plans;
1898
+ const snapEntries = packed.entries;
1899
+ if (packed.pages.length === 0 || typeof document === "undefined") {
1900
+ deferRevoke(shownUrls.current);
1901
+ shownUrls.current = [];
1902
+ setFrame({ plans: snapPlans, entries: snapEntries, pages: pageShells(packed.pages) });
1903
+ return;
1904
+ }
1905
+ const stale = () => seq !== seqRef.current;
1906
+ let built2 = [];
1907
+ buildAtlasPages(packed.pages, textureLighting, document, atlasScale, stale).then(async (nextPages) => {
1908
+ built2 = blobUrlsOf(nextPages);
1909
+ await decodeBlobUrls(built2);
1910
+ if (!mountedRef.current || stale()) {
1911
+ deferRevoke(built2);
1912
+ return;
1913
+ }
1914
+ const prev = shownUrls.current;
1915
+ shownUrls.current = built2;
1916
+ built2 = [];
1917
+ deferRevoke(prev);
1918
+ setFrame({ plans: snapPlans, entries: snapEntries, pages: nextPages });
1919
+ }).catch(() => {
1920
+ });
1921
+ return;
1922
+ }
1923
+ let cancelled = false;
1924
+ if (packed.pages.length === 0) {
1925
+ deferRevoke(shownUrls.current);
1926
+ shownUrls.current = [];
1927
+ setPages((prev) => prev.length === 0 ? prev : []);
1928
+ return () => {
1929
+ };
1930
+ }
1931
+ if (typeof document === "undefined") return () => {
1932
+ };
1933
+ setPages((prev) => prev.some((page) => page.url) ? prev : pageShells(packed.pages));
1934
+ let built = [];
1935
+ buildAtlasPages(packed.pages, textureLighting, document, atlasScale, () => cancelled).then(async (nextPages) => {
1936
+ built = blobUrlsOf(nextPages);
1937
+ await decodeBlobUrls(built);
1938
+ if (cancelled) {
1939
+ deferRevoke(built);
1940
+ return;
1941
+ }
1942
+ const stale = shownUrls.current;
1943
+ shownUrls.current = built;
1944
+ built = [];
1945
+ deferRevoke(stale);
1946
+ setPages(nextPages);
1947
+ }).catch(() => {
1948
+ });
1949
+ return () => {
1950
+ cancelled = true;
1951
+ deferRevoke(built);
1952
+ };
1953
+ }, [packed, textureLighting, atlasScale, atomic]);
1954
+ if (atomic) {
1955
+ return {
1956
+ plans: frame.plans,
1957
+ entries: frame.entries,
1958
+ pages: frame.pages,
1959
+ ready: frame.pages.length === 0 || frame.pages.every((page) => !!page.url)
1960
+ };
1961
+ }
1962
+ return {
1963
+ plans,
1964
+ entries: packed.entries,
1965
+ pages,
1966
+ ready: pages.length === 0 || pages.every((page) => !!page.url)
1967
+ };
1968
+ }
1969
+
1970
+ // src/scene/atlas/triangle.tsx
1971
+ import { memo as memo3 } from "react";
1972
+ import { jsx as jsx3 } from "react/jsx-runtime";
1973
+ var TextureTrianglePoly = memo3(function TextureTrianglePoly2({
1974
+ entry,
1975
+ textureLighting,
1976
+ solidPaintDefaults,
1977
+ className,
1978
+ style: styleProp,
1979
+ domAttrs,
1980
+ domEventHandlers,
1981
+ pointerEvents = "auto"
1982
+ }) {
1983
+ const triangleStyle = solidTriangleStyle(entry, textureLighting, pointerEvents, solidPaintDefaults);
1984
+ if (!triangleStyle) return null;
1985
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
1986
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
1987
+ ) : {};
1988
+ const elementClassName = className?.trim() || void 0;
1989
+ return /* @__PURE__ */ jsx3(
1990
+ "u",
1991
+ {
1992
+ className: elementClassName,
1993
+ style: { ...triangleStyle, ...styleProp },
1994
+ "data-poly-index": entry.index,
1995
+ ...domEventHandlers,
1996
+ ...dataAttrs,
1997
+ ...domAttrs
1998
+ }
1999
+ );
2000
+ });
2001
+
2002
+ // src/scene/atlas/borderShape.tsx
2003
+ import { memo as memo4, useCallback as useCallback4 } from "react";
2004
+ import {
2005
+ isFullRectSolid,
2006
+ cssBorderShapeForPlan,
2007
+ formatSolidQuadEntryMatrix,
2008
+ formatBorderShapeEntryMatrix
2009
+ } from "@layoutit/polycss-core";
2010
+ import { jsx as jsx4 } from "react/jsx-runtime";
2011
+ var BRUSH_INLINE_STYLE_ORDER = /* @__PURE__ */ new Map([
2012
+ ["transform", 0],
2013
+ ["border-shape", 1],
2014
+ ["border-width", 2],
2015
+ ["width", 3],
2016
+ ["height", 4],
2017
+ ["color", 5]
2018
+ ]);
2019
+ function orderBrushInlineStyle(el) {
2020
+ const current = el.getAttribute("style");
2021
+ if (!current) return;
2022
+ const declarations = current.split(";").map((d) => d.trim()).filter(Boolean);
2023
+ const next = declarations.map((declaration, index) => {
2024
+ const property = declaration.slice(0, declaration.indexOf(":")).trim().toLowerCase();
2025
+ return { declaration, index, order: BRUSH_INLINE_STYLE_ORDER.get(property) ?? Number.POSITIVE_INFINITY };
2026
+ }).sort((a, b) => a.order - b.order || a.index - b.index).map(({ declaration }) => declaration).join(";");
2027
+ if (next !== current) el.setAttribute("style", next);
2028
+ }
2029
+ var TextureBorderShapePoly = memo4(function TextureBorderShapePoly2({
2030
+ entry,
2031
+ textureLighting,
2032
+ solidPaintDefaults,
2033
+ className,
2034
+ style: styleProp,
2035
+ domAttrs,
2036
+ domEventHandlers,
2037
+ pointerEvents = "auto",
2038
+ disabledStrategies
2039
+ }) {
2040
+ const fullRect = !entry.texture && isFullRectSolid(entry);
2041
+ const bDisabled = disabledStrategies?.has("b") ?? false;
2042
+ const useIForFullRect = bDisabled && isBorderShapeSupported();
2043
+ const borderShape = !fullRect || useIForFullRect ? cssBorderShapeForPlan(entry) : null;
2044
+ const dynamic = textureLighting === "dynamic";
2045
+ const base = parseHex2(entry.polygon.color ?? "#cccccc");
2046
+ const useDefaultDynamicColor = dynamic && rgbKey2(base) === solidPaintDefaults?.dynamicColorKey;
2047
+ const setElementRef = useCallback4((el) => {
2048
+ if (!el) return;
2049
+ if (borderShape) el.style.setProperty("border-shape", borderShape);
2050
+ else el.style.removeProperty("border-shape");
2051
+ orderBrushInlineStyle(el);
2052
+ }, [borderShape]);
2053
+ const transform = borderShape ? formatBorderShapeEntryMatrix(entry) : formatSolidQuadEntryMatrix(entry);
2054
+ const style = {
2055
+ transform,
2056
+ // Baked mode: always emit per-leaf shaded color when known so the
2057
+ // first render matches the post-light-nudge commit path. Vanilla
2058
+ // dropped the `=== solidPaintDefaults.paintColor` shortcut in
2059
+ // commit 0423777 — leaves with undefined shadedColor would inherit
2060
+ // the (often-wrong) wrapper --polycss-paint default, producing
2061
+ // facets that look dimmer than they should until a light change
2062
+ // forced an explicit re-bake.
2063
+ color: dynamic ? void 0 : entry.shadedColor,
2064
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
2065
+ ...dynamic ? {
2066
+ ["--pnx"]: entry.normal[0].toFixed(4),
2067
+ ["--pny"]: entry.normal[1].toFixed(4),
2068
+ ["--pnz"]: entry.normal[2].toFixed(4),
2069
+ ...useDefaultDynamicColor ? null : {
2070
+ ["--psr"]: (base.r / 255).toFixed(4),
2071
+ ["--psg"]: (base.g / 255).toFixed(4),
2072
+ ["--psb"]: (base.b / 255).toFixed(4)
2073
+ }
2074
+ } : null,
2075
+ ...styleProp
2076
+ };
2077
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
2078
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2079
+ ) : {};
2080
+ const elementClassName = className?.trim() || void 0;
2081
+ if (fullRect && !useIForFullRect) {
2082
+ return /* @__PURE__ */ jsx4(
2083
+ "b",
2084
+ {
2085
+ className: elementClassName,
2086
+ style,
2087
+ "data-poly-index": entry.index,
2088
+ ...domEventHandlers,
2089
+ ...dataAttrs,
2090
+ ...domAttrs
2091
+ }
2092
+ );
2093
+ }
2094
+ return /* @__PURE__ */ jsx4(
2095
+ "i",
2096
+ {
2097
+ ref: setElementRef,
2098
+ className: elementClassName,
2099
+ style,
2100
+ "data-poly-index": entry.index,
2101
+ ...domEventHandlers,
2102
+ ...dataAttrs,
2103
+ ...domAttrs
2104
+ }
2105
+ );
2106
+ });
2107
+
2108
+ // src/scene/atlas/projectiveSolid.tsx
2109
+ import { memo as memo5 } from "react";
2110
+ import { jsx as jsx5 } from "react/jsx-runtime";
2111
+ var TextureProjectiveSolidPoly = memo5(function TextureProjectiveSolidPoly2({
2112
+ entry,
2113
+ textureLighting,
2114
+ solidPaintDefaults,
2115
+ className,
2116
+ style: styleProp,
2117
+ domAttrs,
2118
+ domEventHandlers,
2119
+ pointerEvents = "auto"
2120
+ }) {
2121
+ const dynamic = textureLighting === "dynamic";
2122
+ const base = parseHex2(entry.polygon.color ?? "#cccccc");
2123
+ const useDefaultDynamicColor = dynamic && rgbKey2(base) === solidPaintDefaults?.dynamicColorKey;
2124
+ const style = {
2125
+ // Emit projectiveMatrix verbatim — it's already formatted with 6-decimal
2126
+ // precision by computeTextureAtlasPlan. Re-rounding via formatMatrix3d
2127
+ // would drop it to 3 decimals and leave visible seam gaps between
2128
+ // adjacent projective quads at zoom-out (matches vanilla scene.add).
2129
+ transform: `matrix3d(${entry.projectiveMatrix})`,
2130
+ // Baked: always emit per-leaf shaded color (vanilla commit 0423777).
2131
+ color: dynamic ? void 0 : entry.shadedColor,
2132
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
2133
+ ...dynamic && !useDefaultDynamicColor ? {
2134
+ ["--pnx"]: entry.normal[0].toFixed(4),
2135
+ ["--pny"]: entry.normal[1].toFixed(4),
2136
+ ["--pnz"]: entry.normal[2].toFixed(4),
2137
+ ["--psr"]: (base.r / 255).toFixed(4),
2138
+ ["--psg"]: (base.g / 255).toFixed(4),
2139
+ ["--psb"]: (base.b / 255).toFixed(4)
2140
+ } : dynamic ? {
2141
+ ["--pnx"]: entry.normal[0].toFixed(4),
2142
+ ["--pny"]: entry.normal[1].toFixed(4),
2143
+ ["--pnz"]: entry.normal[2].toFixed(4)
2144
+ } : null,
2145
+ ...styleProp
2146
+ };
2147
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
2148
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2149
+ ) : {};
2150
+ const elementClassName = className?.trim() || void 0;
2151
+ return /* @__PURE__ */ jsx5(
2152
+ "b",
2153
+ {
2154
+ className: elementClassName,
2155
+ style,
2156
+ "data-poly-index": entry.index,
2157
+ ...domEventHandlers,
2158
+ ...dataAttrs,
2159
+ ...domAttrs
2160
+ }
2161
+ );
2162
+ });
2163
+
2164
+ // src/scene/atlas/cornerShapeSolid.tsx
2165
+ import { memo as memo6, useCallback as useCallback5 } from "react";
2166
+ import {
2167
+ formatCornerShapeElementStyle,
2168
+ parseHex as parseHex3,
2169
+ rgbKey as rgbKey4
2170
+ } from "@layoutit/polycss-core";
2171
+ import { jsx as jsx6 } from "react/jsx-runtime";
2172
+ function formatPaintCss(entry, textureLighting, solidPaintDefaults) {
2173
+ if (textureLighting === "dynamic") {
2174
+ const base = parseHex3(entry.polygon.color ?? "#cccccc");
2175
+ let style = `;--pnx:${entry.normal[0].toFixed(4)};--pny:${entry.normal[1].toFixed(4)};--pnz:${entry.normal[2].toFixed(4)}`;
2176
+ if (rgbKey4(base) !== solidPaintDefaults?.dynamicColorKey) {
2177
+ style += `;--psr:${(base.r / 255).toFixed(4)};--psg:${(base.g / 255).toFixed(4)};--psb:${(base.b / 255).toFixed(4)}`;
2178
+ }
2179
+ return style;
2180
+ }
2181
+ return entry.shadedColor ? `;color:${entry.shadedColor}` : "";
2182
+ }
2183
+ var TextureCornerShapeSolidPoly = memo6(function TextureCornerShapeSolidPoly2({
2184
+ entry,
2185
+ geometry,
2186
+ textureLighting,
2187
+ solidPaintDefaults,
2188
+ className,
2189
+ style: styleProp,
2190
+ domAttrs,
2191
+ domEventHandlers,
2192
+ pointerEvents = "auto"
2193
+ }) {
2194
+ const cornerShapeCss = formatCornerShapeElementStyle(entry, geometry);
2195
+ const paintCss = formatPaintCss(entry, textureLighting, solidPaintDefaults);
2196
+ const pointerCss = pointerEvents === "none" ? ";pointer-events:none" : "";
2197
+ const fullCss = cornerShapeCss + paintCss + pointerCss;
2198
+ const setRef = useCallback5((el) => {
2199
+ if (!el) return;
2200
+ el.setAttribute("style", fullCss);
2201
+ if (styleProp) {
2202
+ for (const [k, v] of Object.entries(styleProp)) {
2203
+ if (v === void 0 || v === null) continue;
2204
+ if (k.startsWith("--")) el.style.setProperty(k, String(v));
2205
+ else el.style[k] = String(v);
2206
+ }
2207
+ }
2208
+ }, [fullCss, styleProp]);
2209
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
2210
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2211
+ ) : {};
2212
+ const elementClassName = className?.trim() || void 0;
2213
+ return /* @__PURE__ */ jsx6(
2214
+ "u",
2215
+ {
2216
+ ref: setRef,
2217
+ className: elementClassName,
2218
+ "data-poly-index": entry.index,
2219
+ ...domEventHandlers,
2220
+ ...dataAttrs,
2221
+ ...domAttrs
2222
+ }
2223
+ );
2224
+ });
2225
+
2226
+ // src/scene/atlas/atlasPoly.tsx
2227
+ import { memo as memo7 } from "react";
2228
+ import { formatMatrix3d, formatCssLengthPx, resolvePolyTextureImageRendering } from "@layoutit/polycss-core";
2229
+ import { jsx as jsx7 } from "react/jsx-runtime";
2230
+ var TextureAtlasPoly = memo7(function TextureAtlasPoly2({
2231
+ entry,
2232
+ page,
2233
+ textureLighting,
2234
+ textureImageRendering,
2235
+ solidPaintDefaults: _solidPaintDefaults,
2236
+ className,
2237
+ style: styleProp,
2238
+ domAttrs,
2239
+ domEventHandlers,
2240
+ pointerEvents = "auto"
2241
+ }) {
2242
+ const ATLAS_CANONICAL_SIZE_EXPLICIT = 64;
2243
+ const dynamic = textureLighting === "dynamic";
2244
+ const resolvedImageRendering = resolvePolyTextureImageRendering(entry.polygon, textureImageRendering);
2245
+ const atlasCanonicalSize = entry.atlasCanonicalSize ?? ATLAS_CANONICAL_SIZE_EXPLICIT;
2246
+ const atlasLeafWidth = entry.atlasLeafWidth ?? atlasCanonicalSize;
2247
+ const atlasLeafHeight = entry.atlasLeafHeight ?? atlasCanonicalSize;
2248
+ const atlasWidth = entry.canvasW || 1;
2249
+ const atlasHeight = entry.canvasH || 1;
2250
+ const atlasPosition = page ? `${formatCssLengthPx(-entry.x / atlasWidth * atlasLeafWidth)} ${formatCssLengthPx(-entry.y / atlasHeight * atlasLeafHeight)}` : void 0;
2251
+ const atlasSize = page ? `${formatCssLengthPx(page.width / atlasWidth * atlasLeafWidth)} ${formatCssLengthPx(page.height / atlasHeight * atlasLeafHeight)}` : void 0;
2252
+ const dynamicMask = dynamic && page?.url ? `url(${page.url})` : void 0;
2253
+ const style = {
2254
+ transform: formatMatrix3d(entry.atlasMatrix),
2255
+ ["--polycss-atlas-size"]: `${atlasCanonicalSize}px`,
2256
+ ["--polycss-atlas-width"]: formatCssLengthPx(atlasLeafWidth),
2257
+ ["--polycss-atlas-height"]: formatCssLengthPx(atlasLeafHeight),
2258
+ ["--polycss-atlas-leaf-sizing"]: entry.atlasLeafSizing ?? "canonical",
2259
+ backgroundImage: page?.url ? `url(${page.url})` : void 0,
2260
+ backgroundPosition: atlasPosition,
2261
+ backgroundSize: atlasSize,
2262
+ backgroundRepeat: page?.url ? "no-repeat" : void 0,
2263
+ imageRendering: resolvedImageRendering === "pixelated" ? "pixelated" : void 0,
2264
+ ...dynamic ? {
2265
+ ["--pnx"]: entry.normal[0].toFixed(4),
2266
+ ["--pny"]: entry.normal[1].toFixed(4),
2267
+ ["--pnz"]: entry.normal[2].toFixed(4)
2268
+ } : null,
2269
+ ...dynamic && dynamicMask ? {
2270
+ maskImage: dynamicMask,
2271
+ maskMode: "alpha",
2272
+ maskPosition: atlasPosition,
2273
+ maskSize: atlasSize,
2274
+ maskRepeat: "no-repeat",
2275
+ WebkitMaskImage: dynamicMask,
2276
+ WebkitMaskPosition: atlasPosition,
2277
+ WebkitMaskSize: atlasSize,
2278
+ WebkitMaskRepeat: "no-repeat"
2279
+ } : null,
2280
+ opacity: page?.url ? void 0 : 0,
2281
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
2282
+ ...styleProp
2283
+ };
2284
+ const dataAttrs = entry.polygon.data ? Object.fromEntries(
2285
+ Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2286
+ ) : {};
2287
+ const elementClassName = className?.trim() || void 0;
2288
+ return /* @__PURE__ */ jsx7(
2289
+ "s",
2290
+ {
2291
+ className: elementClassName,
2292
+ style,
2293
+ "data-poly-index": entry.index,
2294
+ "data-polycss-leaf": "polygon",
2295
+ "data-polycss-texture-backend": "atlas",
2296
+ "data-polycss-texture-leaf-sizing": entry.atlasLeafSizing ?? "canonical",
2297
+ "data-polycss-texture-ready": page?.url ? "true" : "false",
2298
+ "data-polycss-texture-image-rendering": resolvedImageRendering,
2299
+ "data-polycss-texture-projection": "affine",
2300
+ "data-polycss-texture-lighting": textureLighting,
2301
+ "data-polycss-texture-leaf-width": atlasLeafWidth,
2302
+ "data-polycss-texture-leaf-height": atlasLeafHeight,
2303
+ "data-polycss-double-sided": entry.polygon.doubleSided ? "true" : void 0,
2304
+ ...domEventHandlers,
2305
+ ...dataAttrs,
2306
+ ...domAttrs
2307
+ }
2308
+ );
2309
+ });
2310
+ var TextureImagePoly = memo7(function TextureImagePoly2({
2311
+ plan,
2312
+ geometry,
2313
+ className,
2314
+ style: styleProp,
2315
+ domAttrs,
2316
+ domEventHandlers,
2317
+ pointerEvents = "auto"
2318
+ }) {
2319
+ const style = {
2320
+ transform: formatMatrix3d(geometry.matrix),
2321
+ ["--polycss-atlas-width"]: formatCssLengthPx(geometry.leafWidth),
2322
+ ["--polycss-atlas-height"]: formatCssLengthPx(geometry.leafHeight),
2323
+ ["--polycss-atlas-leaf-sizing"]: "image",
2324
+ backgroundImage: `url(${geometry.url})`,
2325
+ backgroundPosition: `${formatCssLengthPx(geometry.backgroundPosition[0])} ${formatCssLengthPx(geometry.backgroundPosition[1])}`,
2326
+ backgroundSize: `${formatCssLengthPx(geometry.backgroundSize[0])} ${formatCssLengthPx(geometry.backgroundSize[1])}`,
2327
+ backgroundRepeat: "no-repeat",
2328
+ backgroundBlendMode: "normal",
2329
+ maskImage: "none",
2330
+ WebkitMaskImage: "none",
2331
+ imageRendering: geometry.imageRendering === "pixelated" ? "pixelated" : void 0,
2332
+ ["--pnx"]: plan.normal[0].toFixed(4),
2333
+ ["--pny"]: plan.normal[1].toFixed(4),
2334
+ ["--pnz"]: plan.normal[2].toFixed(4),
2335
+ pointerEvents: pointerEvents === "none" ? "none" : void 0,
2336
+ ...styleProp
2337
+ };
2338
+ const dataAttrs = plan.polygon.data ? Object.fromEntries(
2339
+ Object.entries(plan.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
2340
+ ) : {};
2341
+ const elementClassName = className?.trim() || void 0;
2342
+ return /* @__PURE__ */ jsx7(
2343
+ "s",
2344
+ {
2345
+ className: elementClassName,
2346
+ style,
2347
+ "data-poly-index": plan.index,
2348
+ "data-polycss-leaf": "polygon",
2349
+ "data-polycss-texture-backend": "image",
2350
+ "data-polycss-texture-leaf-sizing": "image",
2351
+ "data-polycss-texture-ready": "true",
2352
+ "data-polycss-texture-image-rendering": geometry.imageRendering,
2353
+ "data-polycss-texture-projection": geometry.projection,
2354
+ "data-polycss-texture-lighting": geometry.lighting,
2355
+ "data-polycss-texture-leaf-width": geometry.leafWidth,
2356
+ "data-polycss-texture-leaf-height": geometry.leafHeight,
2357
+ "data-polycss-double-sided": plan.polygon.doubleSided ? "true" : void 0,
2358
+ "data-polycss-texture-source-x": geometry.sourceRect.x,
2359
+ "data-polycss-texture-source-y": geometry.sourceRect.y,
2360
+ "data-polycss-texture-source-width": geometry.sourceRect.width,
2361
+ "data-polycss-texture-source-height": geometry.sourceRect.height,
2362
+ ...domEventHandlers,
2363
+ ...dataAttrs,
2364
+ ...domAttrs
2365
+ }
2366
+ );
2367
+ });
2368
+
2369
+ // src/scene/PolyMesh.tsx
2370
+ import { createPortal } from "react-dom";
2371
+
2372
+ // src/scene/sceneContext.ts
2373
+ import { createContext as createContext2, useContext as useContext2 } from "react";
2374
+ var PolySceneContext = createContext2(null);
2375
+ function usePolySceneContext() {
2376
+ return useContext2(PolySceneContext);
2377
+ }
2378
+
2379
+ // src/scene/voxelRenderer.ts
2380
+ import {
2381
+ BASE_TILE,
2382
+ computeProjectiveQuadMatrix,
2383
+ normalFacesCamera,
2384
+ parsePureColor as parsePureColor2,
2385
+ PROJECTIVE_QUAD_BLEED,
2386
+ resolveProjectiveQuadGuards,
2387
+ rotateVec3,
2388
+ shadePolygon as shadePolygon2,
2389
+ SOLID_QUAD_CANONICAL_SIZE
2390
+ } from "@layoutit/polycss-core";
2391
+ var DEFAULT_LIGHT_DIR2 = [0.4, -0.7, 0.59];
2392
+ var DEFAULT_LIGHT_COLOR2 = "#ffffff";
2393
+ var DEFAULT_LIGHT_INTENSITY2 = 1;
2394
+ var DEFAULT_AMBIENT_COLOR2 = "#ffffff";
2395
+ var DEFAULT_AMBIENT_INTENSITY2 = 0.4;
2396
+ var DESKTOP_PRIMITIVE_SIZE = 1;
2397
+ var MOBILE_PRIMITIVE_SIZE = 8;
2398
+ var VOXEL_SEAM_BLEED = PROJECTIVE_QUAD_BLEED;
2399
+ var VOXEL_SEAM_EPS = 1e-6;
2400
+ var VOXEL_PROJECTIVE_QUAD_GUARDS = resolveProjectiveQuadGuards({ bleed: 0 });
2401
+ var FACE_NORMALS = {
2402
+ t: [0, 0, 1],
2403
+ b: [0, 0, -1],
2404
+ fl: [0, 1, 0],
2405
+ br: [0, -1, 0],
2406
+ fr: [1, 0, 0],
2407
+ bl: [-1, 0, 0]
2408
+ };
2409
+ var FACE_ORDER = ["t", "b", "bl", "br", "fr", "fl"];
2410
+ var FACE_BY_NORMAL = /* @__PURE__ */ new Map([
2411
+ ["0,0,1", "t"],
2412
+ ["0,0,-1", "b"],
2413
+ ["0,1,0", "fl"],
2414
+ ["0,-1,0", "br"],
2415
+ ["1,0,0", "fr"],
2416
+ ["-1,0,0", "bl"]
2417
+ ]);
2418
+ function visibleFaceSignature(rotation) {
2419
+ const visible = [];
2420
+ for (const face of FACE_ORDER) {
2421
+ if (normalFacesCamera(FACE_NORMALS[face], rotation)) visible.push(face);
2422
+ }
2423
+ return visible.join("|");
2424
+ }
2425
+ function applyBrush(el, color, transform) {
2426
+ const state = el.__polycssVoxelBrushState ?? (el.__polycssVoxelBrushState = {});
2427
+ if (state.color !== color) {
2428
+ el.style.color = color;
2429
+ state.color = color;
2430
+ }
2431
+ if (state.transform !== transform) {
2432
+ el.style.transform = transform;
2433
+ state.transform = transform;
2434
+ }
2435
+ }
2436
+ function cssNormalForPolygon(polygon) {
2437
+ const vertices = polygon.vertices;
2438
+ if (vertices.length < 3) return null;
2439
+ const v0 = vertices[0];
2440
+ let nx = 0;
2441
+ let ny = 0;
2442
+ let nz = 0;
2443
+ for (let i = 1; i + 1 < vertices.length; i += 1) {
2444
+ const v1 = vertices[i];
2445
+ const v2 = vertices[i + 1];
2446
+ const e1x = v1[1] - v0[1];
2447
+ const e1y = v1[0] - v0[0];
2448
+ const e1z = v1[2] - v0[2];
2449
+ const e2x = v2[1] - v0[1];
2450
+ const e2y = v2[0] - v0[0];
2451
+ const e2z = v2[2] - v0[2];
2452
+ nx -= e1y * e2z - e1z * e2y;
2453
+ ny -= e1z * e2x - e1x * e2z;
2454
+ nz -= e1x * e2y - e1y * e2x;
2455
+ }
2456
+ const len = Math.hypot(nx, ny, nz);
2457
+ if (len <= 1e-9) return null;
2458
+ return [
2459
+ Math.round(nx / len),
2460
+ Math.round(ny / len),
2461
+ Math.round(nz / len)
2462
+ ];
2463
+ }
2464
+ function polygonBrush(polygon) {
2465
+ if (polygon.texture || polygon.material || polygon.uvs || polygon.textureTriangles) return null;
2466
+ if (polygon.vertices.length !== 4) return null;
2467
+ const normal = cssNormalForPolygon(polygon);
2468
+ const face = normal ? FACE_BY_NORMAL.get(normal.join(",")) : void 0;
2469
+ if (!face) return null;
2470
+ let minX = Infinity;
2471
+ let minY = Infinity;
2472
+ let minZ = Infinity;
2473
+ let maxX = -Infinity;
2474
+ let maxY = -Infinity;
2475
+ let maxZ = -Infinity;
2476
+ for (const v of polygon.vertices) {
2477
+ minX = Math.min(minX, v[0]);
2478
+ minY = Math.min(minY, v[1]);
2479
+ minZ = Math.min(minZ, v[2]);
2480
+ maxX = Math.max(maxX, v[0]);
2481
+ maxY = Math.max(maxY, v[1]);
2482
+ maxZ = Math.max(maxZ, v[2]);
2483
+ }
2484
+ const eps = 1e-6;
2485
+ const baseColor = canonicalBrushColor(polygon.color);
2486
+ if (Math.abs(maxZ - minZ) <= eps) {
2487
+ return {
2488
+ axis: "z",
2489
+ face,
2490
+ left: minY * BASE_TILE,
2491
+ top: minX * BASE_TILE,
2492
+ width: Math.max(0, (maxY - minY) * BASE_TILE),
2493
+ height: Math.max(0, (maxX - minX) * BASE_TILE),
2494
+ z: minZ * BASE_TILE,
2495
+ baseColor,
2496
+ bleed: zeroVoxelSeamBleed()
2497
+ };
2498
+ }
2499
+ if (Math.abs(maxX - minX) <= eps) {
2500
+ return {
2501
+ axis: "x",
2502
+ face,
2503
+ left: minY * BASE_TILE,
2504
+ top: minZ * BASE_TILE,
2505
+ width: Math.max(0, (maxY - minY) * BASE_TILE),
2506
+ height: Math.max(0, (maxZ - minZ) * BASE_TILE),
2507
+ z: -minX * BASE_TILE,
2508
+ baseColor,
2509
+ bleed: zeroVoxelSeamBleed()
2510
+ };
2511
+ }
2512
+ if (Math.abs(maxY - minY) <= eps) {
2513
+ return {
2514
+ axis: "y",
2515
+ face,
2516
+ left: minZ * BASE_TILE,
2517
+ top: minX * BASE_TILE,
2518
+ width: Math.max(0, (maxZ - minZ) * BASE_TILE),
2519
+ height: Math.max(0, (maxX - minX) * BASE_TILE),
2520
+ z: -minY * BASE_TILE,
2521
+ baseColor,
2522
+ bleed: zeroVoxelSeamBleed()
2523
+ };
2524
+ }
2525
+ return null;
2526
+ }
2527
+ function zeroVoxelSeamBleed() {
2528
+ return { left: 0, right: 0, top: 0, bottom: 0 };
2529
+ }
2530
+ function worldLineKey(segment) {
2531
+ const coordKey = (value) => String(Number(value.toFixed(6)));
2532
+ let key = `${segment.item.baseColor}|${segment.variableAxis}`;
2533
+ for (let axis = 0; axis < 3; axis += 1) {
2534
+ if (axis === segment.variableAxis) continue;
2535
+ key += `|${axis}:${coordKey(segment.fixed[axis])}`;
2536
+ }
2537
+ return key;
2538
+ }
2539
+ function cssPointForVertex(v) {
2540
+ return [v[1] * BASE_TILE, v[0] * BASE_TILE, v[2] * BASE_TILE];
2541
+ }
2542
+ function localPointForItem(item, p) {
2543
+ if (item.axis === "x") return [p[0], p[2]];
2544
+ if (item.axis === "y") return [p[2], p[1]];
2545
+ return [p[0], p[1]];
2546
+ }
2547
+ function sideForLocalEdge(item, a, b) {
2548
+ const left = item.left;
2549
+ const right = item.left + item.width;
2550
+ const top = item.top;
2551
+ const bottom = item.top + item.height;
2552
+ if (Math.abs(a[0] - b[0]) <= VOXEL_SEAM_EPS) {
2553
+ if (Math.abs(a[0] - left) <= VOXEL_SEAM_EPS) return "left";
2554
+ if (Math.abs(a[0] - right) <= VOXEL_SEAM_EPS) return "right";
2555
+ }
2556
+ if (Math.abs(a[1] - b[1]) <= VOXEL_SEAM_EPS) {
2557
+ if (Math.abs(a[1] - top) <= VOXEL_SEAM_EPS) return "top";
2558
+ if (Math.abs(a[1] - bottom) <= VOXEL_SEAM_EPS) return "bottom";
2559
+ }
2560
+ return null;
2561
+ }
2562
+ function variableAxisForSegment(a, b) {
2563
+ let axis = null;
2564
+ for (let i = 0; i < 3; i += 1) {
2565
+ if (Math.abs(a[i] - b[i]) <= VOXEL_SEAM_EPS) continue;
2566
+ if (axis !== null) return null;
2567
+ axis = i;
2568
+ }
2569
+ return axis;
2570
+ }
2571
+ function voxelSeamSegmentForEdge(item, polygon, edgeIndex) {
2572
+ const vertices = polygon.vertices;
2573
+ const a = cssPointForVertex(vertices[edgeIndex]);
2574
+ const b = cssPointForVertex(vertices[(edgeIndex + 1) % vertices.length]);
2575
+ const side = sideForLocalEdge(item, localPointForItem(item, a), localPointForItem(item, b));
2576
+ if (!side) return null;
2577
+ const variableAxis = variableAxisForSegment(a, b);
2578
+ if (variableAxis === null) return null;
2579
+ const start = Math.min(a[variableAxis], b[variableAxis]);
2580
+ const end = Math.max(a[variableAxis], b[variableAxis]);
2581
+ return end - start > VOXEL_SEAM_EPS ? { item, side, variableAxis, fixed: a, start, end } : null;
2582
+ }
2583
+ function markVoxelSeam(segment) {
2584
+ segment.item.bleed[segment.side] = Math.max(segment.item.bleed[segment.side], VOXEL_SEAM_BLEED);
2585
+ }
2586
+ function applyVoxelSeamBleed(polygons, items) {
2587
+ const groups = /* @__PURE__ */ new Map();
2588
+ for (const item of items) {
2589
+ const polygon = polygons[item.sourceIndex];
2590
+ if (!polygon) continue;
2591
+ for (let edgeIndex = 0; edgeIndex < polygon.vertices.length; edgeIndex += 1) {
2592
+ const segment = voxelSeamSegmentForEdge(item, polygon, edgeIndex);
2593
+ if (!segment) continue;
2594
+ const key = worldLineKey(segment);
2595
+ const group = groups.get(key);
2596
+ if (group) group.push(segment);
2597
+ else groups.set(key, [segment]);
2598
+ }
2599
+ }
2600
+ for (const segments of groups.values()) {
2601
+ if (segments.length < 2) continue;
2602
+ segments.sort((a, b) => a.start - b.start || a.end - b.end);
2603
+ let active = [];
2604
+ for (const segment of segments) {
2605
+ active = active.filter((candidate) => candidate.end > segment.start + VOXEL_SEAM_EPS);
2606
+ for (const candidate of active) {
2607
+ if (candidate.item.sourceIndex === segment.item.sourceIndex) continue;
2608
+ markVoxelSeam(candidate);
2609
+ markVoxelSeam(segment);
2610
+ }
2611
+ active.push(segment);
2612
+ }
2613
+ }
2614
+ }
2615
+ function parseColor(input) {
2616
+ const parsed = parsePureColor2(input);
2617
+ if (!parsed) return { r: 255, g: 255, b: 255, alpha: 1 };
2618
+ return {
2619
+ r: parsed.rgb[0],
2620
+ g: parsed.rgb[1],
2621
+ b: parsed.rgb[2],
2622
+ alpha: parsed.alpha
2623
+ };
2624
+ }
2625
+ function canonicalBrushColor(input) {
2626
+ if (!input) return "#cccccc";
2627
+ const parsed = parsePureColor2(input);
2628
+ if (!parsed) return input;
2629
+ const rgb = {
2630
+ r: parsed.rgb[0],
2631
+ g: parsed.rgb[1],
2632
+ b: parsed.rgb[2],
2633
+ alpha: parsed.alpha
2634
+ };
2635
+ if (rgb.alpha < 1) {
2636
+ const alpha = Math.round(Math.max(0, rgb.alpha) * 1e3) / 1e3;
2637
+ return `rgba(${clampChannel(rgb.r)}, ${clampChannel(rgb.g)}, ${clampChannel(rgb.b)}, ${alpha})`;
2638
+ }
2639
+ return rgbToHex2(rgb);
2640
+ }
2641
+ function rgbToHex2({ r, g, b }) {
2642
+ const f = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
2643
+ return `#${f(r)}${f(g)}${f(b)}`;
2644
+ }
2645
+ function clampChannel(value) {
2646
+ return Math.round(Math.max(0, Math.min(255, value)));
2647
+ }
2648
+ function shadeBrushColor(normal, baseColor, directionalLight, ambientLight) {
2649
+ const lightDir = directionalLight?.direction ?? DEFAULT_LIGHT_DIR2;
2650
+ const lightLen = Math.hypot(lightDir[0], lightDir[1], lightDir[2]) || 1;
2651
+ const lx = lightDir[0] / lightLen;
2652
+ const ly = lightDir[1] / lightLen;
2653
+ const lz = lightDir[2] / lightLen;
2654
+ const directScale = Math.max(0, directionalLight?.intensity ?? DEFAULT_LIGHT_INTENSITY2) * Math.max(0, normal[0] * lx + normal[1] * ly + normal[2] * lz);
2655
+ const ambientIntensity = Math.max(0, ambientLight?.intensity ?? DEFAULT_AMBIENT_INTENSITY2);
2656
+ const lightColor = directionalLight?.color ?? DEFAULT_LIGHT_COLOR2;
2657
+ const ambientColor = ambientLight?.color ?? DEFAULT_AMBIENT_COLOR2;
2658
+ const shaded = shadePolygon2(baseColor, directScale, lightColor, ambientColor, ambientIntensity);
2659
+ const base = parseColor(baseColor);
2660
+ if (base.alpha >= 1) return shaded;
2661
+ const r = parseInt(shaded.slice(1, 3), 16);
2662
+ const g = parseInt(shaded.slice(3, 5), 16);
2663
+ const b = parseInt(shaded.slice(5, 7), 16);
2664
+ return `rgba(${r}, ${g}, ${b}, ${base.alpha})`;
2665
+ }
2666
+ function buildDirectMatrixItems(polygons) {
2667
+ if (!polygons?.length) return [];
2668
+ const items = [];
2669
+ for (let sourceIndex = 0; sourceIndex < polygons.length; sourceIndex += 1) {
2670
+ const polygon = polygons[sourceIndex];
2671
+ const brush = polygonBrush(polygon);
2672
+ if (!brush || brush.width <= 0 || brush.height <= 0) return [];
2673
+ items.push({
2674
+ ...brush,
2675
+ sourceIndex
2676
+ });
2677
+ }
2678
+ applyVoxelSeamBleed(polygons, items);
2679
+ return items;
2680
+ }
2681
+ function voxelProjectiveBasis(item) {
2682
+ if (item.axis === "x") {
2683
+ return {
2684
+ xAxis: [1, 0, 0],
2685
+ yAxis: [0, 0, 1],
2686
+ normal: [0, -1, 0],
2687
+ tx: 0,
2688
+ ty: -item.z,
2689
+ tz: 0
2690
+ };
2691
+ }
2692
+ if (item.axis === "y") {
2693
+ return {
2694
+ xAxis: [0, 0, 1],
2695
+ yAxis: [0, 1, 0],
2696
+ normal: [-1, 0, 0],
2697
+ tx: -item.z,
2698
+ ty: 0,
2699
+ tz: 0
2700
+ };
2701
+ }
2702
+ return {
2703
+ xAxis: [1, 0, 0],
2704
+ yAxis: [0, 1, 0],
2705
+ normal: [0, 0, 1],
2706
+ tx: 0,
2707
+ ty: 0,
2708
+ tz: item.z
2709
+ };
2710
+ }
2711
+ function voxelScreenPts(item) {
2712
+ const left = item.left;
2713
+ const top = item.top;
2714
+ const right = item.left + item.width;
2715
+ const bottom = item.top + item.height;
2716
+ return [
2717
+ left,
2718
+ top,
2719
+ right,
2720
+ top,
2721
+ right,
2722
+ bottom,
2723
+ left,
2724
+ bottom
2725
+ ];
2726
+ }
2727
+ function voxelSeamEdgeAmounts(item) {
2728
+ return /* @__PURE__ */ new Map([
2729
+ [0, item.bleed.top],
2730
+ [1, item.bleed.right],
2731
+ [2, item.bleed.bottom],
2732
+ [3, item.bleed.left]
2733
+ ]);
2734
+ }
2735
+ function rescaleProjectiveMatrix(matrix, primitiveSize) {
2736
+ const values = matrix.split(",").map(Number);
2737
+ if (values.length !== 16 || values.some((value) => !Number.isFinite(value))) return null;
2738
+ const scale = SOLID_QUAD_CANONICAL_SIZE / primitiveSize;
2739
+ for (let i = 0; i < 8; i += 1) values[i] *= scale;
2740
+ return `matrix3d(${values.map((value) => Number(value.toFixed(6))).join(",")})`;
2741
+ }
2742
+ function affineVoxelMatrix(item, primitiveSize) {
2743
+ const left = item.left - item.bleed.left;
2744
+ const top = item.top - item.bleed.top;
2745
+ const width = item.width + item.bleed.left + item.bleed.right;
2746
+ const height = item.height + item.bleed.top + item.bleed.bottom;
2747
+ const zOffset = item.z;
2748
+ const scaleX = width / primitiveSize;
2749
+ const scaleY = height / primitiveSize;
2750
+ const values = item.axis === "x" ? [
2751
+ scaleX,
2752
+ 0,
2753
+ 0,
2754
+ 0,
2755
+ 0,
2756
+ 0,
2757
+ scaleY,
2758
+ 0,
2759
+ 0,
2760
+ -1,
2761
+ 0,
2762
+ 0,
2763
+ left,
2764
+ -zOffset,
2765
+ top,
2766
+ 1
2767
+ ] : item.axis === "y" ? [
2768
+ 0,
2769
+ 0,
2770
+ scaleX,
2771
+ 0,
2772
+ 0,
2773
+ scaleY,
2774
+ 0,
2775
+ 0,
2776
+ -1,
2777
+ 0,
2778
+ 0,
2779
+ 0,
2780
+ -zOffset,
2781
+ top,
2782
+ left,
2783
+ 1
2784
+ ] : [
2785
+ scaleX,
2786
+ 0,
2787
+ 0,
2788
+ 0,
2789
+ 0,
2790
+ scaleY,
2791
+ 0,
2792
+ 0,
2793
+ 0,
2794
+ 0,
2795
+ 1,
2796
+ 0,
2797
+ left,
2798
+ top,
2799
+ zOffset,
2800
+ 1
2801
+ ];
2802
+ return `matrix3d(${values.map((value) => Number(value.toFixed(6))).join(",")})`;
2803
+ }
2804
+ function directMatrix(item, primitiveSize) {
2805
+ const { xAxis, yAxis, normal, tx, ty, tz } = voxelProjectiveBasis(item);
2806
+ const projective = computeProjectiveQuadMatrix(
2807
+ voxelScreenPts(item),
2808
+ xAxis,
2809
+ yAxis,
2810
+ normal,
2811
+ tx,
2812
+ ty,
2813
+ tz,
2814
+ VOXEL_PROJECTIVE_QUAD_GUARDS,
2815
+ voxelSeamEdgeAmounts(item)
2816
+ );
2817
+ return projective ? rescaleProjectiveMatrix(projective, primitiveSize) ?? affineVoxelMatrix(item, primitiveSize) : affineVoxelMatrix(item, primitiveSize);
2818
+ }
2819
+ function isMobileDocument2(doc) {
2820
+ const media = doc.defaultView?.matchMedia;
2821
+ if (!media) return false;
2822
+ return media("(pointer: coarse)").matches || media("(hover: none)").matches;
2823
+ }
2824
+ function primitiveSizeForDocument(doc) {
2825
+ return isMobileDocument2(doc) ? MOBILE_PRIMITIVE_SIZE : DESKTOP_PRIMITIVE_SIZE;
2826
+ }
2827
+ function itemCenter(item) {
2828
+ if (item.axis === "x") {
2829
+ return [item.left + item.width / 2, -item.z, item.top + item.height / 2];
2830
+ }
2831
+ if (item.axis === "y") {
2832
+ return [-item.z, item.top + item.height / 2, item.left + item.width / 2];
2833
+ }
2834
+ return [item.left + item.width / 2, item.top + item.height / 2, item.z];
2835
+ }
2836
+ function projectedPoint(item, rotation) {
2837
+ let center = itemCenter(item);
2838
+ const meshRotation = rotation.meshRotation;
2839
+ if (meshRotation) {
2840
+ center = rotateVec3(center, meshRotation[0] ?? 0, meshRotation[1] ?? 0, meshRotation[2] ?? 0);
2841
+ }
2842
+ const [x, y] = rotateVec3(center, rotation.rotX, 0, rotation.rotY);
2843
+ return { x, y };
2844
+ }
2845
+ function orderDirectMatrixItems(items, visibleFaces, rotation) {
2846
+ const entries = items.filter((item) => visibleFaces.has(item.face)).map((item) => ({ item, ...projectedPoint(item, rotation) }));
2847
+ if (entries.length === 0) return [];
2848
+ let minX = Infinity;
2849
+ let maxX = -Infinity;
2850
+ let minY = Infinity;
2851
+ let maxY = -Infinity;
2852
+ for (const entry of entries) {
2853
+ minX = Math.min(minX, entry.x);
2854
+ maxX = Math.max(maxX, entry.x);
2855
+ minY = Math.min(minY, entry.y);
2856
+ maxY = Math.max(maxY, entry.y);
2857
+ }
2858
+ const tileCount = 4;
2859
+ const spanX = Math.max(1e-6, maxX - minX);
2860
+ const spanY = Math.max(1e-6, maxY - minY);
2861
+ const tiles = /* @__PURE__ */ new Map();
2862
+ for (const entry of entries) {
2863
+ const tx = Math.min(
2864
+ tileCount - 1,
2865
+ Math.max(0, Math.floor((entry.x - minX) / spanX * tileCount))
2866
+ );
2867
+ const ty = Math.min(
2868
+ tileCount - 1,
2869
+ Math.max(0, Math.floor((entry.y - minY) / spanY * tileCount))
2870
+ );
2871
+ const key = `${tx}:${ty}`;
2872
+ let tile = tiles.get(key);
2873
+ if (!tile) {
2874
+ tile = { tx, ty, sourceIndex: entry.item.sourceIndex, items: [] };
2875
+ tiles.set(key, tile);
2876
+ }
2877
+ tile.items.push(entry.item);
2878
+ tile.sourceIndex = Math.min(tile.sourceIndex, entry.item.sourceIndex);
2879
+ }
2880
+ return Array.from(tiles.values()).sort((a, b) => a.ty - b.ty || a.tx - b.tx || a.sourceIndex - b.sourceIndex).flatMap((tile) => tile.items);
2881
+ }
2882
+ function createPolyVoxelRenderer(options) {
2883
+ const { doc, wrapper, polygons, directionalLight, ambientLight } = options;
2884
+ const directMatrixItems = buildDirectMatrixItems(polygons);
2885
+ if (directMatrixItems.length === 0) return null;
2886
+ const itemFaces = new Set(directMatrixItems.map((item) => item.face));
2887
+ wrapper.classList.add("polycss-voxel-mesh");
2888
+ const primitiveSize = primitiveSizeForDocument(doc);
2889
+ if (primitiveSize !== DESKTOP_PRIMITIVE_SIZE) {
2890
+ wrapper.style.setProperty("--polycss-voxel-primitive", `${primitiveSize}px`);
2891
+ }
2892
+ const colorCache = /* @__PURE__ */ new Map();
2893
+ const shadedColor = (face, baseColor) => {
2894
+ const key = `${face}|${baseColor}`;
2895
+ const cached = colorCache.get(key);
2896
+ if (cached) return cached;
2897
+ const shaded = shadeBrushColor(FACE_NORMALS[face], baseColor, directionalLight, ambientLight);
2898
+ colorCache.set(key, shaded);
2899
+ return shaded;
2900
+ };
2901
+ const elementBySourceIndex = /* @__PURE__ */ new Map();
2902
+ const hostByFace = /* @__PURE__ */ new Map();
2903
+ const faceOrderKeys = /* @__PURE__ */ new Map();
2904
+ const directMatrixItemsByFace = /* @__PURE__ */ new Map();
2905
+ for (const item of directMatrixItems) {
2906
+ let faceItems = directMatrixItemsByFace.get(item.face);
2907
+ if (!faceItems) {
2908
+ faceItems = [];
2909
+ directMatrixItemsByFace.set(item.face, faceItems);
2910
+ }
2911
+ faceItems.push(item);
2912
+ }
2913
+ let lastSignature = "";
2914
+ let mountedBrushCount = 0;
2915
+ let mountedFaces = /* @__PURE__ */ new Set();
2916
+ const brushForItem = (item) => {
2917
+ let el = elementBySourceIndex.get(item.sourceIndex);
2918
+ if (!el) {
2919
+ el = doc.createElement("b");
2920
+ elementBySourceIndex.set(item.sourceIndex, el);
2921
+ applyBrush(
2922
+ el,
2923
+ shadedColor(item.face, item.baseColor),
2924
+ directMatrix(item, primitiveSize)
2925
+ );
2926
+ }
2927
+ return el;
2928
+ };
2929
+ const hostForFace = (face) => {
2930
+ let host = hostByFace.get(face);
2931
+ if (!host) {
2932
+ host = doc.createElement("span");
2933
+ host.className = `polycss-voxel-face polycss-voxel-face-${face}`;
2934
+ host.dataset.polycssVoxelFace = face;
2935
+ host.__polycssVoxelFaceHost = true;
2936
+ hostByFace.set(face, host);
2937
+ }
2938
+ return host;
2939
+ };
2940
+ const firstPreservedChild = () => {
2941
+ for (const child of Array.from(wrapper.childNodes)) {
2942
+ if (child.__polycssVoxelFaceHost) continue;
2943
+ return child;
2944
+ }
2945
+ return null;
2946
+ };
2947
+ const syncFaceHost = (face, items) => {
2948
+ const nextOrderKey = items.map((item) => item.sourceIndex).join(",");
2949
+ if (faceOrderKeys.get(face) === nextOrderKey) return;
2950
+ const host = hostForFace(face);
2951
+ const fragment = doc.createDocumentFragment();
2952
+ for (const item of items) fragment.appendChild(brushForItem(item));
2953
+ host.replaceChildren(fragment);
2954
+ faceOrderKeys.set(face, nextOrderKey);
2955
+ };
2956
+ const prebuildFaceHosts = () => {
2957
+ for (const face of FACE_ORDER) {
2958
+ if (!itemFaces.has(face)) continue;
2959
+ syncFaceHost(face, directMatrixItemsByFace.get(face) ?? []);
2960
+ }
2961
+ };
2962
+ const facesForSignature = (signature) => new Set(signature.split("|").filter(Boolean));
2963
+ const faceOrderForSignature = (signature) => {
2964
+ const visibleFaces = facesForSignature(signature);
2965
+ return FACE_ORDER.filter((face) => visibleFaces.has(face) && itemFaces.has(face));
2966
+ };
2967
+ const countBrushesForFaces = (faces) => {
2968
+ let count = 0;
2969
+ for (const face of faces) count += directMatrixItemsByFace.get(face)?.length ?? 0;
2970
+ return count;
2971
+ };
2972
+ const itemsByFaceOrder = (orderedItems) => {
2973
+ const seen = /* @__PURE__ */ new Set();
2974
+ const orderedFaces = [];
2975
+ const itemsByFace = /* @__PURE__ */ new Map();
2976
+ for (const item of orderedItems) {
2977
+ if (!seen.has(item.face)) {
2978
+ seen.add(item.face);
2979
+ orderedFaces.push(item.face);
2980
+ }
2981
+ let faceItems = itemsByFace.get(item.face);
2982
+ if (!faceItems) {
2983
+ faceItems = [];
2984
+ itemsByFace.set(item.face, faceItems);
2985
+ }
2986
+ faceItems.push(item);
2987
+ }
2988
+ return { orderedFaces, itemsByFace };
2989
+ };
2990
+ const mountFaceHosts = (orderedFaces, reorderMountedFaces) => {
2991
+ const nextFaces = new Set(orderedFaces);
2992
+ for (const face of mountedFaces) {
2993
+ if (nextFaces.has(face)) continue;
2994
+ const host = hostByFace.get(face);
2995
+ if (host?.parentNode === wrapper) wrapper.removeChild(host);
2996
+ }
2997
+ if (reorderMountedFaces) {
2998
+ const fragment = doc.createDocumentFragment();
2999
+ for (const face of orderedFaces) fragment.appendChild(hostForFace(face));
3000
+ wrapper.insertBefore(fragment, firstPreservedChild());
3001
+ mountedFaces = nextFaces;
3002
+ return;
3003
+ }
3004
+ for (let i = 0; i < orderedFaces.length; i += 1) {
3005
+ const face = orderedFaces[i];
3006
+ const host = hostForFace(face);
3007
+ if (host.parentNode === wrapper) continue;
3008
+ let reference = null;
3009
+ for (let j = i + 1; j < orderedFaces.length; j += 1) {
3010
+ const nextHost = hostByFace.get(orderedFaces[j]);
3011
+ if (nextHost?.parentNode === wrapper) {
3012
+ reference = nextHost;
3013
+ break;
3014
+ }
3015
+ }
3016
+ wrapper.insertBefore(host, reference ?? firstPreservedChild());
3017
+ }
3018
+ mountedFaces = nextFaces;
3019
+ };
3020
+ const draw = (signature, rotation, syncOrder) => {
3021
+ if (!syncOrder) {
3022
+ const orderedFaces2 = faceOrderForSignature(signature);
3023
+ mountFaceHosts(orderedFaces2, false);
3024
+ mountedBrushCount = countBrushesForFaces(orderedFaces2);
3025
+ return;
3026
+ }
3027
+ const visibleFaces = new Set(signature.split("|").filter(Boolean));
3028
+ const orderedItems = orderDirectMatrixItems(directMatrixItems, visibleFaces, rotation);
3029
+ const { orderedFaces, itemsByFace } = itemsByFaceOrder(orderedItems);
3030
+ for (const face of orderedFaces) {
3031
+ syncFaceHost(face, itemsByFace.get(face) ?? []);
3032
+ }
3033
+ mountFaceHosts(orderedFaces, true);
3034
+ mountedBrushCount = orderedItems.length;
3035
+ };
3036
+ prebuildFaceHosts();
3037
+ return {
3038
+ get brushCount() {
3039
+ return mountedBrushCount;
3040
+ },
3041
+ render(rotation) {
3042
+ lastSignature = visibleFaceSignature(rotation);
3043
+ draw(lastSignature, rotation, true);
3044
+ },
3045
+ syncCamera(rotation) {
3046
+ const nextSignature = visibleFaceSignature(rotation);
3047
+ if (nextSignature === lastSignature) return;
3048
+ lastSignature = nextSignature;
3049
+ draw(nextSignature, rotation, false);
3050
+ },
3051
+ dispose() {
3052
+ for (const host of hostByFace.values()) host.remove();
3053
+ wrapper.classList.remove("polycss-voxel-mesh");
3054
+ wrapper.style.removeProperty("--polycss-voxel-primitive");
3055
+ elementBySourceIndex.clear();
3056
+ hostByFace.clear();
3057
+ faceOrderKeys.clear();
3058
+ mountedBrushCount = 0;
3059
+ mountedFaces = /* @__PURE__ */ new Set();
3060
+ lastSignature = "";
3061
+ }
3062
+ };
3063
+ }
3064
+
3065
+ // src/scene/events.ts
3066
+ var MESH_REGISTRY = /* @__PURE__ */ new WeakMap();
3067
+ function registerMeshElement(el, handle) {
3068
+ MESH_REGISTRY.set(el, handle);
3069
+ }
3070
+ function unregisterMeshElement(el) {
3071
+ MESH_REGISTRY.delete(el);
3072
+ }
3073
+ function findPolyMeshHandle(el) {
3074
+ let cur = el;
3075
+ while (cur) {
3076
+ if (cur instanceof HTMLElement) {
3077
+ const h = MESH_REGISTRY.get(cur);
3078
+ if (h) return h;
3079
+ }
3080
+ cur = cur.parentElement;
3081
+ }
3082
+ return null;
3083
+ }
3084
+
3085
+ // src/scene/PolyMesh.tsx
3086
+ import { Fragment, jsx as jsx8, jsxs } from "react/jsx-runtime";
3087
+ var reactEdgeOwnersCache = /* @__PURE__ */ new WeakMap();
3088
+ var reactEdgeOwnersCacheKey = /* @__PURE__ */ new WeakMap();
3089
+ function solidPaintVars(defaults) {
3090
+ const out = {};
3091
+ if (defaults.paintColor) out["--polycss-paint"] = defaults.paintColor;
3092
+ if (defaults.dynamicColor) {
3093
+ out["--psr"] = (defaults.dynamicColor.r / 255).toFixed(4);
3094
+ out["--psg"] = (defaults.dynamicColor.g / 255).toFixed(4);
3095
+ out["--psb"] = (defaults.dynamicColor.b / 255).toFixed(4);
3096
+ }
3097
+ return Object.keys(out).length > 0 ? out : null;
3098
+ }
3099
+ function recenterPolygons(polygons) {
3100
+ if (polygons.length === 0) return polygons;
3101
+ const bbox = computeSceneBbox(polygons);
3102
+ const cx = (bbox.min[0] + bbox.max[0]) / 2;
3103
+ const cy = (bbox.min[1] + bbox.max[1]) / 2;
3104
+ const cz = (bbox.min[2] + bbox.max[2]) / 2;
3105
+ if (cx === 0 && cy === 0 && cz === 0) return polygons;
3106
+ const shift = (v) => [v[0] - cx, v[1] - cy, v[2] - cz];
3107
+ return polygons.map((p) => ({
3108
+ ...p,
3109
+ vertices: p.vertices.map(shift),
3110
+ ...p.textureTriangles?.length ? {
3111
+ textureTriangles: p.textureTriangles.map((triangle) => ({
3112
+ ...triangle,
3113
+ vertices: triangle.vertices.map(shift)
3114
+ }))
3115
+ } : null
3116
+ }));
3117
+ }
3118
+ var PolyMesh = forwardRef(function PolyMesh2({
3119
+ id,
3120
+ src,
3121
+ mtl,
3122
+ polygons: polygonsProp,
3123
+ voxelSource: voxelSourceProp,
3124
+ autoCenter,
3125
+ textureLighting,
3126
+ textureQuality,
3127
+ textureLeafSizing,
3128
+ textureImageRendering,
3129
+ textureBackend,
3130
+ textureProjection,
3131
+ seamBleed,
3132
+ atomicAtlas,
3133
+ onFrameReady,
3134
+ castShadow,
3135
+ receiveShadow,
3136
+ shadowDefinition,
3137
+ merge = true,
3138
+ children,
3139
+ fallback,
3140
+ errorFallback,
3141
+ parseOptions,
3142
+ meshResolution,
3143
+ position,
3144
+ scale,
3145
+ rotation,
3146
+ className,
3147
+ style,
3148
+ onClick,
3149
+ onContextMenu,
3150
+ onDoubleClick,
3151
+ onWheel,
3152
+ onPointerDown,
3153
+ onPointerUp,
3154
+ onPointerMove,
3155
+ onPointerOver,
3156
+ onPointerOut,
3157
+ onPointerEnter,
3158
+ onPointerLeave,
3159
+ onPointerCancel
3160
+ }, forwardedRef) {
3161
+ const mergedOptions = useMemo5(() => {
3162
+ if (!mtl && !parseOptions && meshResolution === void 0) return void 0;
3163
+ return {
3164
+ ...parseOptions ?? {},
3165
+ ...mtl ? { mtlUrl: mtl } : {},
3166
+ ...meshResolution !== void 0 ? { meshResolution } : {}
3167
+ };
3168
+ }, [mtl, parseOptions, meshResolution]);
3169
+ const fetched = usePolyMesh(src ?? "", mergedOptions);
3170
+ const externalPolygons = src ? fetched.polygons : polygonsProp ?? [];
3171
+ const externalVoxelSource = src ? fetched.voxelSource : voxelSourceProp;
3172
+ const [localPolygons, setLocalPolygons] = useState5(null);
3173
+ const prevExternalRef = useRef7(externalPolygons);
3174
+ if (prevExternalRef.current !== externalPolygons) {
3175
+ prevExternalRef.current = externalPolygons;
3176
+ if (localPolygons !== null) setLocalPolygons(null);
3177
+ }
3178
+ const rawSourcePolygons = localPolygons ?? externalPolygons;
3179
+ const sourcePolygons = useMemo5(
3180
+ () => merge ? optimizeMeshPolygons(rawSourcePolygons, meshResolution !== void 0 ? { meshResolution } : void 0) : rawSourcePolygons,
3181
+ [rawSourcePolygons, merge, meshResolution]
3182
+ );
3183
+ const hasRenderProp = typeof children === "function";
3184
+ const renderPolygon = hasRenderProp ? children : null;
3185
+ const staticChildren = hasRenderProp ? null : children;
3186
+ const hasStaticChildren = staticChildren !== null && staticChildren !== void 0 && staticChildren !== false;
3187
+ const polygons = useMemo5(
3188
+ () => autoCenter ? recenterPolygons(sourcePolygons) : sourcePolygons,
3189
+ [sourcePolygons, autoCenter]
3190
+ );
3191
+ const transform = buildPolyMeshTransform({ position, scale, rotation });
3192
+ const wrapperRef = useRef7(null);
3193
+ const propsRef = useRef7({ position, scale, rotation });
3194
+ propsRef.current = { position, scale, rotation };
3195
+ const polygonsRef = useRef7(polygons);
3196
+ polygonsRef.current = polygons;
3197
+ const [bakedRotation, setBakedRotation] = useState5(rotation);
3198
+ const stableTriangleColorFrameRef = useRef7(0);
3199
+ const setPolygonsImplRef = useRef7(() => {
3200
+ });
3201
+ const textureReadyRef = useRef7(true);
3202
+ const textureReadyWaitersRef = useRef7([]);
3203
+ const resolveTextureReadyWaiters = useCallback6(() => {
3204
+ const waiters = textureReadyWaitersRef.current.splice(0);
3205
+ for (const resolve of waiters) resolve();
3206
+ }, []);
3207
+ const handle = useMemo5(() => ({
3208
+ get element() {
3209
+ return wrapperRef.current;
3210
+ },
3211
+ id,
3212
+ getPosition: () => propsRef.current.position,
3213
+ getRotation: () => propsRef.current.rotation,
3214
+ getScale: () => propsRef.current.scale,
3215
+ getPolygons: () => polygonsRef.current,
3216
+ setPolygons(nextPolygons) {
3217
+ setPolygonsImplRef.current(nextPolygons);
3218
+ },
3219
+ rebakeAtlas: () => setBakedRotation(propsRef.current.rotation),
3220
+ whenTexturesReady() {
3221
+ if (textureReadyRef.current) return Promise.resolve();
3222
+ return new Promise((resolve) => {
3223
+ textureReadyWaitersRef.current.push(resolve);
3224
+ });
3225
+ },
3226
+ updatePolygon(target, partial) {
3227
+ const current = polygonsRef.current;
3228
+ const idx = typeof target === "number" ? target : current.indexOf(target);
3229
+ if (idx < 0 || idx >= current.length) return;
3230
+ Object.assign(current[idx], partial);
3231
+ setLocalPolygons([...current]);
3232
+ }
3233
+ }), [id]);
3234
+ useImperativeHandle(forwardedRef, () => handle, [handle]);
3235
+ useEffect6(() => {
3236
+ const el = wrapperRef.current;
3237
+ if (!el) return;
3238
+ registerMeshElement(el, handle);
3239
+ return () => unregisterMeshElement(el);
3240
+ }, [handle]);
3241
+ const cameraCtx = useContext3(PolyCameraContext);
3242
+ const cameraElRef = cameraCtx?.cameraElRef ?? null;
3243
+ const pointerDownAtRef = useRef7(null);
3244
+ const makeEvent = useCallback6(
3245
+ function makeEvent2(nativeEvent, clientX, clientY) {
3246
+ const intersections = [];
3247
+ if (typeof document !== "undefined" && typeof document.elementsFromPoint === "function") {
3248
+ const stacked = document.elementsFromPoint(clientX, clientY);
3249
+ const seen = /* @__PURE__ */ new Set();
3250
+ for (const el of stacked) {
3251
+ const h = findPolyMeshHandle(el);
3252
+ if (h && !seen.has(h)) {
3253
+ seen.add(h);
3254
+ intersections.push({ object: h });
3255
+ }
3256
+ }
3257
+ }
3258
+ let nx = 0;
3259
+ let ny = 0;
3260
+ const camEl = cameraElRef?.current;
3261
+ if (camEl) {
3262
+ const r = camEl.getBoundingClientRect();
3263
+ if (r.width > 0 && r.height > 0) {
3264
+ nx = (clientX - r.left) / r.width * 2 - 1;
3265
+ ny = -((clientY - r.top) / r.height * 2 - 1);
3266
+ }
3267
+ }
3268
+ let delta = 0;
3269
+ const pd = pointerDownAtRef.current;
3270
+ if (pd) delta = Math.hypot(clientX - pd.x, clientY - pd.y);
3271
+ return {
3272
+ object: intersections[0]?.object ?? handle,
3273
+ eventObject: handle,
3274
+ intersections,
3275
+ pointer: { x: nx, y: ny },
3276
+ delta,
3277
+ nativeEvent,
3278
+ stopPropagation: () => nativeEvent.stopPropagation()
3279
+ };
3280
+ },
3281
+ [cameraElRef, handle]
3282
+ );
3283
+ const wrapperHandlers = useMemo5(() => {
3284
+ const dispatch = (polyHandler, reactEvent, nativeEvent, clientX, clientY) => {
3285
+ if (!polyHandler) return;
3286
+ const polyEvent = makeEvent(nativeEvent, clientX, clientY);
3287
+ const originalStop = polyEvent.stopPropagation;
3288
+ polyEvent.stopPropagation = () => {
3289
+ originalStop();
3290
+ reactEvent.stopPropagation();
3291
+ };
3292
+ polyHandler(polyEvent);
3293
+ };
3294
+ const out = {};
3295
+ if (onClick) {
3296
+ out.onClick = (e) => dispatch(onClick, e, e.nativeEvent, e.clientX, e.clientY);
3297
+ }
3298
+ if (onContextMenu) {
3299
+ out.onContextMenu = (e) => dispatch(onContextMenu, e, e.nativeEvent, e.clientX, e.clientY);
3300
+ }
3301
+ if (onDoubleClick) {
3302
+ out.onDoubleClick = (e) => dispatch(onDoubleClick, e, e.nativeEvent, e.clientX, e.clientY);
3303
+ }
3304
+ if (onWheel) {
3305
+ out.onWheel = (e) => dispatch(onWheel, e, e.nativeEvent, e.clientX, e.clientY);
3306
+ }
3307
+ if (onPointerDown) {
3308
+ out.onPointerDown = (e) => {
3309
+ pointerDownAtRef.current = { x: e.clientX, y: e.clientY };
3310
+ dispatch(onPointerDown, e, e.nativeEvent, e.clientX, e.clientY);
3311
+ };
3312
+ } else {
3313
+ out.onPointerDown = (e) => {
3314
+ pointerDownAtRef.current = { x: e.clientX, y: e.clientY };
3315
+ };
3316
+ }
3317
+ if (onPointerUp) {
3318
+ out.onPointerUp = (e) => {
3319
+ dispatch(onPointerUp, e, e.nativeEvent, e.clientX, e.clientY);
3320
+ pointerDownAtRef.current = null;
3321
+ };
3322
+ } else {
3323
+ out.onPointerUp = () => {
3324
+ pointerDownAtRef.current = null;
3325
+ };
3326
+ }
3327
+ if (onPointerMove) {
3328
+ out.onPointerMove = (e) => dispatch(onPointerMove, e, e.nativeEvent, e.clientX, e.clientY);
3329
+ }
3330
+ if (onPointerOver || onPointerEnter) {
3331
+ out.onPointerEnter = (e) => {
3332
+ if (onPointerOver) dispatch(onPointerOver, e, e.nativeEvent, e.clientX, e.clientY);
3333
+ if (onPointerEnter) dispatch(onPointerEnter, e, e.nativeEvent, e.clientX, e.clientY);
3334
+ };
3335
+ }
3336
+ if (onPointerOut || onPointerLeave) {
3337
+ out.onPointerLeave = (e) => {
3338
+ if (onPointerOut) dispatch(onPointerOut, e, e.nativeEvent, e.clientX, e.clientY);
3339
+ if (onPointerLeave) dispatch(onPointerLeave, e, e.nativeEvent, e.clientX, e.clientY);
3340
+ };
3341
+ }
3342
+ if (onPointerCancel) {
3343
+ out.onPointerCancel = (e) => {
3344
+ dispatch(onPointerCancel, e, e.nativeEvent, e.clientX, e.clientY);
3345
+ pointerDownAtRef.current = null;
3346
+ };
3347
+ }
3348
+ return out;
3349
+ }, [
3350
+ makeEvent,
3351
+ onClick,
3352
+ onContextMenu,
3353
+ onDoubleClick,
3354
+ onWheel,
3355
+ onPointerDown,
3356
+ onPointerUp,
3357
+ onPointerMove,
3358
+ onPointerOver,
3359
+ onPointerOut,
3360
+ onPointerEnter,
3361
+ onPointerLeave,
3362
+ onPointerCancel
3363
+ ]);
3364
+ const sceneCtx = usePolySceneContext();
3365
+ const effectiveTextureLighting = textureLighting ?? sceneCtx?.textureLighting ?? "baked";
3366
+ const effectiveStrategies = sceneCtx?.strategies;
3367
+ const disabledStrategies = useMemo5(
3368
+ () => effectiveStrategies?.disable?.length ? new Set(effectiveStrategies.disable) : void 0,
3369
+ [effectiveStrategies]
3370
+ );
3371
+ const effectiveSeamBleed = seamBleed ?? sceneCtx?.seamBleed ?? DEFAULT_SEAM_BLEED;
3372
+ const effectiveTextureLeafSizing = textureLeafSizing ?? sceneCtx?.textureLeafSizing;
3373
+ const effectiveTextureImageRendering = textureImageRendering ?? sceneCtx?.textureImageRendering;
3374
+ const effectiveTextureBackend = textureBackend ?? sceneCtx?.textureBackend;
3375
+ const effectiveTextureProjection = textureProjection ?? sceneCtx?.textureProjection;
3376
+ const effectiveDirectional = sceneCtx?.directionalLight;
3377
+ const effectiveAmbient = sceneCtx?.ambientLight;
3378
+ const directVoxelEnabled = Boolean(
3379
+ externalVoxelSource && localPolygons === null && !renderPolygon && !hasStaticChildren && effectiveTextureLighting === "baked" && !castShadow
3380
+ );
3381
+ const sceneDirectionalLight = sceneCtx?.directionalLight;
3382
+ const dynamicLightOverride = useMemo5(() => {
3383
+ if (effectiveTextureLighting !== "dynamic") return null;
3384
+ if (!rotation || rotation[0] === 0 && rotation[1] === 0 && rotation[2] === 0) return null;
3385
+ if (!sceneDirectionalLight) return null;
3386
+ const dir = sceneDirectionalLight.direction;
3387
+ const localDir = inverseRotateVec3(dir, rotation);
3388
+ const len = Math.hypot(localDir[0], localDir[1], localDir[2]) || 1;
3389
+ return {
3390
+ ["--plx"]: (localDir[0] / len).toFixed(2),
3391
+ ["--ply"]: (localDir[1] / len).toFixed(2),
3392
+ ["--plz"]: (localDir[2] / len).toFixed(2)
3393
+ };
3394
+ }, [effectiveTextureLighting, rotation, sceneDirectionalLight]);
3395
+ const bakedDirectional = useMemo5(() => {
3396
+ if (!effectiveDirectional) return effectiveDirectional;
3397
+ const rot = bakedRotation ?? [0, 0, 0];
3398
+ const cssLight = worldDirectionalLightToCss(effectiveDirectional);
3399
+ if (rot[0] === 0 && rot[1] === 0 && rot[2] === 0) return cssLight;
3400
+ return {
3401
+ ...cssLight,
3402
+ direction: inverseRotateVec3(cssLight.direction, rot)
3403
+ };
3404
+ }, [effectiveDirectional, bakedRotation]);
3405
+ const bakedPointLights = useMemo5(() => {
3406
+ const pls = sceneCtx?.pointLights;
3407
+ if (!pls || pls.length === 0) return void 0;
3408
+ const pos = position ?? [0, 0, 0];
3409
+ const rot = bakedRotation ?? [0, 0, 0];
3410
+ const hasRot = rot[0] !== 0 || rot[1] !== 0 || rot[2] !== 0;
3411
+ return pls.map((pl) => {
3412
+ const rel = [pl.position[0] - pos[0], pl.position[1] - pos[1], pl.position[2] - pos[2]];
3413
+ const local = hasRot ? inverseRotateVec3(rel, rot) : rel;
3414
+ return { ...pl, position: local };
3415
+ });
3416
+ }, [sceneCtx?.pointLights, position, bakedRotation]);
3417
+ const lightOccludedPolyIndices = void 0;
3418
+ const atlasPlans = useMemo5(
3419
+ () => {
3420
+ if (renderPolygon || directVoxelEnabled) return [];
3421
+ const repairEdges = buildTextureEdgeRepairSets(polygons);
3422
+ const seamBleedEdges = effectiveSeamBleed === "auto" || typeof effectiveSeamBleed === "number" && Number.isFinite(effectiveSeamBleed) && effectiveSeamBleed > 0 ? buildSeamBleedPolygonEdges(polygons, {
3423
+ directionalLight: bakedDirectional,
3424
+ ambientLight: effectiveAmbient
3425
+ }) : null;
3426
+ const basisHints = buildBasisHints(polygons, {
3427
+ directionalLight: bakedDirectional,
3428
+ ambientLight: effectiveAmbient
3429
+ });
3430
+ return polygons.map((p, i) => computeTextureAtlasPlan(
3431
+ p,
3432
+ i,
3433
+ {
3434
+ directionalLight: bakedDirectional,
3435
+ pointLights: bakedPointLights,
3436
+ ambientLight: effectiveAmbient,
3437
+ seamBleed: seamBleedEdges?.has(i) ? effectiveSeamBleed : void 0,
3438
+ seamEdges: seamBleedEdges?.get(i),
3439
+ textureEdgeRepairEdges: repairEdges[i],
3440
+ lightOccludedPolyIndices
3441
+ },
3442
+ basisHints[i]
3443
+ ));
3444
+ },
3445
+ [renderPolygon, directVoxelEnabled, polygons, bakedDirectional, bakedPointLights, effectiveAmbient, effectiveSeamBleed, lightOccludedPolyIndices]
3446
+ );
3447
+ const textureAtlas = useTextureAtlas(
3448
+ atlasPlans,
3449
+ effectiveTextureLighting,
3450
+ textureQuality,
3451
+ effectiveTextureLeafSizing,
3452
+ effectiveTextureBackend,
3453
+ effectiveTextureImageRendering,
3454
+ effectiveTextureProjection,
3455
+ effectiveStrategies,
3456
+ atomicAtlas
3457
+ );
3458
+ textureReadyRef.current = textureAtlas.ready;
3459
+ useEffect6(() => {
3460
+ if (textureAtlas.ready) resolveTextureReadyWaiters();
3461
+ }, [textureAtlas.ready, resolveTextureReadyWaiters]);
3462
+ useEffect6(() => resolveTextureReadyWaiters, [resolveTextureReadyWaiters]);
3463
+ const solidPaintDefaults = useMemo5(
3464
+ () => !renderPolygon ? getSolidPaintDefaults(textureAtlas.plans, effectiveTextureLighting, effectiveStrategies) : {},
3465
+ [renderPolygon, textureAtlas.plans, effectiveTextureLighting, effectiveStrategies]
3466
+ );
3467
+ const onFrameReadyRef = useRef7(onFrameReady);
3468
+ onFrameReadyRef.current = onFrameReady;
3469
+ useLayoutEffect(() => {
3470
+ if (atomicAtlas && textureAtlas.ready) onFrameReadyRef.current?.();
3471
+ }, [textureAtlas.entries]);
3472
+ const defaultPaintVars = useMemo5(
3473
+ () => solidPaintVars(solidPaintDefaults),
3474
+ [solidPaintDefaults]
3475
+ );
3476
+ const meshIdRef = useRef7(/* @__PURE__ */ Symbol());
3477
+ const sceneRegisterShadowCaster = sceneCtx?.registerShadowCaster;
3478
+ const renderedPolygonIndices = useMemo5(() => {
3479
+ const dedupDrop = findOverlappingPolygonDuplicates(polygons, {
3480
+ normalTolerance: 0.1,
3481
+ distanceTolerance: 0.5,
3482
+ overlapFraction: 0.95,
3483
+ preserveDoubleSidedBackfaces: false
3484
+ });
3485
+ const s = /* @__PURE__ */ new Set();
3486
+ for (let i = 0; i < atlasPlans.length; i++) {
3487
+ if (atlasPlans[i] && !dedupDrop.has(i)) s.add(i);
3488
+ }
3489
+ return s;
3490
+ }, [atlasPlans, polygons]);
3491
+ const shadowCasterRegisteredRef = useRef7(false);
3492
+ const lastShadowPolyCountRef = useRef7(-1);
3493
+ useEffect6(() => {
3494
+ if (!sceneRegisterShadowCaster || !castShadow) return;
3495
+ return () => {
3496
+ sceneRegisterShadowCaster(meshIdRef.current, null);
3497
+ shadowCasterRegisteredRef.current = false;
3498
+ lastShadowPolyCountRef.current = -1;
3499
+ };
3500
+ }, [sceneRegisterShadowCaster, castShadow]);
3501
+ useEffect6(() => {
3502
+ if (!sceneRegisterShadowCaster || !castShadow) return;
3503
+ const followAnimation = sceneCtx?.shadow?.followAnimation ?? false;
3504
+ const topologyChanged = polygons.length !== lastShadowPolyCountRef.current;
3505
+ if (shadowCasterRegisteredRef.current && !followAnimation && !topologyChanged) return;
3506
+ lastShadowPolyCountRef.current = polygons.length;
3507
+ shadowCasterRegisteredRef.current = true;
3508
+ sceneRegisterShadowCaster(meshIdRef.current, {
3509
+ polygons,
3510
+ position: position ?? [0, 0, 0],
3511
+ scale,
3512
+ rotation,
3513
+ renderedPolygonIndices,
3514
+ shadowDefinition
3515
+ });
3516
+ }, [sceneRegisterShadowCaster, castShadow, polygons, position, scale, rotation, renderedPolygonIndices, shadowDefinition, sceneCtx?.shadow]);
3517
+ const sceneRegisterShadowReceiver = sceneCtx?.registerShadowReceiver;
3518
+ useEffect6(() => {
3519
+ if (!sceneRegisterShadowReceiver) return;
3520
+ sceneRegisterShadowReceiver(meshIdRef.current, !!receiveShadow);
3521
+ return () => {
3522
+ sceneRegisterShadowReceiver(meshIdRef.current, false);
3523
+ };
3524
+ }, [sceneRegisterShadowReceiver, receiveShadow]);
3525
+ const bakedShadowGroundCssZ = sceneCtx?.groundCssZ ?? null;
3526
+ const sceneShadow = sceneCtx?.shadow;
3527
+ const sceneHasReceiver = sceneCtx?.hasShadowReceiver ?? false;
3528
+ const shadowSvgNode = useMemo5(() => {
3529
+ if (!castShadow || renderPolygon) return null;
3530
+ if (sceneHasReceiver) return null;
3531
+ if (bakedShadowGroundCssZ === null) return null;
3532
+ const userGroundLightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
3533
+ const lightDir = worldDirectionToCss(userGroundLightDir);
3534
+ const meshPosZ = position?.[2] ?? 0;
3535
+ const localGroundCssZ = bakedShadowGroundCssZ - meshPosZ * BASE_TILE2;
3536
+ const shadowDedupDrop = findOverlappingPolygonDuplicates(polygons, {
3537
+ normalTolerance: 0.1,
3538
+ distanceTolerance: 0.5,
3539
+ overlapFraction: 0.95,
3540
+ preserveDoubleSidedBackfaces: false
3541
+ });
3542
+ const projections = [];
3543
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
3544
+ let fpMinX = Infinity, fpMinY = Infinity, fpMaxX = -Infinity, fpMaxY = -Infinity;
3545
+ for (let i = 0; i < polygons.length; i++) {
3546
+ const polygon = polygons[i];
3547
+ if (shadowDedupDrop.has(i)) continue;
3548
+ const plan = atlasPlans[i];
3549
+ if (!plan) continue;
3550
+ const projected = [];
3551
+ for (const v of polygon.vertices) {
3552
+ const cssVertex = [
3553
+ v[1] * BASE_TILE2,
3554
+ v[0] * BASE_TILE2,
3555
+ v[2] * BASE_TILE2
3556
+ ];
3557
+ if (cssVertex[0] < fpMinX) fpMinX = cssVertex[0];
3558
+ if (cssVertex[1] < fpMinY) fpMinY = cssVertex[1];
3559
+ if (cssVertex[0] > fpMaxX) fpMaxX = cssVertex[0];
3560
+ if (cssVertex[1] > fpMaxY) fpMaxY = cssVertex[1];
3561
+ const p = projectCssVertexToGround(cssVertex, lightDir, localGroundCssZ);
3562
+ projected.push(p);
3563
+ if (p[0] < minX) minX = p[0];
3564
+ if (p[1] < minY) minY = p[1];
3565
+ if (p[0] > maxX) maxX = p[0];
3566
+ if (p[1] > maxY) maxY = p[1];
3567
+ }
3568
+ projections.push(projected);
3569
+ }
3570
+ if (projections.length === 0) return null;
3571
+ const maxExtend = sceneShadow?.maxExtend ?? 2e3;
3572
+ const bx0 = Math.max(minX, fpMinX - maxExtend);
3573
+ const by0 = Math.max(minY, fpMinY - maxExtend);
3574
+ const bx1 = Math.min(maxX, fpMaxX + maxExtend);
3575
+ const by1 = Math.min(maxY, fpMaxY + maxExtend);
3576
+ const width = bx1 - bx0;
3577
+ const height = by1 - by0;
3578
+ if (!(width > 0) || !(height > 0)) return null;
3579
+ const shadowColor = sceneShadow?.color ?? "#000000";
3580
+ const shadowOpacity = sceneShadow?.opacity ?? 0.25;
3581
+ const parsed = parseHexColor(shadowColor)?.rgb ?? [0, 0, 0];
3582
+ let d = "";
3583
+ for (const verts of projections) {
3584
+ const ccw = ensureCcw2D(verts);
3585
+ d += `M${(ccw[0][0] - bx0).toFixed(3)},${(ccw[0][1] - by0).toFixed(3)}`;
3586
+ for (let j = 1; j < ccw.length; j++) {
3587
+ d += `L${(ccw[j][0] - bx0).toFixed(3)},${(ccw[j][1] - by0).toFixed(3)}`;
3588
+ }
3589
+ d += "Z";
3590
+ }
3591
+ return /* @__PURE__ */ jsx8(
3592
+ "svg",
3593
+ {
3594
+ className: "polycss-shadow polycss-shadow-svg",
3595
+ width,
3596
+ height,
3597
+ viewBox: `0 0 ${width} ${height}`,
3598
+ style: {
3599
+ position: "absolute",
3600
+ top: 0,
3601
+ left: 0,
3602
+ display: "block",
3603
+ overflow: "hidden",
3604
+ transformOrigin: "0 0",
3605
+ pointerEvents: "none",
3606
+ willChange: "transform",
3607
+ transform: `translate3d(${bx0.toFixed(3)}px,${by0.toFixed(3)}px,${localGroundCssZ.toFixed(3)}px)`
3608
+ },
3609
+ children: /* @__PURE__ */ jsx8(
3610
+ "path",
3611
+ {
3612
+ d,
3613
+ fill: `rgb(${parsed[0]},${parsed[1]},${parsed[2]})`,
3614
+ fillRule: "nonzero",
3615
+ stroke: `rgb(${parsed[0]},${parsed[1]},${parsed[2]})`,
3616
+ strokeWidth: "3",
3617
+ strokeLinejoin: "round",
3618
+ opacity: shadowOpacity.toFixed(4)
3619
+ }
3620
+ )
3621
+ },
3622
+ "shadow-svg"
3623
+ );
3624
+ }, [castShadow, renderPolygon, polygons, atlasPlans, sceneDirectionalLight, bakedShadowGroundCssZ, sceneShadow, sceneHasReceiver]);
3625
+ const shadowCasters = sceneCtx?.shadowCasters;
3626
+ const shadowCastersVersion = sceneCtx?.shadowCastersVersion ?? 0;
3627
+ const [cameraTick, setCameraTick] = useState5(0);
3628
+ useEffect6(() => {
3629
+ if (!receiveShadow) return;
3630
+ return cameraCtx?.store.subscribe(() => setCameraTick((n) => n + 1));
3631
+ }, [receiveShadow, cameraCtx?.store]);
3632
+ void cameraTick;
3633
+ const selfShadowEdgeMap = useMemo5(
3634
+ () => receiveShadow ? buildSharedEdgeMap(polygons) : void 0,
3635
+ [polygons, receiveShadow]
3636
+ );
3637
+ const receiverShadowSvgs = useMemo5(() => {
3638
+ if (!receiveShadow) return null;
3639
+ if (!shadowCasters || shadowCasters.size === 0) return null;
3640
+ const userLightDir = sceneDirectionalLight?.direction ?? [0.4, -0.7, 0.59];
3641
+ const lightDir = worldDirectionToCss(userLightDir);
3642
+ const dynamicShading = effectiveTextureLighting === "dynamic";
3643
+ const scenePoints = dynamicShading ? [] : sceneCtx?.pointLights ?? [];
3644
+ const allPointLightsCss = scenePoints.map((pl) => ({
3645
+ position: worldPositionToCss(pl.position),
3646
+ color: pl.color,
3647
+ intensity: pl.intensity
3648
+ }));
3649
+ const shadowPointIndices = scenePoints.map((pl, i) => pl.castShadow ? i : -1).filter((i) => i >= 0);
3650
+ const runDirectionalShadow = !!sceneDirectionalLight?.direction && (sceneDirectionalLight.intensity ?? 1) > 0;
3651
+ const hasShadowPoints = shadowPointIndices.length > 0;
3652
+ const shadowLift = sceneShadow?.lift ?? 1e-3;
3653
+ const planes = prepareReceiverFacePlanes(
3654
+ polygons,
3655
+ position ?? [0, 0, 0],
3656
+ scale,
3657
+ /* @__PURE__ */ new Set(),
3658
+ shadowLift,
3659
+ rotation
3660
+ );
3661
+ if (planes.length === 0) return null;
3662
+ const casterInputs = [];
3663
+ for (const [casterId, data] of shadowCasters) {
3664
+ const items = prepareCasterPolyItems(
3665
+ data.polygons,
3666
+ data.position,
3667
+ data.scale,
3668
+ () => true,
3669
+ data.rotation ?? null
3670
+ );
3671
+ const isSelf = data.polygons === polygons;
3672
+ const selfMap = isSelf ? selfShadowEdgeMap : void 0;
3673
+ let edgeOwners;
3674
+ if (!isSelf && (data.polygons.length >= 40 || hasShadowPoints)) {
3675
+ const dposArr = data.position;
3676
+ const drot = data.rotation ?? null;
3677
+ const dsKey = JSON.stringify(data.scale ?? null);
3678
+ const eoKey = `${dposArr[0]},${dposArr[1]},${dposArr[2]}|${drot ? drot.join(",") : "n"}|${dsKey}`;
3679
+ let cachedOwners = reactEdgeOwnersCache.get(data.polygons);
3680
+ if (cachedOwners === void 0 || reactEdgeOwnersCacheKey.get(data.polygons) !== eoKey) {
3681
+ cachedOwners = prepareCasterEdgeOwners(data.polygons, dposArr, data.scale, drot);
3682
+ reactEdgeOwnersCache.set(data.polygons, cachedOwners);
3683
+ reactEdgeOwnersCacheKey.set(data.polygons, eoKey);
3684
+ }
3685
+ edgeOwners = cachedOwners;
3686
+ }
3687
+ let overrideSilhouette;
3688
+ let overridePointSilhouettes;
3689
+ if (sceneShadow?.parametric) {
3690
+ const def = data.shadowDefinition ?? sceneShadow.definition ?? 16;
3691
+ const result = buildParametricCasterOverride({
3692
+ polysWorldVerts: items.map((it) => it.wv),
3693
+ lightDir,
3694
+ definition: def,
3695
+ isSelf,
3696
+ style: sceneShadow.style,
3697
+ pointLights: shadowPointIndices.map((i) => ({ position: allPointLightsCss[i].position, index: i }))
3698
+ });
3699
+ overrideSilhouette = result.overrideSilhouette;
3700
+ overridePointSilhouettes = result.overridePointSilhouettes;
3701
+ }
3702
+ casterInputs.push({
3703
+ id: casterId,
3704
+ items,
3705
+ selfShadowEdgeMap: selfMap,
3706
+ edgeOwners,
3707
+ casterPolygonCount: data.polygons.length,
3708
+ overrideSilhouette,
3709
+ overridePointSilhouettes
3710
+ });
3711
+ }
3712
+ const cameraState = cameraCtx?.store.getState().cameraState;
3713
+ const cameraRot = {
3714
+ rotX: cameraState?.rotX ?? 65,
3715
+ rotY: cameraState?.rotY ?? 45,
3716
+ meshRotation: rotation
3717
+ };
3718
+ const faces = computeMergedReceiverShadows({
3719
+ receiverPlanes: planes,
3720
+ receiverPolygons: polygons,
3721
+ receiverHasTexture: polygons.some((p) => p.texture !== void 0),
3722
+ casters: casterInputs,
3723
+ lightDir,
3724
+ runDirectional: runDirectionalShadow,
3725
+ pointPasses: shadowPointIndices.map((i) => ({ lightPos: allPointLightsCss[i].position, index: i })),
3726
+ allPointLights: allPointLightsCss,
3727
+ cameraRot,
3728
+ ambientLight: sceneCtx?.ambientLight,
3729
+ directionalLight: sceneDirectionalLight,
3730
+ shadow: { color: sceneShadow?.color, opacity: sceneShadow?.opacity ?? 0.25, maxExtend: sceneShadow?.maxExtend }
3731
+ });
3732
+ if (faces.length === 0) return null;
3733
+ return /* @__PURE__ */ jsx8(Fragment, { children: faces.map((fc) => /* @__PURE__ */ jsxs(
3734
+ "svg",
3735
+ {
3736
+ className: "polycss-shadow polycss-shadow-svg polycss-shadow-receiver",
3737
+ "data-poly-shadow-type": "receiver",
3738
+ "data-poly-shadow-receiver-face": fc.faceIndex,
3739
+ "data-poly-shadow-receiver-polys": JSON.stringify(fc.memberPolyIndices),
3740
+ width: fc.width,
3741
+ height: fc.height,
3742
+ viewBox: `0 0 ${fc.width} ${fc.height}`,
3743
+ style: {
3744
+ position: "absolute",
3745
+ top: 0,
3746
+ left: 0,
3747
+ display: "block",
3748
+ overflow: "hidden",
3749
+ transformOrigin: "0 0",
3750
+ pointerEvents: "none",
3751
+ willChange: "transform",
3752
+ opacity: fc.svgOpacity,
3753
+ transform: fc.matrixCss
3754
+ },
3755
+ children: [
3756
+ fc.baseFill && fc.baseD ? /* @__PURE__ */ jsx8("path", { d: fc.baseD, fill: fc.baseFill, fillRule: "nonzero" }) : null,
3757
+ fc.layers.map((layer, i) => /* @__PURE__ */ jsx8(
3758
+ "path",
3759
+ {
3760
+ d: layer.d,
3761
+ fill: layer.fill,
3762
+ fillRule: "nonzero",
3763
+ opacity: layer.opacity !== 1 ? layer.opacity.toFixed(4) : void 0,
3764
+ style: layer.multiply ? { mixBlendMode: "multiply" } : void 0
3765
+ },
3766
+ i
3767
+ ))
3768
+ ]
3769
+ },
3770
+ `receiver-${fc.faceIndex}`
3771
+ )) });
3772
+ }, [receiveShadow, shadowCasters, shadowCastersVersion, polygons, position, scale, rotation, sceneDirectionalLight, sceneCtx?.pointLights, effectiveTextureLighting, sceneShadow, sceneCtx?.ambientLight, cameraCtx?.store, cameraTick, selfShadowEdgeMap]);
3773
+ const portalSceneEl = sceneCtx?.sceneEl ?? null;
3774
+ const portaledReceiverShadowSvgs = portalSceneEl && receiverShadowSvgs ? createPortal(receiverShadowSvgs, portalSceneEl) : null;
3775
+ setPolygonsImplRef.current = (nextPolygons) => {
3776
+ const nextRenderedPolygons = autoCenter ? recenterPolygons(nextPolygons) : nextPolygons;
3777
+ polygonsRef.current = nextRenderedPolygons;
3778
+ const root = wrapperRef.current;
3779
+ if (root && !renderPolygon && updateStableTriangleDom(root, nextRenderedPolygons, {
3780
+ directionalLight: bakedDirectional,
3781
+ ambientLight: effectiveAmbient,
3782
+ textureLighting: effectiveTextureLighting,
3783
+ strategies: effectiveStrategies,
3784
+ seamBleed: effectiveSeamBleed,
3785
+ colorFrame: ++stableTriangleColorFrameRef.current,
3786
+ // Animated low-poly triangles can swing face normals sharply; keep the
3787
+ // mounted baked color pinned and animate transforms only.
3788
+ colorFreezeFrames: 0
3789
+ })) {
3790
+ return;
3791
+ }
3792
+ setLocalPolygons([...nextPolygons]);
3793
+ };
3794
+ const voxelRendererRef = useRef7(null);
3795
+ useLayoutEffect(() => {
3796
+ const root = wrapperRef.current;
3797
+ voxelRendererRef.current?.dispose();
3798
+ voxelRendererRef.current = null;
3799
+ if (!directVoxelEnabled || !root) return;
3800
+ const renderer = createPolyVoxelRenderer({
3801
+ doc: root.ownerDocument,
3802
+ wrapper: root,
3803
+ polygons,
3804
+ directionalLight: bakedDirectional,
3805
+ ambientLight: effectiveAmbient
3806
+ });
3807
+ if (!renderer) return;
3808
+ const cameraRotation = () => {
3809
+ const cameraState = cameraCtx?.store.getState().cameraState;
3810
+ return {
3811
+ rotX: cameraState?.rotX ?? 65,
3812
+ rotY: cameraState?.rotY ?? 45,
3813
+ meshRotation: rotation
3814
+ };
3815
+ };
3816
+ voxelRendererRef.current = renderer;
3817
+ renderer.render(cameraRotation());
3818
+ const unsubscribe = cameraCtx?.store.subscribe(() => {
3819
+ renderer.syncCamera(cameraRotation());
3820
+ });
3821
+ return () => {
3822
+ unsubscribe?.();
3823
+ renderer.dispose();
3824
+ if (voxelRendererRef.current === renderer) voxelRendererRef.current = null;
3825
+ };
3826
+ }, [
3827
+ directVoxelEnabled,
3828
+ polygons,
3829
+ bakedDirectional,
3830
+ effectiveAmbient,
3831
+ cameraCtx?.store,
3832
+ rotation
3833
+ ]);
3834
+ const wrapperStyle = {
3835
+ transform,
3836
+ ...dynamicLightOverride,
3837
+ ...style,
3838
+ ...defaultPaintVars
3839
+ };
3840
+ const renderedPolygons = renderPolygon ? polygons.map((p, i) => (
3841
+ // Render-prop: caller controls how each polygon renders. We still
3842
+ // wrap in a fragment with key so React reconciliation works.
3843
+ /* @__PURE__ */ jsx8(RenderPropPolygon, { polygon: p, index: i, children: renderPolygon }, i)
3844
+ )) : textureAtlas.entries.map((entry, index) => {
3845
+ if (entry) {
3846
+ return /* @__PURE__ */ jsx8(
3847
+ TextureAtlasPoly,
3848
+ {
3849
+ entry,
3850
+ page: textureAtlas.pages[entry.pageIndex],
3851
+ textureLighting: effectiveTextureLighting,
3852
+ textureImageRendering: effectiveTextureImageRendering,
3853
+ solidPaintDefaults
3854
+ },
3855
+ entry.index
3856
+ );
3857
+ }
3858
+ const plan = textureAtlas.plans[index];
3859
+ const imageGeometry = plan ? resolvePolyTextureLeafGeometry(plan, {
3860
+ imageRendering: effectiveTextureImageRendering,
3861
+ backend: effectiveTextureBackend,
3862
+ projection: effectiveTextureProjection
3863
+ }) : null;
3864
+ if (plan && imageGeometry) {
3865
+ return /* @__PURE__ */ jsx8(
3866
+ TextureImagePoly,
3867
+ {
3868
+ plan,
3869
+ geometry: imageGeometry
3870
+ },
3871
+ plan.index
3872
+ );
3873
+ }
3874
+ if (!plan || plan.texture) return null;
3875
+ if (isProjectiveQuadPlan(plan)) {
3876
+ return /* @__PURE__ */ jsx8(
3877
+ TextureProjectiveSolidPoly,
3878
+ {
3879
+ entry: plan,
3880
+ textureLighting: effectiveTextureLighting,
3881
+ solidPaintDefaults
3882
+ },
3883
+ plan.index
3884
+ );
3885
+ }
3886
+ if (isSolidTrianglePlan2(plan)) {
3887
+ return /* @__PURE__ */ jsx8(
3888
+ TextureTrianglePoly,
3889
+ {
3890
+ entry: plan,
3891
+ textureLighting: effectiveTextureLighting,
3892
+ solidPaintDefaults
3893
+ },
3894
+ plan.index
3895
+ );
3896
+ }
3897
+ const cornerGeo = !disabledStrategies?.has("i") ? cornerShapeGeometryForPlan(plan) : null;
3898
+ if (cornerGeo) {
3899
+ return /* @__PURE__ */ jsx8(
3900
+ TextureCornerShapeSolidPoly,
3901
+ {
3902
+ entry: plan,
3903
+ geometry: cornerGeo,
3904
+ textureLighting: effectiveTextureLighting,
3905
+ solidPaintDefaults
3906
+ },
3907
+ plan.index
3908
+ );
3909
+ }
3910
+ return /* @__PURE__ */ jsx8(
3911
+ TextureBorderShapePoly,
3912
+ {
3913
+ entry: plan,
3914
+ textureLighting: effectiveTextureLighting,
3915
+ solidPaintDefaults,
3916
+ disabledStrategies
3917
+ },
3918
+ plan.index
3919
+ );
3920
+ });
3921
+ if (src) {
3922
+ if (fetched.loading && fetched.polygons.length === 0) {
3923
+ return /* @__PURE__ */ jsx8(
3924
+ "div",
3925
+ {
3926
+ ref: wrapperRef,
3927
+ "data-poly-mesh-id": id,
3928
+ className: `polycss-mesh polycss-mesh-loading${className ? ` ${className}` : ""}`,
3929
+ style: wrapperStyle,
3930
+ ...wrapperHandlers,
3931
+ children: fallback ?? null
3932
+ }
3933
+ );
3934
+ }
3935
+ if (fetched.error && fetched.polygons.length === 0) {
3936
+ return /* @__PURE__ */ jsx8(
3937
+ "div",
3938
+ {
3939
+ ref: wrapperRef,
3940
+ "data-poly-mesh-id": id,
3941
+ className: `polycss-mesh polycss-mesh-error${className ? ` ${className}` : ""}`,
3942
+ style: wrapperStyle,
3943
+ ...wrapperHandlers,
3944
+ children: errorFallback ? errorFallback(fetched.error) : null
3945
+ }
3946
+ );
3947
+ }
3948
+ }
3949
+ return /* @__PURE__ */ jsxs(
3950
+ "div",
3951
+ {
3952
+ ref: wrapperRef,
3953
+ "data-poly-mesh-id": id,
3954
+ className: `polycss-mesh${directVoxelEnabled ? " polycss-voxel-mesh" : ""}${className ? ` ${className}` : ""}`,
3955
+ style: wrapperStyle,
3956
+ ...wrapperHandlers,
3957
+ children: [
3958
+ shadowSvgNode,
3959
+ portaledReceiverShadowSvgs,
3960
+ renderedPolygons,
3961
+ staticChildren
3962
+ ]
3963
+ }
3964
+ );
3965
+ });
3966
+ function RenderPropPolygon({
3967
+ polygon,
3968
+ index,
3969
+ children
3970
+ }) {
3971
+ return /* @__PURE__ */ jsx8(Fragment, { children: children(polygon, index) });
3972
+ }
3973
+
3974
+ // src/three/PolyThreeMesh.tsx
3975
+ import { Fragment as Fragment2, jsx as jsx9 } from "react/jsx-runtime";
3976
+ function applyObjectProps(object, {
3977
+ position,
3978
+ rotation,
3979
+ scale
3980
+ }) {
3981
+ if (position) object.position.set(position[0], position[1], position[2]);
3982
+ if (rotation) object.rotation.set(rotation[0], rotation[1], rotation[2]);
3983
+ if (typeof scale === "number") object.scale.set(scale, scale, scale);
3984
+ else if (scale) object.scale.set(scale[0], scale[1], scale[2]);
3985
+ return object;
3986
+ }
3987
+ function recenterPolygons2(polygons) {
3988
+ if (polygons.length === 0) return polygons;
3989
+ const bbox = computeSceneBbox2(polygons);
3990
+ const center = [
3991
+ (bbox.min[0] + bbox.max[0]) / 2,
3992
+ (bbox.min[1] + bbox.max[1]) / 2,
3993
+ (bbox.min[2] + bbox.max[2]) / 2
3994
+ ];
3995
+ if (center[0] === 0 && center[1] === 0 && center[2] === 0) return polygons;
3996
+ const shift = (v) => [v[0] - center[0], v[1] - center[1], v[2] - center[2]];
3997
+ return polygons.map((polygon) => ({
3998
+ ...polygon,
3999
+ vertices: polygon.vertices.map(shift),
4000
+ ...polygon.textureTriangles?.length ? {
4001
+ textureTriangles: polygon.textureTriangles.map((triangle) => ({
4002
+ ...triangle,
4003
+ vertices: triangle.vertices.map(shift)
4004
+ }))
4005
+ } : null
4006
+ }));
4007
+ }
4008
+ function PolyThreeMeshInner({
4009
+ object: objectProp,
4010
+ polygons: polygonsProp,
4011
+ src,
4012
+ mtl,
4013
+ position,
4014
+ rotation,
4015
+ scale,
4016
+ autoCenter = false,
4017
+ parseOptions,
4018
+ meshResolution,
4019
+ fallback,
4020
+ errorFallback,
4021
+ ...meshProps
4022
+ }) {
4023
+ const objectRef = useRef8(objectProp ?? new Object3D());
4024
+ const mergedOptions = useMemo6(() => {
4025
+ if (!mtl && !parseOptions && meshResolution === void 0) return void 0;
4026
+ return {
4027
+ ...parseOptions ?? {},
4028
+ ...mtl ? { mtlUrl: mtl } : {},
4029
+ ...meshResolution !== void 0 ? { meshResolution } : {}
4030
+ };
4031
+ }, [mtl, parseOptions, meshResolution]);
4032
+ const fetched = usePolyMesh(src ?? "", mergedOptions);
4033
+ const rawPolygons = polygonsProp ?? (src ? fetched.polygons : []);
4034
+ const sourcePolygons = useMemo6(
4035
+ () => autoCenter ? recenterPolygons2(rawPolygons) : rawPolygons,
4036
+ [rawPolygons, autoCenter]
4037
+ );
4038
+ const polyPolygons = useMemo6(() => {
4039
+ const object = objectProp ?? objectRef.current;
4040
+ return transformPolygonsToPoly(
4041
+ sourcePolygons,
4042
+ applyObjectProps(object, { position, rotation, scale })
4043
+ );
4044
+ }, [sourcePolygons, objectProp, position, rotation, scale]);
4045
+ if (src && fetched.loading && polyPolygons.length === 0 && fallback !== void 0) {
4046
+ return /* @__PURE__ */ jsx9(Fragment2, { children: fallback });
4047
+ }
4048
+ if (src && fetched.error && errorFallback) {
4049
+ return /* @__PURE__ */ jsx9(Fragment2, { children: errorFallback(fetched.error) });
4050
+ }
4051
+ return /* @__PURE__ */ jsx9(
4052
+ PolyMesh,
4053
+ {
4054
+ ...meshProps,
4055
+ polygons: polyPolygons,
4056
+ autoCenter: false,
4057
+ meshResolution
4058
+ }
4059
+ );
4060
+ }
4061
+ var PolyThreeMesh = memo8(PolyThreeMeshInner);
4062
+ export {
4063
+ AmbientLight,
4064
+ DirectionalLight,
4065
+ Euler,
4066
+ Object3D2 as Object3D,
4067
+ OrthographicCamera2 as OrthographicCamera,
4068
+ PerspectiveCamera2 as PerspectiveCamera,
4069
+ PointLight,
4070
+ PolyThreeMesh,
4071
+ PolyThreeOrthographicCamera,
4072
+ PolyThreePerspectiveCamera,
4073
+ Vector3,
4074
+ polyToThreeDirection,
4075
+ polyToThreePoint,
4076
+ threeToPolyDirection,
4077
+ threeToPolyPoint,
4078
+ transformPointToPoly,
4079
+ transformPolygonsToPoly2 as transformPolygonsToPoly
4080
+ };