@layoutit/polycss-react 0.0.1 → 0.2.0
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 +5 -5
- package/dist/index.cjs +2312 -475
- package/dist/index.d.cts +159 -18
- package/dist/index.d.ts +159 -18
- package/dist/index.js +2284 -440
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -20,7 +20,8 @@ import { createIsometricCamera, BASE_TILE } from "@layoutit/polycss-core";
|
|
|
20
20
|
import { useSyncExternalStore, useRef, useCallback } from "react";
|
|
21
21
|
function createSceneStore(initial) {
|
|
22
22
|
let state = {
|
|
23
|
-
cameraState: { ...initial }
|
|
23
|
+
cameraState: { ...initial },
|
|
24
|
+
autoCenterOffset: [0, 0, 0]
|
|
24
25
|
};
|
|
25
26
|
const listeners = /* @__PURE__ */ new Set();
|
|
26
27
|
function notify() {
|
|
@@ -39,15 +40,24 @@ function createSceneStore(initial) {
|
|
|
39
40
|
return () => listeners.delete(listener);
|
|
40
41
|
},
|
|
41
42
|
updateCameraFromRef(handle) {
|
|
42
|
-
state = { cameraState: { ...handle.state } };
|
|
43
|
+
state = { ...state, cameraState: { ...handle.state } };
|
|
43
44
|
notify();
|
|
44
45
|
return true;
|
|
45
46
|
},
|
|
46
47
|
notifyAll() {
|
|
47
48
|
notify();
|
|
49
|
+
},
|
|
50
|
+
setAutoCenterOffset(offset) {
|
|
51
|
+
state = { ...state, autoCenterOffset: offset };
|
|
48
52
|
}
|
|
49
53
|
};
|
|
50
54
|
}
|
|
55
|
+
function useStoreSelector(store, selector) {
|
|
56
|
+
const selectorRef = useRef(selector);
|
|
57
|
+
selectorRef.current = selector;
|
|
58
|
+
const getSnapshot = useCallback(() => selectorRef.current(store.getState()), [store]);
|
|
59
|
+
return useSyncExternalStore(store.subscribe, getSnapshot);
|
|
60
|
+
}
|
|
51
61
|
|
|
52
62
|
// src/camera/useCamera.ts
|
|
53
63
|
function usePolyCamera(options) {
|
|
@@ -80,11 +90,14 @@ function usePolyCamera(options) {
|
|
|
80
90
|
const el = sceneElRef.current;
|
|
81
91
|
if (el) {
|
|
82
92
|
const s = handle.state;
|
|
93
|
+
const [ox, oy, oz] = store.getState().autoCenterOffset;
|
|
83
94
|
const tileSize = BASE_TILE;
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
95
|
+
const wx = s.target[0] + ox;
|
|
96
|
+
const wy = s.target[1] + oy;
|
|
97
|
+
const wz = s.target[2] + oz;
|
|
98
|
+
const cssX = wy * tileSize;
|
|
99
|
+
const cssY = wx * tileSize;
|
|
100
|
+
const cssZ = wz * tileSize;
|
|
88
101
|
const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
|
|
89
102
|
el.style.transform = `${distancePart}scale(${s.zoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
|
|
90
103
|
}
|
|
@@ -97,14 +110,17 @@ function usePolyCamera(options) {
|
|
|
97
110
|
if (!el) return;
|
|
98
111
|
const handle = handleRef.current;
|
|
99
112
|
const s = handle.state;
|
|
113
|
+
const [ox, oy, oz] = store.getState().autoCenterOffset;
|
|
100
114
|
const tileSize = BASE_TILE;
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
115
|
+
const wx = s.target[0] + ox;
|
|
116
|
+
const wy = s.target[1] + oy;
|
|
117
|
+
const wz = s.target[2] + oz;
|
|
118
|
+
const cssX = wy * tileSize;
|
|
119
|
+
const cssY = wx * tileSize;
|
|
120
|
+
const cssZ = wz * tileSize;
|
|
105
121
|
const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
|
|
106
122
|
el.style.transform = `${distancePart}scale(${s.zoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
|
|
107
|
-
}, []);
|
|
123
|
+
}, [store]);
|
|
108
124
|
return {
|
|
109
125
|
store,
|
|
110
126
|
cameraRef: handleRef,
|
|
@@ -198,7 +214,7 @@ var PolyOrthographicCamera = memo2(PolyOrthographicCameraInner);
|
|
|
198
214
|
|
|
199
215
|
// src/scene/PolyScene.tsx
|
|
200
216
|
import { memo as memo3, useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo6, useRef as useRef3 } from "react";
|
|
201
|
-
import {
|
|
217
|
+
import { BASE_TILE as BASE_TILE2, parseHexColor } from "@layoutit/polycss-core";
|
|
202
218
|
|
|
203
219
|
// src/scene/useSceneContext.ts
|
|
204
220
|
import { useMemo as useMemo4 } from "react";
|
|
@@ -242,12 +258,6 @@ var CORE_BASE_STYLES = `
|
|
|
242
258
|
height: 0;
|
|
243
259
|
transform-style: preserve-3d;
|
|
244
260
|
perspective: none;
|
|
245
|
-
transform: var(--scene-transform);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.polycss-offset {
|
|
249
|
-
transform-style: preserve-3d;
|
|
250
|
-
transform: var(--offset-transform);
|
|
251
261
|
}
|
|
252
262
|
|
|
253
263
|
/* \u2500\u2500 Camera wrapper (perspective + interactive drag) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -270,6 +280,22 @@ var CORE_BASE_STYLES = `
|
|
|
270
280
|
position: absolute;
|
|
271
281
|
}
|
|
272
282
|
|
|
283
|
+
/* \u2500\u2500 First-person controls perspective context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
284
|
+
|
|
285
|
+
/* PolyFirstPersonControls toggles this class on its host element (the camera
|
|
286
|
+
wrapper). FPV needs a real perspective context so scene Z translation
|
|
287
|
+
produces visible depth motion - without it, walking forward looks like a
|
|
288
|
+
planar pan. The class wins over inline perspective styles (e.g.
|
|
289
|
+
PolyOrthographicCamera's perspective: none) via !important. The actual
|
|
290
|
+
perspective value is set inline by the controls as the
|
|
291
|
+
--polycss-fpv-perspective custom property; the default of 2000px matches
|
|
292
|
+
the controls' lookOffset fallback so the FPV math and visual perspective
|
|
293
|
+
stay in sync. */
|
|
294
|
+
.polycss-fpv-host {
|
|
295
|
+
perspective: var(--polycss-fpv-perspective, 2000px) !important;
|
|
296
|
+
transform-style: preserve-3d !important;
|
|
297
|
+
}
|
|
298
|
+
|
|
273
299
|
/* \u2500\u2500 Polygon leaf element \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
274
300
|
|
|
275
301
|
/*
|
|
@@ -297,24 +323,37 @@ var CORE_BASE_STYLES = `
|
|
|
297
323
|
background-repeat: no-repeat;
|
|
298
324
|
}
|
|
299
325
|
|
|
326
|
+
.polycss-scene b,
|
|
327
|
+
.polycss-scene i,
|
|
328
|
+
.polycss-scene u {
|
|
329
|
+
color: var(--polycss-paint, currentColor);
|
|
330
|
+
}
|
|
331
|
+
|
|
300
332
|
.polycss-scene b {
|
|
333
|
+
background: currentColor;
|
|
334
|
+
width: 64px;
|
|
335
|
+
height: 64px;
|
|
301
336
|
}
|
|
302
337
|
|
|
303
338
|
.polycss-scene i {
|
|
304
|
-
|
|
305
|
-
|
|
339
|
+
width: 16px;
|
|
340
|
+
height: 16px;
|
|
306
341
|
border-color: currentColor;
|
|
307
342
|
}
|
|
308
343
|
|
|
309
344
|
.polycss-scene s {
|
|
345
|
+
width: var(--polycss-atlas-size, 64px);
|
|
346
|
+
height: var(--polycss-atlas-size, 64px);
|
|
310
347
|
}
|
|
311
348
|
|
|
312
349
|
.polycss-scene u {
|
|
313
|
-
width:
|
|
314
|
-
height:
|
|
350
|
+
width: 0;
|
|
351
|
+
height: 0;
|
|
315
352
|
background: transparent;
|
|
316
353
|
box-sizing: content-box;
|
|
317
354
|
border: 0 solid transparent;
|
|
355
|
+
border-color: transparent transparent currentColor transparent;
|
|
356
|
+
border-width: 0 32px 64px 32px;
|
|
318
357
|
}
|
|
319
358
|
|
|
320
359
|
/* \u2500\u2500 Gizmo override \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -327,17 +366,43 @@ var CORE_BASE_STYLES = `
|
|
|
327
366
|
* up half-culled (you see only the side faces, not the caps), and the
|
|
328
367
|
* arrow looks like a flat strip instead of a 3D bar.
|
|
329
368
|
*
|
|
330
|
-
* Transitions on border-color and background-color smooth the
|
|
331
|
-
* idle \u2192 hover \u2192 drag alpha changes
|
|
332
|
-
*
|
|
333
|
-
* (rgb-with-alpha CSS calc); transitioning both covers either path.
|
|
369
|
+
* Transitions on color, border-color, and background-color smooth the
|
|
370
|
+
* idle \u2192 hover \u2192 drag alpha changes across rect, border-shape, triangle,
|
|
371
|
+
* and atlas paths.
|
|
334
372
|
*/
|
|
335
373
|
.polycss-transform-controls i,
|
|
336
374
|
.polycss-transform-controls b,
|
|
337
375
|
.polycss-transform-controls s,
|
|
338
376
|
.polycss-transform-controls u {
|
|
339
377
|
backface-visibility: visible;
|
|
340
|
-
transition: border-color 150ms ease-out, background-color 150ms ease-out;
|
|
378
|
+
transition: color 150ms ease-out, border-color 150ms ease-out, background-color 150ms ease-out;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/*
|
|
382
|
+
* Rotate rings are rendered as a single square quad per ring, then masked
|
|
383
|
+
* to a donut via a radial-gradient. --ring-inner-ratio is set inline by
|
|
384
|
+
* <PolyTransformControls> (= innerR / outerR, where outerR is the edge of
|
|
385
|
+
* the quad mapped to 50%). Hit-testing also uses the donut shape \u2014 see
|
|
386
|
+
* the ring-aware path in TransformControls.tsx. Single DOM node per ring.
|
|
387
|
+
*/
|
|
388
|
+
.polycss-transform-ring i,
|
|
389
|
+
.polycss-transform-ring b,
|
|
390
|
+
.polycss-transform-ring s,
|
|
391
|
+
.polycss-transform-ring u {
|
|
392
|
+
--ring-inner-r: calc(var(--ring-inner-ratio, 0.92) * 50%);
|
|
393
|
+
--ring-outer-r: calc(var(--ring-outer-ratio, 1) * 50%);
|
|
394
|
+
-webkit-mask: radial-gradient(circle at 50% 50%,
|
|
395
|
+
transparent 0%,
|
|
396
|
+
transparent var(--ring-inner-r),
|
|
397
|
+
black var(--ring-inner-r),
|
|
398
|
+
black var(--ring-outer-r),
|
|
399
|
+
transparent var(--ring-outer-r));
|
|
400
|
+
mask: radial-gradient(circle at 50% 50%,
|
|
401
|
+
transparent 0%,
|
|
402
|
+
transparent var(--ring-inner-r),
|
|
403
|
+
black var(--ring-inner-r),
|
|
404
|
+
black var(--ring-outer-r),
|
|
405
|
+
transparent var(--ring-outer-r));
|
|
341
406
|
}
|
|
342
407
|
|
|
343
408
|
/* \u2500\u2500 Dynamic lighting cascade vars (scene root \u2192 polygons) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -357,6 +422,20 @@ var CORE_BASE_STYLES = `
|
|
|
357
422
|
@property --plx { syntax: "<number>"; inherits: true; initial-value: 0; }
|
|
358
423
|
@property --ply { syntax: "<number>"; inherits: true; initial-value: 0; }
|
|
359
424
|
@property --plz { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
425
|
+
|
|
426
|
+
/* CSS-space light components (world-Y\u2192cssX, world-X\u2192cssY, world-Z\u2192cssZ).
|
|
427
|
+
Used by the shadow projection matrix. --clz is clamped away from 0 in JS
|
|
428
|
+
to avoid divide-by-zero when the light is near-horizontal. */
|
|
429
|
+
@property --clx { syntax: "<number>"; inherits: true; initial-value: 0.01; }
|
|
430
|
+
@property --cly { syntax: "<number>"; inherits: true; initial-value: 0; }
|
|
431
|
+
@property --clz { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
432
|
+
|
|
433
|
+
/* Ground-plane position in CSS pixels along the CSS-Z axis (= world-Z, the
|
|
434
|
+
up axis in polycss's world convention). Stored as a <number> so it can be
|
|
435
|
+
used directly inside matrix3d() calc() expressions (matrix3d requires
|
|
436
|
+
dimensionless entries \u2014 no px units).
|
|
437
|
+
Set by PolyScene from the min world-Z of casting meshes. */
|
|
438
|
+
@property --shadow-ground-cssz { syntax: "<number>"; inherits: true; initial-value: 0; }
|
|
360
439
|
@property --plr { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
361
440
|
@property --plg { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
362
441
|
@property --plb { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
@@ -366,14 +445,14 @@ var CORE_BASE_STYLES = `
|
|
|
366
445
|
@property --pab { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
367
446
|
@property --pai { syntax: "<number>"; inherits: true; initial-value: 0.4; }
|
|
368
447
|
|
|
369
|
-
/* Per-polygon surface normal \u2014 set inline by the renderer.
|
|
370
|
-
|
|
448
|
+
/* Per-polygon surface normal \u2014 set inline by the renderer. Base RGB channels
|
|
449
|
+
may be hoisted to a mesh wrapper, so they inherit unless overridden inline. */
|
|
371
450
|
@property --pnx { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
372
451
|
@property --pny { syntax: "<number>"; inherits: false; initial-value: 0; }
|
|
373
452
|
@property --pnz { syntax: "<number>"; inherits: false; initial-value: 1; }
|
|
374
|
-
@property --psr { syntax: "<number>"; inherits:
|
|
375
|
-
@property --psg { syntax: "<number>"; inherits:
|
|
376
|
-
@property --psb { syntax: "<number>"; inherits:
|
|
453
|
+
@property --psr { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
454
|
+
@property --psg { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
455
|
+
@property --psb { syntax: "<number>"; inherits: true; initial-value: 1; }
|
|
377
456
|
|
|
378
457
|
/* Calc-driven Lambert + tint, scoped to dynamic-lighting scenes. Lives
|
|
379
458
|
here (not inline per polygon) so each leaf only carries its tiny normal
|
|
@@ -399,8 +478,9 @@ var CORE_BASE_STYLES = `
|
|
|
399
478
|
background-blend-mode: multiply;
|
|
400
479
|
}
|
|
401
480
|
|
|
481
|
+
.polycss-scene[data-polycss-lighting="dynamic"] b,
|
|
402
482
|
.polycss-scene[data-polycss-lighting="dynamic"] u {
|
|
403
|
-
|
|
483
|
+
color: rgb(
|
|
404
484
|
calc(255 * var(--psr) * (var(--par) * var(--pai)
|
|
405
485
|
+ var(--plr) * var(--pli) * max(0,
|
|
406
486
|
var(--pnx) * var(--plx) +
|
|
@@ -418,6 +498,92 @@ var CORE_BASE_STYLES = `
|
|
|
418
498
|
var(--pnz) * var(--plz))))
|
|
419
499
|
);
|
|
420
500
|
}
|
|
501
|
+
|
|
502
|
+
/* <q> \u2014 dedicated shadow leaf. Same border-shape rendering trick as <i>
|
|
503
|
+
(border-color: currentColor fills the polygon outline) but with its
|
|
504
|
+
own tag so we don't have to thread :not(.polycss-shadow) exclusions
|
|
505
|
+
through every dynamic-mode color rule. backface-visibility must be
|
|
506
|
+
visible because the projection matrix is near-rank-deficient and the
|
|
507
|
+
resulting plane's normal can read as back-facing under some camera
|
|
508
|
+
angles; the leaf is intentionally always painted. Strip the UA's
|
|
509
|
+
default ::before/::after open-/close-quote so the element is just a
|
|
510
|
+
styled box. */
|
|
511
|
+
.polycss-scene q {
|
|
512
|
+
position: absolute;
|
|
513
|
+
display: block;
|
|
514
|
+
transform-origin: 0 0;
|
|
515
|
+
transform-style: preserve-3d;
|
|
516
|
+
margin: 0;
|
|
517
|
+
padding: 0;
|
|
518
|
+
font: inherit;
|
|
519
|
+
font-weight: normal;
|
|
520
|
+
font-style: normal;
|
|
521
|
+
line-height: 0;
|
|
522
|
+
text-decoration: none;
|
|
523
|
+
backface-visibility: visible;
|
|
524
|
+
border-color: currentColor;
|
|
525
|
+
pointer-events: none;
|
|
526
|
+
will-change: transform;
|
|
527
|
+
}
|
|
528
|
+
.polycss-scene q::before,
|
|
529
|
+
.polycss-scene q::after {
|
|
530
|
+
content: none;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* \u2500\u2500 Cast shadows (dynamic mode only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
534
|
+
|
|
535
|
+
/*
|
|
536
|
+
* Shadow projection matrix. Projects any 3D point P onto the horizontal
|
|
537
|
+
* ground plane (cssZ \u2248 G) along the CSS-space light direction (--clx/y/z).
|
|
538
|
+
*
|
|
539
|
+
* In polycss's world convention world Z is up (red-green plane is the
|
|
540
|
+
* floor in the axes helper). After the world\u2192CSS swap (Y\u2194X), world Z stays
|
|
541
|
+
* as CSS Z, so the ground plane normal in CSS space is +cssZ.
|
|
542
|
+
*
|
|
543
|
+
* The strict projection formula would set m22=0 (output.z is a constant G,
|
|
544
|
+
* the polygon is exactly flat). But Chromium SKIPS rendering elements
|
|
545
|
+
* whose composed transform matrix is non-invertible (singular). m22=0
|
|
546
|
+
* makes the matrix singular, so the shadow paints nothing even though it
|
|
547
|
+
* has a valid layout box. The fix: collapse along z by a near-zero
|
|
548
|
+
* scale (Z_SQUASH = 0.01) instead of exactly zero \u2014 output.z is then
|
|
549
|
+
* approximately G with ~1% drift from the input, full-rank and renderable.
|
|
550
|
+
* The shadow still looks flat to the eye (the drift is sub-pixel for
|
|
551
|
+
* any realistic scene size).
|
|
552
|
+
*
|
|
553
|
+
* out.cssX = P.cssX - (--clx/--clz) * (P.cssZ - G)
|
|
554
|
+
* out.cssY = P.cssY - (--cly/--clz) * (P.cssZ - G)
|
|
555
|
+
* out.cssZ = Z_SQUASH * P.cssZ + (1 - Z_SQUASH) * G
|
|
556
|
+
*
|
|
557
|
+
* As column-major 4\xD74 (CSS matrix3d order):
|
|
558
|
+
* col1: [1, 0, 0, 0]
|
|
559
|
+
* col2: [0, 1, 0, 0]
|
|
560
|
+
* col3: [-(--clx/--clz), -(--cly/--clz), Z_SQUASH, 0]
|
|
561
|
+
* col4: [G*(--clx/--clz), G*(--cly/--clz), G*(1-Z_SQUASH), 1]
|
|
562
|
+
*/
|
|
563
|
+
.polycss-scene[data-polycss-lighting="dynamic"] {
|
|
564
|
+
--shadow-proj: matrix3d(
|
|
565
|
+
1, 0, 0, 0,
|
|
566
|
+
0, 1, 0, 0,
|
|
567
|
+
calc(-1 * var(--clx) / var(--clz)),
|
|
568
|
+
calc(-1 * var(--cly) / var(--clz)),
|
|
569
|
+
0.01,
|
|
570
|
+
0,
|
|
571
|
+
calc(var(--shadow-ground-cssz) * var(--clx) / var(--clz)),
|
|
572
|
+
calc(var(--shadow-ground-cssz) * var(--cly) / var(--clz)),
|
|
573
|
+
calc(var(--shadow-ground-cssz) * 0.99),
|
|
574
|
+
1
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* <q> shadow leaf \u2014 Lambert-gated opacity. Polygons facing the light cast
|
|
579
|
+
full shadow; polygons facing away cast zero shadow (their projection
|
|
580
|
+
would stack inside the silhouette and produce ugly overdraw). The
|
|
581
|
+
* 10 multiplier sharpens the cutoff so small positive Lambert values
|
|
582
|
+
jump quickly to 1, giving a near-binary visibility decision with a
|
|
583
|
+
smooth edge transition. Pure CSS calc \u2014 no JS at light-change time. */
|
|
584
|
+
.polycss-scene q {
|
|
585
|
+
opacity: clamp(0, calc((var(--pnx) * var(--clx) + var(--pny) * var(--cly) + var(--pnz) * var(--clz)) * 10), 1);
|
|
586
|
+
}
|
|
421
587
|
`;
|
|
422
588
|
|
|
423
589
|
// src/scene/textureAtlas.tsx
|
|
@@ -437,15 +603,30 @@ var MAX_ATLAS_SCALE = 1;
|
|
|
437
603
|
var AUTO_ATLAS_LOW_AREA = ATLAS_MAX_SIZE * ATLAS_MAX_SIZE;
|
|
438
604
|
var AUTO_ATLAS_MEDIUM_AREA = AUTO_ATLAS_LOW_AREA * 3;
|
|
439
605
|
var AUTO_ATLAS_MAX_BITMAP_SIDE = 2048;
|
|
440
|
-
var
|
|
606
|
+
var AUTO_ATLAS_MAX_DECODED_BYTES_MOBILE = 4 * 1024 * 1024;
|
|
607
|
+
var AUTO_ATLAS_MAX_DECODED_BYTES_DESKTOP = 16 * 1024 * 1024;
|
|
441
608
|
var AUTO_ATLAS_SCALE_GUARD = 0.995;
|
|
442
609
|
var DEFAULT_MATRIX_DECIMALS = 3;
|
|
443
610
|
var DEFAULT_BORDER_SHAPE_DECIMALS = 2;
|
|
611
|
+
var DEFAULT_ATLAS_CSS_DECIMALS = 4;
|
|
612
|
+
var SOLID_QUAD_CANONICAL_SIZE = 64;
|
|
613
|
+
var SOLID_TRIANGLE_CANONICAL_SIZE = 64;
|
|
614
|
+
var ATLAS_CANONICAL_SIZE_EXPLICIT = 64;
|
|
615
|
+
var ATLAS_CANONICAL_SIZE_AUTO_DESKTOP = 128;
|
|
616
|
+
var BORDER_SHAPE_CENTER_PERCENT = 50;
|
|
617
|
+
var BORDER_SHAPE_POINT_EPS = 1e-7;
|
|
618
|
+
var BORDER_SHAPE_CANONICAL_SIZE = 16;
|
|
619
|
+
var PROJECTIVE_QUAD_DENOM_EPS = 0.05;
|
|
620
|
+
var PROJECTIVE_QUAD_MAX_WEIGHT_RATIO = 4;
|
|
621
|
+
var PROJECTIVE_QUAD_BLEED = 0.6;
|
|
444
622
|
var BASIS_EPS = 1e-9;
|
|
445
|
-
var SOLID_TRIANGLE_BLEED = 0.
|
|
623
|
+
var SOLID_TRIANGLE_BLEED = 0.6;
|
|
446
624
|
var TEXTURE_IMAGE_CACHE = /* @__PURE__ */ new Map();
|
|
447
625
|
var RECT_EPS = 1e-3;
|
|
448
626
|
var TEXTURE_TRIANGLE_BLEED = 0.75;
|
|
627
|
+
var TEXTURE_EDGE_REPAIR_ALPHA_MIN = 1;
|
|
628
|
+
var TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN = 250;
|
|
629
|
+
var TEXTURE_EDGE_REPAIR_RADIUS = 1.5;
|
|
449
630
|
function loadTextureImage(url) {
|
|
450
631
|
let p = TEXTURE_IMAGE_CACHE.get(url);
|
|
451
632
|
if (!p) {
|
|
@@ -477,6 +658,10 @@ function roundDecimal(value, decimals) {
|
|
|
477
658
|
const next = value.toFixed(decimals).replace(/\.?0+$/, "");
|
|
478
659
|
return Object.is(Number(next), -0) ? "0" : next;
|
|
479
660
|
}
|
|
661
|
+
function formatCssLength(value, decimals = DEFAULT_ATLAS_CSS_DECIMALS) {
|
|
662
|
+
const next = roundDecimal(value, decimals);
|
|
663
|
+
return Number(next) === 0 || Object.is(Number(next), -0) ? "0" : `${next}px`;
|
|
664
|
+
}
|
|
480
665
|
function formatMatrix3d(matrix, decimals = DEFAULT_MATRIX_DECIMALS) {
|
|
481
666
|
return `matrix3d(${matrix.split(",").map((value) => {
|
|
482
667
|
const parsed = Number(value.trim());
|
|
@@ -484,12 +669,31 @@ function formatMatrix3d(matrix, decimals = DEFAULT_MATRIX_DECIMALS) {
|
|
|
484
669
|
}).join(",")})`;
|
|
485
670
|
}
|
|
486
671
|
function formatPercent(value, decimals = DEFAULT_BORDER_SHAPE_DECIMALS) {
|
|
487
|
-
|
|
672
|
+
const next = roundDecimal(value, decimals);
|
|
673
|
+
return Number(next) === 0 ? "0" : `${next}%`;
|
|
674
|
+
}
|
|
675
|
+
function pointOnSegment(px, py, ax, ay, bx, by) {
|
|
676
|
+
const cross = (px - ax) * (by - ay) - (py - ay) * (bx - ax);
|
|
677
|
+
if (Math.abs(cross) > BORDER_SHAPE_POINT_EPS) return false;
|
|
678
|
+
const dot = (px - ax) * (px - bx) + (py - ay) * (py - by);
|
|
679
|
+
return dot <= BORDER_SHAPE_POINT_EPS;
|
|
680
|
+
}
|
|
681
|
+
function polygonContainsPoint(points, px = BORDER_SHAPE_CENTER_PERCENT, py = BORDER_SHAPE_CENTER_PERCENT) {
|
|
682
|
+
let inside = false;
|
|
683
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
684
|
+
const [xi, yi] = points[i];
|
|
685
|
+
const [xj, yj] = points[j];
|
|
686
|
+
if (pointOnSegment(px, py, xi, yi, xj, yj)) return true;
|
|
687
|
+
if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
|
|
688
|
+
inside = !inside;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return inside;
|
|
488
692
|
}
|
|
489
693
|
function atlasArea(pages) {
|
|
490
694
|
return pages.reduce((sum, page) => sum + page.width * page.height, 0);
|
|
491
695
|
}
|
|
492
|
-
function autoAtlasScaleCap(pages) {
|
|
696
|
+
function autoAtlasScaleCap(pages, maxDecodedBytes) {
|
|
493
697
|
const area = atlasArea(pages);
|
|
494
698
|
if (area <= 0) return 1;
|
|
495
699
|
const maxSide = Math.max(
|
|
@@ -497,15 +701,52 @@ function autoAtlasScaleCap(pages) {
|
|
|
497
701
|
...pages.map((page) => Math.max(page.width, page.height))
|
|
498
702
|
);
|
|
499
703
|
const sideScale = AUTO_ATLAS_MAX_BITMAP_SIDE / maxSide;
|
|
500
|
-
const memoryScale = Math.sqrt(
|
|
704
|
+
const memoryScale = Math.sqrt(maxDecodedBytes / (area * 4));
|
|
501
705
|
return normalizeAtlasScale(Math.min(sideScale, memoryScale));
|
|
502
706
|
}
|
|
503
|
-
function
|
|
707
|
+
function isMobileDocument(doc) {
|
|
708
|
+
if (!doc) return false;
|
|
709
|
+
const win = doc.defaultView ?? (typeof window !== "undefined" ? window : void 0);
|
|
710
|
+
const media = win?.matchMedia;
|
|
711
|
+
if (!media) return false;
|
|
712
|
+
return media("(pointer: coarse)").matches || media("(hover: none)").matches;
|
|
713
|
+
}
|
|
714
|
+
function autoAtlasMaxDecodedBytes(doc) {
|
|
715
|
+
return isMobileDocument(doc) ? AUTO_ATLAS_MAX_DECODED_BYTES_MOBILE : AUTO_ATLAS_MAX_DECODED_BYTES_DESKTOP;
|
|
716
|
+
}
|
|
717
|
+
function atlasCanonicalSizeForTextureQuality(textureQualityInput, doc) {
|
|
718
|
+
if (textureQualityInput !== void 0 && textureQualityInput !== "auto") {
|
|
719
|
+
return ATLAS_CANONICAL_SIZE_EXPLICIT;
|
|
720
|
+
}
|
|
721
|
+
return isMobileDocument(doc) ? ATLAS_CANONICAL_SIZE_EXPLICIT : ATLAS_CANONICAL_SIZE_AUTO_DESKTOP;
|
|
722
|
+
}
|
|
723
|
+
function formatAtlasMatrix(entry, atlasCanonicalSize) {
|
|
724
|
+
const values = entry.matrix.split(",").map((value) => Number(value));
|
|
725
|
+
if (values.length !== 16 || values.some((value) => !Number.isFinite(value))) {
|
|
726
|
+
return entry.canonicalMatrix;
|
|
727
|
+
}
|
|
728
|
+
values[0] *= entry.canvasW / atlasCanonicalSize;
|
|
729
|
+
values[1] *= entry.canvasW / atlasCanonicalSize;
|
|
730
|
+
values[2] *= entry.canvasW / atlasCanonicalSize;
|
|
731
|
+
values[4] *= entry.canvasH / atlasCanonicalSize;
|
|
732
|
+
values[5] *= entry.canvasH / atlasCanonicalSize;
|
|
733
|
+
values[6] *= entry.canvasH / atlasCanonicalSize;
|
|
734
|
+
return values.join(",");
|
|
735
|
+
}
|
|
736
|
+
function applyPackedAtlasCanonicalSize(packed, atlasCanonicalSize) {
|
|
737
|
+
for (const entry of packed.entries) {
|
|
738
|
+
if (!entry) continue;
|
|
739
|
+
entry.atlasCanonicalSize = atlasCanonicalSize;
|
|
740
|
+
entry.atlasMatrix = formatAtlasMatrix(entry, atlasCanonicalSize);
|
|
741
|
+
}
|
|
742
|
+
return packed;
|
|
743
|
+
}
|
|
744
|
+
function autoAtlasScale(pages, maxDecodedBytes) {
|
|
504
745
|
const area = atlasArea(pages);
|
|
505
746
|
let atlasScale = 0.5;
|
|
506
747
|
if (area <= AUTO_ATLAS_LOW_AREA) atlasScale = 1;
|
|
507
748
|
else if (area <= AUTO_ATLAS_MEDIUM_AREA) atlasScale = 0.75;
|
|
508
|
-
return normalizeAtlasScale(Math.min(atlasScale, autoAtlasScaleCap(pages)));
|
|
749
|
+
return normalizeAtlasScale(Math.min(atlasScale, autoAtlasScaleCap(pages, maxDecodedBytes)));
|
|
509
750
|
}
|
|
510
751
|
function atlasBitmapMaxSide(pages, atlasScale) {
|
|
511
752
|
return pages.reduce((max, page) => Math.max(
|
|
@@ -520,18 +761,18 @@ function atlasDecodedBytes(pages, atlasScale) {
|
|
|
520
761
|
0
|
|
521
762
|
);
|
|
522
763
|
}
|
|
523
|
-
function autoAtlasBudgetFactor(pages, atlasScale) {
|
|
764
|
+
function autoAtlasBudgetFactor(pages, atlasScale, maxDecodedBytes) {
|
|
524
765
|
const maxSide = atlasBitmapMaxSide(pages, atlasScale);
|
|
525
766
|
const decodedBytes = atlasDecodedBytes(pages, atlasScale);
|
|
526
767
|
const sideFactor = maxSide > AUTO_ATLAS_MAX_BITMAP_SIDE ? AUTO_ATLAS_MAX_BITMAP_SIDE / maxSide : 1;
|
|
527
|
-
const memoryFactor = decodedBytes >
|
|
768
|
+
const memoryFactor = decodedBytes > maxDecodedBytes ? Math.sqrt(maxDecodedBytes / decodedBytes) : 1;
|
|
528
769
|
return Math.min(sideFactor, memoryFactor);
|
|
529
770
|
}
|
|
530
|
-
function packTextureAtlasPlansAuto(plans, fullScalePacked) {
|
|
531
|
-
let atlasScale = autoAtlasScale(fullScalePacked.pages);
|
|
771
|
+
function packTextureAtlasPlansAuto(plans, fullScalePacked, maxDecodedBytes) {
|
|
772
|
+
let atlasScale = autoAtlasScale(fullScalePacked.pages, maxDecodedBytes);
|
|
532
773
|
let packed = atlasScale === 1 ? fullScalePacked : packTextureAtlasPlans(plans, atlasScale);
|
|
533
774
|
for (let i = 0; i < 4; i++) {
|
|
534
|
-
const factor = autoAtlasBudgetFactor(packed.pages, atlasScale);
|
|
775
|
+
const factor = autoAtlasBudgetFactor(packed.pages, atlasScale, maxDecodedBytes);
|
|
535
776
|
if (factor >= 1) break;
|
|
536
777
|
const nextAtlasScale = normalizeAtlasScale(atlasScale * factor * AUTO_ATLAS_SCALE_GUARD);
|
|
537
778
|
if (nextAtlasScale >= atlasScale) break;
|
|
@@ -540,13 +781,23 @@ function packTextureAtlasPlansAuto(plans, fullScalePacked) {
|
|
|
540
781
|
}
|
|
541
782
|
return { packed, atlasScale };
|
|
542
783
|
}
|
|
543
|
-
function packTextureAtlasPlansWithScale(plans,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
784
|
+
function packTextureAtlasPlansWithScale(plans, textureQualityInput, doc) {
|
|
785
|
+
const atlasCanonicalSize = atlasCanonicalSizeForTextureQuality(textureQualityInput, doc);
|
|
786
|
+
if (textureQualityInput !== void 0 && textureQualityInput !== "auto") {
|
|
787
|
+
const atlasScale = normalizeAtlasScale(textureQualityInput);
|
|
788
|
+
return {
|
|
789
|
+
packed: applyPackedAtlasCanonicalSize(packTextureAtlasPlans(plans, atlasScale), atlasCanonicalSize),
|
|
790
|
+
atlasScale,
|
|
791
|
+
atlasCanonicalSize
|
|
792
|
+
};
|
|
547
793
|
}
|
|
548
794
|
const fullScalePacked = packTextureAtlasPlans(plans, 1);
|
|
549
|
-
|
|
795
|
+
const autoPacked = packTextureAtlasPlansAuto(plans, fullScalePacked, autoAtlasMaxDecodedBytes(doc));
|
|
796
|
+
return {
|
|
797
|
+
packed: applyPackedAtlasCanonicalSize(autoPacked.packed, atlasCanonicalSize),
|
|
798
|
+
atlasScale: autoPacked.atlasScale,
|
|
799
|
+
atlasCanonicalSize
|
|
800
|
+
};
|
|
550
801
|
}
|
|
551
802
|
function atlasPadding(atlasScale) {
|
|
552
803
|
return Math.max(ATLAS_PADDING, Math.ceil(ATLAS_PADDING / atlasScale));
|
|
@@ -566,6 +817,9 @@ function parseHex(hex) {
|
|
|
566
817
|
if (!parsed) return { r: 255, g: 255, b: 255 };
|
|
567
818
|
return { r: parsed.rgb[0], g: parsed.rgb[1], b: parsed.rgb[2] };
|
|
568
819
|
}
|
|
820
|
+
function rgbKey({ r, g, b }) {
|
|
821
|
+
return `${r},${g},${b}`;
|
|
822
|
+
}
|
|
569
823
|
function parseAlpha(input) {
|
|
570
824
|
return parsePureColor(input)?.alpha ?? 1;
|
|
571
825
|
}
|
|
@@ -601,60 +855,369 @@ function isFullRectSolid(entry) {
|
|
|
601
855
|
function isSolidTrianglePlan(entry) {
|
|
602
856
|
return !entry.texture && entry.polygon.vertices.length === 3;
|
|
603
857
|
}
|
|
858
|
+
function isProjectiveQuadPlan(entry) {
|
|
859
|
+
return !entry.texture && !!entry.projectiveMatrix && !isFullRectSolid(entry);
|
|
860
|
+
}
|
|
604
861
|
function borderShapeSupported() {
|
|
605
862
|
const supportsBorderShape = !!globalThis.CSS?.supports?.(
|
|
606
863
|
"border-shape",
|
|
607
|
-
"polygon(0 0, 100% 0, 0 100%)
|
|
864
|
+
"polygon(0 0, 100% 0, 0 100%) circle(0)"
|
|
608
865
|
);
|
|
609
866
|
if (!supportsBorderShape) return false;
|
|
610
867
|
const media = globalThis.matchMedia;
|
|
611
868
|
if (typeof media !== "function") return true;
|
|
612
869
|
return media("(pointer: fine)").matches && media("(hover: hover)").matches;
|
|
613
870
|
}
|
|
614
|
-
function
|
|
615
|
-
const
|
|
871
|
+
function solidTriangleSupported() {
|
|
872
|
+
const userAgent = (typeof window !== "undefined" ? window.navigator : globalThis.navigator)?.userAgent ?? "";
|
|
873
|
+
if (!userAgent) return true;
|
|
874
|
+
const isChromiumFamily = /\b(?:Chrome|HeadlessChrome|Chromium|Edg|OPR)\//.test(userAgent);
|
|
875
|
+
const isSafariFamily = /\bVersion\/[\d.]+.*\bSafari\//.test(userAgent);
|
|
876
|
+
return !isSafariFamily || isChromiumFamily;
|
|
877
|
+
}
|
|
878
|
+
function incrementCount(map, key) {
|
|
879
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
880
|
+
}
|
|
881
|
+
function dominantCountKey(map) {
|
|
882
|
+
let bestKey;
|
|
883
|
+
let bestCount = 1;
|
|
884
|
+
for (const [key, count] of map) {
|
|
885
|
+
if (count > bestCount) {
|
|
886
|
+
bestKey = key;
|
|
887
|
+
bestCount = count;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return bestKey;
|
|
891
|
+
}
|
|
892
|
+
var BRUSH_INLINE_STYLE_ORDER = /* @__PURE__ */ new Map([
|
|
893
|
+
["transform", 0],
|
|
894
|
+
["border-shape", 1],
|
|
895
|
+
["border-width", 2],
|
|
896
|
+
["width", 3],
|
|
897
|
+
["height", 4],
|
|
898
|
+
["color", 5]
|
|
899
|
+
]);
|
|
900
|
+
function orderBrushInlineStyle(el) {
|
|
901
|
+
const current = el.getAttribute("style");
|
|
902
|
+
if (!current) return;
|
|
903
|
+
const declarations = current.split(";").map((declaration) => declaration.trim()).filter(Boolean);
|
|
904
|
+
const next = declarations.map((declaration, index) => {
|
|
905
|
+
const property = declaration.slice(0, declaration.indexOf(":")).trim().toLowerCase();
|
|
906
|
+
return {
|
|
907
|
+
declaration,
|
|
908
|
+
index,
|
|
909
|
+
order: BRUSH_INLINE_STYLE_ORDER.get(property) ?? Number.POSITIVE_INFINITY
|
|
910
|
+
};
|
|
911
|
+
}).sort((a, b) => a.order - b.order || a.index - b.index).map(({ declaration }) => declaration).join(";");
|
|
912
|
+
if (next !== current) el.setAttribute("style", next);
|
|
913
|
+
}
|
|
914
|
+
function getSolidPaintDefaults(plans, textureLighting) {
|
|
915
|
+
const paintCounts = /* @__PURE__ */ new Map();
|
|
916
|
+
const dynamicCounts = /* @__PURE__ */ new Map();
|
|
917
|
+
const dynamicColors = /* @__PURE__ */ new Map();
|
|
918
|
+
const useStableTriangle = solidTriangleSupported();
|
|
919
|
+
const useBorderShape = textureLighting !== "dynamic" && borderShapeSupported();
|
|
920
|
+
for (const plan of plans) {
|
|
921
|
+
if (!plan || plan.texture) continue;
|
|
922
|
+
if (textureLighting === "dynamic") {
|
|
923
|
+
if (!(useStableTriangle && isSolidTrianglePlan(plan)) && !isFullRectSolid(plan)) continue;
|
|
924
|
+
const color = parseHex(plan.polygon.color ?? "#cccccc");
|
|
925
|
+
const key = rgbKey(color);
|
|
926
|
+
incrementCount(dynamicCounts, key);
|
|
927
|
+
if (!dynamicColors.has(key)) dynamicColors.set(key, color);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (!(useStableTriangle && isSolidTrianglePlan(plan)) && !isFullRectSolid(plan) && !useBorderShape) continue;
|
|
931
|
+
incrementCount(paintCounts, plan.shadedColor);
|
|
932
|
+
}
|
|
933
|
+
const paintColor = dominantCountKey(paintCounts);
|
|
934
|
+
const dynamicColorKey = dominantCountKey(dynamicCounts);
|
|
935
|
+
return {
|
|
936
|
+
paintColor,
|
|
937
|
+
dynamicColorKey,
|
|
938
|
+
dynamicColor: dynamicColorKey ? dynamicColors.get(dynamicColorKey) : void 0
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function borderShapePointsForPlan(entry) {
|
|
942
|
+
const points = [];
|
|
616
943
|
const width = entry.canvasW || 1;
|
|
617
944
|
const height = entry.canvasH || 1;
|
|
618
945
|
for (let i = 0; i < entry.screenPts.length; i += 2) {
|
|
619
946
|
const x = Math.max(0, Math.min(100, entry.screenPts[i] / width * 100));
|
|
620
947
|
const y = Math.max(0, Math.min(100, entry.screenPts[i + 1] / height * 100));
|
|
621
|
-
|
|
948
|
+
points.push([x, y]);
|
|
622
949
|
}
|
|
623
|
-
return
|
|
950
|
+
return points;
|
|
951
|
+
}
|
|
952
|
+
function cssBorderShapePoint([x, y]) {
|
|
953
|
+
return `${formatPercent(x)} ${formatPercent(y)}`;
|
|
624
954
|
}
|
|
625
|
-
function
|
|
955
|
+
function cssPolygonShapeForPoints(points) {
|
|
956
|
+
return `polygon(${points.map(cssBorderShapePoint).join(",")})`;
|
|
957
|
+
}
|
|
958
|
+
function cssCollapsedInnerShapeForPoints(points) {
|
|
959
|
+
if (polygonContainsPoint(points)) return "circle(0)";
|
|
626
960
|
let xSum = 0;
|
|
627
961
|
let ySum = 0;
|
|
628
|
-
const
|
|
629
|
-
for (
|
|
630
|
-
xSum +=
|
|
631
|
-
ySum +=
|
|
962
|
+
const pointCount = Math.max(1, points.length);
|
|
963
|
+
for (const [x2, y2] of points) {
|
|
964
|
+
xSum += x2;
|
|
965
|
+
ySum += y2;
|
|
632
966
|
}
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
const y = formatPercent(Math.max(0, Math.min(100, ySum / points / height * 100)));
|
|
637
|
-
return `polygon(${Array.from({ length: points }, () => `${x} ${y}`).join(", ")})`;
|
|
967
|
+
const x = formatPercent(Math.max(0, Math.min(100, xSum / pointCount)));
|
|
968
|
+
const y = formatPercent(Math.max(0, Math.min(100, ySum / pointCount)));
|
|
969
|
+
return `circle(0 at ${x} ${y})`;
|
|
638
970
|
}
|
|
639
971
|
function cssBorderShapeForPlan(entry) {
|
|
640
|
-
|
|
972
|
+
const points = borderShapePointsForPlan(entry);
|
|
973
|
+
return `${cssPolygonShapeForPoints(points)} ${cssCollapsedInnerShapeForPoints(points)}`;
|
|
641
974
|
}
|
|
642
975
|
function formatMatrix3dValues(values, decimals = DEFAULT_MATRIX_DECIMALS) {
|
|
643
976
|
return values.map((value) => roundDecimal(value, decimals)).join(",");
|
|
644
977
|
}
|
|
978
|
+
function formatScaledMatrixFromPlan(entry, scaleX, scaleY) {
|
|
979
|
+
const values = entry.matrix.split(",").map((value) => Number(value));
|
|
980
|
+
if (values.length !== 16 || values.some((value) => !Number.isFinite(value))) {
|
|
981
|
+
return entry.matrix;
|
|
982
|
+
}
|
|
983
|
+
values[0] *= scaleX;
|
|
984
|
+
values[1] *= scaleX;
|
|
985
|
+
values[2] *= scaleX;
|
|
986
|
+
values[4] *= scaleY;
|
|
987
|
+
values[5] *= scaleY;
|
|
988
|
+
values[6] *= scaleY;
|
|
989
|
+
return formatMatrix3dValues(values);
|
|
990
|
+
}
|
|
991
|
+
function formatBorderShapeMatrix(entry) {
|
|
992
|
+
return formatScaledMatrixFromPlan(
|
|
993
|
+
entry,
|
|
994
|
+
(entry.canvasW || 1) / BORDER_SHAPE_CANONICAL_SIZE,
|
|
995
|
+
(entry.canvasH || 1) / BORDER_SHAPE_CANONICAL_SIZE
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
function formatSolidQuadMatrix(entry) {
|
|
999
|
+
return formatScaledMatrixFromPlan(
|
|
1000
|
+
entry,
|
|
1001
|
+
(entry.canvasW || 1) / SOLID_QUAD_CANONICAL_SIZE,
|
|
1002
|
+
(entry.canvasH || 1) / SOLID_QUAD_CANONICAL_SIZE
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
function isConvexPolygonPoints(points) {
|
|
1006
|
+
if (points.length < 3) return false;
|
|
1007
|
+
let sign = 0;
|
|
1008
|
+
for (let i = 0; i < points.length; i++) {
|
|
1009
|
+
const a = points[i];
|
|
1010
|
+
const b = points[(i + 1) % points.length];
|
|
1011
|
+
const c = points[(i + 2) % points.length];
|
|
1012
|
+
const cross = (b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0]);
|
|
1013
|
+
if (Math.abs(cross) <= BASIS_EPS) return false;
|
|
1014
|
+
const nextSign = Math.sign(cross);
|
|
1015
|
+
if (sign === 0) sign = nextSign;
|
|
1016
|
+
else if (nextSign !== sign) return false;
|
|
1017
|
+
}
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
function signedArea2D(points) {
|
|
1021
|
+
let area = 0;
|
|
1022
|
+
for (let i = 0; i < points.length; i++) {
|
|
1023
|
+
const a = points[i];
|
|
1024
|
+
const b = points[(i + 1) % points.length];
|
|
1025
|
+
area += a[0] * b[1] - a[1] * b[0];
|
|
1026
|
+
}
|
|
1027
|
+
return area / 2;
|
|
1028
|
+
}
|
|
1029
|
+
function intersect2DLines(a0, a1, b0, b1) {
|
|
1030
|
+
const rx = a1[0] - a0[0];
|
|
1031
|
+
const ry = a1[1] - a0[1];
|
|
1032
|
+
const sx = b1[0] - b0[0];
|
|
1033
|
+
const sy = b1[1] - b0[1];
|
|
1034
|
+
const det = rx * sy - ry * sx;
|
|
1035
|
+
if (Math.abs(det) <= BASIS_EPS) return null;
|
|
1036
|
+
const qpx = b0[0] - a0[0];
|
|
1037
|
+
const qpy = b0[1] - a0[1];
|
|
1038
|
+
const t = (qpx * sy - qpy * sx) / det;
|
|
1039
|
+
return [a0[0] + t * rx, a0[1] + t * ry];
|
|
1040
|
+
}
|
|
1041
|
+
function offsetConvexPolygonPoints(points, amount) {
|
|
1042
|
+
if (points.length < 6 || points.length % 2 !== 0 || amount <= 0) return points;
|
|
1043
|
+
const q = [];
|
|
1044
|
+
for (let i = 0; i < points.length; i += 2) q.push([points[i], points[i + 1]]);
|
|
1045
|
+
if (!isConvexPolygonPoints(q)) return expandClipPoints(points, amount);
|
|
1046
|
+
const area = signedArea2D(q);
|
|
1047
|
+
if (Math.abs(area) <= BASIS_EPS) return expandClipPoints(points, amount);
|
|
1048
|
+
const outwardSign = area > 0 ? 1 : -1;
|
|
1049
|
+
const offsetLines = [];
|
|
1050
|
+
for (let i = 0; i < q.length; i++) {
|
|
1051
|
+
const a = q[i];
|
|
1052
|
+
const b = q[(i + 1) % q.length];
|
|
1053
|
+
const dx = b[0] - a[0];
|
|
1054
|
+
const dy = b[1] - a[1];
|
|
1055
|
+
const length = Math.hypot(dx, dy);
|
|
1056
|
+
if (length <= BASIS_EPS) return expandClipPoints(points, amount);
|
|
1057
|
+
const ox = outwardSign * (dy / length) * amount;
|
|
1058
|
+
const oy = outwardSign * (-dx / length) * amount;
|
|
1059
|
+
offsetLines.push({
|
|
1060
|
+
a: [a[0] + ox, a[1] + oy],
|
|
1061
|
+
b: [b[0] + ox, b[1] + oy]
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
const expanded = [];
|
|
1065
|
+
const maxMiter = Math.max(2, amount * 4);
|
|
1066
|
+
for (let i = 0; i < q.length; i++) {
|
|
1067
|
+
const prev = offsetLines[(i + q.length - 1) % q.length];
|
|
1068
|
+
const next = offsetLines[i];
|
|
1069
|
+
const intersection = intersect2DLines(prev.a, prev.b, next.a, next.b);
|
|
1070
|
+
if (!intersection) return expandClipPoints(points, amount);
|
|
1071
|
+
const original = q[i];
|
|
1072
|
+
const dx = intersection[0] - original[0];
|
|
1073
|
+
const dy = intersection[1] - original[1];
|
|
1074
|
+
const miter = Math.hypot(dx, dy);
|
|
1075
|
+
if (miter > maxMiter) {
|
|
1076
|
+
expanded.push(
|
|
1077
|
+
original[0] + dx / miter * maxMiter,
|
|
1078
|
+
original[1] + dy / miter * maxMiter
|
|
1079
|
+
);
|
|
1080
|
+
} else {
|
|
1081
|
+
expanded.push(intersection[0], intersection[1]);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return expanded;
|
|
1085
|
+
}
|
|
1086
|
+
function computeProjectiveQuadCoefficients(q) {
|
|
1087
|
+
if (q.length !== 4 || !isConvexPolygonPoints(q)) return null;
|
|
1088
|
+
const [q0, q1, q2, q3] = q;
|
|
1089
|
+
const sx = q0[0] - q1[0] + q2[0] - q3[0];
|
|
1090
|
+
const sy = q0[1] - q1[1] + q2[1] - q3[1];
|
|
1091
|
+
const dx1 = q1[0] - q2[0];
|
|
1092
|
+
const dx2 = q3[0] - q2[0];
|
|
1093
|
+
const dy1 = q1[1] - q2[1];
|
|
1094
|
+
const dy2 = q3[1] - q2[1];
|
|
1095
|
+
const det = dx1 * dy2 - dy1 * dx2;
|
|
1096
|
+
if (Math.abs(det) <= BASIS_EPS) return null;
|
|
1097
|
+
const g = (sx * dy2 - sy * dx2) / det;
|
|
1098
|
+
const h = (dx1 * sy - dy1 * sx) / det;
|
|
1099
|
+
const weights = [1, 1 + g, 1 + g + h, 1 + h];
|
|
1100
|
+
if (weights.some((weight) => !Number.isFinite(weight) || weight <= PROJECTIVE_QUAD_DENOM_EPS)) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
const minWeight = Math.min(...weights);
|
|
1104
|
+
const maxWeight = Math.max(...weights);
|
|
1105
|
+
if (maxWeight / minWeight > PROJECTIVE_QUAD_MAX_WEIGHT_RATIO) return null;
|
|
1106
|
+
return {
|
|
1107
|
+
g,
|
|
1108
|
+
h,
|
|
1109
|
+
w1: 1 + g,
|
|
1110
|
+
w3: 1 + h
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function computeProjectiveQuadMatrix(screenPts, xAxis, yAxis, normal, tx, ty, tz) {
|
|
1114
|
+
if (screenPts.length !== 8) return null;
|
|
1115
|
+
const rawQ = [
|
|
1116
|
+
[screenPts[0], screenPts[1]],
|
|
1117
|
+
[screenPts[2], screenPts[3]],
|
|
1118
|
+
[screenPts[4], screenPts[5]],
|
|
1119
|
+
[screenPts[6], screenPts[7]]
|
|
1120
|
+
];
|
|
1121
|
+
if (!computeProjectiveQuadCoefficients(rawQ)) return null;
|
|
1122
|
+
const expandedPts = offsetConvexPolygonPoints(screenPts, PROJECTIVE_QUAD_BLEED);
|
|
1123
|
+
const q = [
|
|
1124
|
+
[expandedPts[0], expandedPts[1]],
|
|
1125
|
+
[expandedPts[2], expandedPts[3]],
|
|
1126
|
+
[expandedPts[4], expandedPts[5]],
|
|
1127
|
+
[expandedPts[6], expandedPts[7]]
|
|
1128
|
+
];
|
|
1129
|
+
const coeffs = computeProjectiveQuadCoefficients(q);
|
|
1130
|
+
if (!coeffs) return null;
|
|
1131
|
+
const { g, h, w1, w3 } = coeffs;
|
|
1132
|
+
const [q0, q1, , q3] = q;
|
|
1133
|
+
const toCssPoint = ([x, y]) => [
|
|
1134
|
+
tx + x * xAxis[0] + y * yAxis[0],
|
|
1135
|
+
ty + x * xAxis[1] + y * yAxis[1],
|
|
1136
|
+
tz + x * xAxis[2] + y * yAxis[2]
|
|
1137
|
+
];
|
|
1138
|
+
const p0 = toCssPoint(q0);
|
|
1139
|
+
const p1 = toCssPoint(q1);
|
|
1140
|
+
const p3 = toCssPoint(q3);
|
|
1141
|
+
const xCol = [
|
|
1142
|
+
p1[0] * w1 - p0[0],
|
|
1143
|
+
p1[1] * w1 - p0[1],
|
|
1144
|
+
p1[2] * w1 - p0[2]
|
|
1145
|
+
];
|
|
1146
|
+
const yCol = [
|
|
1147
|
+
p3[0] * w3 - p0[0],
|
|
1148
|
+
p3[1] * w3 - p0[1],
|
|
1149
|
+
p3[2] * w3 - p0[2]
|
|
1150
|
+
];
|
|
1151
|
+
const values = [
|
|
1152
|
+
xCol[0],
|
|
1153
|
+
xCol[1],
|
|
1154
|
+
xCol[2],
|
|
1155
|
+
g,
|
|
1156
|
+
yCol[0],
|
|
1157
|
+
yCol[1],
|
|
1158
|
+
yCol[2],
|
|
1159
|
+
h,
|
|
1160
|
+
normal[0],
|
|
1161
|
+
normal[1],
|
|
1162
|
+
normal[2],
|
|
1163
|
+
0,
|
|
1164
|
+
p0[0],
|
|
1165
|
+
p0[1],
|
|
1166
|
+
p0[2],
|
|
1167
|
+
1
|
|
1168
|
+
];
|
|
1169
|
+
for (let i = 0; i < 8; i += 1) values[i] /= SOLID_QUAD_CANONICAL_SIZE;
|
|
1170
|
+
return values.join(",");
|
|
1171
|
+
}
|
|
645
1172
|
function cssPoints(vertices, tile, elev) {
|
|
646
1173
|
return vertices.map((v) => [v[1] * tile, v[0] * tile, v[2] * elev]);
|
|
647
1174
|
}
|
|
1175
|
+
function pointKey(point) {
|
|
1176
|
+
return `${point[0]},${point[1]},${point[2]}`;
|
|
1177
|
+
}
|
|
1178
|
+
function edgeKey(a, b) {
|
|
1179
|
+
const ak = pointKey(a);
|
|
1180
|
+
const bk = pointKey(b);
|
|
1181
|
+
return ak < bk ? `${ak}|${bk}` : `${bk}|${ak}`;
|
|
1182
|
+
}
|
|
1183
|
+
function buildTextureEdgeRepairSets(polygons) {
|
|
1184
|
+
const edgeOwners = /* @__PURE__ */ new Map();
|
|
1185
|
+
for (let polygonIndex = 0; polygonIndex < polygons.length; polygonIndex++) {
|
|
1186
|
+
const vertices = polygons[polygonIndex].vertices;
|
|
1187
|
+
if (!vertices || vertices.length < 3 || !polygons[polygonIndex].texture) continue;
|
|
1188
|
+
for (let edgeIndex = 0; edgeIndex < vertices.length; edgeIndex++) {
|
|
1189
|
+
const key = edgeKey(vertices[edgeIndex], vertices[(edgeIndex + 1) % vertices.length]);
|
|
1190
|
+
const owner = { polygon: polygonIndex, edge: edgeIndex };
|
|
1191
|
+
const owners = edgeOwners.get(key);
|
|
1192
|
+
if (owners) owners.push(owner);
|
|
1193
|
+
else edgeOwners.set(key, [owner]);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
const repairEdges = polygons.map(() => /* @__PURE__ */ new Set());
|
|
1197
|
+
for (const owners of edgeOwners.values()) {
|
|
1198
|
+
if (owners.length < 2) continue;
|
|
1199
|
+
for (let i = 0; i < owners.length; i++) {
|
|
1200
|
+
for (let j = i + 1; j < owners.length; j++) {
|
|
1201
|
+
repairEdges[owners[i].polygon].add(owners[i].edge);
|
|
1202
|
+
repairEdges[owners[j].polygon].add(owners[j].edge);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return repairEdges.map((edges) => edges.size > 0 ? edges : void 0);
|
|
1207
|
+
}
|
|
648
1208
|
function computeSurfaceNormal(pts) {
|
|
649
1209
|
if (pts.length < 3) return null;
|
|
650
|
-
const p0 = pts[0]
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1210
|
+
const p0 = pts[0];
|
|
1211
|
+
const normal = [0, 0, 0];
|
|
1212
|
+
for (let i = 1; i + 1 < pts.length; i++) {
|
|
1213
|
+
const p1 = pts[i];
|
|
1214
|
+
const p2 = pts[i + 1];
|
|
1215
|
+
const e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
|
|
1216
|
+
const e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
|
|
1217
|
+
normal[0] -= e1[1] * e2[2] - e1[2] * e2[1];
|
|
1218
|
+
normal[1] -= e1[2] * e2[0] - e1[0] * e2[2];
|
|
1219
|
+
normal[2] -= e1[0] * e2[1] - e1[1] * e2[0];
|
|
1220
|
+
}
|
|
658
1221
|
const len = Math.hypot(normal[0], normal[1], normal[2]);
|
|
659
1222
|
if (len <= BASIS_EPS) return null;
|
|
660
1223
|
return [normal[0] / len, normal[1] / len, normal[2] / len];
|
|
@@ -669,7 +1232,7 @@ function crossVec(a, b) {
|
|
|
669
1232
|
a[0] * b[1] - a[1] * b[0]
|
|
670
1233
|
];
|
|
671
1234
|
}
|
|
672
|
-
function solidTriangleStyle(entry, textureLighting, pointerEvents) {
|
|
1235
|
+
function solidTriangleStyle(entry, textureLighting, pointerEvents, solidPaintDefaults) {
|
|
673
1236
|
if (!isSolidTrianglePlan(entry)) return null;
|
|
674
1237
|
const pts = cssPoints(entry.polygon.vertices, entry.tileSize, entry.layerElevation);
|
|
675
1238
|
const normal = computeSurfaceNormal(pts);
|
|
@@ -753,47 +1316,89 @@ function solidTriangleStyle(entry, textureLighting, pointerEvents) {
|
|
|
753
1316
|
}
|
|
754
1317
|
const left = Math.max(0, Math.min(baseLength, apexX));
|
|
755
1318
|
const right = Math.max(0, baseLength - left);
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
const rightPx = right + bleed;
|
|
759
|
-
const heightPx = height + bleed * 2;
|
|
760
|
-
const tx = cv[0] - leftPx * xAxis[0] - bleed * yAxis[0];
|
|
761
|
-
const ty = cv[1] - leftPx * xAxis[1] - bleed * yAxis[1];
|
|
762
|
-
const tz = cv[2] - leftPx * xAxis[2] - bleed * yAxis[2];
|
|
763
|
-
const matrix = formatMatrix3dValues([
|
|
764
|
-
xAxis[0],
|
|
765
|
-
xAxis[1],
|
|
766
|
-
xAxis[2],
|
|
1319
|
+
const expanded = offsetConvexPolygonPoints([
|
|
1320
|
+
left,
|
|
767
1321
|
0,
|
|
768
|
-
yAxis[0],
|
|
769
|
-
yAxis[1],
|
|
770
|
-
yAxis[2],
|
|
771
1322
|
0,
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
]
|
|
1323
|
+
height,
|
|
1324
|
+
left + right,
|
|
1325
|
+
height
|
|
1326
|
+
], SOLID_TRIANGLE_BLEED);
|
|
1327
|
+
const apex2 = [expanded[0], expanded[1]];
|
|
1328
|
+
const baseLeft2 = [expanded[2], expanded[3]];
|
|
1329
|
+
const baseRight2 = [expanded[4], expanded[5]];
|
|
1330
|
+
const baseY = (baseLeft2[1] + baseRight2[1]) / 2;
|
|
1331
|
+
const leftPx = apex2[0] - baseLeft2[0];
|
|
1332
|
+
const rightPx = baseRight2[0] - apex2[0];
|
|
1333
|
+
const heightPx = baseY - apex2[1];
|
|
1334
|
+
if (leftPx <= BASIS_EPS || rightPx <= BASIS_EPS || heightPx <= BASIS_EPS || !Number.isFinite(leftPx + rightPx + heightPx)) {
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
781
1337
|
const dynamic = textureLighting === "dynamic";
|
|
782
1338
|
const base = parseHex(entry.polygon.color ?? "#cccccc");
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
borderBottomColor: dynamic ? void 0 : entry.shadedColor,
|
|
1339
|
+
const useDefaultDynamicColor = dynamic && rgbKey(base) === solidPaintDefaults?.dynamicColorKey;
|
|
1340
|
+
const sharedStyle = {
|
|
1341
|
+
color: dynamic || entry.shadedColor === solidPaintDefaults?.paintColor ? void 0 : entry.shadedColor,
|
|
787
1342
|
pointerEvents: pointerEvents === "none" ? "none" : void 0,
|
|
788
|
-
...dynamic ? {
|
|
1343
|
+
...dynamic && !useDefaultDynamicColor ? {
|
|
789
1344
|
["--pnx"]: normal[0].toFixed(4),
|
|
790
1345
|
["--pny"]: normal[1].toFixed(4),
|
|
791
1346
|
["--pnz"]: normal[2].toFixed(4),
|
|
792
1347
|
["--psr"]: (base.r / 255).toFixed(4),
|
|
793
1348
|
["--psg"]: (base.g / 255).toFixed(4),
|
|
794
1349
|
["--psb"]: (base.b / 255).toFixed(4)
|
|
1350
|
+
} : dynamic ? {
|
|
1351
|
+
["--pnx"]: normal[0].toFixed(4),
|
|
1352
|
+
["--pny"]: normal[1].toFixed(4),
|
|
1353
|
+
["--pnz"]: normal[2].toFixed(4)
|
|
795
1354
|
} : null
|
|
796
1355
|
};
|
|
1356
|
+
const worldPoint = ([x, y]) => [
|
|
1357
|
+
cv[0] + (x - left) * xAxis[0] + y * yAxis[0],
|
|
1358
|
+
cv[1] + (x - left) * xAxis[1] + y * yAxis[1],
|
|
1359
|
+
cv[2] + (x - left) * xAxis[2] + y * yAxis[2]
|
|
1360
|
+
];
|
|
1361
|
+
const apex = worldPoint(apex2);
|
|
1362
|
+
const baseLeft = worldPoint([baseLeft2[0], baseY]);
|
|
1363
|
+
const baseRight = worldPoint([baseRight2[0], baseY]);
|
|
1364
|
+
const halfBase = SOLID_TRIANGLE_CANONICAL_SIZE / 2;
|
|
1365
|
+
const xCol = [
|
|
1366
|
+
(baseRight[0] - baseLeft[0]) / SOLID_TRIANGLE_CANONICAL_SIZE,
|
|
1367
|
+
(baseRight[1] - baseLeft[1]) / SOLID_TRIANGLE_CANONICAL_SIZE,
|
|
1368
|
+
(baseRight[2] - baseLeft[2]) / SOLID_TRIANGLE_CANONICAL_SIZE
|
|
1369
|
+
];
|
|
1370
|
+
const txCol = [
|
|
1371
|
+
apex[0] - xCol[0] * halfBase,
|
|
1372
|
+
apex[1] - xCol[1] * halfBase,
|
|
1373
|
+
apex[2] - xCol[2] * halfBase
|
|
1374
|
+
];
|
|
1375
|
+
const yCol = [
|
|
1376
|
+
(baseLeft[0] - txCol[0]) / SOLID_TRIANGLE_CANONICAL_SIZE,
|
|
1377
|
+
(baseLeft[1] - txCol[1]) / SOLID_TRIANGLE_CANONICAL_SIZE,
|
|
1378
|
+
(baseLeft[2] - txCol[2]) / SOLID_TRIANGLE_CANONICAL_SIZE
|
|
1379
|
+
];
|
|
1380
|
+
const canonicalMatrix = formatMatrix3dValues([
|
|
1381
|
+
xCol[0],
|
|
1382
|
+
xCol[1],
|
|
1383
|
+
xCol[2],
|
|
1384
|
+
0,
|
|
1385
|
+
yCol[0],
|
|
1386
|
+
yCol[1],
|
|
1387
|
+
yCol[2],
|
|
1388
|
+
0,
|
|
1389
|
+
normal[0],
|
|
1390
|
+
normal[1],
|
|
1391
|
+
normal[2],
|
|
1392
|
+
0,
|
|
1393
|
+
txCol[0],
|
|
1394
|
+
txCol[1],
|
|
1395
|
+
txCol[2],
|
|
1396
|
+
1
|
|
1397
|
+
]);
|
|
1398
|
+
return {
|
|
1399
|
+
transform: `matrix3d(${canonicalMatrix})`,
|
|
1400
|
+
...sharedStyle
|
|
1401
|
+
};
|
|
797
1402
|
}
|
|
798
1403
|
function rgbToHex({ r, g, b }) {
|
|
799
1404
|
const f = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
|
|
@@ -936,6 +1541,15 @@ function expandClipPoints(points, amount) {
|
|
|
936
1541
|
}
|
|
937
1542
|
return expanded;
|
|
938
1543
|
}
|
|
1544
|
+
function tracePolygonPath(ctx, x, y, points) {
|
|
1545
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
1546
|
+
const px = x + points[i];
|
|
1547
|
+
const py = y + points[i + 1];
|
|
1548
|
+
if (i === 0) ctx.moveTo(px, py);
|
|
1549
|
+
else ctx.lineTo(px, py);
|
|
1550
|
+
}
|
|
1551
|
+
ctx.closePath();
|
|
1552
|
+
}
|
|
939
1553
|
function canvasToUrl(canvas) {
|
|
940
1554
|
if (typeof canvas.toBlob === "function") {
|
|
941
1555
|
return new Promise((resolve) => {
|
|
@@ -979,6 +1593,176 @@ function drawImageUvSample(ctx, img, rect, x, y, width, height, atlasScale) {
|
|
|
979
1593
|
setCssTransform(ctx, atlasScale);
|
|
980
1594
|
ctx.drawImage(img, sx, sy, sw, sh, x, y, width, height);
|
|
981
1595
|
}
|
|
1596
|
+
function traceOffsetPolygonPath(ctx, x, y, points, offsetX, offsetY) {
|
|
1597
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
1598
|
+
const px = x + points[i] + offsetX;
|
|
1599
|
+
const py = y + points[i + 1] + offsetY;
|
|
1600
|
+
if (i === 0) ctx.moveTo(px, py);
|
|
1601
|
+
else ctx.lineTo(px, py);
|
|
1602
|
+
}
|
|
1603
|
+
ctx.closePath();
|
|
1604
|
+
}
|
|
1605
|
+
function drawTexturedAtlasEntry(ctx, entry, srcImg, atlasScale, offsetX = 0, offsetY = 0) {
|
|
1606
|
+
if (entry.textureTriangles?.length) {
|
|
1607
|
+
const imgW = srcImg.naturalWidth || srcImg.width || 1;
|
|
1608
|
+
const imgH = srcImg.naturalHeight || srcImg.height || 1;
|
|
1609
|
+
for (const triangle of entry.textureTriangles) {
|
|
1610
|
+
const clipPts = expandClipPoints(triangle.screenPts, TEXTURE_TRIANGLE_BLEED);
|
|
1611
|
+
ctx.save();
|
|
1612
|
+
setCssTransform(ctx, atlasScale);
|
|
1613
|
+
ctx.beginPath();
|
|
1614
|
+
traceOffsetPolygonPath(ctx, entry.x, entry.y, clipPts, offsetX, offsetY);
|
|
1615
|
+
ctx.clip();
|
|
1616
|
+
if (triangle.uvAffine) {
|
|
1617
|
+
setCssTransform(
|
|
1618
|
+
ctx,
|
|
1619
|
+
atlasScale,
|
|
1620
|
+
triangle.uvAffine.a / imgW,
|
|
1621
|
+
triangle.uvAffine.c / imgW,
|
|
1622
|
+
triangle.uvAffine.b / imgH,
|
|
1623
|
+
triangle.uvAffine.d / imgH,
|
|
1624
|
+
entry.x + triangle.uvAffine.e + offsetX,
|
|
1625
|
+
entry.y + triangle.uvAffine.f + offsetY
|
|
1626
|
+
);
|
|
1627
|
+
ctx.drawImage(srcImg, 0, 0);
|
|
1628
|
+
} else if (triangle.uvSampleRect) {
|
|
1629
|
+
drawImageUvSample(
|
|
1630
|
+
ctx,
|
|
1631
|
+
srcImg,
|
|
1632
|
+
triangle.uvSampleRect,
|
|
1633
|
+
entry.x + offsetX,
|
|
1634
|
+
entry.y + offsetY,
|
|
1635
|
+
entry.canvasW,
|
|
1636
|
+
entry.canvasH,
|
|
1637
|
+
atlasScale
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
ctx.restore();
|
|
1641
|
+
}
|
|
1642
|
+
} else if (entry.uvAffine) {
|
|
1643
|
+
const imgW = srcImg.naturalWidth || srcImg.width || 1;
|
|
1644
|
+
const imgH = srcImg.naturalHeight || srcImg.height || 1;
|
|
1645
|
+
setCssTransform(
|
|
1646
|
+
ctx,
|
|
1647
|
+
atlasScale,
|
|
1648
|
+
entry.uvAffine.a / imgW,
|
|
1649
|
+
entry.uvAffine.c / imgW,
|
|
1650
|
+
entry.uvAffine.b / imgH,
|
|
1651
|
+
entry.uvAffine.d / imgH,
|
|
1652
|
+
entry.x + entry.uvAffine.e + offsetX,
|
|
1653
|
+
entry.y + entry.uvAffine.f + offsetY
|
|
1654
|
+
);
|
|
1655
|
+
ctx.drawImage(srcImg, 0, 0);
|
|
1656
|
+
} else if (entry.uvSampleRect) {
|
|
1657
|
+
drawImageUvSample(
|
|
1658
|
+
ctx,
|
|
1659
|
+
srcImg,
|
|
1660
|
+
entry.uvSampleRect,
|
|
1661
|
+
entry.x + offsetX,
|
|
1662
|
+
entry.y + offsetY,
|
|
1663
|
+
entry.canvasW,
|
|
1664
|
+
entry.canvasH,
|
|
1665
|
+
atlasScale
|
|
1666
|
+
);
|
|
1667
|
+
} else {
|
|
1668
|
+
drawImageCover(
|
|
1669
|
+
ctx,
|
|
1670
|
+
srcImg,
|
|
1671
|
+
entry.x + offsetX,
|
|
1672
|
+
entry.y + offsetY,
|
|
1673
|
+
entry.canvasW,
|
|
1674
|
+
entry.canvasH,
|
|
1675
|
+
atlasScale
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function distanceToSegment(px, py, ax, ay, bx, by) {
|
|
1680
|
+
const dx = bx - ax;
|
|
1681
|
+
const dy = by - ay;
|
|
1682
|
+
const lenSq = dx * dx + dy * dy;
|
|
1683
|
+
if (lenSq <= BASIS_EPS) return Math.hypot(px - ax, py - ay);
|
|
1684
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));
|
|
1685
|
+
return Math.hypot(px - (ax + dx * t), py - (ay + dy * t));
|
|
1686
|
+
}
|
|
1687
|
+
function distanceToPolygonEdges(px, py, points, edgeIndices) {
|
|
1688
|
+
let best = Infinity;
|
|
1689
|
+
const count = points.length / 2;
|
|
1690
|
+
for (const edgeIndex of edgeIndices) {
|
|
1691
|
+
if (edgeIndex < 0 || edgeIndex >= count) continue;
|
|
1692
|
+
const i = edgeIndex * 2;
|
|
1693
|
+
const next = (edgeIndex + 1) % count * 2;
|
|
1694
|
+
best = Math.min(
|
|
1695
|
+
best,
|
|
1696
|
+
distanceToSegment(px, py, points[i], points[i + 1], points[next], points[next + 1])
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
return best;
|
|
1700
|
+
}
|
|
1701
|
+
function nearestOpaquePixelOffset(data, width, height, x, y, radius) {
|
|
1702
|
+
const minX = Math.max(0, x - radius);
|
|
1703
|
+
const maxX = Math.min(width - 1, x + radius);
|
|
1704
|
+
const minY = Math.max(0, y - radius);
|
|
1705
|
+
const maxY = Math.min(height - 1, y + radius);
|
|
1706
|
+
let bestOffset = null;
|
|
1707
|
+
let bestDistanceSq = Infinity;
|
|
1708
|
+
for (let yy = minY; yy <= maxY; yy++) {
|
|
1709
|
+
for (let xx = minX; xx <= maxX; xx++) {
|
|
1710
|
+
if (xx === x && yy === y) continue;
|
|
1711
|
+
const dx = xx - x;
|
|
1712
|
+
const dy = yy - y;
|
|
1713
|
+
const distanceSq = dx * dx + dy * dy;
|
|
1714
|
+
if (distanceSq > radius * radius || distanceSq >= bestDistanceSq) continue;
|
|
1715
|
+
const offset = (yy * width + xx) * 4;
|
|
1716
|
+
if (data[offset + 3] < TEXTURE_EDGE_REPAIR_SOURCE_ALPHA_MIN) continue;
|
|
1717
|
+
bestOffset = offset;
|
|
1718
|
+
bestDistanceSq = distanceSq;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return bestOffset;
|
|
1722
|
+
}
|
|
1723
|
+
function repairTextureEdgeAlpha(ctx, entry, atlasScale) {
|
|
1724
|
+
if (!entry.textureEdgeRepair || !entry.texture) return;
|
|
1725
|
+
if (!entry.textureEdgeRepairEdges || entry.textureEdgeRepairEdges.size === 0) return;
|
|
1726
|
+
const canvas = ctx.canvas;
|
|
1727
|
+
if (!canvas) return;
|
|
1728
|
+
const pixelX = Math.max(0, Math.floor(entry.x * atlasScale));
|
|
1729
|
+
const pixelY = Math.max(0, Math.floor(entry.y * atlasScale));
|
|
1730
|
+
const pixelW = Math.max(1, Math.min(canvas.width - pixelX, Math.ceil(entry.canvasW * atlasScale)));
|
|
1731
|
+
const pixelH = Math.max(1, Math.min(canvas.height - pixelY, Math.ceil(entry.canvasH * atlasScale)));
|
|
1732
|
+
if (pixelW <= 0 || pixelH <= 0) return;
|
|
1733
|
+
let imageData;
|
|
1734
|
+
try {
|
|
1735
|
+
imageData = ctx.getImageData(pixelX, pixelY, pixelW, pixelH);
|
|
1736
|
+
} catch {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
const data = imageData.data;
|
|
1740
|
+
const source = new Uint8ClampedArray(data);
|
|
1741
|
+
const radius = Math.max(TEXTURE_EDGE_REPAIR_RADIUS, TEXTURE_EDGE_REPAIR_RADIUS / atlasScale);
|
|
1742
|
+
const sourceRadius = Math.max(2, Math.ceil(radius * atlasScale) + 1);
|
|
1743
|
+
let changed = false;
|
|
1744
|
+
for (let y = 0; y < pixelH; y++) {
|
|
1745
|
+
for (let x = 0; x < pixelW; x++) {
|
|
1746
|
+
const offset = (y * pixelW + x) * 4;
|
|
1747
|
+
const alpha = data[offset + 3];
|
|
1748
|
+
if (alpha < TEXTURE_EDGE_REPAIR_ALPHA_MIN || alpha === 255) continue;
|
|
1749
|
+
const localX = (pixelX + x + 0.5) / atlasScale - entry.x;
|
|
1750
|
+
const localY = (pixelY + y + 0.5) / atlasScale - entry.y;
|
|
1751
|
+
if (distanceToPolygonEdges(localX, localY, entry.screenPts, entry.textureEdgeRepairEdges) > radius) {
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
const sourceOffset = nearestOpaquePixelOffset(source, pixelW, pixelH, x, y, sourceRadius);
|
|
1755
|
+
if (sourceOffset === null) continue;
|
|
1756
|
+
data[offset] = source[sourceOffset];
|
|
1757
|
+
data[offset + 1] = source[sourceOffset + 1];
|
|
1758
|
+
data[offset + 2] = source[sourceOffset + 2];
|
|
1759
|
+
data[offset + 3] = 255;
|
|
1760
|
+
changed = true;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
if (!changed) return;
|
|
1764
|
+
ctx.putImageData(imageData, pixelX, pixelY);
|
|
1765
|
+
}
|
|
982
1766
|
function computeTextureAtlasPlan(polygon, index, options = {}) {
|
|
983
1767
|
const { vertices, texture, uvs } = polygon;
|
|
984
1768
|
if (!vertices || vertices.length < 3) return null;
|
|
@@ -1025,18 +1809,20 @@ function computeTextureAtlasPlan(polygon, index, options = {}) {
|
|
|
1025
1809
|
if (y < yMin) yMin = y;
|
|
1026
1810
|
if (y > yMax) yMax = y;
|
|
1027
1811
|
}
|
|
1028
|
-
const shiftX = -xMin;
|
|
1029
|
-
const shiftY = -yMin;
|
|
1030
1812
|
const w = xMax - xMin;
|
|
1031
1813
|
const h = yMax - yMin;
|
|
1032
1814
|
if (!Number.isFinite(w) || !Number.isFinite(h)) return null;
|
|
1815
|
+
const textureEdgeRepairEdges = texture && options.textureEdgeRepairEdges?.size ? options.textureEdgeRepairEdges : null;
|
|
1816
|
+
const textureEdgeRepair = Boolean(texture && textureEdgeRepairEdges);
|
|
1817
|
+
const shiftX = -xMin;
|
|
1818
|
+
const shiftY = -yMin;
|
|
1033
1819
|
const screenPts = [];
|
|
1034
1820
|
for (let i = 0; i < local2D.length; i++) {
|
|
1035
1821
|
const [x, y] = local2D[i];
|
|
1036
|
-
|
|
1037
|
-
const sy = y + shiftY;
|
|
1038
|
-
screenPts.push(sx, sy);
|
|
1822
|
+
screenPts.push(x + shiftX, y + shiftY);
|
|
1039
1823
|
}
|
|
1824
|
+
const canvasW = Math.max(1, Math.ceil(w));
|
|
1825
|
+
const canvasH = Math.max(1, Math.ceil(h));
|
|
1040
1826
|
const tx = p0[0] - shiftX * xAxis[0] - shiftY * yAxis[0];
|
|
1041
1827
|
const ty = p0[1] - shiftX * xAxis[1] - shiftY * yAxis[1];
|
|
1042
1828
|
const tz = p0[2] - shiftX * xAxis[2] - shiftY * yAxis[2];
|
|
@@ -1058,6 +1844,44 @@ function computeTextureAtlasPlan(polygon, index, options = {}) {
|
|
|
1058
1844
|
tz,
|
|
1059
1845
|
1
|
|
1060
1846
|
].join(",");
|
|
1847
|
+
const canonicalMatrix = [
|
|
1848
|
+
xAxis[0] * canvasW,
|
|
1849
|
+
xAxis[1] * canvasW,
|
|
1850
|
+
xAxis[2] * canvasW,
|
|
1851
|
+
0,
|
|
1852
|
+
yAxis[0] * canvasH,
|
|
1853
|
+
yAxis[1] * canvasH,
|
|
1854
|
+
yAxis[2] * canvasH,
|
|
1855
|
+
0,
|
|
1856
|
+
nx,
|
|
1857
|
+
ny,
|
|
1858
|
+
nz,
|
|
1859
|
+
0,
|
|
1860
|
+
tx,
|
|
1861
|
+
ty,
|
|
1862
|
+
tz,
|
|
1863
|
+
1
|
|
1864
|
+
].join(",");
|
|
1865
|
+
const atlasMatrix = [
|
|
1866
|
+
xAxis[0] * canvasW / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1867
|
+
xAxis[1] * canvasW / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1868
|
+
xAxis[2] * canvasW / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1869
|
+
0,
|
|
1870
|
+
yAxis[0] * canvasH / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1871
|
+
yAxis[1] * canvasH / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1872
|
+
yAxis[2] * canvasH / ATLAS_CANONICAL_SIZE_EXPLICIT,
|
|
1873
|
+
0,
|
|
1874
|
+
nx,
|
|
1875
|
+
ny,
|
|
1876
|
+
nz,
|
|
1877
|
+
0,
|
|
1878
|
+
tx,
|
|
1879
|
+
ty,
|
|
1880
|
+
tz,
|
|
1881
|
+
1
|
|
1882
|
+
].join(",");
|
|
1883
|
+
const normal = [nx, ny, nz];
|
|
1884
|
+
const projectiveMatrix = !texture && vertices.length === 4 ? computeProjectiveQuadMatrix(screenPts, xAxis, yAxis, normal, tx, ty, tz) : null;
|
|
1061
1885
|
const directionalCfg = options.directionalLight;
|
|
1062
1886
|
const ambientCfg = options.ambientLight;
|
|
1063
1887
|
const lightDir = directionalCfg?.direction ?? DEFAULT_LIGHT_DIR;
|
|
@@ -1089,13 +1913,18 @@ function computeTextureAtlasPlan(polygon, index, options = {}) {
|
|
|
1089
1913
|
tileSize: tile,
|
|
1090
1914
|
layerElevation: elev,
|
|
1091
1915
|
matrix,
|
|
1092
|
-
|
|
1093
|
-
|
|
1916
|
+
canonicalMatrix,
|
|
1917
|
+
atlasMatrix,
|
|
1918
|
+
projectiveMatrix,
|
|
1919
|
+
canvasW,
|
|
1920
|
+
canvasH,
|
|
1094
1921
|
screenPts,
|
|
1095
1922
|
uvAffine,
|
|
1096
1923
|
uvSampleRect,
|
|
1097
1924
|
textureTriangles,
|
|
1098
|
-
|
|
1925
|
+
textureEdgeRepairEdges,
|
|
1926
|
+
textureEdgeRepair,
|
|
1927
|
+
normal,
|
|
1099
1928
|
textureTint,
|
|
1100
1929
|
shadedColor
|
|
1101
1930
|
};
|
|
@@ -1179,7 +2008,10 @@ async function buildAtlasPage(page, textureLighting, doc, atlasScale) {
|
|
|
1179
2008
|
const canvas = doc.createElement("canvas");
|
|
1180
2009
|
canvas.width = Math.max(1, Math.ceil(page.width * atlasScale));
|
|
1181
2010
|
canvas.height = Math.max(1, Math.ceil(page.height * atlasScale));
|
|
1182
|
-
const
|
|
2011
|
+
const needsReadback = page.entries.some(
|
|
2012
|
+
(entry) => entry.textureEdgeRepair && entry.texture && entry.textureEdgeRepairEdges && entry.textureEdgeRepairEdges.size > 0
|
|
2013
|
+
);
|
|
2014
|
+
const ctx = canvas.getContext("2d", needsReadback ? { willReadFrequently: true } : void 0);
|
|
1183
2015
|
if (!ctx) return { width: page.width, height: page.height, url: null };
|
|
1184
2016
|
const uniqueTextures = Array.from(new Set(
|
|
1185
2017
|
page.entries.flatMap((entry) => entry.texture ? [entry.texture] : [])
|
|
@@ -1190,97 +2022,36 @@ async function buildAtlasPage(page, textureLighting, doc, atlasScale) {
|
|
|
1190
2022
|
}));
|
|
1191
2023
|
for (const entry of page.entries) {
|
|
1192
2024
|
const srcImg = entry.texture ? loaded.get(entry.texture) : null;
|
|
1193
|
-
ctx.save();
|
|
1194
|
-
setCssTransform(ctx, atlasScale);
|
|
1195
|
-
ctx.beginPath();
|
|
1196
|
-
for (let i = 0; i < entry.screenPts.length; i += 2) {
|
|
1197
|
-
const px = entry.x + entry.screenPts[i];
|
|
1198
|
-
const py = entry.y + entry.screenPts[i + 1];
|
|
1199
|
-
if (i === 0) ctx.moveTo(px, py);
|
|
1200
|
-
else ctx.lineTo(px, py);
|
|
1201
|
-
}
|
|
1202
|
-
ctx.closePath();
|
|
1203
|
-
ctx.clip();
|
|
1204
2025
|
if (!entry.texture) {
|
|
2026
|
+
ctx.save();
|
|
1205
2027
|
setCssTransform(ctx, atlasScale);
|
|
2028
|
+
ctx.beginPath();
|
|
2029
|
+
tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
|
|
2030
|
+
ctx.clip();
|
|
1206
2031
|
ctx.fillStyle = textureLighting === "dynamic" ? entry.polygon.color ?? "#cccccc" : entry.shadedColor;
|
|
1207
2032
|
ctx.fillRect(entry.x, entry.y, entry.canvasW, entry.canvasH);
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const py = entry.y + clipPts[i + 1];
|
|
1220
|
-
if (i === 0) ctx.moveTo(px, py);
|
|
1221
|
-
else ctx.lineTo(px, py);
|
|
1222
|
-
}
|
|
1223
|
-
ctx.closePath();
|
|
1224
|
-
ctx.clip();
|
|
1225
|
-
if (triangle.uvAffine) {
|
|
1226
|
-
setCssTransform(
|
|
1227
|
-
ctx,
|
|
1228
|
-
atlasScale,
|
|
1229
|
-
triangle.uvAffine.a / imgW,
|
|
1230
|
-
triangle.uvAffine.c / imgW,
|
|
1231
|
-
triangle.uvAffine.b / imgH,
|
|
1232
|
-
triangle.uvAffine.d / imgH,
|
|
1233
|
-
entry.x + triangle.uvAffine.e,
|
|
1234
|
-
entry.y + triangle.uvAffine.f
|
|
1235
|
-
);
|
|
1236
|
-
ctx.drawImage(srcImg, 0, 0);
|
|
1237
|
-
} else if (triangle.uvSampleRect) {
|
|
1238
|
-
drawImageUvSample(
|
|
1239
|
-
ctx,
|
|
1240
|
-
srcImg,
|
|
1241
|
-
triangle.uvSampleRect,
|
|
1242
|
-
entry.x,
|
|
1243
|
-
entry.y,
|
|
1244
|
-
entry.canvasW,
|
|
1245
|
-
entry.canvasH,
|
|
1246
|
-
atlasScale
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1249
|
-
ctx.restore();
|
|
1250
|
-
}
|
|
1251
|
-
} else if (srcImg && entry.uvAffine) {
|
|
1252
|
-
const imgW = srcImg.naturalWidth || srcImg.width || 1;
|
|
1253
|
-
const imgH = srcImg.naturalHeight || srcImg.height || 1;
|
|
1254
|
-
setCssTransform(
|
|
1255
|
-
ctx,
|
|
1256
|
-
atlasScale,
|
|
1257
|
-
entry.uvAffine.a / imgW,
|
|
1258
|
-
entry.uvAffine.c / imgW,
|
|
1259
|
-
entry.uvAffine.b / imgH,
|
|
1260
|
-
entry.uvAffine.d / imgH,
|
|
1261
|
-
entry.x + entry.uvAffine.e,
|
|
1262
|
-
entry.y + entry.uvAffine.f
|
|
1263
|
-
);
|
|
1264
|
-
ctx.drawImage(srcImg, 0, 0);
|
|
1265
|
-
} else if (srcImg && entry.uvSampleRect) {
|
|
1266
|
-
drawImageUvSample(
|
|
1267
|
-
ctx,
|
|
1268
|
-
srcImg,
|
|
1269
|
-
entry.uvSampleRect,
|
|
1270
|
-
entry.x,
|
|
1271
|
-
entry.y,
|
|
1272
|
-
entry.canvasW,
|
|
1273
|
-
entry.canvasH,
|
|
1274
|
-
atlasScale
|
|
1275
|
-
);
|
|
1276
|
-
} else if (srcImg) {
|
|
1277
|
-
drawImageCover(ctx, srcImg, entry.x, entry.y, entry.canvasW, entry.canvasH, atlasScale);
|
|
1278
|
-
}
|
|
2033
|
+
ctx.restore();
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (srcImg) {
|
|
2037
|
+
ctx.save();
|
|
2038
|
+
setCssTransform(ctx, atlasScale);
|
|
2039
|
+
ctx.beginPath();
|
|
2040
|
+
tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
|
|
2041
|
+
ctx.clip();
|
|
2042
|
+
drawTexturedAtlasEntry(ctx, entry, srcImg, atlasScale);
|
|
2043
|
+
ctx.restore();
|
|
1279
2044
|
}
|
|
1280
2045
|
if (entry.texture && textureLighting === "baked") {
|
|
2046
|
+
ctx.save();
|
|
2047
|
+
setCssTransform(ctx, atlasScale);
|
|
2048
|
+
ctx.beginPath();
|
|
2049
|
+
tracePolygonPath(ctx, entry.x, entry.y, entry.screenPts);
|
|
2050
|
+
ctx.clip();
|
|
1281
2051
|
applyTextureTint(ctx, entry.x, entry.y, entry.canvasW, entry.canvasH, entry.textureTint, atlasScale);
|
|
2052
|
+
ctx.restore();
|
|
1282
2053
|
}
|
|
1283
|
-
ctx
|
|
2054
|
+
repairTextureEdgeAlpha(ctx, entry, atlasScale);
|
|
1284
2055
|
}
|
|
1285
2056
|
const url = await canvasToUrl(canvas);
|
|
1286
2057
|
canvas.width = 1;
|
|
@@ -1299,21 +2070,32 @@ async function buildAtlasPages(pages, textureLighting, doc, atlasScale, isCancel
|
|
|
1299
2070
|
}
|
|
1300
2071
|
return built;
|
|
1301
2072
|
}
|
|
1302
|
-
function useTextureAtlas(plans, textureLighting,
|
|
1303
|
-
const
|
|
2073
|
+
function useTextureAtlas(plans, textureLighting, textureQualityInput, strategies) {
|
|
2074
|
+
const disableB = strategies?.disable?.includes("b") ?? false;
|
|
2075
|
+
const disableI = strategies?.disable?.includes("i") ?? false;
|
|
2076
|
+
const disableU = strategies?.disable?.includes("u") ?? false;
|
|
2077
|
+
const useFullRectSolid = !disableB;
|
|
2078
|
+
const useProjectiveQuad = useFullRectSolid;
|
|
2079
|
+
const useStableTriangle = !disableU && solidTriangleSupported();
|
|
2080
|
+
const useBorderShape = !disableI && textureLighting !== "dynamic" && borderShapeSupported();
|
|
1304
2081
|
const atlasPlans = useMemo5(
|
|
1305
2082
|
() => plans.map((plan) => {
|
|
1306
2083
|
if (!plan) return plan;
|
|
1307
2084
|
if (plan.texture) return plan;
|
|
1308
|
-
if (isSolidTrianglePlan(plan)) return null;
|
|
1309
|
-
|
|
2085
|
+
if (useStableTriangle && isSolidTrianglePlan(plan)) return null;
|
|
2086
|
+
const fullRect = isFullRectSolid(plan);
|
|
2087
|
+
if (useFullRectSolid && fullRect || useProjectiveQuad && isProjectiveQuadPlan(plan) || textureLighting !== "dynamic" && useBorderShape && (!fullRect || disableB)) return null;
|
|
1310
2088
|
return plan;
|
|
1311
2089
|
}),
|
|
1312
|
-
[plans, textureLighting, useBorderShape]
|
|
2090
|
+
[plans, textureLighting, useFullRectSolid, useProjectiveQuad, useStableTriangle, useBorderShape]
|
|
1313
2091
|
);
|
|
1314
2092
|
const { packed, atlasScale } = useMemo5(
|
|
1315
|
-
() => packTextureAtlasPlansWithScale(
|
|
1316
|
-
|
|
2093
|
+
() => packTextureAtlasPlansWithScale(
|
|
2094
|
+
atlasPlans,
|
|
2095
|
+
textureQualityInput,
|
|
2096
|
+
typeof document !== "undefined" ? document : null
|
|
2097
|
+
),
|
|
2098
|
+
[atlasPlans, textureQualityInput]
|
|
1317
2099
|
);
|
|
1318
2100
|
const [pages, setPages] = useState(
|
|
1319
2101
|
() => packed.pages.map((page) => ({ width: page.width, height: page.height, url: null }))
|
|
@@ -1353,54 +2135,103 @@ function useTextureAtlas(plans, textureLighting, atlasScaleInput) {
|
|
|
1353
2135
|
}
|
|
1354
2136
|
function TextureBorderShapePoly({
|
|
1355
2137
|
entry,
|
|
2138
|
+
solidPaintDefaults,
|
|
1356
2139
|
className,
|
|
1357
2140
|
style: styleProp,
|
|
1358
2141
|
domAttrs,
|
|
1359
2142
|
domEventHandlers,
|
|
1360
|
-
pointerEvents = "auto"
|
|
2143
|
+
pointerEvents = "auto",
|
|
2144
|
+
disabledStrategies
|
|
1361
2145
|
}) {
|
|
1362
2146
|
const fullRect = isFullRectSolid(entry);
|
|
1363
|
-
const
|
|
2147
|
+
const bDisabled = disabledStrategies?.has("b") ?? false;
|
|
2148
|
+
const useIForFullRect = bDisabled && borderShapeSupported();
|
|
2149
|
+
const borderShape = !fullRect || useIForFullRect ? cssBorderShapeForPlan(entry) : null;
|
|
2150
|
+
const useDefaultPaint = entry.shadedColor === solidPaintDefaults?.paintColor;
|
|
1364
2151
|
const setElementRef = useCallback3((el) => {
|
|
1365
2152
|
if (!el) return;
|
|
1366
2153
|
if (borderShape) el.style.setProperty("border-shape", borderShape);
|
|
1367
2154
|
else el.style.removeProperty("border-shape");
|
|
2155
|
+
orderBrushInlineStyle(el);
|
|
1368
2156
|
}, [borderShape]);
|
|
2157
|
+
const transform = formatMatrix3d(borderShape ? formatBorderShapeMatrix(entry) : formatSolidQuadMatrix(entry));
|
|
1369
2158
|
const style = fullRect ? {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
transform: formatMatrix3d(entry.matrix),
|
|
1373
|
-
background: entry.shadedColor,
|
|
2159
|
+
transform,
|
|
2160
|
+
color: useDefaultPaint ? void 0 : entry.shadedColor,
|
|
1374
2161
|
pointerEvents: pointerEvents === "none" ? "none" : void 0,
|
|
1375
2162
|
...styleProp
|
|
1376
2163
|
} : {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
2164
|
+
transform,
|
|
2165
|
+
color: useDefaultPaint ? void 0 : entry.shadedColor,
|
|
2166
|
+
pointerEvents: pointerEvents === "none" ? "none" : void 0,
|
|
2167
|
+
...styleProp
|
|
2168
|
+
};
|
|
2169
|
+
const dataAttrs = entry.polygon.data ? Object.fromEntries(
|
|
2170
|
+
Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
|
|
2171
|
+
) : {};
|
|
2172
|
+
const elementClassName = className?.trim() || void 0;
|
|
2173
|
+
if (fullRect && !useIForFullRect) {
|
|
2174
|
+
return /* @__PURE__ */ jsx3(
|
|
2175
|
+
"b",
|
|
2176
|
+
{
|
|
2177
|
+
className: elementClassName,
|
|
2178
|
+
style,
|
|
2179
|
+
...domEventHandlers,
|
|
2180
|
+
...dataAttrs,
|
|
2181
|
+
...domAttrs
|
|
2182
|
+
}
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
return /* @__PURE__ */ jsx3(
|
|
2186
|
+
"i",
|
|
2187
|
+
{
|
|
2188
|
+
ref: setElementRef,
|
|
2189
|
+
className: elementClassName,
|
|
2190
|
+
style,
|
|
2191
|
+
...domEventHandlers,
|
|
2192
|
+
...dataAttrs,
|
|
2193
|
+
...domAttrs
|
|
2194
|
+
}
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
function TextureProjectiveSolidPoly({
|
|
2198
|
+
entry,
|
|
2199
|
+
textureLighting,
|
|
2200
|
+
solidPaintDefaults,
|
|
2201
|
+
className,
|
|
2202
|
+
style: styleProp,
|
|
2203
|
+
domAttrs,
|
|
2204
|
+
domEventHandlers,
|
|
2205
|
+
pointerEvents = "auto"
|
|
2206
|
+
}) {
|
|
2207
|
+
const dynamic = textureLighting === "dynamic";
|
|
2208
|
+
const base = parseHex(entry.polygon.color ?? "#cccccc");
|
|
2209
|
+
const useDefaultDynamicColor = dynamic && rgbKey(base) === solidPaintDefaults?.dynamicColorKey;
|
|
2210
|
+
const style = {
|
|
2211
|
+
transform: formatMatrix3d(entry.projectiveMatrix),
|
|
2212
|
+
color: dynamic || entry.shadedColor === solidPaintDefaults?.paintColor ? void 0 : entry.shadedColor,
|
|
1381
2213
|
pointerEvents: pointerEvents === "none" ? "none" : void 0,
|
|
2214
|
+
...dynamic && !useDefaultDynamicColor ? {
|
|
2215
|
+
["--pnx"]: entry.normal[0].toFixed(4),
|
|
2216
|
+
["--pny"]: entry.normal[1].toFixed(4),
|
|
2217
|
+
["--pnz"]: entry.normal[2].toFixed(4),
|
|
2218
|
+
["--psr"]: (base.r / 255).toFixed(4),
|
|
2219
|
+
["--psg"]: (base.g / 255).toFixed(4),
|
|
2220
|
+
["--psb"]: (base.b / 255).toFixed(4)
|
|
2221
|
+
} : dynamic ? {
|
|
2222
|
+
["--pnx"]: entry.normal[0].toFixed(4),
|
|
2223
|
+
["--pny"]: entry.normal[1].toFixed(4),
|
|
2224
|
+
["--pnz"]: entry.normal[2].toFixed(4)
|
|
2225
|
+
} : null,
|
|
1382
2226
|
...styleProp
|
|
1383
2227
|
};
|
|
1384
2228
|
const dataAttrs = entry.polygon.data ? Object.fromEntries(
|
|
1385
2229
|
Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
|
|
1386
2230
|
) : {};
|
|
1387
2231
|
const elementClassName = className?.trim() || void 0;
|
|
1388
|
-
if (fullRect) {
|
|
1389
|
-
return /* @__PURE__ */ jsx3(
|
|
1390
|
-
"b",
|
|
1391
|
-
{
|
|
1392
|
-
className: elementClassName,
|
|
1393
|
-
style,
|
|
1394
|
-
...domEventHandlers,
|
|
1395
|
-
...dataAttrs,
|
|
1396
|
-
...domAttrs
|
|
1397
|
-
}
|
|
1398
|
-
);
|
|
1399
|
-
}
|
|
1400
2232
|
return /* @__PURE__ */ jsx3(
|
|
1401
|
-
"
|
|
2233
|
+
"b",
|
|
1402
2234
|
{
|
|
1403
|
-
ref: setElementRef,
|
|
1404
2235
|
className: elementClassName,
|
|
1405
2236
|
style,
|
|
1406
2237
|
...domEventHandlers,
|
|
@@ -1412,13 +2243,14 @@ function TextureBorderShapePoly({
|
|
|
1412
2243
|
function TextureTrianglePoly({
|
|
1413
2244
|
entry,
|
|
1414
2245
|
textureLighting,
|
|
2246
|
+
solidPaintDefaults,
|
|
1415
2247
|
className,
|
|
1416
2248
|
style: styleProp,
|
|
1417
2249
|
domAttrs,
|
|
1418
2250
|
domEventHandlers,
|
|
1419
2251
|
pointerEvents = "auto"
|
|
1420
2252
|
}) {
|
|
1421
|
-
const triangleStyle = solidTriangleStyle(entry, textureLighting, pointerEvents);
|
|
2253
|
+
const triangleStyle = solidTriangleStyle(entry, textureLighting, pointerEvents, solidPaintDefaults);
|
|
1422
2254
|
if (!triangleStyle) return null;
|
|
1423
2255
|
const dataAttrs = entry.polygon.data ? Object.fromEntries(
|
|
1424
2256
|
Object.entries(entry.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
|
|
@@ -1446,16 +2278,20 @@ function TextureAtlasPoly({
|
|
|
1446
2278
|
pointerEvents = "auto"
|
|
1447
2279
|
}) {
|
|
1448
2280
|
const dynamic = textureLighting === "dynamic";
|
|
2281
|
+
const atlasCanonicalSize = entry.atlasCanonicalSize ?? ATLAS_CANONICAL_SIZE_EXPLICIT;
|
|
2282
|
+
const atlasWidth = entry.canvasW || 1;
|
|
2283
|
+
const atlasHeight = entry.canvasH || 1;
|
|
2284
|
+
const atlasPosition = page ? `${formatCssLength(-entry.x / atlasWidth * atlasCanonicalSize)} ${formatCssLength(-entry.y / atlasHeight * atlasCanonicalSize)}` : void 0;
|
|
2285
|
+
const atlasSize = page ? `${formatCssLength(page.width / atlasWidth * atlasCanonicalSize)} ${formatCssLength(page.height / atlasHeight * atlasCanonicalSize)}` : void 0;
|
|
1449
2286
|
const dynamicMask = dynamic && page?.url ? `url(${page.url})` : void 0;
|
|
1450
|
-
const background = !dynamic && page?.url ? `url(${page.url})
|
|
2287
|
+
const background = !dynamic && page?.url ? `url(${page.url}) ${atlasPosition} / ${atlasSize} no-repeat` : void 0;
|
|
1451
2288
|
const style = {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
transform: formatMatrix3d(entry.matrix),
|
|
2289
|
+
transform: formatMatrix3d(entry.atlasMatrix),
|
|
2290
|
+
["--polycss-atlas-size"]: `${atlasCanonicalSize}px`,
|
|
1455
2291
|
background,
|
|
1456
2292
|
backgroundImage: dynamic && page?.url ? `url(${page.url})` : void 0,
|
|
1457
|
-
backgroundPosition: dynamic ?
|
|
1458
|
-
backgroundSize: dynamic
|
|
2293
|
+
backgroundPosition: dynamic ? atlasPosition : void 0,
|
|
2294
|
+
backgroundSize: dynamic ? atlasSize : void 0,
|
|
1459
2295
|
...dynamic ? {
|
|
1460
2296
|
["--pnx"]: entry.normal[0].toFixed(4),
|
|
1461
2297
|
["--pny"]: entry.normal[1].toFixed(4),
|
|
@@ -1466,12 +2302,12 @@ function TextureAtlasPoly({
|
|
|
1466
2302
|
// the polygon don't get painted with the tint.
|
|
1467
2303
|
maskImage: dynamicMask,
|
|
1468
2304
|
maskMode: "alpha",
|
|
1469
|
-
maskPosition:
|
|
1470
|
-
maskSize:
|
|
2305
|
+
maskPosition: atlasPosition,
|
|
2306
|
+
maskSize: atlasSize,
|
|
1471
2307
|
maskRepeat: "no-repeat",
|
|
1472
2308
|
WebkitMaskImage: dynamicMask,
|
|
1473
|
-
WebkitMaskPosition:
|
|
1474
|
-
WebkitMaskSize:
|
|
2309
|
+
WebkitMaskPosition: atlasPosition,
|
|
2310
|
+
WebkitMaskSize: atlasSize,
|
|
1475
2311
|
WebkitMaskRepeat: "no-repeat"
|
|
1476
2312
|
} : null,
|
|
1477
2313
|
opacity: page?.url ? void 0 : 0,
|
|
@@ -1502,7 +2338,7 @@ function usePolySceneContext2() {
|
|
|
1502
2338
|
}
|
|
1503
2339
|
|
|
1504
2340
|
// src/scene/PolyScene.tsx
|
|
1505
|
-
import {
|
|
2341
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
1506
2342
|
function PolySceneInner({
|
|
1507
2343
|
polygons: polygonsProp,
|
|
1508
2344
|
centerPolygons: centerPolygonsProp,
|
|
@@ -1513,8 +2349,10 @@ function PolySceneInner({
|
|
|
1513
2349
|
directionalLight,
|
|
1514
2350
|
ambientLight,
|
|
1515
2351
|
textureLighting = "baked",
|
|
1516
|
-
|
|
2352
|
+
textureQuality,
|
|
2353
|
+
strategies,
|
|
1517
2354
|
autoCenter = false,
|
|
2355
|
+
shadow,
|
|
1518
2356
|
className,
|
|
1519
2357
|
style,
|
|
1520
2358
|
children,
|
|
@@ -1558,14 +2396,6 @@ function PolySceneInner({
|
|
|
1558
2396
|
{ directionalLight }
|
|
1559
2397
|
);
|
|
1560
2398
|
const sceneBbox = centerInputPolygons ? centerSceneBbox : renderSceneBbox;
|
|
1561
|
-
const sceneStyle = useMemo6(() => {
|
|
1562
|
-
const handle = createIsometricCamera2(cameraState);
|
|
1563
|
-
const cameraStyle = handle.getStyle();
|
|
1564
|
-
return {
|
|
1565
|
-
["--scene-transform"]: cameraStyle.transform
|
|
1566
|
-
};
|
|
1567
|
-
}, [cameraState]);
|
|
1568
|
-
const computedClassName = `polycss-scene${className ? ` ${className}` : ""}`;
|
|
1569
2399
|
const directionalForAtlas = textureLighting === "dynamic" ? void 0 : directionalLight;
|
|
1570
2400
|
const ambientForAtlas = textureLighting === "dynamic" ? void 0 : ambientLight;
|
|
1571
2401
|
const polyContext = useMemo6(() => {
|
|
@@ -1578,11 +2408,43 @@ function PolySceneInner({
|
|
|
1578
2408
|
textureLighting
|
|
1579
2409
|
};
|
|
1580
2410
|
}, [directionalForAtlas, ambientForAtlas, textureLighting]);
|
|
2411
|
+
const autoCenterOffset = useMemo6(() => {
|
|
2412
|
+
if (!autoCenter) return [0, 0, 0];
|
|
2413
|
+
return [
|
|
2414
|
+
(sceneBbox.min[0] + sceneBbox.max[0]) / 2,
|
|
2415
|
+
(sceneBbox.min[1] + sceneBbox.max[1]) / 2,
|
|
2416
|
+
(sceneBbox.min[2] + sceneBbox.max[2]) / 2
|
|
2417
|
+
];
|
|
2418
|
+
}, [autoCenter, sceneBbox]);
|
|
2419
|
+
useEffect3(() => {
|
|
2420
|
+
store.setAutoCenterOffset(autoCenterOffset);
|
|
2421
|
+
}, [store, autoCenterOffset]);
|
|
2422
|
+
const sceneStyle = useMemo6(() => {
|
|
2423
|
+
const s = cameraState;
|
|
2424
|
+
const [ox, oy, oz] = autoCenterOffset;
|
|
2425
|
+
const tileSize = BASE_TILE2;
|
|
2426
|
+
const wx = s.target[0] + ox;
|
|
2427
|
+
const wy = s.target[1] + oy;
|
|
2428
|
+
const wz = s.target[2] + oz;
|
|
2429
|
+
const cssX = wy * tileSize;
|
|
2430
|
+
const cssY = wx * tileSize;
|
|
2431
|
+
const cssZ = wz * tileSize;
|
|
2432
|
+
const distancePart = s.distance !== 0 ? `translateZ(${-s.distance}px) ` : "";
|
|
2433
|
+
const transform = `${distancePart}scale(${s.zoom}) rotateX(${s.rotX}deg) rotate(${s.rotY}deg) translate3d(${-cssX}px, ${-cssY}px, ${-cssZ}px)`;
|
|
2434
|
+
return { transform };
|
|
2435
|
+
}, [cameraState, autoCenterOffset]);
|
|
2436
|
+
const computedClassName = `polycss-scene${className ? ` ${className}` : ""}`;
|
|
1581
2437
|
const textureAtlasPlans = useMemo6(
|
|
1582
|
-
() =>
|
|
2438
|
+
() => {
|
|
2439
|
+
const repairEdges = buildTextureEdgeRepairSets(polygons);
|
|
2440
|
+
return polygons.map((p, i) => computeTextureAtlasPlan(p, i, {
|
|
2441
|
+
...polyContext,
|
|
2442
|
+
textureEdgeRepairEdges: repairEdges[i]
|
|
2443
|
+
}));
|
|
2444
|
+
},
|
|
1583
2445
|
[polygons, polyContext]
|
|
1584
2446
|
);
|
|
1585
|
-
const textureAtlas = useTextureAtlas(textureAtlasPlans, textureLighting,
|
|
2447
|
+
const textureAtlas = useTextureAtlas(textureAtlasPlans, textureLighting, textureQuality, strategies);
|
|
1586
2448
|
const dynamicLightVars = useMemo6(() => {
|
|
1587
2449
|
if (textureLighting !== "dynamic") return null;
|
|
1588
2450
|
const dir = directionalLight?.direction ?? [0.4, -0.7, 0.59];
|
|
@@ -1593,6 +2455,8 @@ function PolySceneInner({
|
|
|
1593
2455
|
const lightIntensity = directionalLight?.intensity ?? 1;
|
|
1594
2456
|
const ambientIntensity = ambientLight?.intensity ?? 0.4;
|
|
1595
2457
|
const ch = (n) => (n / 255).toFixed(4);
|
|
2458
|
+
const rawClz = lz;
|
|
2459
|
+
const clz = Math.sign(rawClz || 1) * Math.max(Math.abs(rawClz), 0.01);
|
|
1596
2460
|
return {
|
|
1597
2461
|
["--plx"]: lx.toFixed(4),
|
|
1598
2462
|
["--ply"]: ly.toFixed(4),
|
|
@@ -1604,16 +2468,52 @@ function PolySceneInner({
|
|
|
1604
2468
|
["--par"]: ch(ambRgb[0]),
|
|
1605
2469
|
["--pag"]: ch(ambRgb[1]),
|
|
1606
2470
|
["--pab"]: ch(ambRgb[2]),
|
|
1607
|
-
["--pai"]: ambientIntensity.toFixed(4)
|
|
2471
|
+
["--pai"]: ambientIntensity.toFixed(4),
|
|
2472
|
+
["--clx"]: lx.toFixed(4),
|
|
2473
|
+
["--cly"]: ly.toFixed(4),
|
|
2474
|
+
["--clz"]: clz.toFixed(4)
|
|
1608
2475
|
};
|
|
1609
2476
|
}, [textureLighting, directionalLight, ambientLight]);
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2477
|
+
const shadowCastersRef = useRef3(/* @__PURE__ */ new Map());
|
|
2478
|
+
const registerShadowCaster = useCallback4((meshId, meshPolygons) => {
|
|
2479
|
+
if (meshPolygons === null) {
|
|
2480
|
+
shadowCastersRef.current.delete(meshId);
|
|
2481
|
+
} else {
|
|
2482
|
+
shadowCastersRef.current.set(meshId, meshPolygons);
|
|
2483
|
+
}
|
|
2484
|
+
const el = sceneElRef.current;
|
|
2485
|
+
if (!el) return;
|
|
2486
|
+
if (textureLighting !== "dynamic") {
|
|
2487
|
+
el.style.removeProperty("--shadow-ground-cssz");
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
let minWorldZ = Infinity;
|
|
2491
|
+
for (const polys of shadowCastersRef.current.values()) {
|
|
2492
|
+
for (const poly of polys) {
|
|
2493
|
+
for (const v of poly.vertices) {
|
|
2494
|
+
if (v[2] < minWorldZ) minWorldZ = v[2];
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
if (!Number.isFinite(minWorldZ)) {
|
|
2499
|
+
el.style.removeProperty("--shadow-ground-cssz");
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
const lift = shadow?.lift ?? 0.05;
|
|
2503
|
+
const groundCssZ = (minWorldZ + lift) * BASE_TILE2;
|
|
2504
|
+
el.style.setProperty("--shadow-ground-cssz", groundCssZ.toFixed(3));
|
|
2505
|
+
}, [sceneElRef, textureLighting, shadow]);
|
|
2506
|
+
useEffect3(() => {
|
|
2507
|
+
const el = sceneElRef.current;
|
|
2508
|
+
if (!el) return;
|
|
2509
|
+
if (textureLighting !== "dynamic") {
|
|
2510
|
+
el.style.removeProperty("--shadow-ground-cssz");
|
|
2511
|
+
}
|
|
2512
|
+
}, [textureLighting, sceneElRef]);
|
|
2513
|
+
const disabledStrategies = useMemo6(
|
|
2514
|
+
() => strategies?.disable?.length ? new Set(strategies.disable) : void 0,
|
|
2515
|
+
[strategies]
|
|
2516
|
+
);
|
|
1617
2517
|
const polyChildren = textureAtlas.entries.map((entry, index) => {
|
|
1618
2518
|
if (entry) {
|
|
1619
2519
|
return /* @__PURE__ */ jsx4(
|
|
@@ -1628,13 +2528,21 @@ function PolySceneInner({
|
|
|
1628
2528
|
}
|
|
1629
2529
|
const plan = textureAtlasPlans[index];
|
|
1630
2530
|
if (!plan || plan.texture) return null;
|
|
1631
|
-
|
|
2531
|
+
const useU = !disabledStrategies?.has("u");
|
|
2532
|
+
const useProjectiveSolid = !disabledStrategies?.has("b");
|
|
2533
|
+
if (useU && isSolidTrianglePlan(plan)) {
|
|
2534
|
+
return /* @__PURE__ */ jsx4(TextureTrianglePoly, { entry: plan, textureLighting }, plan.index);
|
|
2535
|
+
}
|
|
2536
|
+
if (useProjectiveSolid && isProjectiveQuadPlan(plan)) {
|
|
2537
|
+
return /* @__PURE__ */ jsx4(TextureProjectiveSolidPoly, { entry: plan, textureLighting }, plan.index);
|
|
2538
|
+
}
|
|
2539
|
+
return /* @__PURE__ */ jsx4(TextureBorderShapePoly, { entry: plan, disabledStrategies }, plan.index);
|
|
1632
2540
|
});
|
|
1633
2541
|
const sceneCtxValue = useMemo6(
|
|
1634
|
-
() => ({ textureLighting, directionalLight, ambientLight }),
|
|
1635
|
-
[textureLighting, directionalLight, ambientLight]
|
|
2542
|
+
() => ({ textureLighting, directionalLight, ambientLight, shadow, registerShadowCaster }),
|
|
2543
|
+
[textureLighting, directionalLight, ambientLight, shadow, registerShadowCaster]
|
|
1636
2544
|
);
|
|
1637
|
-
return /* @__PURE__ */ jsx4(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */
|
|
2545
|
+
return /* @__PURE__ */ jsx4(PolySceneContext.Provider, { value: sceneCtxValue, children: /* @__PURE__ */ jsxs(
|
|
1638
2546
|
"div",
|
|
1639
2547
|
{
|
|
1640
2548
|
ref: localSceneRef,
|
|
@@ -1648,13 +2556,10 @@ function PolySceneInner({
|
|
|
1648
2556
|
// No more --polycss-rows / --polycss-cols — CSS Grid was dropped
|
|
1649
2557
|
// in Phase 4 (per §Design.4a).
|
|
1650
2558
|
},
|
|
1651
|
-
children:
|
|
1652
|
-
polyChildren,
|
|
1653
|
-
children
|
|
1654
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2559
|
+
children: [
|
|
1655
2560
|
polyChildren,
|
|
1656
2561
|
children
|
|
1657
|
-
]
|
|
2562
|
+
]
|
|
1658
2563
|
}
|
|
1659
2564
|
) });
|
|
1660
2565
|
}
|
|
@@ -1671,7 +2576,7 @@ import {
|
|
|
1671
2576
|
useRef as useRef5,
|
|
1672
2577
|
useState as useState3
|
|
1673
2578
|
} from "react";
|
|
1674
|
-
import { computeSceneBbox, inverseRotateVec3 } from "@layoutit/polycss-core";
|
|
2579
|
+
import { computeSceneBbox, findOverlappingPolygonDuplicates, inverseRotateVec3, parseHexColor as parseHexColor2 } from "@layoutit/polycss-core";
|
|
1675
2580
|
|
|
1676
2581
|
// src/scene/useMesh.ts
|
|
1677
2582
|
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
|
|
@@ -1802,7 +2707,17 @@ function findMeshUnderPoint(clientX, clientY, filter) {
|
|
|
1802
2707
|
}
|
|
1803
2708
|
|
|
1804
2709
|
// src/scene/PolyMesh.tsx
|
|
1805
|
-
import { Fragment as
|
|
2710
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2711
|
+
function solidPaintVars(defaults) {
|
|
2712
|
+
const out = {};
|
|
2713
|
+
if (defaults.paintColor) out["--polycss-paint"] = defaults.paintColor;
|
|
2714
|
+
if (defaults.dynamicColor) {
|
|
2715
|
+
out["--psr"] = (defaults.dynamicColor.r / 255).toFixed(4);
|
|
2716
|
+
out["--psg"] = (defaults.dynamicColor.g / 255).toFixed(4);
|
|
2717
|
+
out["--psb"] = (defaults.dynamicColor.b / 255).toFixed(4);
|
|
2718
|
+
}
|
|
2719
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
2720
|
+
}
|
|
1806
2721
|
function buildTransform(position, scale, rotation) {
|
|
1807
2722
|
const parts = [];
|
|
1808
2723
|
if (position) {
|
|
@@ -1848,7 +2763,8 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
1848
2763
|
polygons: polygonsProp,
|
|
1849
2764
|
autoCenter,
|
|
1850
2765
|
textureLighting,
|
|
1851
|
-
|
|
2766
|
+
textureQuality,
|
|
2767
|
+
castShadow,
|
|
1852
2768
|
children,
|
|
1853
2769
|
fallback,
|
|
1854
2770
|
errorFallback,
|
|
@@ -1876,12 +2792,43 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
1876
2792
|
return { ...parseOptions ?? {}, ...mtl ? { mtlUrl: mtl } : {} };
|
|
1877
2793
|
}, [mtl, parseOptions]);
|
|
1878
2794
|
const fetched = usePolyMesh(src ?? "", mergedOptions);
|
|
1879
|
-
const
|
|
2795
|
+
const externalPolygons = src ? fetched.polygons : polygonsProp ?? [];
|
|
2796
|
+
const [localPolygons, setLocalPolygons] = useState3(null);
|
|
2797
|
+
const prevExternalRef = useRef5(externalPolygons);
|
|
2798
|
+
if (prevExternalRef.current !== externalPolygons) {
|
|
2799
|
+
prevExternalRef.current = externalPolygons;
|
|
2800
|
+
if (localPolygons !== null) setLocalPolygons(null);
|
|
2801
|
+
}
|
|
2802
|
+
const sourcePolygons = localPolygons ?? externalPolygons;
|
|
2803
|
+
const hasRenderProp = typeof children === "function";
|
|
2804
|
+
const renderPolygon = hasRenderProp ? children : null;
|
|
2805
|
+
const staticChildren = hasRenderProp ? null : children;
|
|
1880
2806
|
const polygons = useMemo7(
|
|
1881
2807
|
() => autoCenter ? recenterPolygons(sourcePolygons) : sourcePolygons,
|
|
1882
2808
|
[sourcePolygons, autoCenter]
|
|
1883
2809
|
);
|
|
1884
2810
|
const transform = buildTransform(position, scale, rotation);
|
|
2811
|
+
const transformOrigin = useMemo7(() => {
|
|
2812
|
+
if (polygons.length === 0) return void 0;
|
|
2813
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
2814
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
2815
|
+
for (const poly of polygons) {
|
|
2816
|
+
for (const v of poly.vertices) {
|
|
2817
|
+
if (v[0] < minX) minX = v[0];
|
|
2818
|
+
if (v[0] > maxX) maxX = v[0];
|
|
2819
|
+
if (v[1] < minY) minY = v[1];
|
|
2820
|
+
if (v[1] > maxY) maxY = v[1];
|
|
2821
|
+
if (v[2] < minZ) minZ = v[2];
|
|
2822
|
+
if (v[2] > maxZ) maxZ = v[2];
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
if (!Number.isFinite(minX)) return void 0;
|
|
2826
|
+
const tile = 50;
|
|
2827
|
+
const x = (minY + maxY) / 2 * tile;
|
|
2828
|
+
const y = (minX + maxX) / 2 * tile;
|
|
2829
|
+
const z = (minZ + maxZ) / 2 * tile;
|
|
2830
|
+
return `${x}px ${y}px ${z}px`;
|
|
2831
|
+
}, [polygons]);
|
|
1885
2832
|
const wrapperRef = useRef5(null);
|
|
1886
2833
|
const propsRef = useRef5({ position, scale, rotation });
|
|
1887
2834
|
propsRef.current = { position, scale, rotation };
|
|
@@ -1897,7 +2844,14 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
1897
2844
|
getRotation: () => propsRef.current.rotation,
|
|
1898
2845
|
getScale: () => propsRef.current.scale,
|
|
1899
2846
|
getPolygons: () => polygonsRef.current,
|
|
1900
|
-
rebakeAtlas: () => setBakedRotation(propsRef.current.rotation)
|
|
2847
|
+
rebakeAtlas: () => setBakedRotation(propsRef.current.rotation),
|
|
2848
|
+
updatePolygon(target, partial) {
|
|
2849
|
+
const current = polygonsRef.current;
|
|
2850
|
+
const idx = typeof target === "number" ? target : current.indexOf(target);
|
|
2851
|
+
if (idx < 0 || idx >= current.length) return;
|
|
2852
|
+
Object.assign(current[idx], partial);
|
|
2853
|
+
setLocalPolygons([...current]);
|
|
2854
|
+
}
|
|
1901
2855
|
}), [id]);
|
|
1902
2856
|
useImperativeHandle(forwardedRef, () => handle, [handle]);
|
|
1903
2857
|
useEffect5(() => {
|
|
@@ -2047,11 +3001,6 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
2047
3001
|
["--plz"]: (localDir[2] / len).toFixed(4)
|
|
2048
3002
|
};
|
|
2049
3003
|
}, [effectiveTextureLighting, rotation, sceneDirectionalLight]);
|
|
2050
|
-
const wrapperStyle = {
|
|
2051
|
-
transform,
|
|
2052
|
-
...dynamicLightOverride,
|
|
2053
|
-
...style
|
|
2054
|
-
};
|
|
2055
3004
|
const bakedDirectional = useMemo7(() => {
|
|
2056
3005
|
if (!effectiveDirectional) return effectiveDirectional;
|
|
2057
3006
|
const rot = bakedRotation ?? [0, 0, 0];
|
|
@@ -2062,21 +3011,84 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
2062
3011
|
};
|
|
2063
3012
|
}, [effectiveDirectional, bakedRotation]);
|
|
2064
3013
|
const atlasPlans = useMemo7(
|
|
2065
|
-
() =>
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
3014
|
+
() => {
|
|
3015
|
+
if (renderPolygon) return [];
|
|
3016
|
+
const repairEdges = buildTextureEdgeRepairSets(polygons);
|
|
3017
|
+
return polygons.map((p, i) => computeTextureAtlasPlan(p, i, {
|
|
3018
|
+
directionalLight: bakedDirectional,
|
|
3019
|
+
ambientLight: effectiveAmbient,
|
|
3020
|
+
textureEdgeRepairEdges: repairEdges[i]
|
|
3021
|
+
}));
|
|
3022
|
+
},
|
|
3023
|
+
[renderPolygon, polygons, bakedDirectional, effectiveAmbient]
|
|
2070
3024
|
);
|
|
2071
3025
|
const textureAtlas = useTextureAtlas(
|
|
2072
3026
|
atlasPlans,
|
|
2073
3027
|
effectiveTextureLighting,
|
|
2074
|
-
|
|
3028
|
+
textureQuality
|
|
3029
|
+
);
|
|
3030
|
+
const solidPaintDefaults = useMemo7(
|
|
3031
|
+
() => !renderPolygon ? getSolidPaintDefaults(atlasPlans, effectiveTextureLighting) : {},
|
|
3032
|
+
[renderPolygon, atlasPlans, effectiveTextureLighting]
|
|
2075
3033
|
);
|
|
2076
|
-
const
|
|
3034
|
+
const defaultPaintVars = useMemo7(
|
|
3035
|
+
() => solidPaintVars(solidPaintDefaults),
|
|
3036
|
+
[solidPaintDefaults]
|
|
3037
|
+
);
|
|
3038
|
+
const meshIdRef = useRef5(/* @__PURE__ */ Symbol());
|
|
3039
|
+
const sceneRegisterShadowCaster = sceneCtx?.registerShadowCaster;
|
|
3040
|
+
useEffect5(() => {
|
|
3041
|
+
if (!sceneRegisterShadowCaster) return;
|
|
3042
|
+
if (castShadow && effectiveTextureLighting === "dynamic") {
|
|
3043
|
+
sceneRegisterShadowCaster(meshIdRef.current, polygons);
|
|
3044
|
+
} else {
|
|
3045
|
+
sceneRegisterShadowCaster(meshIdRef.current, null);
|
|
3046
|
+
}
|
|
3047
|
+
return () => {
|
|
3048
|
+
sceneRegisterShadowCaster(meshIdRef.current, null);
|
|
3049
|
+
};
|
|
3050
|
+
}, [sceneRegisterShadowCaster, castShadow, effectiveTextureLighting, polygons]);
|
|
3051
|
+
const shadowLeaves = useMemo7(() => {
|
|
3052
|
+
if (!castShadow || effectiveTextureLighting !== "dynamic" || renderPolygon) return [];
|
|
3053
|
+
const shadowColor = sceneCtx?.shadow?.color ?? "#000000";
|
|
3054
|
+
const shadowOpacity = sceneCtx?.shadow?.opacity ?? 0.25;
|
|
3055
|
+
const parsed = parseHexColor2(shadowColor)?.rgb ?? [0, 0, 0];
|
|
3056
|
+
const shadowColorCss = `rgba(${parsed[0]},${parsed[1]},${parsed[2]},${shadowOpacity})`;
|
|
3057
|
+
const shadowDedupDrop = findOverlappingPolygonDuplicates(polygons, {
|
|
3058
|
+
normalTolerance: 0.1,
|
|
3059
|
+
distanceTolerance: 0.5,
|
|
3060
|
+
overlapFraction: 0.4
|
|
3061
|
+
});
|
|
3062
|
+
const leaves = [];
|
|
3063
|
+
for (const plan of atlasPlans) {
|
|
3064
|
+
if (!plan) continue;
|
|
3065
|
+
if (shadowDedupDrop.has(plan.index)) continue;
|
|
3066
|
+
const borderShape = cssBorderShapeForPlan(plan);
|
|
3067
|
+
leaves.push(
|
|
3068
|
+
/* @__PURE__ */ jsx5(
|
|
3069
|
+
ShadowLeaf,
|
|
3070
|
+
{
|
|
3071
|
+
plan,
|
|
3072
|
+
shadowColorCss,
|
|
3073
|
+
borderShape
|
|
3074
|
+
},
|
|
3075
|
+
`shadow-${plan.index}`
|
|
3076
|
+
)
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3079
|
+
return leaves;
|
|
3080
|
+
}, [castShadow, effectiveTextureLighting, renderPolygon, polygons, atlasPlans, sceneCtx?.shadow]);
|
|
3081
|
+
const wrapperStyle = {
|
|
3082
|
+
transform,
|
|
3083
|
+
...transformOrigin ? { transformOrigin } : null,
|
|
3084
|
+
...dynamicLightOverride,
|
|
3085
|
+
...style,
|
|
3086
|
+
...defaultPaintVars
|
|
3087
|
+
};
|
|
3088
|
+
const renderedPolygons = renderPolygon ? polygons.map((p, i) => (
|
|
2077
3089
|
// Render-prop: caller controls how each polygon renders. We still
|
|
2078
3090
|
// wrap in a fragment with key so React reconciliation works.
|
|
2079
|
-
/* @__PURE__ */ jsx5(RenderPropPolygon, { polygon: p, index: i, children }, i)
|
|
3091
|
+
/* @__PURE__ */ jsx5(RenderPropPolygon, { polygon: p, index: i, children: renderPolygon }, i)
|
|
2080
3092
|
)) : textureAtlas.entries.map((entry, index) => {
|
|
2081
3093
|
if (entry) {
|
|
2082
3094
|
return /* @__PURE__ */ jsx5(
|
|
@@ -2091,7 +3103,22 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
2091
3103
|
}
|
|
2092
3104
|
const plan = atlasPlans[index];
|
|
2093
3105
|
if (!plan || plan.texture) return null;
|
|
2094
|
-
return isSolidTrianglePlan(plan) ? /* @__PURE__ */ jsx5(
|
|
3106
|
+
return isSolidTrianglePlan(plan) ? /* @__PURE__ */ jsx5(
|
|
3107
|
+
TextureTrianglePoly,
|
|
3108
|
+
{
|
|
3109
|
+
entry: plan,
|
|
3110
|
+
textureLighting: effectiveTextureLighting,
|
|
3111
|
+
solidPaintDefaults
|
|
3112
|
+
},
|
|
3113
|
+
plan.index
|
|
3114
|
+
) : /* @__PURE__ */ jsx5(
|
|
3115
|
+
TextureBorderShapePoly,
|
|
3116
|
+
{
|
|
3117
|
+
entry: plan,
|
|
3118
|
+
solidPaintDefaults
|
|
3119
|
+
},
|
|
3120
|
+
plan.index
|
|
3121
|
+
);
|
|
2095
3122
|
});
|
|
2096
3123
|
if (src) {
|
|
2097
3124
|
if (fetched.loading && fetched.polygons.length === 0) {
|
|
@@ -2121,7 +3148,7 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
2121
3148
|
);
|
|
2122
3149
|
}
|
|
2123
3150
|
}
|
|
2124
|
-
return /* @__PURE__ */
|
|
3151
|
+
return /* @__PURE__ */ jsxs2(
|
|
2125
3152
|
"div",
|
|
2126
3153
|
{
|
|
2127
3154
|
ref: wrapperRef,
|
|
@@ -2129,7 +3156,11 @@ var PolyMesh = forwardRef(function PolyMesh2({
|
|
|
2129
3156
|
className: `polycss-mesh${className ? ` ${className}` : ""}`,
|
|
2130
3157
|
style: wrapperStyle,
|
|
2131
3158
|
...wrapperHandlers,
|
|
2132
|
-
children:
|
|
3159
|
+
children: [
|
|
3160
|
+
shadowLeaves,
|
|
3161
|
+
renderedPolygons,
|
|
3162
|
+
staticChildren
|
|
3163
|
+
]
|
|
2133
3164
|
}
|
|
2134
3165
|
);
|
|
2135
3166
|
});
|
|
@@ -2138,13 +3169,70 @@ function RenderPropPolygon({
|
|
|
2138
3169
|
index,
|
|
2139
3170
|
children
|
|
2140
3171
|
}) {
|
|
2141
|
-
return /* @__PURE__ */ jsx5(
|
|
3172
|
+
return /* @__PURE__ */ jsx5(Fragment, { children: children(polygon, index) });
|
|
3173
|
+
}
|
|
3174
|
+
function ShadowLeaf({
|
|
3175
|
+
plan,
|
|
3176
|
+
shadowColorCss,
|
|
3177
|
+
borderShape
|
|
3178
|
+
}) {
|
|
3179
|
+
const setRef = useCallback6((el) => {
|
|
3180
|
+
if (!el) return;
|
|
3181
|
+
el.style.setProperty("border-shape", borderShape);
|
|
3182
|
+
}, [borderShape]);
|
|
3183
|
+
return /* @__PURE__ */ jsx5(
|
|
3184
|
+
"q",
|
|
3185
|
+
{
|
|
3186
|
+
ref: setRef,
|
|
3187
|
+
className: "polycss-shadow",
|
|
3188
|
+
style: {
|
|
3189
|
+
transform: `var(--shadow-proj) matrix3d(${plan.matrix})`,
|
|
3190
|
+
color: shadowColorCss,
|
|
3191
|
+
width: plan.canvasW,
|
|
3192
|
+
height: plan.canvasH,
|
|
3193
|
+
["--pnx"]: plan.normal[0].toFixed(4),
|
|
3194
|
+
["--pny"]: plan.normal[1].toFixed(4),
|
|
3195
|
+
["--pnz"]: plan.normal[2].toFixed(4)
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
// src/scene/PolyGround.tsx
|
|
3202
|
+
import { useMemo as useMemo8 } from "react";
|
|
3203
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
3204
|
+
function PolyGround({
|
|
3205
|
+
size = 6,
|
|
3206
|
+
z = 0,
|
|
3207
|
+
center = [0, 0],
|
|
3208
|
+
color = "#7d848e",
|
|
3209
|
+
className
|
|
3210
|
+
}) {
|
|
3211
|
+
const polygons = useMemo8(() => {
|
|
3212
|
+
const half = size / 2;
|
|
3213
|
+
const [cx, cy] = center;
|
|
3214
|
+
const vertices = [
|
|
3215
|
+
[cx - half, cy - half, z],
|
|
3216
|
+
[cx + half, cy - half, z],
|
|
3217
|
+
[cx + half, cy + half, z],
|
|
3218
|
+
[cx - half, cy + half, z]
|
|
3219
|
+
];
|
|
3220
|
+
return [{ vertices, color }];
|
|
3221
|
+
}, [size, z, center, color]);
|
|
3222
|
+
return /* @__PURE__ */ jsx6(
|
|
3223
|
+
PolyMesh,
|
|
3224
|
+
{
|
|
3225
|
+
polygons,
|
|
3226
|
+
castShadow: false,
|
|
3227
|
+
className: className ? `polycss-ground ${className}` : "polycss-ground"
|
|
3228
|
+
}
|
|
3229
|
+
);
|
|
2142
3230
|
}
|
|
2143
3231
|
|
|
2144
3232
|
// src/scene/usePolyMaterial.ts
|
|
2145
|
-
import { useMemo as
|
|
3233
|
+
import { useMemo as useMemo9 } from "react";
|
|
2146
3234
|
function usePolyMaterial(options) {
|
|
2147
|
-
return
|
|
3235
|
+
return useMemo9(
|
|
2148
3236
|
() => ({ texture: options.texture, key: options.key }),
|
|
2149
3237
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2150
3238
|
[options.texture, options.key]
|
|
@@ -2152,8 +3240,13 @@ function usePolyMaterial(options) {
|
|
|
2152
3240
|
}
|
|
2153
3241
|
|
|
2154
3242
|
// src/shapes/Poly.tsx
|
|
2155
|
-
import { memo as memo4, useMemo as
|
|
2156
|
-
import { jsx as
|
|
3243
|
+
import { memo as memo4, useMemo as useMemo10 } from "react";
|
|
3244
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
3245
|
+
var DIRECT_TEXTURE_CSS_DECIMALS = 4;
|
|
3246
|
+
function formatCssLength2(value, decimals = DIRECT_TEXTURE_CSS_DECIMALS) {
|
|
3247
|
+
const next = value.toFixed(decimals).replace(/\.?0+$/, "");
|
|
3248
|
+
return Number(next) === 0 || Object.is(Number(next), -0) ? "0" : `${next}px`;
|
|
3249
|
+
}
|
|
2157
3250
|
function isAxisAlignedRectUVs(uvs) {
|
|
2158
3251
|
if (uvs.length !== 4) return null;
|
|
2159
3252
|
const us = [...new Set(uvs.map((uv) => uv[0]))].sort((a, b) => a - b);
|
|
@@ -2183,18 +3276,16 @@ function MaterialDirectPoly({
|
|
|
2183
3276
|
const { u0, u1, v0, v1 } = uvRect;
|
|
2184
3277
|
const du = u1 - u0;
|
|
2185
3278
|
const dv = v1 - v0;
|
|
2186
|
-
const sourceW =
|
|
2187
|
-
const sourceH =
|
|
3279
|
+
const sourceW = 1 / du;
|
|
3280
|
+
const sourceH = 1 / dv;
|
|
2188
3281
|
const vMax = Math.max(v0, v1);
|
|
2189
|
-
const offsetX = u0
|
|
2190
|
-
const offsetY = (1 - vMax)
|
|
3282
|
+
const offsetX = u0 / du;
|
|
3283
|
+
const offsetY = (1 - vMax) / dv;
|
|
2191
3284
|
const style = {
|
|
2192
|
-
|
|
2193
|
-
height: plan.canvasH,
|
|
2194
|
-
transform: `matrix3d(${plan.matrix})`,
|
|
3285
|
+
transform: `matrix3d(${plan.canonicalMatrix})`,
|
|
2195
3286
|
backgroundImage: `url(${material.texture})`,
|
|
2196
|
-
backgroundSize: `${sourceW}
|
|
2197
|
-
backgroundPosition:
|
|
3287
|
+
backgroundSize: `${formatCssLength2(sourceW)} ${formatCssLength2(sourceH)}`,
|
|
3288
|
+
backgroundPosition: `${formatCssLength2(-offsetX)} ${formatCssLength2(-offsetY)}`,
|
|
2198
3289
|
pointerEvents: pointerEvents === "none" ? "none" : void 0,
|
|
2199
3290
|
...styleProp
|
|
2200
3291
|
};
|
|
@@ -2202,7 +3293,7 @@ function MaterialDirectPoly({
|
|
|
2202
3293
|
Object.entries(plan.polygon.data).map(([k, v]) => [`data-${k}`, String(v)])
|
|
2203
3294
|
) : {};
|
|
2204
3295
|
const elementClassName = className?.trim() || void 0;
|
|
2205
|
-
return /* @__PURE__ */
|
|
3296
|
+
return /* @__PURE__ */ jsx7(
|
|
2206
3297
|
"i",
|
|
2207
3298
|
{
|
|
2208
3299
|
className: elementClassName,
|
|
@@ -2245,17 +3336,17 @@ function PolyInner({
|
|
|
2245
3336
|
pointerEvents: pointerEventsProp,
|
|
2246
3337
|
context,
|
|
2247
3338
|
textureLighting: textureLightingProp,
|
|
2248
|
-
|
|
3339
|
+
textureQuality: textureQualityProp,
|
|
2249
3340
|
baseColor: baseColorProp,
|
|
2250
3341
|
...dataAttrs
|
|
2251
3342
|
}) {
|
|
2252
3343
|
const tileSize = context?.tileSize ?? 50;
|
|
2253
3344
|
const layerElevation = context?.layerElevation ?? tileSize;
|
|
2254
3345
|
const textureLighting = textureLightingProp ?? context?.textureLighting ?? "baked";
|
|
2255
|
-
const
|
|
3346
|
+
const textureQuality = textureQualityProp ?? context?.textureQuality;
|
|
2256
3347
|
const polygonColor = baseColorProp ?? color;
|
|
2257
3348
|
const effectiveTexture = material?.texture ?? texture;
|
|
2258
|
-
const atlasPlan =
|
|
3349
|
+
const atlasPlan = useMemo10(
|
|
2259
3350
|
() => computeTextureAtlasPlan(
|
|
2260
3351
|
{ vertices, color: polygonColor, texture: effectiveTexture, uvs, data },
|
|
2261
3352
|
0,
|
|
@@ -2276,15 +3367,15 @@ function PolyInner({
|
|
|
2276
3367
|
context?.directionalLight
|
|
2277
3368
|
]
|
|
2278
3369
|
);
|
|
2279
|
-
const materialUvRect =
|
|
3370
|
+
const materialUvRect = useMemo10(
|
|
2280
3371
|
() => material && uvs ? isAxisAlignedRectUVs(uvs) : null,
|
|
2281
3372
|
[material, uvs]
|
|
2282
3373
|
);
|
|
2283
|
-
const atlasPlans =
|
|
3374
|
+
const atlasPlans = useMemo10(
|
|
2284
3375
|
() => materialUvRect ? [] : [atlasPlan],
|
|
2285
3376
|
[materialUvRect, atlasPlan]
|
|
2286
3377
|
);
|
|
2287
|
-
const textureAtlas = useTextureAtlas(atlasPlans, textureLighting,
|
|
3378
|
+
const textureAtlas = useTextureAtlas(atlasPlans, textureLighting, textureQuality);
|
|
2288
3379
|
const domEventHandlers = {
|
|
2289
3380
|
onClick,
|
|
2290
3381
|
onDoubleClick,
|
|
@@ -2333,7 +3424,7 @@ function PolyInner({
|
|
|
2333
3424
|
const wrapperTransform = transformParts.length > 0 ? transformParts.join(" ") : void 0;
|
|
2334
3425
|
let front = null;
|
|
2335
3426
|
if (materialUvRect && material && atlasPlan) {
|
|
2336
|
-
front = /* @__PURE__ */
|
|
3427
|
+
front = /* @__PURE__ */ jsx7(
|
|
2337
3428
|
MaterialDirectPoly,
|
|
2338
3429
|
{
|
|
2339
3430
|
plan: atlasPlan,
|
|
@@ -2349,7 +3440,7 @@ function PolyInner({
|
|
|
2349
3440
|
} else {
|
|
2350
3441
|
const atlasEntry = textureAtlas.entries[0];
|
|
2351
3442
|
if (atlasEntry) {
|
|
2352
|
-
front = /* @__PURE__ */
|
|
3443
|
+
front = /* @__PURE__ */ jsx7(
|
|
2353
3444
|
TextureAtlasPoly,
|
|
2354
3445
|
{
|
|
2355
3446
|
entry: atlasEntry,
|
|
@@ -2363,7 +3454,7 @@ function PolyInner({
|
|
|
2363
3454
|
}
|
|
2364
3455
|
);
|
|
2365
3456
|
} else if (atlasPlan && !atlasPlan.texture) {
|
|
2366
|
-
front = isSolidTrianglePlan(atlasPlan) ? /* @__PURE__ */
|
|
3457
|
+
front = isSolidTrianglePlan(atlasPlan) ? /* @__PURE__ */ jsx7(
|
|
2367
3458
|
TextureTrianglePoly,
|
|
2368
3459
|
{
|
|
2369
3460
|
entry: atlasPlan,
|
|
@@ -2374,7 +3465,18 @@ function PolyInner({
|
|
|
2374
3465
|
domEventHandlers,
|
|
2375
3466
|
pointerEvents: pointerEventsProp ?? "auto"
|
|
2376
3467
|
}
|
|
2377
|
-
) : /* @__PURE__ */
|
|
3468
|
+
) : isProjectiveQuadPlan(atlasPlan) ? /* @__PURE__ */ jsx7(
|
|
3469
|
+
TextureProjectiveSolidPoly,
|
|
3470
|
+
{
|
|
3471
|
+
entry: atlasPlan,
|
|
3472
|
+
textureLighting,
|
|
3473
|
+
className,
|
|
3474
|
+
style: styleProp,
|
|
3475
|
+
domAttrs,
|
|
3476
|
+
domEventHandlers,
|
|
3477
|
+
pointerEvents: pointerEventsProp ?? "auto"
|
|
3478
|
+
}
|
|
3479
|
+
) : /* @__PURE__ */ jsx7(
|
|
2378
3480
|
TextureBorderShapePoly,
|
|
2379
3481
|
{
|
|
2380
3482
|
entry: atlasPlan,
|
|
@@ -2389,14 +3491,14 @@ function PolyInner({
|
|
|
2389
3491
|
}
|
|
2390
3492
|
if (!front) return null;
|
|
2391
3493
|
if (!wrapperTransform) return front;
|
|
2392
|
-
return /* @__PURE__ */
|
|
3494
|
+
return /* @__PURE__ */ jsx7(
|
|
2393
3495
|
"div",
|
|
2394
3496
|
{
|
|
2395
3497
|
className: "polycss-poly-wrapper",
|
|
2396
3498
|
style: {
|
|
3499
|
+
transform: wrapperTransform,
|
|
2397
3500
|
position: "absolute",
|
|
2398
|
-
transformStyle: "preserve-3d"
|
|
2399
|
-
transform: wrapperTransform
|
|
3501
|
+
transformStyle: "preserve-3d"
|
|
2400
3502
|
},
|
|
2401
3503
|
children: front
|
|
2402
3504
|
}
|
|
@@ -2404,11 +3506,426 @@ function PolyInner({
|
|
|
2404
3506
|
}
|
|
2405
3507
|
var Poly = memo4(PolyInner);
|
|
2406
3508
|
|
|
2407
|
-
// src/controls/
|
|
2408
|
-
import { useEffect as useEffect6, useRef as useRef6 } from "react";
|
|
3509
|
+
// src/controls/PolyFirstPersonControls.tsx
|
|
3510
|
+
import { useEffect as useEffect6, useRef as useRef6, useImperativeHandle as useImperativeHandle2, forwardRef as forwardRef2 } from "react";
|
|
3511
|
+
import { BASE_TILE as BASE_TILE3 } from "@layoutit/polycss-core";
|
|
3512
|
+
var DEFAULTS = {
|
|
3513
|
+
enabled: true,
|
|
3514
|
+
lookEnabled: true,
|
|
3515
|
+
moveEnabled: true,
|
|
3516
|
+
jumpEnabled: true,
|
|
3517
|
+
crouchEnabled: true,
|
|
3518
|
+
lookSensitivity: 0.15,
|
|
3519
|
+
invertY: false,
|
|
3520
|
+
moveSpeed: 5,
|
|
3521
|
+
jumpVelocity: 7,
|
|
3522
|
+
gravity: 18,
|
|
3523
|
+
eyeHeight: 1.7,
|
|
3524
|
+
crouchHeight: 1,
|
|
3525
|
+
groundZ: 0,
|
|
3526
|
+
minPitch: 5,
|
|
3527
|
+
maxPitch: 175
|
|
3528
|
+
};
|
|
3529
|
+
function resolveOptions(base, partial) {
|
|
3530
|
+
return {
|
|
3531
|
+
enabled: partial.enabled ?? base.enabled,
|
|
3532
|
+
lookEnabled: partial.lookEnabled ?? base.lookEnabled,
|
|
3533
|
+
moveEnabled: partial.moveEnabled ?? base.moveEnabled,
|
|
3534
|
+
jumpEnabled: partial.jumpEnabled ?? base.jumpEnabled,
|
|
3535
|
+
crouchEnabled: partial.crouchEnabled ?? base.crouchEnabled,
|
|
3536
|
+
lookSensitivity: partial.lookSensitivity ?? base.lookSensitivity,
|
|
3537
|
+
invertY: partial.invertY ?? base.invertY,
|
|
3538
|
+
moveSpeed: partial.moveSpeed ?? base.moveSpeed,
|
|
3539
|
+
jumpVelocity: partial.jumpVelocity ?? base.jumpVelocity,
|
|
3540
|
+
gravity: partial.gravity ?? base.gravity,
|
|
3541
|
+
eyeHeight: partial.eyeHeight ?? base.eyeHeight,
|
|
3542
|
+
crouchHeight: partial.crouchHeight ?? base.crouchHeight,
|
|
3543
|
+
groundZ: partial.groundZ ?? base.groundZ,
|
|
3544
|
+
minPitch: partial.minPitch ?? base.minPitch,
|
|
3545
|
+
maxPitch: partial.maxPitch ?? base.maxPitch
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
var FORWARD_KEYS = /* @__PURE__ */ new Set(["KeyW", "ArrowUp"]);
|
|
3549
|
+
var BACK_KEYS = /* @__PURE__ */ new Set(["KeyS", "ArrowDown"]);
|
|
3550
|
+
var LEFT_KEYS = /* @__PURE__ */ new Set(["KeyA", "ArrowLeft"]);
|
|
3551
|
+
var RIGHT_KEYS = /* @__PURE__ */ new Set(["KeyD", "ArrowRight"]);
|
|
3552
|
+
var JUMP_KEYS = /* @__PURE__ */ new Set(["Space"]);
|
|
3553
|
+
var CROUCH_KEYS = /* @__PURE__ */ new Set(["ControlLeft", "ControlRight"]);
|
|
3554
|
+
function isFpvKey(code) {
|
|
3555
|
+
return FORWARD_KEYS.has(code) || BACK_KEYS.has(code) || LEFT_KEYS.has(code) || RIGHT_KEYS.has(code) || JUMP_KEYS.has(code) || CROUCH_KEYS.has(code);
|
|
3556
|
+
}
|
|
3557
|
+
function makeRegistry() {
|
|
3558
|
+
return { change: [], start: [], end: [] };
|
|
3559
|
+
}
|
|
3560
|
+
function emitEvent(registry, type) {
|
|
3561
|
+
const list = [...registry[type]];
|
|
3562
|
+
for (const fn of list) {
|
|
3563
|
+
try {
|
|
3564
|
+
fn();
|
|
3565
|
+
} catch {
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
var PolyFirstPersonControls = forwardRef2(function PolyFirstPersonControls2(props, ref) {
|
|
3570
|
+
const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
|
|
3571
|
+
const onChangeRef = useRef6(props.onChange);
|
|
3572
|
+
const onInteractionStartRef = useRef6(props.onInteractionStart);
|
|
3573
|
+
const onInteractionEndRef = useRef6(props.onInteractionEnd);
|
|
3574
|
+
useEffect6(() => {
|
|
3575
|
+
onChangeRef.current = props.onChange;
|
|
3576
|
+
onInteractionStartRef.current = props.onInteractionStart;
|
|
3577
|
+
onInteractionEndRef.current = props.onInteractionEnd;
|
|
3578
|
+
});
|
|
3579
|
+
const optsRef = useRef6(resolveOptions(DEFAULTS, props));
|
|
3580
|
+
const cameraOriginRef = useRef6([0, 0, 0]);
|
|
3581
|
+
const rafIdRef = useRef6(null);
|
|
3582
|
+
const lastTimeRef = useRef6(0);
|
|
3583
|
+
const stoppedRef = useRef6(false);
|
|
3584
|
+
const pointerLockedRef = useRef6(false);
|
|
3585
|
+
const interactingRef = useRef6(false);
|
|
3586
|
+
const keysHeldRef = useRef6(/* @__PURE__ */ new Set());
|
|
3587
|
+
const verticalVelRef = useRef6(0);
|
|
3588
|
+
const jumpOffsetRef = useRef6(0);
|
|
3589
|
+
const registryRef = useRef6(makeRegistry());
|
|
3590
|
+
useImperativeHandle2(ref, () => ({
|
|
3591
|
+
update(partial) {
|
|
3592
|
+
const prev = optsRef.current;
|
|
3593
|
+
optsRef.current = resolveOptions(prev, partial);
|
|
3594
|
+
if (!stoppedRef.current) {
|
|
3595
|
+
const host = cameraElRef.current;
|
|
3596
|
+
if (host) host.style.cursor = optsRef.current.lookEnabled ? "crosshair" : "";
|
|
3597
|
+
}
|
|
3598
|
+
},
|
|
3599
|
+
resume() {
|
|
3600
|
+
if (!stoppedRef.current) return;
|
|
3601
|
+
stoppedRef.current = false;
|
|
3602
|
+
const host = cameraElRef.current;
|
|
3603
|
+
if (host) host.style.cursor = optsRef.current.lookEnabled ? "crosshair" : "";
|
|
3604
|
+
startLoop();
|
|
3605
|
+
},
|
|
3606
|
+
pause() {
|
|
3607
|
+
if (stoppedRef.current) return;
|
|
3608
|
+
stoppedRef.current = true;
|
|
3609
|
+
stopLoop();
|
|
3610
|
+
const host = cameraElRef.current;
|
|
3611
|
+
if (host) host.style.cursor = "";
|
|
3612
|
+
if (interactingRef.current) {
|
|
3613
|
+
interactingRef.current = false;
|
|
3614
|
+
emitEvent(registryRef.current, "end");
|
|
3615
|
+
try {
|
|
3616
|
+
onInteractionEndRef.current?.();
|
|
3617
|
+
} catch {
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
},
|
|
3621
|
+
destroy() {
|
|
3622
|
+
stoppedRef.current = true;
|
|
3623
|
+
stopLoop();
|
|
3624
|
+
},
|
|
3625
|
+
lock() {
|
|
3626
|
+
const opts = optsRef.current;
|
|
3627
|
+
if (!opts.enabled || !opts.lookEnabled || stoppedRef.current) return;
|
|
3628
|
+
const host = cameraElRef.current;
|
|
3629
|
+
try {
|
|
3630
|
+
host?.requestPointerLock();
|
|
3631
|
+
} catch {
|
|
3632
|
+
}
|
|
3633
|
+
},
|
|
3634
|
+
unlock() {
|
|
3635
|
+
if (pointerLockedRef.current) {
|
|
3636
|
+
const host = cameraElRef.current;
|
|
3637
|
+
try {
|
|
3638
|
+
host?.ownerDocument?.exitPointerLock();
|
|
3639
|
+
} catch {
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
},
|
|
3643
|
+
isLocked() {
|
|
3644
|
+
return pointerLockedRef.current;
|
|
3645
|
+
},
|
|
3646
|
+
getOrigin() {
|
|
3647
|
+
const o = cameraOriginRef.current;
|
|
3648
|
+
return [o[0], o[1], o[2]];
|
|
3649
|
+
},
|
|
3650
|
+
setOrigin(origin) {
|
|
3651
|
+
cameraOriginRef.current[0] = origin[0];
|
|
3652
|
+
cameraOriginRef.current[1] = origin[1];
|
|
3653
|
+
cameraOriginRef.current[2] = origin[2];
|
|
3654
|
+
syncTargetFromOrigin();
|
|
3655
|
+
emitEvent(registryRef.current, "change");
|
|
3656
|
+
try {
|
|
3657
|
+
onChangeRef.current?.();
|
|
3658
|
+
} catch {
|
|
3659
|
+
}
|
|
3660
|
+
},
|
|
3661
|
+
addEventListener(type, listener) {
|
|
3662
|
+
const arr = registryRef.current[type];
|
|
3663
|
+
if (!arr.includes(listener)) arr.push(listener);
|
|
3664
|
+
},
|
|
3665
|
+
removeEventListener(type, listener) {
|
|
3666
|
+
const arr = registryRef.current[type];
|
|
3667
|
+
const idx = arr.indexOf(listener);
|
|
3668
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
3669
|
+
},
|
|
3670
|
+
hasEventListener(type, listener) {
|
|
3671
|
+
return registryRef.current[type].includes(listener);
|
|
3672
|
+
}
|
|
3673
|
+
}));
|
|
3674
|
+
function forwardDir(rotX, rotY) {
|
|
3675
|
+
const rx = rotX * Math.PI / 180;
|
|
3676
|
+
const ry = rotY * Math.PI / 180;
|
|
3677
|
+
return [
|
|
3678
|
+
-Math.sin(rx) * Math.cos(ry),
|
|
3679
|
+
-Math.sin(rx) * Math.sin(ry),
|
|
3680
|
+
-Math.cos(rx)
|
|
3681
|
+
];
|
|
3682
|
+
}
|
|
3683
|
+
function lookOffset() {
|
|
3684
|
+
const host = cameraElRef.current;
|
|
3685
|
+
const perspStr = host ? getComputedStyle(host).perspective : "";
|
|
3686
|
+
const n = parseFloat(perspStr);
|
|
3687
|
+
return (Number.isFinite(n) && n > 0 ? n : 8e3) / BASE_TILE3;
|
|
3688
|
+
}
|
|
3689
|
+
function deriveTarget() {
|
|
3690
|
+
const s = cameraRef.current.state;
|
|
3691
|
+
const f = forwardDir(s.rotX ?? 90, s.rotY ?? 0);
|
|
3692
|
+
const d = lookOffset();
|
|
3693
|
+
const o = cameraOriginRef.current;
|
|
3694
|
+
return [o[0] + f[0] * d, o[1] + f[1] * d, o[2] + f[2] * d];
|
|
3695
|
+
}
|
|
3696
|
+
function syncTargetFromOrigin() {
|
|
3697
|
+
const t = deriveTarget();
|
|
3698
|
+
const handle = cameraRef.current;
|
|
3699
|
+
handle.update({ target: t });
|
|
3700
|
+
applyTransformDirect();
|
|
3701
|
+
store.updateCameraFromRef(handle);
|
|
3702
|
+
}
|
|
3703
|
+
const ANIM_DT_CLAMP = 0.05;
|
|
3704
|
+
function tick(now) {
|
|
3705
|
+
if (rafIdRef.current === null || stoppedRef.current) return;
|
|
3706
|
+
const dt = Math.min(ANIM_DT_CLAMP, lastTimeRef.current ? (now - lastTimeRef.current) / 1e3 : 0.0167);
|
|
3707
|
+
lastTimeRef.current = now;
|
|
3708
|
+
const opts = optsRef.current;
|
|
3709
|
+
if (opts.enabled) {
|
|
3710
|
+
let dirty = false;
|
|
3711
|
+
const s = cameraRef.current.state;
|
|
3712
|
+
const o = cameraOriginRef.current;
|
|
3713
|
+
if (opts.moveEnabled) {
|
|
3714
|
+
let mf = 0, mr = 0;
|
|
3715
|
+
for (const code of keysHeldRef.current) {
|
|
3716
|
+
if (FORWARD_KEYS.has(code)) mf += 1;
|
|
3717
|
+
else if (BACK_KEYS.has(code)) mf -= 1;
|
|
3718
|
+
else if (RIGHT_KEYS.has(code)) mr += 1;
|
|
3719
|
+
else if (LEFT_KEYS.has(code)) mr -= 1;
|
|
3720
|
+
}
|
|
3721
|
+
if (mf !== 0 || mr !== 0) {
|
|
3722
|
+
const rotY = s.rotY ?? 0;
|
|
3723
|
+
const r = rotY * Math.PI / 180;
|
|
3724
|
+
const fx = -Math.cos(r), fy = -Math.sin(r);
|
|
3725
|
+
const rx = -Math.sin(r), ry = Math.cos(r);
|
|
3726
|
+
const len = Math.hypot(mf, mr) || 1;
|
|
3727
|
+
const step = opts.moveSpeed * dt;
|
|
3728
|
+
o[0] += (fx * mf + rx * mr) / len * step;
|
|
3729
|
+
o[1] += (fy * mf + ry * mr) / len * step;
|
|
3730
|
+
dirty = true;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
const crouched = opts.crouchEnabled && (keysHeldRef.current.has("ControlLeft") || keysHeldRef.current.has("ControlRight"));
|
|
3734
|
+
const baseHeight = crouched ? opts.crouchHeight : opts.eyeHeight;
|
|
3735
|
+
if (opts.jumpEnabled && (verticalVelRef.current !== 0 || jumpOffsetRef.current > 0)) {
|
|
3736
|
+
verticalVelRef.current -= opts.gravity * dt;
|
|
3737
|
+
jumpOffsetRef.current += verticalVelRef.current * dt;
|
|
3738
|
+
if (jumpOffsetRef.current <= 0) {
|
|
3739
|
+
jumpOffsetRef.current = 0;
|
|
3740
|
+
verticalVelRef.current = 0;
|
|
3741
|
+
}
|
|
3742
|
+
} else if (!opts.jumpEnabled) {
|
|
3743
|
+
jumpOffsetRef.current = 0;
|
|
3744
|
+
verticalVelRef.current = 0;
|
|
3745
|
+
}
|
|
3746
|
+
const originZ = opts.groundZ + baseHeight + jumpOffsetRef.current;
|
|
3747
|
+
if (Math.abs(o[2] - originZ) > 1e-4) {
|
|
3748
|
+
o[2] = originZ;
|
|
3749
|
+
dirty = true;
|
|
3750
|
+
}
|
|
3751
|
+
if (dirty) {
|
|
3752
|
+
const t = deriveTarget();
|
|
3753
|
+
const handle = cameraRef.current;
|
|
3754
|
+
handle.update({ target: t });
|
|
3755
|
+
applyTransformDirect();
|
|
3756
|
+
store.updateCameraFromRef(handle);
|
|
3757
|
+
emitEvent(registryRef.current, "change");
|
|
3758
|
+
try {
|
|
3759
|
+
onChangeRef.current?.();
|
|
3760
|
+
} catch {
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
rafIdRef.current = requestAnimationFrame(tick);
|
|
3765
|
+
}
|
|
3766
|
+
function startLoop() {
|
|
3767
|
+
if (rafIdRef.current !== null || stoppedRef.current) return;
|
|
3768
|
+
lastTimeRef.current = 0;
|
|
3769
|
+
rafIdRef.current = requestAnimationFrame(tick);
|
|
3770
|
+
}
|
|
3771
|
+
function stopLoop() {
|
|
3772
|
+
if (rafIdRef.current === null) return;
|
|
3773
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
3774
|
+
rafIdRef.current = null;
|
|
3775
|
+
}
|
|
3776
|
+
useEffect6(() => {
|
|
3777
|
+
const host = cameraElRef.current;
|
|
3778
|
+
if (!host) return;
|
|
3779
|
+
const doc = host.ownerDocument ?? document;
|
|
3780
|
+
const win = doc.defaultView ?? globalThis;
|
|
3781
|
+
stoppedRef.current = false;
|
|
3782
|
+
pointerLockedRef.current = false;
|
|
3783
|
+
interactingRef.current = false;
|
|
3784
|
+
keysHeldRef.current.clear();
|
|
3785
|
+
verticalVelRef.current = 0;
|
|
3786
|
+
jumpOffsetRef.current = 0;
|
|
3787
|
+
const s = cameraRef.current.state;
|
|
3788
|
+
const t = s.target ?? [0, 0, 0];
|
|
3789
|
+
const opts = optsRef.current;
|
|
3790
|
+
cameraOriginRef.current = [t[0], t[1], opts.groundZ + opts.eyeHeight];
|
|
3791
|
+
syncTargetFromOrigin();
|
|
3792
|
+
host.style.cursor = opts.lookEnabled ? "crosshair" : "";
|
|
3793
|
+
const computedPersp = win.getComputedStyle(host).perspective;
|
|
3794
|
+
const persp = parseFloat(computedPersp);
|
|
3795
|
+
const effectivePersp = Number.isFinite(persp) && persp > 0 ? persp : 2e3;
|
|
3796
|
+
host.style.setProperty("--polycss-fpv-perspective", `${effectivePersp}px`);
|
|
3797
|
+
host.classList.add("polycss-fpv-host");
|
|
3798
|
+
const onHostClick = () => {
|
|
3799
|
+
const o = optsRef.current;
|
|
3800
|
+
if (!o.enabled || !o.lookEnabled || stoppedRef.current || pointerLockedRef.current) return;
|
|
3801
|
+
try {
|
|
3802
|
+
host.requestPointerLock();
|
|
3803
|
+
} catch {
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
const onPointerLockChange = () => {
|
|
3807
|
+
const locked = doc.pointerLockElement === host;
|
|
3808
|
+
if (locked === pointerLockedRef.current) return;
|
|
3809
|
+
pointerLockedRef.current = locked;
|
|
3810
|
+
if (locked) {
|
|
3811
|
+
interactingRef.current = true;
|
|
3812
|
+
emitEvent(registryRef.current, "start");
|
|
3813
|
+
try {
|
|
3814
|
+
onInteractionStartRef.current?.();
|
|
3815
|
+
} catch {
|
|
3816
|
+
}
|
|
3817
|
+
} else {
|
|
3818
|
+
if (interactingRef.current) {
|
|
3819
|
+
interactingRef.current = false;
|
|
3820
|
+
emitEvent(registryRef.current, "end");
|
|
3821
|
+
try {
|
|
3822
|
+
onInteractionEndRef.current?.();
|
|
3823
|
+
} catch {
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
3828
|
+
const onMouseMove = (e) => {
|
|
3829
|
+
if (!pointerLockedRef.current || stoppedRef.current) return;
|
|
3830
|
+
const o = optsRef.current;
|
|
3831
|
+
if (!o.enabled || !o.lookEnabled) return;
|
|
3832
|
+
const dx = e.movementX ?? 0;
|
|
3833
|
+
const dy = e.movementY ?? 0;
|
|
3834
|
+
if (dx === 0 && dy === 0) return;
|
|
3835
|
+
const handle = cameraRef.current;
|
|
3836
|
+
const sceneOpts = handle.state;
|
|
3837
|
+
const sens = o.lookSensitivity;
|
|
3838
|
+
const dyDir = o.invertY ? -1 : 1;
|
|
3839
|
+
const rotY = (((sceneOpts.rotY ?? 0) - dx * sens) % 360 + 360) % 360;
|
|
3840
|
+
let rotX = (sceneOpts.rotX ?? 90) - dy * sens * dyDir;
|
|
3841
|
+
if (rotX < o.minPitch) rotX = o.minPitch;
|
|
3842
|
+
else if (rotX > o.maxPitch) rotX = o.maxPitch;
|
|
3843
|
+
const f = forwardDir(rotX, rotY);
|
|
3844
|
+
const d = lookOffset();
|
|
3845
|
+
const origin = cameraOriginRef.current;
|
|
3846
|
+
const target = [
|
|
3847
|
+
origin[0] + f[0] * d,
|
|
3848
|
+
origin[1] + f[1] * d,
|
|
3849
|
+
origin[2] + f[2] * d
|
|
3850
|
+
];
|
|
3851
|
+
handle.update({ rotX, rotY, target });
|
|
3852
|
+
applyTransformDirect();
|
|
3853
|
+
store.updateCameraFromRef(handle);
|
|
3854
|
+
emitEvent(registryRef.current, "change");
|
|
3855
|
+
try {
|
|
3856
|
+
onChangeRef.current?.();
|
|
3857
|
+
} catch {
|
|
3858
|
+
}
|
|
3859
|
+
};
|
|
3860
|
+
const onKeyDown = (e) => {
|
|
3861
|
+
const o = optsRef.current;
|
|
3862
|
+
if (!o.enabled || stoppedRef.current) return;
|
|
3863
|
+
if (!isFpvKey(e.code)) return;
|
|
3864
|
+
if (!pointerLockedRef.current && !o.moveEnabled) return;
|
|
3865
|
+
if (JUMP_KEYS.has(e.code)) {
|
|
3866
|
+
if (!o.jumpEnabled) return;
|
|
3867
|
+
e.preventDefault();
|
|
3868
|
+
if (!keysHeldRef.current.has(e.code) && verticalVelRef.current === 0 && jumpOffsetRef.current === 0) {
|
|
3869
|
+
verticalVelRef.current = o.jumpVelocity;
|
|
3870
|
+
}
|
|
3871
|
+
keysHeldRef.current.add(e.code);
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
if (CROUCH_KEYS.has(e.code) && !o.crouchEnabled) return;
|
|
3875
|
+
if (!o.moveEnabled && !CROUCH_KEYS.has(e.code)) return;
|
|
3876
|
+
e.preventDefault();
|
|
3877
|
+
keysHeldRef.current.add(e.code);
|
|
3878
|
+
};
|
|
3879
|
+
const onKeyUp = (e) => {
|
|
3880
|
+
if (!isFpvKey(e.code)) return;
|
|
3881
|
+
keysHeldRef.current.delete(e.code);
|
|
3882
|
+
};
|
|
3883
|
+
const onBlur = () => {
|
|
3884
|
+
keysHeldRef.current.clear();
|
|
3885
|
+
};
|
|
3886
|
+
host.addEventListener("click", onHostClick);
|
|
3887
|
+
doc.addEventListener("pointerlockchange", onPointerLockChange);
|
|
3888
|
+
doc.addEventListener("mousemove", onMouseMove);
|
|
3889
|
+
win.addEventListener("keydown", onKeyDown);
|
|
3890
|
+
win.addEventListener("keyup", onKeyUp);
|
|
3891
|
+
win.addEventListener("blur", onBlur);
|
|
3892
|
+
startLoop();
|
|
3893
|
+
return () => {
|
|
3894
|
+
stoppedRef.current = true;
|
|
3895
|
+
stopLoop();
|
|
3896
|
+
host.removeEventListener("click", onHostClick);
|
|
3897
|
+
doc.removeEventListener("pointerlockchange", onPointerLockChange);
|
|
3898
|
+
doc.removeEventListener("mousemove", onMouseMove);
|
|
3899
|
+
win.removeEventListener("keydown", onKeyDown);
|
|
3900
|
+
win.removeEventListener("keyup", onKeyUp);
|
|
3901
|
+
win.removeEventListener("blur", onBlur);
|
|
3902
|
+
host.style.cursor = "";
|
|
3903
|
+
host.classList.remove("polycss-fpv-host");
|
|
3904
|
+
host.style.removeProperty("--polycss-fpv-perspective");
|
|
3905
|
+
keysHeldRef.current.clear();
|
|
3906
|
+
if (pointerLockedRef.current) {
|
|
3907
|
+
try {
|
|
3908
|
+
doc.exitPointerLock();
|
|
3909
|
+
} catch {
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
}, [cameraElRef, cameraRef, applyTransformDirect, store]);
|
|
3914
|
+
useEffect6(() => {
|
|
3915
|
+
optsRef.current = resolveOptions(optsRef.current, props);
|
|
3916
|
+
const host = cameraElRef.current;
|
|
3917
|
+
if (host && !stoppedRef.current) {
|
|
3918
|
+
host.style.cursor = optsRef.current.lookEnabled ? "crosshair" : "";
|
|
3919
|
+
}
|
|
3920
|
+
});
|
|
3921
|
+
return null;
|
|
3922
|
+
});
|
|
3923
|
+
|
|
3924
|
+
// src/controls/PolyMapControls.tsx
|
|
3925
|
+
import { useEffect as useEffect7, useRef as useRef7 } from "react";
|
|
2409
3926
|
|
|
2410
3927
|
// src/controls/sharedControls.ts
|
|
2411
|
-
import { BASE_TILE as
|
|
3928
|
+
import { BASE_TILE as BASE_TILE4 } from "@layoutit/polycss-core";
|
|
2412
3929
|
var POINTER_DRAG_SPEED = 4;
|
|
2413
3930
|
function invertFactor(invert) {
|
|
2414
3931
|
if (invert === true) return -1;
|
|
@@ -2429,7 +3946,7 @@ function applyPan(dx, dy, s, handle, _invert) {
|
|
|
2429
3946
|
const cosRotX = cosRotXRaw >= 0 ? Math.max(0.1, cosRotXRaw) : Math.min(-0.1, cosRotXRaw);
|
|
2430
3947
|
const cZ = Math.cos(s.rotY * Math.PI / 180);
|
|
2431
3948
|
const sZ = Math.sin(s.rotY * Math.PI / 180);
|
|
2432
|
-
const k = z *
|
|
3949
|
+
const k = z * BASE_TILE4;
|
|
2433
3950
|
const targetD0 = (dx * sZ - dy * cZ / cosRotX) / k;
|
|
2434
3951
|
const targetD1 = -(dx * cZ + dy * sZ / cosRotX) / k;
|
|
2435
3952
|
const t = s.target;
|
|
@@ -2562,8 +4079,8 @@ function makeAnimateEffect({
|
|
|
2562
4079
|
};
|
|
2563
4080
|
}
|
|
2564
4081
|
|
|
2565
|
-
// src/controls/
|
|
2566
|
-
function
|
|
4082
|
+
// src/controls/PolyMapControls.tsx
|
|
4083
|
+
function PolyMapControls({
|
|
2567
4084
|
drag = true,
|
|
2568
4085
|
wheel = true,
|
|
2569
4086
|
dolly = false,
|
|
@@ -2578,19 +4095,19 @@ function PolyOrbitControls({
|
|
|
2578
4095
|
onInteractionEnd
|
|
2579
4096
|
}) {
|
|
2580
4097
|
const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
|
|
2581
|
-
const dragRef =
|
|
2582
|
-
const wheelRef =
|
|
2583
|
-
const dollyRef =
|
|
2584
|
-
const invertRef =
|
|
2585
|
-
const zoomMinRef =
|
|
2586
|
-
const zoomMaxRef =
|
|
2587
|
-
const distanceMinRef =
|
|
2588
|
-
const distanceMaxRef =
|
|
2589
|
-
const animateRef =
|
|
2590
|
-
const onChangeRef =
|
|
2591
|
-
const onInteractionStartRef =
|
|
2592
|
-
const onInteractionEndRef =
|
|
2593
|
-
|
|
4098
|
+
const dragRef = useRef7(drag);
|
|
4099
|
+
const wheelRef = useRef7(wheel);
|
|
4100
|
+
const dollyRef = useRef7(dolly);
|
|
4101
|
+
const invertRef = useRef7(invert);
|
|
4102
|
+
const zoomMinRef = useRef7(minZoom);
|
|
4103
|
+
const zoomMaxRef = useRef7(maxZoom);
|
|
4104
|
+
const distanceMinRef = useRef7(minDistance);
|
|
4105
|
+
const distanceMaxRef = useRef7(maxDistance);
|
|
4106
|
+
const animateRef = useRef7(animate);
|
|
4107
|
+
const onChangeRef = useRef7(onChange);
|
|
4108
|
+
const onInteractionStartRef = useRef7(onInteractionStart);
|
|
4109
|
+
const onInteractionEndRef = useRef7(onInteractionEnd);
|
|
4110
|
+
useEffect7(() => {
|
|
2594
4111
|
dragRef.current = drag;
|
|
2595
4112
|
wheelRef.current = wheel;
|
|
2596
4113
|
dollyRef.current = dolly;
|
|
@@ -2614,7 +4131,7 @@ function PolyOrbitControls({
|
|
|
2614
4131
|
try {
|
|
2615
4132
|
fn(cameraSnapshot());
|
|
2616
4133
|
} catch (err) {
|
|
2617
|
-
console.error("[polycss/react]
|
|
4134
|
+
console.error("[polycss/react] PolyMapControls onChange threw:", err);
|
|
2618
4135
|
}
|
|
2619
4136
|
};
|
|
2620
4137
|
const fireStart = () => {
|
|
@@ -2623,7 +4140,7 @@ function PolyOrbitControls({
|
|
|
2623
4140
|
try {
|
|
2624
4141
|
fn(cameraSnapshot());
|
|
2625
4142
|
} catch (err) {
|
|
2626
|
-
console.error("[polycss/react]
|
|
4143
|
+
console.error("[polycss/react] PolyMapControls onInteractionStart threw:", err);
|
|
2627
4144
|
}
|
|
2628
4145
|
};
|
|
2629
4146
|
const fireEnd = () => {
|
|
@@ -2632,11 +4149,11 @@ function PolyOrbitControls({
|
|
|
2632
4149
|
try {
|
|
2633
4150
|
fn(cameraSnapshot());
|
|
2634
4151
|
} catch (err) {
|
|
2635
|
-
console.error("[polycss/react]
|
|
4152
|
+
console.error("[polycss/react] PolyMapControls onInteractionEnd threw:", err);
|
|
2636
4153
|
}
|
|
2637
4154
|
};
|
|
2638
|
-
const animationPausedShared =
|
|
2639
|
-
|
|
4155
|
+
const animationPausedShared = useRef7({ value: false }).current;
|
|
4156
|
+
useEffect7(() => {
|
|
2640
4157
|
if (!drag) return;
|
|
2641
4158
|
const el = cameraElRef.current;
|
|
2642
4159
|
if (!el) return;
|
|
@@ -2673,9 +4190,9 @@ function PolyOrbitControls({
|
|
|
2673
4190
|
pointer = { x: e.clientX, y: e.clientY };
|
|
2674
4191
|
const handle = cameraRef.current;
|
|
2675
4192
|
if (e.shiftKey) {
|
|
2676
|
-
buildOrbitControls.applyPan(dx, dy, handle.state, handle, invertRef.current);
|
|
2677
|
-
} else {
|
|
2678
4193
|
buildOrbitControls.applyOrbit(dx, dy, handle.state, handle, invertRef.current);
|
|
4194
|
+
} else {
|
|
4195
|
+
buildOrbitControls.applyPan(dx, dy, handle.state, handle, invertRef.current);
|
|
2679
4196
|
}
|
|
2680
4197
|
applyTransformDirect();
|
|
2681
4198
|
store.updateCameraFromRef(handle);
|
|
@@ -2709,7 +4226,7 @@ function PolyOrbitControls({
|
|
|
2709
4226
|
const dy = e.clientY - rightPointer.y;
|
|
2710
4227
|
rightPointer = { x: e.clientX, y: e.clientY };
|
|
2711
4228
|
const handle = cameraRef.current;
|
|
2712
|
-
buildOrbitControls.
|
|
4229
|
+
buildOrbitControls.applyOrbit(dx, dy, handle.state, handle, invertRef.current);
|
|
2713
4230
|
applyTransformDirect();
|
|
2714
4231
|
store.updateCameraFromRef(handle);
|
|
2715
4232
|
fireChange();
|
|
@@ -2746,12 +4263,12 @@ function PolyOrbitControls({
|
|
|
2746
4263
|
el.style.userSelect = "";
|
|
2747
4264
|
};
|
|
2748
4265
|
}, [drag, applyTransformDirect, cameraElRef, cameraRef, store]);
|
|
2749
|
-
|
|
4266
|
+
useEffect7(
|
|
2750
4267
|
() => makeWheelEffect({ wheel, dollyRef, wheelRef, zoomMinRef, zoomMaxRef, distanceMinRef, distanceMaxRef, cameraElRef, cameraRef, applyTransformDirect, store, fireStart, fireChange, fireEnd }),
|
|
2751
4268
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2752
4269
|
[wheel, applyTransformDirect, cameraElRef, cameraRef, store]
|
|
2753
4270
|
);
|
|
2754
|
-
|
|
4271
|
+
useEffect7(
|
|
2755
4272
|
() => makeAnimateEffect({ animateOn: !!animate, animateRef, animationPausedShared, applyTransformDirect, cameraRef, store, fireChange }),
|
|
2756
4273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2757
4274
|
[!!animate, animationPausedShared, applyTransformDirect, cameraRef, store]
|
|
@@ -2759,9 +4276,9 @@ function PolyOrbitControls({
|
|
|
2759
4276
|
return null;
|
|
2760
4277
|
}
|
|
2761
4278
|
|
|
2762
|
-
// src/controls/
|
|
2763
|
-
import { useEffect as
|
|
2764
|
-
function
|
|
4279
|
+
// src/controls/PolyOrbitControls.tsx
|
|
4280
|
+
import { useEffect as useEffect8, useRef as useRef8 } from "react";
|
|
4281
|
+
function PolyOrbitControls({
|
|
2765
4282
|
drag = true,
|
|
2766
4283
|
wheel = true,
|
|
2767
4284
|
dolly = false,
|
|
@@ -2776,19 +4293,19 @@ function PolyMapControls({
|
|
|
2776
4293
|
onInteractionEnd
|
|
2777
4294
|
}) {
|
|
2778
4295
|
const { store, cameraRef, cameraElRef, applyTransformDirect } = useCameraContext();
|
|
2779
|
-
const dragRef =
|
|
2780
|
-
const wheelRef =
|
|
2781
|
-
const dollyRef =
|
|
2782
|
-
const invertRef =
|
|
2783
|
-
const zoomMinRef =
|
|
2784
|
-
const zoomMaxRef =
|
|
2785
|
-
const distanceMinRef =
|
|
2786
|
-
const distanceMaxRef =
|
|
2787
|
-
const animateRef =
|
|
2788
|
-
const onChangeRef =
|
|
2789
|
-
const onInteractionStartRef =
|
|
2790
|
-
const onInteractionEndRef =
|
|
2791
|
-
|
|
4296
|
+
const dragRef = useRef8(drag);
|
|
4297
|
+
const wheelRef = useRef8(wheel);
|
|
4298
|
+
const dollyRef = useRef8(dolly);
|
|
4299
|
+
const invertRef = useRef8(invert);
|
|
4300
|
+
const zoomMinRef = useRef8(minZoom);
|
|
4301
|
+
const zoomMaxRef = useRef8(maxZoom);
|
|
4302
|
+
const distanceMinRef = useRef8(minDistance);
|
|
4303
|
+
const distanceMaxRef = useRef8(maxDistance);
|
|
4304
|
+
const animateRef = useRef8(animate);
|
|
4305
|
+
const onChangeRef = useRef8(onChange);
|
|
4306
|
+
const onInteractionStartRef = useRef8(onInteractionStart);
|
|
4307
|
+
const onInteractionEndRef = useRef8(onInteractionEnd);
|
|
4308
|
+
useEffect8(() => {
|
|
2792
4309
|
dragRef.current = drag;
|
|
2793
4310
|
wheelRef.current = wheel;
|
|
2794
4311
|
dollyRef.current = dolly;
|
|
@@ -2812,7 +4329,7 @@ function PolyMapControls({
|
|
|
2812
4329
|
try {
|
|
2813
4330
|
fn(cameraSnapshot());
|
|
2814
4331
|
} catch (err) {
|
|
2815
|
-
console.error("[polycss/react]
|
|
4332
|
+
console.error("[polycss/react] PolyOrbitControls onChange threw:", err);
|
|
2816
4333
|
}
|
|
2817
4334
|
};
|
|
2818
4335
|
const fireStart = () => {
|
|
@@ -2821,7 +4338,7 @@ function PolyMapControls({
|
|
|
2821
4338
|
try {
|
|
2822
4339
|
fn(cameraSnapshot());
|
|
2823
4340
|
} catch (err) {
|
|
2824
|
-
console.error("[polycss/react]
|
|
4341
|
+
console.error("[polycss/react] PolyOrbitControls onInteractionStart threw:", err);
|
|
2825
4342
|
}
|
|
2826
4343
|
};
|
|
2827
4344
|
const fireEnd = () => {
|
|
@@ -2830,11 +4347,11 @@ function PolyMapControls({
|
|
|
2830
4347
|
try {
|
|
2831
4348
|
fn(cameraSnapshot());
|
|
2832
4349
|
} catch (err) {
|
|
2833
|
-
console.error("[polycss/react]
|
|
4350
|
+
console.error("[polycss/react] PolyOrbitControls onInteractionEnd threw:", err);
|
|
2834
4351
|
}
|
|
2835
4352
|
};
|
|
2836
|
-
const animationPausedShared =
|
|
2837
|
-
|
|
4353
|
+
const animationPausedShared = useRef8({ value: false }).current;
|
|
4354
|
+
useEffect8(() => {
|
|
2838
4355
|
if (!drag) return;
|
|
2839
4356
|
const el = cameraElRef.current;
|
|
2840
4357
|
if (!el) return;
|
|
@@ -2871,9 +4388,9 @@ function PolyMapControls({
|
|
|
2871
4388
|
pointer = { x: e.clientX, y: e.clientY };
|
|
2872
4389
|
const handle = cameraRef.current;
|
|
2873
4390
|
if (e.shiftKey) {
|
|
2874
|
-
buildOrbitControls.applyOrbit(dx, dy, handle.state, handle, invertRef.current);
|
|
2875
|
-
} else {
|
|
2876
4391
|
buildOrbitControls.applyPan(dx, dy, handle.state, handle, invertRef.current);
|
|
4392
|
+
} else {
|
|
4393
|
+
buildOrbitControls.applyOrbit(dx, dy, handle.state, handle, invertRef.current);
|
|
2877
4394
|
}
|
|
2878
4395
|
applyTransformDirect();
|
|
2879
4396
|
store.updateCameraFromRef(handle);
|
|
@@ -2907,7 +4424,7 @@ function PolyMapControls({
|
|
|
2907
4424
|
const dy = e.clientY - rightPointer.y;
|
|
2908
4425
|
rightPointer = { x: e.clientX, y: e.clientY };
|
|
2909
4426
|
const handle = cameraRef.current;
|
|
2910
|
-
buildOrbitControls.
|
|
4427
|
+
buildOrbitControls.applyPan(dx, dy, handle.state, handle, invertRef.current);
|
|
2911
4428
|
applyTransformDirect();
|
|
2912
4429
|
store.updateCameraFromRef(handle);
|
|
2913
4430
|
fireChange();
|
|
@@ -2944,12 +4461,12 @@ function PolyMapControls({
|
|
|
2944
4461
|
el.style.userSelect = "";
|
|
2945
4462
|
};
|
|
2946
4463
|
}, [drag, applyTransformDirect, cameraElRef, cameraRef, store]);
|
|
2947
|
-
|
|
4464
|
+
useEffect8(
|
|
2948
4465
|
() => makeWheelEffect({ wheel, dollyRef, wheelRef, zoomMinRef, zoomMaxRef, distanceMinRef, distanceMaxRef, cameraElRef, cameraRef, applyTransformDirect, store, fireStart, fireChange, fireEnd }),
|
|
2949
4466
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2950
4467
|
[wheel, applyTransformDirect, cameraElRef, cameraRef, store]
|
|
2951
4468
|
);
|
|
2952
|
-
|
|
4469
|
+
useEffect8(
|
|
2953
4470
|
() => makeAnimateEffect({ animateOn: !!animate, animateRef, animationPausedShared, applyTransformDirect, cameraRef, store, fireChange }),
|
|
2954
4471
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2955
4472
|
[!!animate, animationPausedShared, applyTransformDirect, cameraRef, store]
|
|
@@ -2961,13 +4478,23 @@ function PolyMapControls({
|
|
|
2961
4478
|
import {
|
|
2962
4479
|
useCallback as useCallback7,
|
|
2963
4480
|
useContext as useContext4,
|
|
2964
|
-
useEffect as
|
|
2965
|
-
useMemo as
|
|
2966
|
-
useRef as
|
|
4481
|
+
useEffect as useEffect9,
|
|
4482
|
+
useMemo as useMemo11,
|
|
4483
|
+
useRef as useRef9,
|
|
2967
4484
|
useState as useState4
|
|
2968
4485
|
} from "react";
|
|
2969
|
-
import {
|
|
2970
|
-
|
|
4486
|
+
import {
|
|
4487
|
+
arrowPolygons,
|
|
4488
|
+
DEFAULT_CAMERA_STATE,
|
|
4489
|
+
eulerXYZFromQuat,
|
|
4490
|
+
planePolygons,
|
|
4491
|
+
quatFromAxisAngle,
|
|
4492
|
+
quatFromEulerXYZ,
|
|
4493
|
+
quatMultiply,
|
|
4494
|
+
ringQuadPolygons
|
|
4495
|
+
} from "@layoutit/polycss-core";
|
|
4496
|
+
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
4497
|
+
var FALLBACK_CAMERA_STORE = createSceneStore(DEFAULT_CAMERA_STATE);
|
|
2971
4498
|
var COLOR_X = "#ff3653";
|
|
2972
4499
|
var COLOR_Y = "#8adb00";
|
|
2973
4500
|
var COLOR_Z = "#2c8fff";
|
|
@@ -2989,8 +4516,10 @@ var SHAFT_HALF_THICKNESS_RATIO = 0.0125;
|
|
|
2989
4516
|
var HEAD_LENGTH_RATIO = 0.15;
|
|
2990
4517
|
var HEAD_HALF_THICKNESS_RATIO = 0.04;
|
|
2991
4518
|
var RING_RADIUS_RATIO = 1;
|
|
2992
|
-
var RING_HALF_THICKNESS_RATIO = 0.
|
|
2993
|
-
var
|
|
4519
|
+
var RING_HALF_THICKNESS_RATIO = 0.02;
|
|
4520
|
+
var RING_QUAD_OUTER_RATIO = 1.04;
|
|
4521
|
+
var PLANE_HALF_SIZE_RATIO = 0.1;
|
|
4522
|
+
var PLANE_OFFSET_RATIO = 0.25;
|
|
2994
4523
|
var SCREEN_AXIS_DEAD_ZONE_SQ = 1e-4;
|
|
2995
4524
|
function gizmoLengthForMesh(polygons) {
|
|
2996
4525
|
if (polygons.length === 0) return FALLBACK_SHAFT_LENGTH;
|
|
@@ -3058,6 +4587,23 @@ function userAxisLetterOf(key) {
|
|
|
3058
4587
|
const last = key.replace("-", "")[0];
|
|
3059
4588
|
return last;
|
|
3060
4589
|
}
|
|
4590
|
+
function isAxisBackFacing(cssAxis, sign, rotXDeg, rotYDeg) {
|
|
4591
|
+
const rx = rotXDeg * Math.PI / 180;
|
|
4592
|
+
const ry = rotYDeg * Math.PI / 180;
|
|
4593
|
+
const a = [0, 0, 0];
|
|
4594
|
+
a[cssAxis] = sign;
|
|
4595
|
+
const bx = a[0] * Math.cos(ry) - a[1] * Math.sin(ry);
|
|
4596
|
+
const by = a[0] * Math.sin(ry) + a[1] * Math.cos(ry);
|
|
4597
|
+
const bz = a[2];
|
|
4598
|
+
const cz = by * Math.sin(rx) + bz * Math.cos(rx);
|
|
4599
|
+
void bx;
|
|
4600
|
+
return cz < 0;
|
|
4601
|
+
}
|
|
4602
|
+
var PLANE_SPECS = [
|
|
4603
|
+
{ perpAxis: 2, axisA: 0, axisB: 1, key: "xy", color: COLOR_Z },
|
|
4604
|
+
{ perpAxis: 1, axisA: 0, axisB: 2, key: "xz", color: COLOR_Y },
|
|
4605
|
+
{ perpAxis: 0, axisA: 1, axisB: 2, key: "yz", color: COLOR_X }
|
|
4606
|
+
];
|
|
3061
4607
|
function startAxisDrag(opts) {
|
|
3062
4608
|
const {
|
|
3063
4609
|
cssAxis,
|
|
@@ -3125,6 +4671,84 @@ function startAxisDrag(opts) {
|
|
|
3125
4671
|
window.addEventListener("pointerup", handleUp);
|
|
3126
4672
|
window.addEventListener("pointercancel", handleUp);
|
|
3127
4673
|
}
|
|
4674
|
+
function startPlaneDrag(opts) {
|
|
4675
|
+
const {
|
|
4676
|
+
axisA,
|
|
4677
|
+
axisB,
|
|
4678
|
+
probeDistanceCss,
|
|
4679
|
+
wrapper,
|
|
4680
|
+
target,
|
|
4681
|
+
startClientX,
|
|
4682
|
+
startClientY,
|
|
4683
|
+
translationSnap,
|
|
4684
|
+
onChange,
|
|
4685
|
+
onObjectChange,
|
|
4686
|
+
onMouseDown,
|
|
4687
|
+
onMouseUp,
|
|
4688
|
+
onDraggingChanged
|
|
4689
|
+
} = opts;
|
|
4690
|
+
const axisAVec = [0, 0, 0];
|
|
4691
|
+
axisAVec[axisA] = 1;
|
|
4692
|
+
const axisBVec = [0, 0, 0];
|
|
4693
|
+
axisBVec[axisB] = 1;
|
|
4694
|
+
function probe(axisVec) {
|
|
4695
|
+
const el = wrapper.ownerDocument.createElement("div");
|
|
4696
|
+
el.style.position = "absolute";
|
|
4697
|
+
el.style.left = "0";
|
|
4698
|
+
el.style.top = "0";
|
|
4699
|
+
el.style.width = "0";
|
|
4700
|
+
el.style.height = "0";
|
|
4701
|
+
el.style.transform = `translate3d(${axisVec[0] * probeDistanceCss}px, ${axisVec[1] * probeDistanceCss}px, ${axisVec[2] * probeDistanceCss}px)`;
|
|
4702
|
+
wrapper.appendChild(el);
|
|
4703
|
+
const wR = wrapper.getBoundingClientRect();
|
|
4704
|
+
const pR = el.getBoundingClientRect();
|
|
4705
|
+
wrapper.removeChild(el);
|
|
4706
|
+
return {
|
|
4707
|
+
x: (pR.left - wR.left) / probeDistanceCss,
|
|
4708
|
+
y: (pR.top - wR.top) / probeDistanceCss
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4711
|
+
const pA = probe(axisAVec);
|
|
4712
|
+
const pB = probe(axisBVec);
|
|
4713
|
+
const det = pA.x * pB.y - pB.x * pA.y;
|
|
4714
|
+
if (Math.abs(det) < SCREEN_AXIS_DEAD_ZONE_SQ) return;
|
|
4715
|
+
const startPos = target.getPosition() ?? [0, 0, 0];
|
|
4716
|
+
onMouseDown?.();
|
|
4717
|
+
onDraggingChanged?.(true);
|
|
4718
|
+
const handleMove = (ev) => {
|
|
4719
|
+
const dx = ev.clientX - startClientX;
|
|
4720
|
+
const dy = ev.clientY - startClientY;
|
|
4721
|
+
let tA = (pB.y * dx - pB.x * dy) / det;
|
|
4722
|
+
let tB = (-pA.y * dx + pA.x * dy) / det;
|
|
4723
|
+
if (translationSnap !== null) {
|
|
4724
|
+
tA = Math.round(tA / translationSnap) * translationSnap;
|
|
4725
|
+
tB = Math.round(tB / translationSnap) * translationSnap;
|
|
4726
|
+
}
|
|
4727
|
+
const next = [
|
|
4728
|
+
startPos[0] + tA * axisAVec[0] + tB * axisBVec[0],
|
|
4729
|
+
startPos[1] + tA * axisAVec[1] + tB * axisBVec[1],
|
|
4730
|
+
startPos[2] + tA * axisAVec[2] + tB * axisBVec[2]
|
|
4731
|
+
];
|
|
4732
|
+
onObjectChange?.({ object: target, position: next });
|
|
4733
|
+
onChange?.();
|
|
4734
|
+
};
|
|
4735
|
+
const handleUp = () => {
|
|
4736
|
+
window.removeEventListener("pointermove", handleMove);
|
|
4737
|
+
window.removeEventListener("pointerup", handleUp);
|
|
4738
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
4739
|
+
const swallow = (e) => {
|
|
4740
|
+
e.stopPropagation();
|
|
4741
|
+
e.stopImmediatePropagation();
|
|
4742
|
+
};
|
|
4743
|
+
window.addEventListener("click", swallow, { capture: true, once: true });
|
|
4744
|
+
setTimeout(() => window.removeEventListener("click", swallow, true), 0);
|
|
4745
|
+
onMouseUp?.();
|
|
4746
|
+
onDraggingChanged?.(false);
|
|
4747
|
+
};
|
|
4748
|
+
window.addEventListener("pointermove", handleMove);
|
|
4749
|
+
window.addEventListener("pointerup", handleUp);
|
|
4750
|
+
window.addEventListener("pointercancel", handleUp);
|
|
4751
|
+
}
|
|
3128
4752
|
function startRingDrag(opts) {
|
|
3129
4753
|
const {
|
|
3130
4754
|
cssAxis,
|
|
@@ -3147,6 +4771,7 @@ function startRingDrag(opts) {
|
|
|
3147
4771
|
const startRotation = target.getRotation() ?? [0, 0, 0];
|
|
3148
4772
|
onMouseDown?.();
|
|
3149
4773
|
onDraggingChanged?.(true);
|
|
4774
|
+
const qStart = quatFromEulerXYZ(startRotation);
|
|
3150
4775
|
const handleMove = (ev) => {
|
|
3151
4776
|
const a = Math.atan2(ev.clientY - centerY, ev.clientX - centerX);
|
|
3152
4777
|
let d = a - lastAngle;
|
|
@@ -3156,12 +4781,10 @@ function startRingDrag(opts) {
|
|
|
3156
4781
|
lastAngle = a;
|
|
3157
4782
|
let degrees = cumulative * 180 / Math.PI;
|
|
3158
4783
|
degrees = snap(degrees, rotationSnap);
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
];
|
|
3164
|
-
newRotation[cssAxis] = startRotation[cssAxis] + degrees;
|
|
4784
|
+
const axisVec = [0, 0, 0];
|
|
4785
|
+
axisVec[cssAxis] = 1;
|
|
4786
|
+
const qDelta = quatFromAxisAngle(axisVec, degrees * Math.PI / 180);
|
|
4787
|
+
const newRotation = eulerXYZFromQuat(quatMultiply(qDelta, qStart));
|
|
3165
4788
|
onObjectChange?.({ object: target, rotation: newRotation });
|
|
3166
4789
|
onChange?.();
|
|
3167
4790
|
};
|
|
@@ -3201,14 +4824,23 @@ function PolyTransformControls({
|
|
|
3201
4824
|
onDraggingChanged
|
|
3202
4825
|
}) {
|
|
3203
4826
|
const [, forceRender] = useState4(0);
|
|
3204
|
-
|
|
4827
|
+
useEffect9(() => {
|
|
3205
4828
|
forceRender((n) => n + 1);
|
|
3206
4829
|
}, [object]);
|
|
4830
|
+
const cameraCtxForRot = useContext4(PolyCameraContext);
|
|
4831
|
+
const rotX = useStoreSelector(
|
|
4832
|
+
cameraCtxForRot?.store ?? FALLBACK_CAMERA_STORE,
|
|
4833
|
+
(s) => s.cameraState.rotX
|
|
4834
|
+
);
|
|
4835
|
+
const rotY = useStoreSelector(
|
|
4836
|
+
cameraCtxForRot?.store ?? FALLBACK_CAMERA_STORE,
|
|
4837
|
+
(s) => s.cameraState.rotY
|
|
4838
|
+
);
|
|
3207
4839
|
const [hoveredKey, setHoveredKey] = useState4(null);
|
|
3208
4840
|
const [draggingKey, setDraggingKey] = useState4(null);
|
|
3209
4841
|
const cameraCtx = useContext4(PolyCameraContext);
|
|
3210
4842
|
const cameraElRef = cameraCtx?.cameraElRef;
|
|
3211
|
-
const dragRef =
|
|
4843
|
+
const dragRef = useRef9({
|
|
3212
4844
|
target: null,
|
|
3213
4845
|
mode: "translate",
|
|
3214
4846
|
shaftLengthCss: 0,
|
|
@@ -3217,7 +4849,7 @@ function PolyTransformControls({
|
|
|
3217
4849
|
translationSnap: null,
|
|
3218
4850
|
rotationSnap: null
|
|
3219
4851
|
});
|
|
3220
|
-
|
|
4852
|
+
useEffect9(() => {
|
|
3221
4853
|
const cameraEl = cameraElRef?.current;
|
|
3222
4854
|
if (!cameraEl) return;
|
|
3223
4855
|
const onPointerDown = (event) => {
|
|
@@ -3226,6 +4858,42 @@ function PolyTransformControls({
|
|
|
3226
4858
|
const targetEl = event.target;
|
|
3227
4859
|
if (targetEl?.closest(".polycss-transform-gizmo")) return;
|
|
3228
4860
|
if (state.mode === "translate") {
|
|
4861
|
+
for (const spec of PLANE_SPECS) {
|
|
4862
|
+
const aL = ["x", "y", "z"][spec.axisA];
|
|
4863
|
+
const bL = ["x", "y", "z"][spec.axisB];
|
|
4864
|
+
if (!state.show[aL] || !state.show[bL]) continue;
|
|
4865
|
+
const planeEl = document.querySelector(
|
|
4866
|
+
`.polycss-transform-plane--${spec.key}`
|
|
4867
|
+
);
|
|
4868
|
+
if (!planeEl) continue;
|
|
4869
|
+
if (!pointInMeshElement(planeEl, event.clientX, event.clientY)) continue;
|
|
4870
|
+
event.preventDefault();
|
|
4871
|
+
event.stopPropagation();
|
|
4872
|
+
const wrapper = planeEl.closest(
|
|
4873
|
+
"[data-poly-transform-controls]"
|
|
4874
|
+
);
|
|
4875
|
+
if (!wrapper) return;
|
|
4876
|
+
setDraggingKey(spec.key);
|
|
4877
|
+
startPlaneDrag({
|
|
4878
|
+
axisA: spec.axisA,
|
|
4879
|
+
axisB: spec.axisB,
|
|
4880
|
+
probeDistanceCss: state.shaftLengthCss,
|
|
4881
|
+
wrapper,
|
|
4882
|
+
target: state.target,
|
|
4883
|
+
startClientX: event.clientX,
|
|
4884
|
+
startClientY: event.clientY,
|
|
4885
|
+
translationSnap: state.translationSnap,
|
|
4886
|
+
onChange: state.onChange,
|
|
4887
|
+
onObjectChange: state.onObjectChange,
|
|
4888
|
+
onMouseDown: state.onMouseDown,
|
|
4889
|
+
onMouseUp: state.onMouseUp,
|
|
4890
|
+
onDraggingChanged: (d) => {
|
|
4891
|
+
if (!d) setDraggingKey(null);
|
|
4892
|
+
state.onDraggingChanged?.(d);
|
|
4893
|
+
}
|
|
4894
|
+
});
|
|
4895
|
+
return;
|
|
4896
|
+
}
|
|
3229
4897
|
for (const spec of ARROW_SPECS) {
|
|
3230
4898
|
if (!state.show[userAxisLetterOf(spec.key)]) continue;
|
|
3231
4899
|
const arrowEl = document.querySelector(
|
|
@@ -3325,9 +4993,9 @@ function PolyTransformControls({
|
|
|
3325
4993
|
onDraggingChanged
|
|
3326
4994
|
};
|
|
3327
4995
|
const wrapperStyle = {
|
|
4996
|
+
transform: `translate3d(${wrapperPos[0]}px, ${wrapperPos[1]}px, ${wrapperPos[2]}px)`,
|
|
3328
4997
|
position: "absolute",
|
|
3329
4998
|
transformStyle: "preserve-3d",
|
|
3330
|
-
transform: `translate3d(${wrapperPos[0]}px, ${wrapperPos[1]}px, ${wrapperPos[2]}px)`,
|
|
3331
4999
|
// No `pointer-events: none` here — that property is inherited, so
|
|
3332
5000
|
// setting it on the wrapper would cascade to every arrow polygon
|
|
3333
5001
|
// and disable native hit-testing on the gizmo entirely. The
|
|
@@ -3335,7 +5003,7 @@ function PolyTransformControls({
|
|
|
3335
5003
|
// own; descendants opt in via the default `auto`.
|
|
3336
5004
|
zIndex: 1e3
|
|
3337
5005
|
};
|
|
3338
|
-
return /* @__PURE__ */
|
|
5006
|
+
return /* @__PURE__ */ jsxs3(
|
|
3339
5007
|
"div",
|
|
3340
5008
|
{
|
|
3341
5009
|
className: "polycss-transform-controls",
|
|
@@ -3350,7 +5018,8 @@ function PolyTransformControls({
|
|
|
3350
5018
|
const hovered = hoveredKey === spec.key;
|
|
3351
5019
|
const dragging = draggingKey === spec.key;
|
|
3352
5020
|
const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
|
|
3353
|
-
|
|
5021
|
+
const backFacing = isAxisBackFacing(spec.cssAxis, spec.sign, rotX, rotY);
|
|
5022
|
+
return /* @__PURE__ */ jsx8(
|
|
3354
5023
|
TranslateArrow,
|
|
3355
5024
|
{
|
|
3356
5025
|
cssAxis: spec.cssAxis,
|
|
@@ -3358,6 +5027,41 @@ function PolyTransformControls({
|
|
|
3358
5027
|
axisKey: spec.key,
|
|
3359
5028
|
color: withAlpha(spec.color, alpha),
|
|
3360
5029
|
shaftLengthCss,
|
|
5030
|
+
includeShaft: !backFacing,
|
|
5031
|
+
target,
|
|
5032
|
+
enabled,
|
|
5033
|
+
translationSnap,
|
|
5034
|
+
onChange,
|
|
5035
|
+
onObjectChange,
|
|
5036
|
+
onMouseDown,
|
|
5037
|
+
onMouseUp,
|
|
5038
|
+
onDraggingChanged,
|
|
5039
|
+
onHoverChange: (h) => setHoveredKey(h ? spec.key : (cur) => cur === spec.key ? null : cur),
|
|
5040
|
+
onDraggingStart: () => setDraggingKey(spec.key),
|
|
5041
|
+
onDraggingStop: () => setDraggingKey((cur) => cur === spec.key ? null : cur)
|
|
5042
|
+
},
|
|
5043
|
+
spec.key
|
|
5044
|
+
);
|
|
5045
|
+
}),
|
|
5046
|
+
mode === "translate" && PLANE_SPECS.map((spec) => {
|
|
5047
|
+
const aLetter = ["x", "y", "z"][spec.axisA];
|
|
5048
|
+
const bLetter = ["x", "y", "z"][spec.axisB];
|
|
5049
|
+
const show = { x: showX, y: showY, z: showZ }[aLetter] && { x: showX, y: showY, z: showZ }[bLetter];
|
|
5050
|
+
if (!show) return null;
|
|
5051
|
+
const hovered = hoveredKey === spec.key;
|
|
5052
|
+
const dragging = draggingKey === spec.key;
|
|
5053
|
+
const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
|
|
5054
|
+
return /* @__PURE__ */ jsx8(
|
|
5055
|
+
TranslatePlane,
|
|
5056
|
+
{
|
|
5057
|
+
axisA: spec.axisA,
|
|
5058
|
+
axisB: spec.axisB,
|
|
5059
|
+
perpAxis: spec.perpAxis,
|
|
5060
|
+
planeKey: spec.key,
|
|
5061
|
+
color: withAlpha(spec.color, alpha),
|
|
5062
|
+
shaftLengthCss,
|
|
5063
|
+
rotX,
|
|
5064
|
+
rotY,
|
|
3361
5065
|
target,
|
|
3362
5066
|
enabled,
|
|
3363
5067
|
translationSnap,
|
|
@@ -3379,7 +5083,7 @@ function PolyTransformControls({
|
|
|
3379
5083
|
const hovered = hoveredKey === spec.key;
|
|
3380
5084
|
const dragging = draggingKey === spec.key;
|
|
3381
5085
|
const alpha = dragging ? ALPHA_DRAGGING : hovered ? ALPHA_HOVER : ALPHA_IDLE;
|
|
3382
|
-
return /* @__PURE__ */
|
|
5086
|
+
return /* @__PURE__ */ jsx8(
|
|
3383
5087
|
RotateRing,
|
|
3384
5088
|
{
|
|
3385
5089
|
cssAxis: spec.cssAxis,
|
|
@@ -3411,6 +5115,7 @@ function TranslateArrow({
|
|
|
3411
5115
|
axisKey,
|
|
3412
5116
|
color,
|
|
3413
5117
|
shaftLengthCss,
|
|
5118
|
+
includeShaft,
|
|
3414
5119
|
target,
|
|
3415
5120
|
enabled,
|
|
3416
5121
|
translationSnap,
|
|
@@ -3423,7 +5128,7 @@ function TranslateArrow({
|
|
|
3423
5128
|
onDraggingStart,
|
|
3424
5129
|
onDraggingStop
|
|
3425
5130
|
}) {
|
|
3426
|
-
const cbRef =
|
|
5131
|
+
const cbRef = useRef9({
|
|
3427
5132
|
onChange,
|
|
3428
5133
|
onObjectChange,
|
|
3429
5134
|
onMouseDown,
|
|
@@ -3445,7 +5150,7 @@ function TranslateArrow({
|
|
|
3445
5150
|
enabled,
|
|
3446
5151
|
translationSnap
|
|
3447
5152
|
};
|
|
3448
|
-
const polygons =
|
|
5153
|
+
const polygons = useMemo11(() => {
|
|
3449
5154
|
const lengthWorld = shaftLengthCss / SCENE_TILE_SIZE;
|
|
3450
5155
|
return arrowPolygons({
|
|
3451
5156
|
axis: WORLD_AXIS_FOR_CSS[cssAxis],
|
|
@@ -3454,9 +5159,10 @@ function TranslateArrow({
|
|
|
3454
5159
|
shaftHalfThickness: lengthWorld * SHAFT_HALF_THICKNESS_RATIO,
|
|
3455
5160
|
headLength: lengthWorld * HEAD_LENGTH_RATIO,
|
|
3456
5161
|
headHalfThickness: lengthWorld * HEAD_HALF_THICKNESS_RATIO,
|
|
3457
|
-
color
|
|
5162
|
+
color,
|
|
5163
|
+
shaft: includeShaft
|
|
3458
5164
|
});
|
|
3459
|
-
}, [cssAxis, sign, color, shaftLengthCss]);
|
|
5165
|
+
}, [cssAxis, sign, color, shaftLengthCss, includeShaft]);
|
|
3460
5166
|
const onPointerDown = useCallback7(
|
|
3461
5167
|
(e) => {
|
|
3462
5168
|
if (!cbRef.current.enabled) return;
|
|
@@ -3486,7 +5192,7 @@ function TranslateArrow({
|
|
|
3486
5192
|
},
|
|
3487
5193
|
[cssAxis, sign, target, shaftLengthCss]
|
|
3488
5194
|
);
|
|
3489
|
-
return /* @__PURE__ */
|
|
5195
|
+
return /* @__PURE__ */ jsx8(
|
|
3490
5196
|
PolyMesh,
|
|
3491
5197
|
{
|
|
3492
5198
|
polygons,
|
|
@@ -3498,6 +5204,107 @@ function TranslateArrow({
|
|
|
3498
5204
|
}
|
|
3499
5205
|
);
|
|
3500
5206
|
}
|
|
5207
|
+
function TranslatePlane({
|
|
5208
|
+
axisA,
|
|
5209
|
+
axisB,
|
|
5210
|
+
perpAxis,
|
|
5211
|
+
planeKey,
|
|
5212
|
+
color,
|
|
5213
|
+
shaftLengthCss,
|
|
5214
|
+
rotX,
|
|
5215
|
+
rotY,
|
|
5216
|
+
target,
|
|
5217
|
+
enabled,
|
|
5218
|
+
translationSnap,
|
|
5219
|
+
onChange,
|
|
5220
|
+
onObjectChange,
|
|
5221
|
+
onMouseDown,
|
|
5222
|
+
onMouseUp,
|
|
5223
|
+
onDraggingChanged,
|
|
5224
|
+
onHoverChange,
|
|
5225
|
+
onDraggingStart,
|
|
5226
|
+
onDraggingStop
|
|
5227
|
+
}) {
|
|
5228
|
+
const cbRef = useRef9({
|
|
5229
|
+
onChange,
|
|
5230
|
+
onObjectChange,
|
|
5231
|
+
onMouseDown,
|
|
5232
|
+
onMouseUp,
|
|
5233
|
+
onDraggingChanged,
|
|
5234
|
+
onDraggingStart,
|
|
5235
|
+
onDraggingStop,
|
|
5236
|
+
enabled,
|
|
5237
|
+
translationSnap
|
|
5238
|
+
});
|
|
5239
|
+
cbRef.current = {
|
|
5240
|
+
onChange,
|
|
5241
|
+
onObjectChange,
|
|
5242
|
+
onMouseDown,
|
|
5243
|
+
onMouseUp,
|
|
5244
|
+
onDraggingChanged,
|
|
5245
|
+
onDraggingStart,
|
|
5246
|
+
onDraggingStop,
|
|
5247
|
+
enabled,
|
|
5248
|
+
translationSnap
|
|
5249
|
+
};
|
|
5250
|
+
const polygons = useMemo11(() => {
|
|
5251
|
+
const lengthWorld = shaftLengthCss / SCENE_TILE_SIZE;
|
|
5252
|
+
const worldPerp = WORLD_AXIS_FOR_CSS[perpAxis];
|
|
5253
|
+
const worldA = (worldPerp + 1) % 3;
|
|
5254
|
+
const worldB = (worldPerp + 2) % 3;
|
|
5255
|
+
const cssAForOffset = WORLD_AXIS_FOR_CSS[worldA];
|
|
5256
|
+
const cssBForOffset = WORLD_AXIS_FOR_CSS[worldB];
|
|
5257
|
+
const signA = isAxisBackFacing(cssAForOffset, 1, rotX, rotY) ? -1 : 1;
|
|
5258
|
+
const signB = isAxisBackFacing(cssBForOffset, 1, rotX, rotY) ? -1 : 1;
|
|
5259
|
+
const mag = lengthWorld * PLANE_OFFSET_RATIO;
|
|
5260
|
+
return planePolygons({
|
|
5261
|
+
axis: worldPerp,
|
|
5262
|
+
size: lengthWorld * PLANE_HALF_SIZE_RATIO,
|
|
5263
|
+
offset: [signA * mag, signB * mag],
|
|
5264
|
+
color
|
|
5265
|
+
});
|
|
5266
|
+
}, [perpAxis, color, shaftLengthCss, rotX, rotY]);
|
|
5267
|
+
const onPointerDown = useCallback7(
|
|
5268
|
+
(e) => {
|
|
5269
|
+
if (!cbRef.current.enabled) return;
|
|
5270
|
+
e.stopPropagation();
|
|
5271
|
+
const meshEl = e.eventObject.element;
|
|
5272
|
+
const wrapper = meshEl?.closest("[data-poly-transform-controls]");
|
|
5273
|
+
if (!wrapper) return;
|
|
5274
|
+
cbRef.current.onDraggingStart?.();
|
|
5275
|
+
startPlaneDrag({
|
|
5276
|
+
axisA,
|
|
5277
|
+
axisB,
|
|
5278
|
+
probeDistanceCss: shaftLengthCss,
|
|
5279
|
+
wrapper,
|
|
5280
|
+
target,
|
|
5281
|
+
startClientX: e.nativeEvent.clientX,
|
|
5282
|
+
startClientY: e.nativeEvent.clientY,
|
|
5283
|
+
translationSnap: cbRef.current.translationSnap,
|
|
5284
|
+
onChange: cbRef.current.onChange,
|
|
5285
|
+
onObjectChange: cbRef.current.onObjectChange,
|
|
5286
|
+
onMouseDown: cbRef.current.onMouseDown,
|
|
5287
|
+
onMouseUp: cbRef.current.onMouseUp,
|
|
5288
|
+
onDraggingChanged: (d) => {
|
|
5289
|
+
if (!d) cbRef.current.onDraggingStop?.();
|
|
5290
|
+
cbRef.current.onDraggingChanged?.(d);
|
|
5291
|
+
}
|
|
5292
|
+
});
|
|
5293
|
+
},
|
|
5294
|
+
[axisA, axisB, target, shaftLengthCss]
|
|
5295
|
+
);
|
|
5296
|
+
return /* @__PURE__ */ jsx8(
|
|
5297
|
+
PolyMesh,
|
|
5298
|
+
{
|
|
5299
|
+
polygons,
|
|
5300
|
+
onPointerDown,
|
|
5301
|
+
onPointerOver: () => onHoverChange?.(true),
|
|
5302
|
+
onPointerOut: () => onHoverChange?.(false),
|
|
5303
|
+
className: `polycss-transform-gizmo polycss-transform-plane polycss-transform-plane--${planeKey}`,
|
|
5304
|
+
textureLighting: "baked"
|
|
5305
|
+
}
|
|
5306
|
+
);
|
|
5307
|
+
}
|
|
3501
5308
|
function RotateRing({
|
|
3502
5309
|
cssAxis,
|
|
3503
5310
|
axisKey,
|
|
@@ -3515,7 +5322,7 @@ function RotateRing({
|
|
|
3515
5322
|
onDraggingStart,
|
|
3516
5323
|
onDraggingStop
|
|
3517
5324
|
}) {
|
|
3518
|
-
const cbRef =
|
|
5325
|
+
const cbRef = useRef9({
|
|
3519
5326
|
onChange,
|
|
3520
5327
|
onObjectChange,
|
|
3521
5328
|
onMouseDown,
|
|
@@ -3537,16 +5344,17 @@ function RotateRing({
|
|
|
3537
5344
|
enabled,
|
|
3538
5345
|
rotationSnap
|
|
3539
5346
|
};
|
|
3540
|
-
const polygons =
|
|
5347
|
+
const polygons = useMemo11(() => {
|
|
3541
5348
|
const radiusWorld = radiusCss / SCENE_TILE_SIZE;
|
|
3542
|
-
|
|
5349
|
+
const outerWorld = radiusWorld * RING_QUAD_OUTER_RATIO;
|
|
5350
|
+
return ringQuadPolygons({
|
|
3543
5351
|
axis: WORLD_AXIS_FOR_CSS[cssAxis],
|
|
3544
|
-
|
|
3545
|
-
halfThickness: radiusWorld * RING_HALF_THICKNESS_RATIO,
|
|
3546
|
-
segments: RING_SEGMENTS,
|
|
5352
|
+
outerRadius: outerWorld,
|
|
3547
5353
|
color
|
|
3548
5354
|
});
|
|
3549
5355
|
}, [cssAxis, color, radiusCss]);
|
|
5356
|
+
const ringInnerRatio = (1 - RING_HALF_THICKNESS_RATIO) / RING_QUAD_OUTER_RATIO;
|
|
5357
|
+
const ringOuterRatio = (1 + RING_HALF_THICKNESS_RATIO) / RING_QUAD_OUTER_RATIO;
|
|
3550
5358
|
const onPointerDown = useCallback7(
|
|
3551
5359
|
(e) => {
|
|
3552
5360
|
if (!cbRef.current.enabled) return;
|
|
@@ -3574,7 +5382,7 @@ function RotateRing({
|
|
|
3574
5382
|
},
|
|
3575
5383
|
[cssAxis, target]
|
|
3576
5384
|
);
|
|
3577
|
-
return /* @__PURE__ */
|
|
5385
|
+
return /* @__PURE__ */ jsx8(
|
|
3578
5386
|
PolyMesh,
|
|
3579
5387
|
{
|
|
3580
5388
|
polygons,
|
|
@@ -3582,6 +5390,10 @@ function RotateRing({
|
|
|
3582
5390
|
onPointerOver: () => onHoverChange?.(true),
|
|
3583
5391
|
onPointerOut: () => onHoverChange?.(false),
|
|
3584
5392
|
className: `polycss-transform-gizmo polycss-transform-ring polycss-transform-ring--${axisKey}`,
|
|
5393
|
+
style: {
|
|
5394
|
+
["--ring-inner-ratio"]: ringInnerRatio,
|
|
5395
|
+
["--ring-outer-ratio"]: ringOuterRatio
|
|
5396
|
+
},
|
|
3585
5397
|
textureLighting: "baked"
|
|
3586
5398
|
}
|
|
3587
5399
|
);
|
|
@@ -3592,12 +5404,12 @@ import {
|
|
|
3592
5404
|
createContext as createContext3,
|
|
3593
5405
|
useCallback as useCallback8,
|
|
3594
5406
|
useContext as useContext5,
|
|
3595
|
-
useEffect as
|
|
3596
|
-
useMemo as
|
|
3597
|
-
useRef as
|
|
5407
|
+
useEffect as useEffect10,
|
|
5408
|
+
useMemo as useMemo12,
|
|
5409
|
+
useRef as useRef10,
|
|
3598
5410
|
useState as useState5
|
|
3599
5411
|
} from "react";
|
|
3600
|
-
import { jsx as
|
|
5412
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
3601
5413
|
var SelectContext = createContext3(null);
|
|
3602
5414
|
function PolySelect({
|
|
3603
5415
|
multiple = false,
|
|
@@ -3610,18 +5422,18 @@ function PolySelect({
|
|
|
3610
5422
|
style
|
|
3611
5423
|
}) {
|
|
3612
5424
|
const [selected, setSelectedState] = useState5([]);
|
|
3613
|
-
const filterRef =
|
|
5425
|
+
const filterRef = useRef10(filter);
|
|
3614
5426
|
filterRef.current = filter;
|
|
3615
|
-
const onChangeRef =
|
|
5427
|
+
const onChangeRef = useRef10(onChange);
|
|
3616
5428
|
onChangeRef.current = onChange;
|
|
3617
|
-
const multipleRef =
|
|
5429
|
+
const multipleRef = useRef10(multiple);
|
|
3618
5430
|
multipleRef.current = multiple;
|
|
3619
5431
|
const apply = useCallback8((next) => {
|
|
3620
5432
|
const filtered = filterRef.current ? filterRef.current(next) : next;
|
|
3621
5433
|
setSelectedState(filtered);
|
|
3622
5434
|
if (onChangeRef.current) onChangeRef.current(filtered);
|
|
3623
5435
|
}, []);
|
|
3624
|
-
const api =
|
|
5436
|
+
const api = useMemo12(() => {
|
|
3625
5437
|
return {
|
|
3626
5438
|
selected,
|
|
3627
5439
|
set: (next) => apply(next),
|
|
@@ -3654,12 +5466,12 @@ function PolySelect({
|
|
|
3654
5466
|
has: (h) => selected.includes(h)
|
|
3655
5467
|
};
|
|
3656
5468
|
}, [selected, apply]);
|
|
3657
|
-
const wrapperRef =
|
|
5469
|
+
const wrapperRef = useRef10(null);
|
|
3658
5470
|
const cameraCtx = useContext5(PolyCameraContext);
|
|
3659
5471
|
const cameraElRef = cameraCtx?.cameraElRef;
|
|
3660
|
-
const clearOnMissRef =
|
|
5472
|
+
const clearOnMissRef = useRef10(clearOnMiss);
|
|
3661
5473
|
clearOnMissRef.current = clearOnMiss;
|
|
3662
|
-
const onPointerMissedRef =
|
|
5474
|
+
const onPointerMissedRef = useRef10(onPointerMissed);
|
|
3663
5475
|
onPointerMissedRef.current = onPointerMissed;
|
|
3664
5476
|
const findMeshUnderPoint2 = useCallback8(
|
|
3665
5477
|
(clientX, clientY) => findMeshUnderPoint(
|
|
@@ -3669,7 +5481,7 @@ function PolySelect({
|
|
|
3669
5481
|
),
|
|
3670
5482
|
[]
|
|
3671
5483
|
);
|
|
3672
|
-
|
|
5484
|
+
useEffect10(() => {
|
|
3673
5485
|
const cameraEl = cameraElRef?.current;
|
|
3674
5486
|
if (!cameraEl) return;
|
|
3675
5487
|
const onClick = (event) => {
|
|
@@ -3716,7 +5528,7 @@ function PolySelect({
|
|
|
3716
5528
|
}
|
|
3717
5529
|
};
|
|
3718
5530
|
const wrapperStyle = { display: "contents", ...style };
|
|
3719
|
-
return /* @__PURE__ */
|
|
5531
|
+
return /* @__PURE__ */ jsx9(SelectContext.Provider, { value: api, children: /* @__PURE__ */ jsx9(
|
|
3720
5532
|
"div",
|
|
3721
5533
|
{
|
|
3722
5534
|
ref: wrapperRef,
|
|
@@ -3740,9 +5552,9 @@ function usePolySelectionApi() {
|
|
|
3740
5552
|
}
|
|
3741
5553
|
|
|
3742
5554
|
// src/helpers/PolyAxesHelper.tsx
|
|
3743
|
-
import { useMemo as
|
|
5555
|
+
import { useMemo as useMemo13 } from "react";
|
|
3744
5556
|
import { axesHelperPolygons } from "@layoutit/polycss-core";
|
|
3745
|
-
import { jsx as
|
|
5557
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3746
5558
|
function PolyAxesHelper({
|
|
3747
5559
|
size,
|
|
3748
5560
|
thickness,
|
|
@@ -3751,17 +5563,17 @@ function PolyAxesHelper({
|
|
|
3751
5563
|
yColor,
|
|
3752
5564
|
zColor
|
|
3753
5565
|
}) {
|
|
3754
|
-
const polygons =
|
|
5566
|
+
const polygons = useMemo13(
|
|
3755
5567
|
() => axesHelperPolygons({ size, thickness, negative, xColor, yColor, zColor }),
|
|
3756
5568
|
[size, thickness, negative, xColor, yColor, zColor]
|
|
3757
5569
|
);
|
|
3758
|
-
return /* @__PURE__ */
|
|
5570
|
+
return /* @__PURE__ */ jsx10(PolyMesh, { polygons });
|
|
3759
5571
|
}
|
|
3760
5572
|
|
|
3761
5573
|
// src/helpers/PolyDirectionalLightHelper.tsx
|
|
3762
|
-
import { useMemo as
|
|
5574
|
+
import { useMemo as useMemo14 } from "react";
|
|
3763
5575
|
import { octahedronPolygons } from "@layoutit/polycss-core";
|
|
3764
|
-
import { jsx as
|
|
5576
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
3765
5577
|
var TILE = 50;
|
|
3766
5578
|
function PolyDirectionalLightHelper({
|
|
3767
5579
|
light,
|
|
@@ -3771,11 +5583,11 @@ function PolyDirectionalLightHelper({
|
|
|
3771
5583
|
color
|
|
3772
5584
|
}) {
|
|
3773
5585
|
const swatch = color ?? light.color ?? "#ffd54a";
|
|
3774
|
-
const polygons =
|
|
5586
|
+
const polygons = useMemo14(
|
|
3775
5587
|
() => octahedronPolygons({ center: [0, 0, 0], size, color: swatch }),
|
|
3776
5588
|
[size, swatch]
|
|
3777
5589
|
);
|
|
3778
|
-
const meshPosition =
|
|
5590
|
+
const meshPosition = useMemo14(() => {
|
|
3779
5591
|
const [dx, dy, dz] = light.direction;
|
|
3780
5592
|
const len = Math.hypot(dx, dy, dz) || 1;
|
|
3781
5593
|
const tx = target?.[0] ?? 0;
|
|
@@ -3786,14 +5598,14 @@ function PolyDirectionalLightHelper({
|
|
|
3786
5598
|
const worldZ = tz + dz / len * distance;
|
|
3787
5599
|
return [worldY * TILE, worldX * TILE, worldZ * TILE];
|
|
3788
5600
|
}, [light.direction, target, distance]);
|
|
3789
|
-
return /* @__PURE__ */
|
|
5601
|
+
return /* @__PURE__ */ jsx11(PolyMesh, { polygons, position: meshPosition });
|
|
3790
5602
|
}
|
|
3791
5603
|
|
|
3792
5604
|
// src/styles/colorResolver.ts
|
|
3793
5605
|
import { parsePureColor as parsePureColor2 } from "@layoutit/polycss-core";
|
|
3794
5606
|
|
|
3795
5607
|
// src/animation/usePolyAnimation.ts
|
|
3796
|
-
import { useEffect as
|
|
5608
|
+
import { useEffect as useEffect11, useRef as useRef11, useMemo as useMemo15 } from "react";
|
|
3797
5609
|
import { createPolyAnimationMixer } from "@layoutit/polycss-core";
|
|
3798
5610
|
function resolveRoot(rootArg) {
|
|
3799
5611
|
if (!rootArg) return null;
|
|
@@ -3801,9 +5613,9 @@ function resolveRoot(rootArg) {
|
|
|
3801
5613
|
return rootArg;
|
|
3802
5614
|
}
|
|
3803
5615
|
function usePolyAnimation(clips, controller, root) {
|
|
3804
|
-
const internalRef =
|
|
3805
|
-
const mixerRef =
|
|
3806
|
-
|
|
5616
|
+
const internalRef = useRef11(null);
|
|
5617
|
+
const mixerRef = useRef11(null);
|
|
5618
|
+
useEffect11(() => {
|
|
3807
5619
|
if (!clips || clips.length === 0 || !controller) {
|
|
3808
5620
|
mixerRef.current = null;
|
|
3809
5621
|
return;
|
|
@@ -3820,7 +5632,7 @@ function usePolyAnimation(clips, controller, root) {
|
|
|
3820
5632
|
mixerRef.current = null;
|
|
3821
5633
|
};
|
|
3822
5634
|
}, [clips, controller]);
|
|
3823
|
-
|
|
5635
|
+
useEffect11(() => {
|
|
3824
5636
|
if (!clips || clips.length === 0 || !controller) return;
|
|
3825
5637
|
let rafId;
|
|
3826
5638
|
let lastTime = null;
|
|
@@ -3843,7 +5655,7 @@ function usePolyAnimation(clips, controller, root) {
|
|
|
3843
5655
|
}, [clips, controller]);
|
|
3844
5656
|
const resolvedClips = clips ?? [];
|
|
3845
5657
|
const resolvedNames = resolvedClips.map((c) => c.name);
|
|
3846
|
-
const actions =
|
|
5658
|
+
const actions = useMemo15(() => {
|
|
3847
5659
|
const target = {};
|
|
3848
5660
|
for (const clip of resolvedClips) {
|
|
3849
5661
|
Object.defineProperty(target, clip.name, {
|
|
@@ -3872,24 +5684,38 @@ function usePolyAnimation(clips, controller, root) {
|
|
|
3872
5684
|
|
|
3873
5685
|
// src/index.ts
|
|
3874
5686
|
import {
|
|
5687
|
+
CAMERA_BACKFACE_CULL_EPS,
|
|
5688
|
+
VOXEL_CAMERA_CULL_AXIS_EPS,
|
|
5689
|
+
VOXEL_CAMERA_CULL_NORMAL_LIMIT,
|
|
3875
5690
|
normalizePolygons,
|
|
3876
5691
|
mergePolygons as mergePolygons2,
|
|
3877
5692
|
coverPlanarPolygons,
|
|
5693
|
+
optimizeMeshPolygons,
|
|
3878
5694
|
cullInteriorPolygons,
|
|
5695
|
+
cameraCullNormalGroups,
|
|
5696
|
+
cameraCullNormalGroupsFromPolygons,
|
|
5697
|
+
cameraCullNormalKey,
|
|
5698
|
+
cameraCullVisibleSignature,
|
|
5699
|
+
cameraFacingDepth,
|
|
5700
|
+
isAxisAlignedSurfaceNormal,
|
|
5701
|
+
isVoxelCameraCullableNormalGroups,
|
|
5702
|
+
normalFacesCamera,
|
|
5703
|
+
polygonCssSurfaceNormal,
|
|
5704
|
+
polygonFacesCamera,
|
|
3879
5705
|
parseObj,
|
|
3880
5706
|
parseMtl,
|
|
3881
5707
|
parseGltf,
|
|
3882
5708
|
bakeSolidTextureSamples,
|
|
3883
5709
|
bakeSolidTextureSampledPolygons,
|
|
3884
5710
|
loadMesh as loadMesh2,
|
|
3885
|
-
createIsometricCamera as
|
|
5711
|
+
createIsometricCamera as createIsometricCamera2,
|
|
3886
5712
|
parseVox,
|
|
3887
5713
|
polygonFaces,
|
|
3888
5714
|
computeTexturePaintMetrics,
|
|
3889
5715
|
computeShapeLighting,
|
|
3890
5716
|
parseColor,
|
|
3891
5717
|
parsePureColor as parsePureColor3,
|
|
3892
|
-
parseHexColor as
|
|
5718
|
+
parseHexColor as parseHexColor3,
|
|
3893
5719
|
parseRgbColor,
|
|
3894
5720
|
formatColor,
|
|
3895
5721
|
clampChannel,
|
|
@@ -3897,13 +5723,14 @@ import {
|
|
|
3897
5723
|
rotateVec3,
|
|
3898
5724
|
inverseRotateVec3 as inverseRotateVec32,
|
|
3899
5725
|
axesHelperPolygons as axesHelperPolygons2,
|
|
5726
|
+
boxPolygons,
|
|
3900
5727
|
arrowPolygons as arrowPolygons2,
|
|
3901
|
-
ringPolygons
|
|
5728
|
+
ringPolygons,
|
|
3902
5729
|
octahedronPolygons as octahedronPolygons2,
|
|
3903
5730
|
buildSceneContext as buildSceneContext2,
|
|
3904
5731
|
computeSceneBbox as computeSceneBbox2,
|
|
3905
|
-
BASE_TILE as
|
|
3906
|
-
DEFAULT_CAMERA_STATE,
|
|
5732
|
+
BASE_TILE as BASE_TILE5,
|
|
5733
|
+
DEFAULT_CAMERA_STATE as DEFAULT_CAMERA_STATE2,
|
|
3907
5734
|
DEFAULT_PROJECTION,
|
|
3908
5735
|
normalizeInvertMultiplier,
|
|
3909
5736
|
createPolyAnimationMixer as createPolyAnimationMixer2,
|
|
@@ -3912,8 +5739,9 @@ import {
|
|
|
3912
5739
|
LoopPingPong
|
|
3913
5740
|
} from "@layoutit/polycss-core";
|
|
3914
5741
|
export {
|
|
3915
|
-
|
|
3916
|
-
|
|
5742
|
+
BASE_TILE5 as BASE_TILE,
|
|
5743
|
+
CAMERA_BACKFACE_CULL_EPS,
|
|
5744
|
+
DEFAULT_CAMERA_STATE2 as DEFAULT_CAMERA_STATE,
|
|
3917
5745
|
DEFAULT_PROJECTION,
|
|
3918
5746
|
LoopOnce,
|
|
3919
5747
|
LoopPingPong,
|
|
@@ -3923,6 +5751,8 @@ export {
|
|
|
3923
5751
|
PolyPerspectiveCamera as PolyCamera,
|
|
3924
5752
|
PolyCameraContext,
|
|
3925
5753
|
PolyDirectionalLightHelper,
|
|
5754
|
+
PolyFirstPersonControls,
|
|
5755
|
+
PolyGround,
|
|
3926
5756
|
PolyMapControls,
|
|
3927
5757
|
PolyMesh,
|
|
3928
5758
|
PolyOrbitControls,
|
|
@@ -3931,17 +5761,25 @@ export {
|
|
|
3931
5761
|
PolyScene,
|
|
3932
5762
|
PolySelect,
|
|
3933
5763
|
PolyTransformControls,
|
|
5764
|
+
VOXEL_CAMERA_CULL_AXIS_EPS,
|
|
5765
|
+
VOXEL_CAMERA_CULL_NORMAL_LIMIT,
|
|
3934
5766
|
arrowPolygons2 as arrowPolygons,
|
|
3935
5767
|
axesHelperPolygons2 as axesHelperPolygons,
|
|
3936
5768
|
bakeSolidTextureSampledPolygons,
|
|
3937
5769
|
bakeSolidTextureSamples,
|
|
5770
|
+
boxPolygons,
|
|
3938
5771
|
buildSceneContext2 as buildSceneContext,
|
|
5772
|
+
cameraCullNormalGroups,
|
|
5773
|
+
cameraCullNormalGroupsFromPolygons,
|
|
5774
|
+
cameraCullNormalKey,
|
|
5775
|
+
cameraCullVisibleSignature,
|
|
5776
|
+
cameraFacingDepth,
|
|
3939
5777
|
clampChannel,
|
|
3940
5778
|
computeSceneBbox2 as computeSceneBbox,
|
|
3941
5779
|
computeShapeLighting,
|
|
3942
5780
|
computeTexturePaintMetrics,
|
|
3943
5781
|
coverPlanarPolygons,
|
|
3944
|
-
|
|
5782
|
+
createIsometricCamera2 as createIsometricCamera,
|
|
3945
5783
|
createPolyAnimationMixer2 as createPolyAnimationMixer,
|
|
3946
5784
|
cullInteriorPolygons,
|
|
3947
5785
|
findMeshUnderPoint,
|
|
@@ -3949,22 +5787,28 @@ export {
|
|
|
3949
5787
|
formatColor,
|
|
3950
5788
|
injectPolyBaseStyles,
|
|
3951
5789
|
inverseRotateVec32 as inverseRotateVec3,
|
|
5790
|
+
isAxisAlignedSurfaceNormal,
|
|
5791
|
+
isVoxelCameraCullableNormalGroups,
|
|
3952
5792
|
loadMesh2 as loadMesh,
|
|
3953
5793
|
mergePolygons2 as mergePolygons,
|
|
5794
|
+
normalFacesCamera,
|
|
3954
5795
|
normalizeInvertMultiplier,
|
|
3955
5796
|
normalizePolygons,
|
|
3956
5797
|
octahedronPolygons2 as octahedronPolygons,
|
|
5798
|
+
optimizeMeshPolygons,
|
|
3957
5799
|
parseColor,
|
|
3958
5800
|
parseGltf,
|
|
3959
|
-
|
|
5801
|
+
parseHexColor3 as parseHexColor,
|
|
3960
5802
|
parseMtl,
|
|
3961
5803
|
parseObj,
|
|
3962
5804
|
parsePureColor3 as parsePureColor,
|
|
3963
5805
|
parseRgbColor,
|
|
3964
5806
|
parseVox,
|
|
3965
5807
|
pointInMeshElement,
|
|
5808
|
+
polygonCssSurfaceNormal,
|
|
3966
5809
|
polygonFaces,
|
|
3967
|
-
|
|
5810
|
+
polygonFacesCamera,
|
|
5811
|
+
ringPolygons,
|
|
3968
5812
|
rotateVec3,
|
|
3969
5813
|
shadeColor,
|
|
3970
5814
|
useCameraContext,
|