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