@liveroom-tech/react-immersive 1.1.7 → 1.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/dist/index.js +301 -185
- package/dist/index.mjs +324 -204
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import {
|
|
5
5
|
useRef as useRef11,
|
|
6
6
|
useState as useState10,
|
|
7
|
-
useEffect as
|
|
7
|
+
useEffect as useEffect15,
|
|
8
8
|
useCallback as useCallback4,
|
|
9
9
|
useMemo as useMemo13,
|
|
10
10
|
Suspense as Suspense2
|
|
@@ -1536,6 +1536,7 @@ function Viewer({
|
|
|
1536
1536
|
dpr: [1, effectiveMaxDpr],
|
|
1537
1537
|
shadows: effectiveShadows ? "soft" : false,
|
|
1538
1538
|
resize: { debounce: 0, scroll: false },
|
|
1539
|
+
raycaster: { firstHitOnly: true },
|
|
1539
1540
|
style: backgroundStyle,
|
|
1540
1541
|
camera: camera ?? {
|
|
1541
1542
|
fov: 50
|
|
@@ -1583,7 +1584,7 @@ function Controls({ controlsRef, onCameraChange }) {
|
|
|
1583
1584
|
}
|
|
1584
1585
|
|
|
1585
1586
|
// src/components/Lights.tsx
|
|
1586
|
-
import { useRef as useRef4, useMemo as useMemo5 } from "react";
|
|
1587
|
+
import { useRef as useRef4, useMemo as useMemo5, useEffect as useEffect6 } from "react";
|
|
1587
1588
|
import { useFrame as useFrame3, useThree as useThree5 } from "@react-three/fiber";
|
|
1588
1589
|
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1589
1590
|
function rotatePositionY(pos, deg) {
|
|
@@ -1593,6 +1594,24 @@ function rotatePositionY(pos, deg) {
|
|
|
1593
1594
|
const s = Math.sin(rad);
|
|
1594
1595
|
return [pos[0] * c + pos[2] * s, pos[1], -pos[0] * s + pos[2] * c];
|
|
1595
1596
|
}
|
|
1597
|
+
function fitShadowCamera(bounds, position) {
|
|
1598
|
+
const [cx, cy, cz] = bounds.center;
|
|
1599
|
+
const radius = bounds.radius;
|
|
1600
|
+
const length = Math.hypot(position[0], position[1], position[2]);
|
|
1601
|
+
const dir = length > 1e-6 ? [position[0] / length, position[1] / length, position[2] / length] : [0, 1, 0];
|
|
1602
|
+
const distance = radius * 2;
|
|
1603
|
+
return {
|
|
1604
|
+
position: [
|
|
1605
|
+
cx + dir[0] * distance,
|
|
1606
|
+
cy + dir[1] * distance,
|
|
1607
|
+
cz + dir[2] * distance
|
|
1608
|
+
],
|
|
1609
|
+
target: bounds.center,
|
|
1610
|
+
extent: radius,
|
|
1611
|
+
near: Math.max(radius / 100, 1e-3),
|
|
1612
|
+
far: distance + radius * 2
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1596
1615
|
var DEFAULT_LIGHTING = {
|
|
1597
1616
|
ambient: { intensity: 0.25, color: "#ffffff" },
|
|
1598
1617
|
lights: [
|
|
@@ -1642,7 +1661,8 @@ function LightInstance({
|
|
|
1642
1661
|
light,
|
|
1643
1662
|
shadows,
|
|
1644
1663
|
shadowsEnabled,
|
|
1645
|
-
environmentRotation
|
|
1664
|
+
environmentRotation,
|
|
1665
|
+
modelBounds
|
|
1646
1666
|
}) {
|
|
1647
1667
|
const dirRef = useRef4(null);
|
|
1648
1668
|
const pointRef = useRef4(null);
|
|
@@ -1658,8 +1678,19 @@ function LightInstance({
|
|
|
1658
1678
|
if (pointRef.current) pointRef.current.position.copy(camera.position);
|
|
1659
1679
|
if (spotRef.current) spotRef.current.position.copy(camera.position);
|
|
1660
1680
|
});
|
|
1661
|
-
if (!light.visible || light.type === "none") return null;
|
|
1662
1681
|
const castShadow = shadowsEnabled && shadows.enabled && light.castShadow;
|
|
1682
|
+
const fit = useMemo5(
|
|
1683
|
+
() => light.type === "directional" && castShadow && modelBounds && !light.attachedToCamera ? fitShadowCamera(modelBounds, position) : null,
|
|
1684
|
+
[light.type, light.attachedToCamera, castShadow, modelBounds, position]
|
|
1685
|
+
);
|
|
1686
|
+
useEffect6(() => {
|
|
1687
|
+
const dirLight = dirRef.current;
|
|
1688
|
+
if (!dirLight) return;
|
|
1689
|
+
const [tx, ty, tz] = fit?.target ?? [0, 0, 0];
|
|
1690
|
+
dirLight.target.position.set(tx, ty, tz);
|
|
1691
|
+
dirLight.target.updateMatrixWorld();
|
|
1692
|
+
}, [fit]);
|
|
1693
|
+
if (!light.visible || light.type === "none") return null;
|
|
1663
1694
|
if (light.type === "directional") {
|
|
1664
1695
|
return /* @__PURE__ */ jsx7(
|
|
1665
1696
|
"directionalLight",
|
|
@@ -1667,19 +1698,19 @@ function LightInstance({
|
|
|
1667
1698
|
ref: dirRef,
|
|
1668
1699
|
color: light.color,
|
|
1669
1700
|
intensity: light.intensity,
|
|
1670
|
-
position,
|
|
1701
|
+
position: fit?.position ?? position,
|
|
1671
1702
|
castShadow,
|
|
1672
1703
|
"shadow-mapSize-width": shadows.mapSize,
|
|
1673
1704
|
"shadow-mapSize-height": shadows.mapSize,
|
|
1674
1705
|
"shadow-bias": light.shadowBias,
|
|
1675
1706
|
"shadow-normalBias": shadows.normalBias,
|
|
1676
1707
|
"shadow-radius": shadows.radius,
|
|
1677
|
-
"shadow-camera-near": 0.5,
|
|
1678
|
-
"shadow-camera-far": 40,
|
|
1679
|
-
"shadow-camera-left": -8,
|
|
1680
|
-
"shadow-camera-right": 8,
|
|
1681
|
-
"shadow-camera-top": 8,
|
|
1682
|
-
"shadow-camera-bottom": -8
|
|
1708
|
+
"shadow-camera-near": fit?.near ?? 0.5,
|
|
1709
|
+
"shadow-camera-far": fit?.far ?? 40,
|
|
1710
|
+
"shadow-camera-left": -(fit?.extent ?? 8),
|
|
1711
|
+
"shadow-camera-right": fit?.extent ?? 8,
|
|
1712
|
+
"shadow-camera-top": fit?.extent ?? 8,
|
|
1713
|
+
"shadow-camera-bottom": -(fit?.extent ?? 8)
|
|
1683
1714
|
}
|
|
1684
1715
|
);
|
|
1685
1716
|
}
|
|
@@ -1715,7 +1746,8 @@ function Lights({
|
|
|
1715
1746
|
shadowConfig,
|
|
1716
1747
|
shadowsEnabled = true,
|
|
1717
1748
|
environmentRotation = 0,
|
|
1718
|
-
environmentConfig
|
|
1749
|
+
environmentConfig,
|
|
1750
|
+
modelBounds
|
|
1719
1751
|
}) {
|
|
1720
1752
|
const lighting = lightingConfig ?? DEFAULT_LIGHTING;
|
|
1721
1753
|
const shadows = shadowConfig ?? DEFAULT_SHADOWS;
|
|
@@ -1735,35 +1767,36 @@ function Lights({
|
|
|
1735
1767
|
light,
|
|
1736
1768
|
shadows,
|
|
1737
1769
|
shadowsEnabled,
|
|
1738
|
-
environmentRotation
|
|
1770
|
+
environmentRotation,
|
|
1771
|
+
modelBounds
|
|
1739
1772
|
},
|
|
1740
1773
|
light.id
|
|
1741
1774
|
)),
|
|
1742
1775
|
sl?.enabled && /* @__PURE__ */ jsx7(
|
|
1743
|
-
|
|
1776
|
+
LightInstance,
|
|
1744
1777
|
{
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1778
|
+
light: {
|
|
1779
|
+
id: "light-env-shadow",
|
|
1780
|
+
type: "directional",
|
|
1781
|
+
color: "#ffffff",
|
|
1782
|
+
intensity: sl.intensity,
|
|
1783
|
+
position: envShadowPosition,
|
|
1784
|
+
attachedToCamera: false,
|
|
1785
|
+
castShadow: true,
|
|
1786
|
+
shadowBias: sl.bias,
|
|
1787
|
+
visible: true
|
|
1788
|
+
},
|
|
1789
|
+
shadows,
|
|
1790
|
+
shadowsEnabled,
|
|
1791
|
+
environmentRotation: 0,
|
|
1792
|
+
modelBounds
|
|
1760
1793
|
}
|
|
1761
1794
|
)
|
|
1762
1795
|
] });
|
|
1763
1796
|
}
|
|
1764
1797
|
|
|
1765
1798
|
// src/components/Model.tsx
|
|
1766
|
-
import { useState as useState3, useEffect as
|
|
1799
|
+
import { useState as useState3, useEffect as useEffect7, useRef as useRef5, useMemo as useMemo7 } from "react";
|
|
1767
1800
|
import { useFrame as useFrame4, useThree as useThree7 } from "@react-three/fiber";
|
|
1768
1801
|
|
|
1769
1802
|
// src/utils/gltfLoader.ts
|
|
@@ -2479,6 +2512,72 @@ function refreshSkinnedBounds(root) {
|
|
|
2479
2512
|
});
|
|
2480
2513
|
}
|
|
2481
2514
|
|
|
2515
|
+
// src/utils/bvh.ts
|
|
2516
|
+
import { Mesh as Mesh4 } from "three";
|
|
2517
|
+
import {
|
|
2518
|
+
acceleratedRaycast,
|
|
2519
|
+
computeBoundsTree,
|
|
2520
|
+
disposeBoundsTree
|
|
2521
|
+
} from "three-mesh-bvh";
|
|
2522
|
+
var raycastPatched = false;
|
|
2523
|
+
function enableAcceleratedRaycast() {
|
|
2524
|
+
if (raycastPatched) return;
|
|
2525
|
+
Mesh4.prototype.raycast = acceleratedRaycast;
|
|
2526
|
+
raycastPatched = true;
|
|
2527
|
+
}
|
|
2528
|
+
function collectGeometries(root) {
|
|
2529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2530
|
+
root.traverse((node) => {
|
|
2531
|
+
const geometry = node.geometry;
|
|
2532
|
+
if (!geometry || !geometry.attributes?.position) return;
|
|
2533
|
+
if (geometry.boundsTree) return;
|
|
2534
|
+
seen.add(geometry);
|
|
2535
|
+
});
|
|
2536
|
+
return [...seen];
|
|
2537
|
+
}
|
|
2538
|
+
async function buildSceneBVH(root, signal) {
|
|
2539
|
+
const geometries = collectGeometries(root);
|
|
2540
|
+
if (geometries.length === 0) return;
|
|
2541
|
+
enableAcceleratedRaycast();
|
|
2542
|
+
let worker = null;
|
|
2543
|
+
if (typeof window !== "undefined" && typeof Worker !== "undefined") {
|
|
2544
|
+
try {
|
|
2545
|
+
const mod = await import("three-mesh-bvh/src/workers/ParallelMeshBVHWorker.js");
|
|
2546
|
+
worker = new mod.ParallelMeshBVHWorker();
|
|
2547
|
+
} catch {
|
|
2548
|
+
worker = null;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
try {
|
|
2552
|
+
for (const geometry of geometries) {
|
|
2553
|
+
if (signal?.aborted) return;
|
|
2554
|
+
let tree;
|
|
2555
|
+
if (worker) {
|
|
2556
|
+
try {
|
|
2557
|
+
tree = await worker.generate(geometry);
|
|
2558
|
+
} catch {
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (tree === void 0) {
|
|
2562
|
+
tree = computeBoundsTree.call(geometry);
|
|
2563
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2564
|
+
}
|
|
2565
|
+
if (signal?.aborted) return;
|
|
2566
|
+
geometry.boundsTree = tree;
|
|
2567
|
+
}
|
|
2568
|
+
} finally {
|
|
2569
|
+
worker?.dispose?.();
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function disposeSceneBVH(root) {
|
|
2573
|
+
root.traverse((node) => {
|
|
2574
|
+
const geometry = node.geometry;
|
|
2575
|
+
if (!geometry?.boundsTree) return;
|
|
2576
|
+
disposeBoundsTree.call(geometry);
|
|
2577
|
+
geometry.boundsTree = void 0;
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2482
2581
|
// src/components/Model.tsx
|
|
2483
2582
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2484
2583
|
function firstMaterial(node) {
|
|
@@ -2511,14 +2610,14 @@ function Model({
|
|
|
2511
2610
|
const invalidate = useThree7((state) => state.invalidate);
|
|
2512
2611
|
const [hoveredItem, setHoveredItem] = useState3(null);
|
|
2513
2612
|
const groupRef = useRef5(null);
|
|
2514
|
-
|
|
2613
|
+
useEffect7(() => {
|
|
2515
2614
|
if (!highlightOnHover) {
|
|
2516
2615
|
setHoveredItem(null);
|
|
2517
2616
|
onItemHover?.(null);
|
|
2518
2617
|
document.body.style.cursor = "default";
|
|
2519
2618
|
}
|
|
2520
2619
|
}, [highlightOnHover, onItemHover]);
|
|
2521
|
-
|
|
2620
|
+
useEffect7(() => {
|
|
2522
2621
|
return () => {
|
|
2523
2622
|
document.body.style.cursor = "default";
|
|
2524
2623
|
};
|
|
@@ -2554,7 +2653,7 @@ function Model({
|
|
|
2554
2653
|
}
|
|
2555
2654
|
return shell;
|
|
2556
2655
|
}, [nodeMap]);
|
|
2557
|
-
|
|
2656
|
+
useEffect7(() => {
|
|
2558
2657
|
scene.traverse((node) => {
|
|
2559
2658
|
if (!isMeshNode(node)) return;
|
|
2560
2659
|
const mesh = node;
|
|
@@ -2562,9 +2661,17 @@ function Model({
|
|
|
2562
2661
|
mesh.receiveShadow = true;
|
|
2563
2662
|
});
|
|
2564
2663
|
}, [scene, shellNodeNames]);
|
|
2565
|
-
|
|
2664
|
+
useEffect7(() => {
|
|
2566
2665
|
refreshSkinnedBounds(scene);
|
|
2567
2666
|
}, [scene]);
|
|
2667
|
+
useEffect7(() => {
|
|
2668
|
+
const controller = new AbortController();
|
|
2669
|
+
void buildSceneBVH(scene, controller.signal);
|
|
2670
|
+
return () => {
|
|
2671
|
+
controller.abort();
|
|
2672
|
+
disposeSceneBVH(scene);
|
|
2673
|
+
};
|
|
2674
|
+
}, [scene]);
|
|
2568
2675
|
const bindingKeyByNodeName = useMemo7(() => {
|
|
2569
2676
|
const map = {};
|
|
2570
2677
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -2593,10 +2700,10 @@ function Model({
|
|
|
2593
2700
|
}
|
|
2594
2701
|
return map;
|
|
2595
2702
|
}, [nodeMap, nodeBindingSignature]);
|
|
2596
|
-
|
|
2703
|
+
useEffect7(() => {
|
|
2597
2704
|
if (materialRefsOut) materialRefsOut.current = perNodeMaterials;
|
|
2598
2705
|
}, [perNodeMaterials, materialRefsOut]);
|
|
2599
|
-
|
|
2706
|
+
useEffect7(() => {
|
|
2600
2707
|
return () => {
|
|
2601
2708
|
for (const material of Object.values(perNodeMaterials)) {
|
|
2602
2709
|
material.dispose();
|
|
@@ -2647,7 +2754,7 @@ function Model({
|
|
|
2647
2754
|
}
|
|
2648
2755
|
return map;
|
|
2649
2756
|
}, [perNodeMaterials, shadingMode, rendererMode]);
|
|
2650
|
-
|
|
2757
|
+
useEffect7(() => {
|
|
2651
2758
|
return () => {
|
|
2652
2759
|
for (const material of Object.values(perNodeBasicMaterials))
|
|
2653
2760
|
material.dispose();
|
|
@@ -2663,10 +2770,10 @@ function Model({
|
|
|
2663
2770
|
if (rendererMode === "matcap") return perNodeMatcapMaterials[nodeName];
|
|
2664
2771
|
return perNodeMaterials[nodeName];
|
|
2665
2772
|
};
|
|
2666
|
-
|
|
2773
|
+
useEffect7(() => {
|
|
2667
2774
|
onModelLoaded?.(scene);
|
|
2668
2775
|
}, [scene, onModelLoaded]);
|
|
2669
|
-
|
|
2776
|
+
useEffect7(() => {
|
|
2670
2777
|
if (!nodeRefsOut) return;
|
|
2671
2778
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
2672
2779
|
const glbNodeName = binding.modelObjectId ?? nodeName;
|
|
@@ -2675,7 +2782,7 @@ function Model({
|
|
|
2675
2782
|
}
|
|
2676
2783
|
}, [nodeMap, objectBindings, nodeRefsOut]);
|
|
2677
2784
|
const appliedTextureUrls = useRef5({});
|
|
2678
|
-
|
|
2785
|
+
useEffect7(() => {
|
|
2679
2786
|
return () => {
|
|
2680
2787
|
for (const nodeTextures of Object.values(appliedTextureUrls.current)) {
|
|
2681
2788
|
for (const [key, appliedUrl] of Object.entries(nodeTextures)) {
|
|
@@ -2690,7 +2797,7 @@ function Model({
|
|
|
2690
2797
|
appliedTextureUrls.current = {};
|
|
2691
2798
|
};
|
|
2692
2799
|
}, []);
|
|
2693
|
-
|
|
2800
|
+
useEffect7(() => {
|
|
2694
2801
|
const seenNodeNames = /* @__PURE__ */ new Set();
|
|
2695
2802
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
2696
2803
|
seenNodeNames.add(nodeName);
|
|
@@ -2797,7 +2904,7 @@ function Model({
|
|
|
2797
2904
|
rendererMode,
|
|
2798
2905
|
invalidate
|
|
2799
2906
|
]);
|
|
2800
|
-
|
|
2907
|
+
useEffect7(() => {
|
|
2801
2908
|
if (!wireframeEnabled) return;
|
|
2802
2909
|
const added = [];
|
|
2803
2910
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -2931,7 +3038,7 @@ function Model({
|
|
|
2931
3038
|
emitPlaybackState();
|
|
2932
3039
|
invalidate();
|
|
2933
3040
|
};
|
|
2934
|
-
|
|
3041
|
+
useEffect7(() => {
|
|
2935
3042
|
const mixer = new AnimationMixer(scene);
|
|
2936
3043
|
mixerRef.current = mixer;
|
|
2937
3044
|
const actions = {};
|
|
@@ -2986,7 +3093,7 @@ function Model({
|
|
|
2986
3093
|
emitPlaybackState();
|
|
2987
3094
|
};
|
|
2988
3095
|
}, [scene, resolvedAnimationClips]);
|
|
2989
|
-
|
|
3096
|
+
useEffect7(() => {
|
|
2990
3097
|
const currentClip = activeClipNameRef.current;
|
|
2991
3098
|
const activeAction = activeActionRef.current;
|
|
2992
3099
|
if (!currentClip || !activeAction) return;
|
|
@@ -3045,7 +3152,7 @@ function Model({
|
|
|
3045
3152
|
const explodeStrengthRef = useRef5(0);
|
|
3046
3153
|
const currentExplodeRef = useRef5(0);
|
|
3047
3154
|
const explodeTmp = useMemo7(() => new Vector32(), []);
|
|
3048
|
-
|
|
3155
|
+
useEffect7(() => {
|
|
3049
3156
|
for (const part of explodePartsRef.current) {
|
|
3050
3157
|
part.node.position.copy(part.restLocalPos);
|
|
3051
3158
|
}
|
|
@@ -3094,7 +3201,7 @@ function Model({
|
|
|
3094
3201
|
explodePartsRef.current = parts;
|
|
3095
3202
|
invalidate();
|
|
3096
3203
|
}, [scene, nodeMap, nodeBindingSignature, invalidate]);
|
|
3097
|
-
|
|
3204
|
+
useEffect7(() => {
|
|
3098
3205
|
invalidate();
|
|
3099
3206
|
}, [explodeFactor, invalidate]);
|
|
3100
3207
|
useFrame4((_, delta) => {
|
|
@@ -3172,7 +3279,7 @@ import {
|
|
|
3172
3279
|
useMemo as useMemo8,
|
|
3173
3280
|
useRef as useRef6,
|
|
3174
3281
|
useState as useState4,
|
|
3175
|
-
useEffect as
|
|
3282
|
+
useEffect as useEffect8,
|
|
3176
3283
|
useCallback
|
|
3177
3284
|
} from "react";
|
|
3178
3285
|
import { Html } from "@react-three/drei";
|
|
@@ -3202,7 +3309,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3202
3309
|
const worldPosition = useMemo8(() => new Vector33(), []);
|
|
3203
3310
|
const biasedWorldPosition = useMemo8(() => new Vector33(), []);
|
|
3204
3311
|
const toCamera = useMemo8(() => new Vector33(), []);
|
|
3205
|
-
|
|
3312
|
+
useEffect8(() => {
|
|
3206
3313
|
anchorNodeRef.current = null;
|
|
3207
3314
|
}, [annotation.anchorNodeName]);
|
|
3208
3315
|
useFrame5(({ camera }) => {
|
|
@@ -3248,7 +3355,7 @@ function AnnotationMarker3D({ annotation, occluders, worldPositionsRef, onClick,
|
|
|
3248
3355
|
const [zIndex, setZIndex] = useState4(1);
|
|
3249
3356
|
const clickTimeoutRef = useRef6(null);
|
|
3250
3357
|
const markerRef = useRef6(null);
|
|
3251
|
-
|
|
3358
|
+
useEffect8(() => {
|
|
3252
3359
|
return () => {
|
|
3253
3360
|
if (clickTimeoutRef.current) {
|
|
3254
3361
|
clearTimeout(clickTimeoutRef.current);
|
|
@@ -3430,13 +3537,13 @@ function AnnotationsLayer({
|
|
|
3430
3537
|
};
|
|
3431
3538
|
invalidateFrameRef.current = window.requestAnimationFrame(step);
|
|
3432
3539
|
}, [invalidate]);
|
|
3433
|
-
|
|
3540
|
+
useEffect8(() => {
|
|
3434
3541
|
requestAnnotationRefresh();
|
|
3435
3542
|
}, [annotations, requestAnnotationRefresh]);
|
|
3436
|
-
|
|
3543
|
+
useEffect8(() => {
|
|
3437
3544
|
requestAnnotationRefresh();
|
|
3438
3545
|
}, [activeAnnotation, requestAnnotationRefresh]);
|
|
3439
|
-
|
|
3546
|
+
useEffect8(() => {
|
|
3440
3547
|
return () => {
|
|
3441
3548
|
if (invalidateFrameRef.current !== null) {
|
|
3442
3549
|
window.cancelAnimationFrame(invalidateFrameRef.current);
|
|
@@ -3499,7 +3606,7 @@ function AnnotationsLayer({
|
|
|
3499
3606
|
onCloseAnnotation?.();
|
|
3500
3607
|
}
|
|
3501
3608
|
}, [isLocked, onCloseAnnotation]);
|
|
3502
|
-
|
|
3609
|
+
useEffect8(() => {
|
|
3503
3610
|
const canvas = canvasRootRef.current;
|
|
3504
3611
|
if (canvas) {
|
|
3505
3612
|
canvas.addEventListener("click", handleCanvasClick);
|
|
@@ -3568,7 +3675,7 @@ function AnnotationsLayer({
|
|
|
3568
3675
|
}
|
|
3569
3676
|
|
|
3570
3677
|
// src/components/ModelViewer/MeasurementLayer.tsx
|
|
3571
|
-
import { useEffect as
|
|
3678
|
+
import { useEffect as useEffect9, useMemo as useMemo9 } from "react";
|
|
3572
3679
|
import { Html as Html2, Line } from "@react-three/drei";
|
|
3573
3680
|
import { BoxGeometry, EdgesGeometry, Vector3 as Vector34 } from "three";
|
|
3574
3681
|
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -3603,7 +3710,7 @@ function DimensionsBox({ box, unit }) {
|
|
|
3603
3710
|
);
|
|
3604
3711
|
return geometry;
|
|
3605
3712
|
}, [box.size]);
|
|
3606
|
-
|
|
3713
|
+
useEffect9(() => {
|
|
3607
3714
|
return () => {
|
|
3608
3715
|
edges.dispose();
|
|
3609
3716
|
};
|
|
@@ -3659,7 +3766,7 @@ function MeasurementLayer({
|
|
|
3659
3766
|
import {
|
|
3660
3767
|
forwardRef as forwardRef2,
|
|
3661
3768
|
useCallback as useCallback2,
|
|
3662
|
-
useEffect as
|
|
3769
|
+
useEffect as useEffect10,
|
|
3663
3770
|
useImperativeHandle as useImperativeHandle2,
|
|
3664
3771
|
useMemo as useMemo10,
|
|
3665
3772
|
useRef as useRef7
|
|
@@ -3767,7 +3874,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3767
3874
|
}),
|
|
3768
3875
|
[cancelPlacementMode, finishPlacementMode, placeCurrent, startPlacementMode]
|
|
3769
3876
|
);
|
|
3770
|
-
|
|
3877
|
+
useEffect10(() => {
|
|
3771
3878
|
if (!inSession) {
|
|
3772
3879
|
placedPosition.current.set(0, 0, -FORWARD_DISTANCE);
|
|
3773
3880
|
userScale.current = 1;
|
|
@@ -3815,7 +3922,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3815
3922
|
},
|
|
3816
3923
|
[isAR, placeCurrent]
|
|
3817
3924
|
);
|
|
3818
|
-
|
|
3925
|
+
useEffect10(() => {
|
|
3819
3926
|
if (!isAR) return;
|
|
3820
3927
|
const onTouchStart = (event) => {
|
|
3821
3928
|
if (event.touches.length !== 2) return;
|
|
@@ -3900,7 +4007,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3900
4007
|
var XRModelPresenter_default = XRModelPresenter;
|
|
3901
4008
|
|
|
3902
4009
|
// src/components/ModelViewer/CinematicCamera.tsx
|
|
3903
|
-
import { useEffect as
|
|
4010
|
+
import { useEffect as useEffect11, useMemo as useMemo11, useRef as useRef8 } from "react";
|
|
3904
4011
|
import { useFrame as useFrame7, useThree as useThree9 } from "@react-three/fiber";
|
|
3905
4012
|
import {
|
|
3906
4013
|
Box3 as Box32,
|
|
@@ -3951,7 +4058,7 @@ function CinematicCamera({
|
|
|
3951
4058
|
const orbitRef = useRef8(null);
|
|
3952
4059
|
const tmpPos = useMemo11(() => new Vector36(), []);
|
|
3953
4060
|
const tmpTarget = useMemo11(() => new Vector36(), []);
|
|
3954
|
-
|
|
4061
|
+
useEffect11(() => {
|
|
3955
4062
|
if (!playing) {
|
|
3956
4063
|
startedRef.current = false;
|
|
3957
4064
|
return;
|
|
@@ -3976,7 +4083,7 @@ function CinematicCamera({
|
|
|
3976
4083
|
startedRef.current = true;
|
|
3977
4084
|
invalidate();
|
|
3978
4085
|
}, [playing, hasPath, controlsRef, sceneRef, invalidate]);
|
|
3979
|
-
|
|
4086
|
+
useEffect11(() => {
|
|
3980
4087
|
if (!playing || !onUserInteract) return;
|
|
3981
4088
|
const controls = controlsRef.current;
|
|
3982
4089
|
if (!controls) return;
|
|
@@ -4034,7 +4141,7 @@ function CinematicCamera({
|
|
|
4034
4141
|
}
|
|
4035
4142
|
|
|
4036
4143
|
// src/hooks/useLicense.ts
|
|
4037
|
-
import { useEffect as
|
|
4144
|
+
import { useEffect as useEffect12, useState as useState5 } from "react";
|
|
4038
4145
|
var LOCAL_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
4039
4146
|
var isLocalHostname = (hostname) => LOCAL_HOSTNAMES.includes(hostname) || hostname.endsWith(".local") || hostname.endsWith(".localhost");
|
|
4040
4147
|
var LICENSE_SERVER = "https://react-immersive.liveroom.dev/api/validate-license";
|
|
@@ -4209,7 +4316,7 @@ var useLicense = (licenseKey) => {
|
|
|
4209
4316
|
// window isn't available during SSR — useEffect below recomputes this on mount
|
|
4210
4317
|
isLocalDomain: false
|
|
4211
4318
|
});
|
|
4212
|
-
|
|
4319
|
+
useEffect12(() => {
|
|
4213
4320
|
if (!canUseBrowserAPI()) {
|
|
4214
4321
|
return;
|
|
4215
4322
|
}
|
|
@@ -4260,10 +4367,10 @@ var useLicense = (licenseKey) => {
|
|
|
4260
4367
|
};
|
|
4261
4368
|
|
|
4262
4369
|
// src/components/MouseController.tsx
|
|
4263
|
-
import { useRef as useRef9, useState as useState6, useCallback as useCallback3, useEffect as
|
|
4370
|
+
import { useRef as useRef9, useState as useState6, useCallback as useCallback3, useEffect as useEffect13 } from "react";
|
|
4264
4371
|
|
|
4265
4372
|
// src/styles/MouseController.css
|
|
4266
|
-
styleInject(".mc__wrapper {\n position:
|
|
4373
|
+
styleInject(".mc__wrapper {\n position: absolute;\n bottom: -10px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: column;\n align-items: center;\n z-index: 100;\n}\n.mc__dpad {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n margin-bottom: 20px;\n}\n.mc__middle-row {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n.mc__joystick {\n width: 60px;\n height: 60px;\n flex-shrink: 0;\n background:\n radial-gradient(\n circle at 40% 35%,\n rgba(255, 255, 255, 0.07),\n rgba(0, 0, 0, 0.55));\n border-radius: 50%;\n border: 1.5px solid rgba(255, 255, 255, 0.15);\n position: relative;\n cursor: grab;\n touch-action: none;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n margin: 4px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), 0 0 0 4px rgba(255, 255, 255, 0.03);\n}\n.mc__joystick:hover {\n border-color: rgba(255, 255, 255, 0.28);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.55), 0 0 0 4px rgba(66, 133, 244, 0.08);\n}\n.mc__joystick:active {\n cursor: grabbing;\n border-color: rgba(66, 133, 244, 0.6);\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5), 0 0 4px rgba(66, 133, 244, 0.15);\n}\n.mc__knob {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 34px;\n height: 34px;\n margin-left: -17px;\n margin-top: -17px;\n background:\n linear-gradient(\n 145deg,\n #5a9bf5,\n #1a66d9);\n border-radius: 50%;\n cursor: grab;\n box-shadow: 0 3px 10px rgba(26, 102, 217, 0.5), 0 1px 0 rgba(255, 255, 255, 0.25) inset;\n transition: transform 0.04s linear, box-shadow 0.1s ease;\n border: 1.5px solid rgba(255, 255, 255, 0.45);\n}\n.mc__joystick:active .mc__knob {\n box-shadow: 0 2px 16px rgba(66, 133, 244, 0.7), 0 1px 0 rgba(255, 255, 255, 0.25) inset;\n animation: pulse 0.6s ease infinite;\n}\n.mc__arrow {\n padding: 0;\n margin: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n outline: none;\n -webkit-appearance: none;\n appearance: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n transition: transform 0.1s ease, filter 0.15s ease;\n overflow: visible;\n}\n.mc__arrow svg {\n display: block !important;\n pointer-events: none !important;\n user-select: none !important;\n -webkit-user-select: none !important;\n overflow: visible !important;\n}\n.mc__arrow--up {\n margin-bottom: 2px;\n}\n.mc__arrow--up:hover {\n transform: scale(1.15) translateY(-2px);\n filter: drop-shadow(0 4px 12px rgba(66, 133, 244, 0.8));\n}\n.mc__arrow--up:active {\n transform: scale(0.92) translateY(2px);\n}\n.mc__arrow--down {\n margin-top: 2px;\n}\n.mc__arrow--down:hover {\n transform: scale(1.15) translateY(2px);\n filter: drop-shadow(0 -4px 12px rgba(66, 133, 244, 0.8));\n}\n.mc__arrow--down:active {\n transform: scale(0.92) translateY(-2px);\n}\n.mc__arrow--left {\n margin-right: 2px;\n}\n.mc__arrow--left:hover {\n transform: scale(1.15) translateX(-2px);\n filter: drop-shadow(4px 0 12px rgba(66, 133, 244, 0.8));\n}\n.mc__arrow--left:active {\n transform: scale(0.92) translateX(2px);\n}\n.mc__arrow--right {\n margin-left: 2px;\n}\n.mc__arrow--right:hover {\n transform: scale(1.15) translateX(2px);\n filter: drop-shadow(-4px 0 12px rgba(66, 133, 244, 0.8));\n}\n.mc__arrow--right:active {\n transform: scale(0.92) translateX(-2px);\n}\n@keyframes pulse {\n 0%, 100% {\n box-shadow: 0 2px 16px rgba(66, 133, 244, 0.6), 0 1px 0 rgba(255, 255, 255, 0.25) inset;\n opacity: 0.85;\n }\n 50% {\n box-shadow: 0 4px 24px rgba(66, 133, 244, 0.95), 0 1px 0 rgba(255, 255, 255, 0.25) inset;\n opacity: 1;\n }\n}\n@media (max-width: 768px) {\n .mc__wrapper {\n bottom: -5px;\n }\n .mc__joystick {\n width: 68px;\n height: 68px;\n }\n .mc__knob {\n width: 30px;\n height: 30px;\n margin-left: -15px;\n margin-top: -15px;\n }\n .mc__arrow--up:hover {\n transform: scale(1.1) translateY(-1px);\n }\n .mc__arrow--down:hover {\n transform: scale(1.1) translateY(1px);\n }\n .mc__arrow--left:hover {\n transform: scale(1.1) translateX(-1px);\n }\n .mc__arrow--right:hover {\n transform: scale(1.1) translateX(1px);\n }\n}\n@media (max-width: 480px) {\n .mc__joystick {\n width: 56px;\n height: 56px;\n }\n .mc__knob {\n width: 26px;\n height: 26px;\n margin-left: -13px;\n margin-top: -13px;\n }\n .mc__arrow--up {\n margin-bottom: 1px;\n }\n .mc__arrow--down {\n margin-top: 1px;\n }\n .mc__arrow--left {\n margin-right: 1px;\n }\n .mc__arrow--right {\n margin-left: 1px;\n }\n}\n@media (hover: none) {\n .mc__arrow:hover {\n transform: none !important;\n filter: none !important;\n }\n .mc__arrow:active {\n transform: scale(0.92) !important;\n }\n}\n");
|
|
4267
4374
|
|
|
4268
4375
|
// src/components/MouseController.tsx
|
|
4269
4376
|
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
@@ -4464,7 +4571,7 @@ var MouseController = ({
|
|
|
4464
4571
|
lastRepeatTimeRef.current = 0;
|
|
4465
4572
|
}
|
|
4466
4573
|
}, []);
|
|
4467
|
-
|
|
4574
|
+
useEffect13(() => {
|
|
4468
4575
|
window.addEventListener("mousemove", onJoystickMove);
|
|
4469
4576
|
window.addEventListener("mouseup", onJoystickEnd);
|
|
4470
4577
|
window.addEventListener("touchmove", onJoystickMove, { passive: false });
|
|
@@ -4695,7 +4802,7 @@ var MaterialPicker = ({
|
|
|
4695
4802
|
};
|
|
4696
4803
|
|
|
4697
4804
|
// src/components/ModelViewer/ObjectBindingDataPanel.tsx
|
|
4698
|
-
import { Fragment as Fragment5, useEffect as
|
|
4805
|
+
import { Fragment as Fragment5, useEffect as useEffect14, useRef as useRef10, useState as useState7 } from "react";
|
|
4699
4806
|
|
|
4700
4807
|
// src/styles/ObjectBindingDataPanel.css
|
|
4701
4808
|
styleInject('.left-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-databinding-bg, #111318);\n border-right: 1px solid var(--ri-databinding-border, rgba(255, 255, 255, 0.06));\n display: flex;\n flex-direction: column;\n padding: 24px 18px;\n overflow-y: auto;\n font-family: var( --ri-databinding-font-family, "Inter", "Segoe UI", sans-serif );\n flex-shrink: 0;\n transition: margin-left 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--ri-databinding-shadow, 4px 0 24px rgba(0, 0, 0, 0.35));\n}\n.left-panel--open {\n margin-left: 0;\n}\n.left-panel--closed {\n margin-left: -280px;\n}\n.left-panel::-webkit-scrollbar {\n width: 4px;\n}\n.left-panel::-webkit-scrollbar-track {\n background: transparent;\n}\n.left-panel::-webkit-scrollbar-thumb {\n background: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n border-radius: 4px;\n}\n.left-panel__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n padding-bottom: 14px;\n border-bottom: 1px solid var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.left-panel__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--ri-databinding-text, #f1f5f9);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n padding-right: 8px;\n margin: 0;\n letter-spacing: 0.02em;\n}\n.left-panel__close-btn {\n width: 26px;\n height: 26px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border: 1px solid var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n border-radius: 6px;\n color: var(--ri-databinding-text-subtle, #64748b);\n font-size: 13px;\n flex-shrink: 0;\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.left-panel__close-btn:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n color: var(--ri-databinding-text-hover, #cbd5e1);\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n}\n.left-panel__data {\n padding: 12px 6px;\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border: 1px solid var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n border-radius: 10px;\n font-size: 11px;\n font-family: var( --ri-databinding-mono-font-family, "JetBrains Mono", "Fira Code", "Menlo", monospace );\n color: var(--ri-databinding-text-muted, #94a3b8);\n line-height: 1.7;\n overflow-x: auto;\n margin-bottom: 18px;\n}\n.left-panel__divider {\n border: none;\n border-top: 1px solid var(--ri-databinding-border, rgba(255, 255, 255, 0.06));\n margin-bottom: 16px;\n}\n.left-panel__actions-label {\n font-size: 10px;\n font-weight: 600;\n color: var(--ri-databinding-text-faint, #475569);\n text-transform: uppercase;\n letter-spacing: 0.12em;\n margin-bottom: 10px;\n}\n.left-panel__actions-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.left-panel__action-extra {\n display: grid;\n grid-template-rows: 0fr;\n opacity: 0;\n margin-top: 0;\n transition:\n grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.2s ease,\n margin-top 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n}\n.left-panel__action-extra--open {\n grid-template-rows: 1fr;\n opacity: 1;\n margin-top: 4px;\n}\n.left-panel__action-extra-inner {\n overflow: hidden;\n min-height: 0;\n}\n.left-panel__action-btn {\n position: relative;\n padding: 9px 14px;\n border-radius: 8px;\n text-align: left;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n overflow: hidden;\n border: 1px solid var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n color: var(--ri-databinding-text-muted, #94a3b8);\n letter-spacing: 0.01em;\n}\n.left-panel__action-btn:active {\n transform: scale(0.97);\n}\n.left-panel__action-btn--active::before {\n content: "";\n position: absolute;\n left: 0;\n top: 20%;\n bottom: 20%;\n width: 3px;\n background: var(--ri-databinding-accent, #93c5fd);\n border-radius: 0 3px 3px 0;\n box-shadow: 0 0 8px var(--ri-databinding-accent-glow, rgba(147, 197, 253, 0.8));\n}\n.left-panel__action-btn--active:hover {\n background: var( --ri-databinding-accent-gradient, linear-gradient(135deg, #1e3a8a 0%, #1d4ed8 60%, #2563eb 100%) );\n border-color: var(--ri-databinding-accent-border, rgba(96, 165, 250, 0.55));\n}\n.left-panel__action-btn--active:active {\n transform: scale(0.97);\n background: var( --ri-databinding-accent-gradient-active, linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%) );\n}\n.left-panel__data {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin-bottom: 18px;\n}\n.left-panel__data-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 10px;\n padding: 7px 10px;\n border-radius: 7px;\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border: 1px solid transparent;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.left-panel__data-row:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.left-panel__data-key {\n font-size: 11px;\n font-weight: 600;\n color: var(--ri-databinding-text-faint, #475569);\n text-transform: uppercase;\n letter-spacing: 0.07em;\n white-space: nowrap;\n flex-shrink: 0;\n}\n.left-panel__data-value {\n font-size: 12px;\n font-family: var( --ri-databinding-mono-font-family, "JetBrains Mono", "Fira Code", "Menlo", monospace );\n text-align: right;\n overflow-wrap: anywhere;\n word-break: break-word;\n min-width: 0;\n}\n.left-panel__data-group {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 7px 10px;\n border-radius: 7px;\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border: 1px solid transparent;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.left-panel__data-group:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.left-panel__data-children {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin-top: 4px;\n padding-left: 10px;\n border-left: 1px solid var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.left-panel__data-children .left-panel__data-row,\n.left-panel__data-children .left-panel__data-group {\n padding: 3px 0;\n background: transparent;\n border-color: transparent;\n}\n.left-panel__data-children .left-panel__data-row:hover,\n.left-panel__data-children .left-panel__data-group:hover {\n background: transparent;\n}\n@media (max-width: 900px) {\n .left-panel {\n width: 50vw;\n }\n .left-panel--open {\n margin-left: 0;\n }\n .left-panel--closed {\n margin-left: -50vw;\n }\n .left-panel {\n padding: 18px 12px;\n }\n .left-panel__data-row {\n flex-direction: column;\n align-items: stretch;\n gap: 2px;\n padding: 6px 8px;\n }\n .left-panel__data-group {\n padding: 6px 8px;\n }\n .left-panel__data-value {\n text-align: left;\n font-size: 11.5px;\n }\n .left-panel__data-children {\n padding-left: 8px;\n }\n}\n');
|
|
@@ -4730,7 +4837,7 @@ function ActionExtra({ content }) {
|
|
|
4730
4837
|
const [expanded, setExpanded] = useState7(hasContent);
|
|
4731
4838
|
const cachedRef = useRef10(content);
|
|
4732
4839
|
if (hasContent) cachedRef.current = content;
|
|
4733
|
-
|
|
4840
|
+
useEffect14(() => {
|
|
4734
4841
|
if (hasContent) {
|
|
4735
4842
|
setMounted(true);
|
|
4736
4843
|
return;
|
|
@@ -4741,7 +4848,7 @@ function ActionExtra({ content }) {
|
|
|
4741
4848
|
return () => clearTimeout(timer);
|
|
4742
4849
|
}
|
|
4743
4850
|
}, [hasContent, mounted]);
|
|
4744
|
-
|
|
4851
|
+
useEffect14(() => {
|
|
4745
4852
|
if (mounted && hasContent) {
|
|
4746
4853
|
const frame = requestAnimationFrame(() => setExpanded(true));
|
|
4747
4854
|
return () => cancelAnimationFrame(frame);
|
|
@@ -4765,7 +4872,7 @@ function ObjectBindingDataPanel({
|
|
|
4765
4872
|
renderActionExtra
|
|
4766
4873
|
}) {
|
|
4767
4874
|
const [displayedObject, setDisplayedObject] = useState7(selectedObject);
|
|
4768
|
-
|
|
4875
|
+
useEffect14(() => {
|
|
4769
4876
|
if (selectedObject) setDisplayedObject(selectedObject);
|
|
4770
4877
|
}, [selectedObject]);
|
|
4771
4878
|
const objectToRender = selectedObject ?? displayedObject;
|
|
@@ -5170,7 +5277,7 @@ function GuidedTourCameraFollow({
|
|
|
5170
5277
|
const goal = useMemo13(() => new Vector37(), []);
|
|
5171
5278
|
const nodeName = annotation?.anchorNodeName;
|
|
5172
5279
|
const localPosition = annotation?.anchorLocalPosition;
|
|
5173
|
-
|
|
5280
|
+
useEffect15(() => {
|
|
5174
5281
|
nodeRef.current = null;
|
|
5175
5282
|
hasPrev.current = false;
|
|
5176
5283
|
}, [nodeName, annotation?.id]);
|
|
@@ -5215,6 +5322,14 @@ function computeMeasureBox(scene) {
|
|
|
5215
5322
|
size: [size.x, size.y, size.z]
|
|
5216
5323
|
};
|
|
5217
5324
|
}
|
|
5325
|
+
function computeModelBounds(scene) {
|
|
5326
|
+
const box = new Box33().setFromObject(scene);
|
|
5327
|
+
if (box.isEmpty()) return null;
|
|
5328
|
+
const center = box.getCenter(new Vector37());
|
|
5329
|
+
const radius = box.getSize(new Vector37()).length() / 2;
|
|
5330
|
+
if (!Number.isFinite(radius) || radius <= 0) return null;
|
|
5331
|
+
return { center: [center.x, center.y, center.z], radius };
|
|
5332
|
+
}
|
|
5218
5333
|
function fitClippingPlanesToScene(scene, controls) {
|
|
5219
5334
|
const camera = controls?.camera;
|
|
5220
5335
|
if (!(camera instanceof PerspectiveCamera3)) return;
|
|
@@ -5315,7 +5430,7 @@ function ModelViewer({
|
|
|
5315
5430
|
[performanceProfile]
|
|
5316
5431
|
);
|
|
5317
5432
|
const controlsRef = useRef11(null);
|
|
5318
|
-
|
|
5433
|
+
useEffect15(() => {
|
|
5319
5434
|
if (!controlsRef.current) return;
|
|
5320
5435
|
controlsRef.current.dollySpeed = Math.max(zoomSensitivity, 0);
|
|
5321
5436
|
}, [zoomSensitivity]);
|
|
@@ -5325,7 +5440,7 @@ function ModelViewer({
|
|
|
5325
5440
|
const pendingObjectId = useRef11(null);
|
|
5326
5441
|
const rendererCaptureRef = useRef11(null);
|
|
5327
5442
|
const createdBlobUrls = useRef11({});
|
|
5328
|
-
|
|
5443
|
+
useEffect15(() => {
|
|
5329
5444
|
return () => {
|
|
5330
5445
|
for (const url of Object.values(createdBlobUrls.current)) {
|
|
5331
5446
|
URL.revokeObjectURL(url);
|
|
@@ -5384,13 +5499,14 @@ function ModelViewer({
|
|
|
5384
5499
|
const [measurePoints, setMeasurePoints] = useState10([]);
|
|
5385
5500
|
const [showDimensions, setShowDimensions] = useState10(false);
|
|
5386
5501
|
const [measureBox, setMeasureBox] = useState10(null);
|
|
5502
|
+
const [modelBounds, setModelBounds] = useState10(null);
|
|
5387
5503
|
const measureModeRef = useRef11(false);
|
|
5388
5504
|
measureModeRef.current = measureMode;
|
|
5389
5505
|
const measurePointCounter = useRef11(0);
|
|
5390
5506
|
const measureDownRef = useRef11(null);
|
|
5391
5507
|
const [explodeFactor, setExplodeFactor] = useState10(0);
|
|
5392
5508
|
const [explodeOpen, setExplodeOpen] = useState10(false);
|
|
5393
|
-
|
|
5509
|
+
useEffect15(() => {
|
|
5394
5510
|
setExplodeFactor(0);
|
|
5395
5511
|
setExplodeOpen(false);
|
|
5396
5512
|
}, [modelUrl]);
|
|
@@ -5411,11 +5527,11 @@ function ModelViewer({
|
|
|
5411
5527
|
}, [cinematic, resolvedSceneConfig]);
|
|
5412
5528
|
const [cinematicPlaying, setCinematicPlaying] = useState10(false);
|
|
5413
5529
|
const cinematicAutoStartedRef = useRef11(false);
|
|
5414
|
-
|
|
5530
|
+
useEffect15(() => {
|
|
5415
5531
|
setCinematicPlaying(false);
|
|
5416
5532
|
cinematicAutoStartedRef.current = false;
|
|
5417
5533
|
}, [modelUrl]);
|
|
5418
|
-
|
|
5534
|
+
useEffect15(() => {
|
|
5419
5535
|
if (!cinematicConfig?.autoPlay || cinematicAutoStartedRef.current) return;
|
|
5420
5536
|
if (modelLoaded && cameraPositioned && !isAutoFraming) {
|
|
5421
5537
|
cinematicAutoStartedRef.current = true;
|
|
@@ -5443,7 +5559,7 @@ function ModelViewer({
|
|
|
5443
5559
|
const [isHandheld, setIsHandheld] = useState10(true);
|
|
5444
5560
|
const [resolvedMobileHandoffUrl, setResolvedMobileHandoffUrl] = useState10(null);
|
|
5445
5561
|
const [isMobileHandoffOpen, setIsMobileHandoffOpen] = useState10(false);
|
|
5446
|
-
|
|
5562
|
+
useEffect15(() => {
|
|
5447
5563
|
setIsHandheld(isHandheldDevice());
|
|
5448
5564
|
}, []);
|
|
5449
5565
|
const resolveMobileHandoffUrl = useCallback4(() => {
|
|
@@ -5458,7 +5574,7 @@ function ModelViewer({
|
|
|
5458
5574
|
return null;
|
|
5459
5575
|
}
|
|
5460
5576
|
}, [mobileHandoffUrl]);
|
|
5461
|
-
|
|
5577
|
+
useEffect15(() => {
|
|
5462
5578
|
if (!enableXR || isHandheld) {
|
|
5463
5579
|
setResolvedMobileHandoffUrl(null);
|
|
5464
5580
|
setIsMobileHandoffOpen(false);
|
|
@@ -5477,17 +5593,17 @@ function ModelViewer({
|
|
|
5477
5593
|
DEFAULT_AR_OVERLAY_STATE
|
|
5478
5594
|
);
|
|
5479
5595
|
const [relocatePressed, setRelocatePressed] = useState10(false);
|
|
5480
|
-
|
|
5596
|
+
useEffect15(() => {
|
|
5481
5597
|
if (!xrStore) return;
|
|
5482
5598
|
return xrStore.subscribe(
|
|
5483
5599
|
(state) => setInARSession(state.session != null && state.mode === "immersive-ar")
|
|
5484
5600
|
);
|
|
5485
5601
|
}, [xrStore]);
|
|
5486
|
-
|
|
5602
|
+
useEffect15(() => {
|
|
5487
5603
|
setRelocatePressed(false);
|
|
5488
5604
|
setArOverlayState(DEFAULT_AR_OVERLAY_STATE);
|
|
5489
5605
|
}, [inARSession]);
|
|
5490
|
-
|
|
5606
|
+
useEffect15(() => {
|
|
5491
5607
|
if (!enableXR) return;
|
|
5492
5608
|
const xr = navigator.xr;
|
|
5493
5609
|
if (!xr?.isSessionSupported) return;
|
|
@@ -5522,7 +5638,7 @@ function ModelViewer({
|
|
|
5522
5638
|
setRelocatePressed(true);
|
|
5523
5639
|
xrPresenterRef.current?.startPlacementMode();
|
|
5524
5640
|
}, []);
|
|
5525
|
-
|
|
5641
|
+
useEffect15(() => {
|
|
5526
5642
|
if (!relocatePressed) return;
|
|
5527
5643
|
const onPointerUp = () => finishRelocation(true);
|
|
5528
5644
|
const onPointerCancel = () => finishRelocation(false);
|
|
@@ -5585,7 +5701,7 @@ function ModelViewer({
|
|
|
5585
5701
|
const [annotations, setAnnotations] = useState10(
|
|
5586
5702
|
() => sceneConfig?.annotations ?? []
|
|
5587
5703
|
);
|
|
5588
|
-
|
|
5704
|
+
useEffect15(() => {
|
|
5589
5705
|
if (annotations.length > 0) {
|
|
5590
5706
|
const maxId = annotations.reduce((max, ann) => Math.max(max, ann.id), 0);
|
|
5591
5707
|
annotationCounterRef.current = maxId;
|
|
@@ -5751,23 +5867,23 @@ function ModelViewer({
|
|
|
5751
5867
|
);
|
|
5752
5868
|
}
|
|
5753
5869
|
}, [setActiveAnnotation]);
|
|
5754
|
-
|
|
5870
|
+
useEffect15(() => {
|
|
5755
5871
|
setCurrentAnnotationIndex(-1);
|
|
5756
5872
|
setIsGuidedTourActive(false);
|
|
5757
5873
|
}, [sortedAnnotations.length]);
|
|
5758
|
-
|
|
5874
|
+
useEffect15(() => {
|
|
5759
5875
|
return () => {
|
|
5760
5876
|
if (annotationSmoothRestoreTimeoutRef.current) {
|
|
5761
5877
|
clearTimeout(annotationSmoothRestoreTimeoutRef.current);
|
|
5762
5878
|
}
|
|
5763
5879
|
};
|
|
5764
5880
|
}, []);
|
|
5765
|
-
|
|
5881
|
+
useEffect15(() => {
|
|
5766
5882
|
if (currentAnnotation && isGuidedTourActive) {
|
|
5767
5883
|
setActiveAnnotation(currentAnnotation);
|
|
5768
5884
|
}
|
|
5769
5885
|
}, [currentAnnotation, setActiveAnnotation, isGuidedTourActive]);
|
|
5770
|
-
|
|
5886
|
+
useEffect15(() => {
|
|
5771
5887
|
if (sceneConfig?.annotations === void 0) {
|
|
5772
5888
|
return;
|
|
5773
5889
|
}
|
|
@@ -5796,7 +5912,7 @@ function ModelViewer({
|
|
|
5796
5912
|
);
|
|
5797
5913
|
return annotation ?? null;
|
|
5798
5914
|
}, [activeAnnotation, annotations]);
|
|
5799
|
-
|
|
5915
|
+
useEffect15(() => {
|
|
5800
5916
|
if (activeAnnotation && !activeViewingAnnotation) {
|
|
5801
5917
|
closeViewingAnnotation();
|
|
5802
5918
|
}
|
|
@@ -5848,12 +5964,12 @@ function ModelViewer({
|
|
|
5848
5964
|
);
|
|
5849
5965
|
const isPanelOpen = showObjectBindingDataPanel && currentSelectedObject !== null && currentSelectedObjectKey !== null;
|
|
5850
5966
|
const currentPanelObject = (currentSelectedObjectKey ? currentObjectBindings[currentSelectedObjectKey] : null) ?? currentSelectedObject;
|
|
5851
|
-
|
|
5967
|
+
useEffect15(() => {
|
|
5852
5968
|
if (!isControlledBindings) {
|
|
5853
5969
|
setInternalObjectBindings(objectBindings);
|
|
5854
5970
|
}
|
|
5855
5971
|
}, [isControlledBindings, objectBindings]);
|
|
5856
|
-
|
|
5972
|
+
useEffect15(() => {
|
|
5857
5973
|
onHiddenObjectsChange?.(currentHiddenObjects);
|
|
5858
5974
|
}, [currentHiddenObjects, onHiddenObjectsChange]);
|
|
5859
5975
|
const applySelection = useCallback4(
|
|
@@ -6035,24 +6151,24 @@ function ModelViewer({
|
|
|
6035
6151
|
bindingKeyIndex
|
|
6036
6152
|
]
|
|
6037
6153
|
);
|
|
6038
|
-
|
|
6154
|
+
useEffect15(() => {
|
|
6039
6155
|
if (!colorPicker) return;
|
|
6040
6156
|
window.addEventListener("pointerdown", closeColorPicker);
|
|
6041
6157
|
return () => window.removeEventListener("pointerdown", closeColorPicker);
|
|
6042
6158
|
}, [colorPicker, closeColorPicker]);
|
|
6043
|
-
|
|
6159
|
+
useEffect15(() => {
|
|
6044
6160
|
if (!materialPopup) return;
|
|
6045
6161
|
const close = () => setMaterialPopup(null);
|
|
6046
6162
|
window.addEventListener("pointerdown", close);
|
|
6047
6163
|
return () => window.removeEventListener("pointerdown", close);
|
|
6048
6164
|
}, [materialPopup]);
|
|
6049
|
-
|
|
6165
|
+
useEffect15(() => {
|
|
6050
6166
|
if (!downloadMenuOpen) return;
|
|
6051
6167
|
const close = () => setDownloadMenuOpen(false);
|
|
6052
6168
|
window.addEventListener("pointerdown", close);
|
|
6053
6169
|
return () => window.removeEventListener("pointerdown", close);
|
|
6054
6170
|
}, [downloadMenuOpen]);
|
|
6055
|
-
|
|
6171
|
+
useEffect15(() => {
|
|
6056
6172
|
if (!activeViewingAnnotation) return;
|
|
6057
6173
|
const handleClickOutside = (event) => {
|
|
6058
6174
|
if (viewAnnotationPopupRef.current && !viewAnnotationPopupRef.current.contains(event.target)) {
|
|
@@ -6070,7 +6186,7 @@ function ModelViewer({
|
|
|
6070
6186
|
document.removeEventListener("touchstart", handleClickOutside);
|
|
6071
6187
|
};
|
|
6072
6188
|
}, [activeViewingAnnotation, closeViewingAnnotation]);
|
|
6073
|
-
|
|
6189
|
+
useEffect15(() => {
|
|
6074
6190
|
if (!pendingAnnotation) return;
|
|
6075
6191
|
const handleClickOutside = (event) => {
|
|
6076
6192
|
const target = event.target;
|
|
@@ -6217,7 +6333,7 @@ function ModelViewer({
|
|
|
6217
6333
|
},
|
|
6218
6334
|
[captureInitialCameraState, notifyViewerReady]
|
|
6219
6335
|
);
|
|
6220
|
-
|
|
6336
|
+
useEffect15(() => {
|
|
6221
6337
|
fitAttemptedRef.current = false;
|
|
6222
6338
|
fitInFlightRef.current = false;
|
|
6223
6339
|
initialCameraStateRef.current = null;
|
|
@@ -6289,7 +6405,7 @@ function ModelViewer({
|
|
|
6289
6405
|
notifyViewerReady,
|
|
6290
6406
|
onAutoFit
|
|
6291
6407
|
]);
|
|
6292
|
-
|
|
6408
|
+
useEffect15(() => {
|
|
6293
6409
|
if (!refitOnResize) {
|
|
6294
6410
|
return;
|
|
6295
6411
|
}
|
|
@@ -6398,7 +6514,7 @@ function ModelViewer({
|
|
|
6398
6514
|
},
|
|
6399
6515
|
[applySelection, resolveBindingKey2, disableZoom]
|
|
6400
6516
|
);
|
|
6401
|
-
|
|
6517
|
+
useEffect15(() => {
|
|
6402
6518
|
notifyViewerReady();
|
|
6403
6519
|
}, [notifyViewerReady]);
|
|
6404
6520
|
const handleCameraChangeInternal = useCallback4(
|
|
@@ -6412,6 +6528,7 @@ function ModelViewer({
|
|
|
6412
6528
|
(scene) => {
|
|
6413
6529
|
sceneRef.current = scene;
|
|
6414
6530
|
setMeasureBox(computeMeasureBox(scene));
|
|
6531
|
+
setModelBounds(computeModelBounds(scene));
|
|
6415
6532
|
setModelLoaded(true);
|
|
6416
6533
|
setModelLoadFailed(false);
|
|
6417
6534
|
setMismatchDismissed(false);
|
|
@@ -6429,7 +6546,7 @@ function ModelViewer({
|
|
|
6429
6546
|
},
|
|
6430
6547
|
[onLoadError]
|
|
6431
6548
|
);
|
|
6432
|
-
|
|
6549
|
+
useEffect15(() => {
|
|
6433
6550
|
const scene = sceneRef.current;
|
|
6434
6551
|
if (!modelLoaded || !scene) {
|
|
6435
6552
|
setBindingsMismatch(false);
|
|
@@ -6490,7 +6607,7 @@ function ModelViewer({
|
|
|
6490
6607
|
},
|
|
6491
6608
|
[]
|
|
6492
6609
|
);
|
|
6493
|
-
|
|
6610
|
+
useEffect15(() => {
|
|
6494
6611
|
setMeasurePoints([]);
|
|
6495
6612
|
}, [modelUrl, resolvedSceneConfig.model.rotation]);
|
|
6496
6613
|
const handleDownloadMesh = useCallback4(() => {
|
|
@@ -6767,7 +6884,8 @@ function ModelViewer({
|
|
|
6767
6884
|
shadowConfig: resolvedSceneConfig.shadows,
|
|
6768
6885
|
shadowsEnabled: shadows,
|
|
6769
6886
|
environmentRotation: resolvedSceneConfig.environment.rotation,
|
|
6770
|
-
environmentConfig: resolvedSceneConfig.environment
|
|
6887
|
+
environmentConfig: resolvedSceneConfig.environment,
|
|
6888
|
+
modelBounds
|
|
6771
6889
|
}
|
|
6772
6890
|
),
|
|
6773
6891
|
/* @__PURE__ */ jsx19(
|
|
@@ -7327,7 +7445,7 @@ function ModelViewer({
|
|
|
7327
7445
|
}
|
|
7328
7446
|
|
|
7329
7447
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
7330
|
-
import { useCallback as useCallback8, useEffect as
|
|
7448
|
+
import { useCallback as useCallback8, useEffect as useEffect20, useMemo as useMemo16, useRef as useRef18, useState as useState24 } from "react";
|
|
7331
7449
|
import {
|
|
7332
7450
|
Redo2,
|
|
7333
7451
|
Undo2,
|
|
@@ -7579,7 +7697,7 @@ function useViewerCamera() {
|
|
|
7579
7697
|
}
|
|
7580
7698
|
|
|
7581
7699
|
// src/hooks/useViewerAnimations.ts
|
|
7582
|
-
import { useCallback as useCallback6, useEffect as
|
|
7700
|
+
import { useCallback as useCallback6, useEffect as useEffect16, useRef as useRef13, useState as useState12 } from "react";
|
|
7583
7701
|
function useViewerAnimations() {
|
|
7584
7702
|
const controlsRef = useRef13(null);
|
|
7585
7703
|
const [clips, setClips] = useState12([]);
|
|
@@ -7590,7 +7708,7 @@ function useViewerAnimations() {
|
|
|
7590
7708
|
const [speed, setSpeedState] = useState12(1);
|
|
7591
7709
|
const [time, setTime] = useState12(0);
|
|
7592
7710
|
const [duration, setDuration] = useState12(0);
|
|
7593
|
-
|
|
7711
|
+
useEffect16(
|
|
7594
7712
|
() => () => {
|
|
7595
7713
|
unsubscribeRef.current?.();
|
|
7596
7714
|
},
|
|
@@ -7852,7 +7970,7 @@ function inferMimeType(path) {
|
|
|
7852
7970
|
}
|
|
7853
7971
|
|
|
7854
7972
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
7855
|
-
import { useState as useState19, useEffect as
|
|
7973
|
+
import { useState as useState19, useEffect as useEffect18 } from "react";
|
|
7856
7974
|
|
|
7857
7975
|
// src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
|
|
7858
7976
|
import { useState as useState14 } from "react";
|
|
@@ -7880,9 +7998,9 @@ function getContrastTextColor(hex) {
|
|
|
7880
7998
|
}
|
|
7881
7999
|
|
|
7882
8000
|
// src/hooks/useClickOutside.ts
|
|
7883
|
-
import { useEffect as
|
|
8001
|
+
import { useEffect as useEffect17 } from "react";
|
|
7884
8002
|
function useClickOutside(refs, onOutside, active) {
|
|
7885
|
-
|
|
8003
|
+
useEffect17(() => {
|
|
7886
8004
|
if (!active) return;
|
|
7887
8005
|
const refList = Array.isArray(refs) ? refs : [refs];
|
|
7888
8006
|
const handlePointerDown = (event) => {
|
|
@@ -11664,7 +11782,7 @@ function SceneSettingsTab({
|
|
|
11664
11782
|
const { tier } = useBindingBuilderContext();
|
|
11665
11783
|
const canUseStarterFeatures = hasTier(tier, "starter");
|
|
11666
11784
|
const canUseGrowthFeatures = hasTier(tier, "growth");
|
|
11667
|
-
|
|
11785
|
+
useEffect18(() => {
|
|
11668
11786
|
if (controlledActiveSubTab !== void 0) {
|
|
11669
11787
|
setInternalActiveSubTab(controlledActiveSubTab);
|
|
11670
11788
|
}
|
|
@@ -13152,7 +13270,7 @@ function MaterialPresetGallery({
|
|
|
13152
13270
|
}
|
|
13153
13271
|
|
|
13154
13272
|
// src/components/BindingBuilder/components/ToolbarExportMenu.tsx
|
|
13155
|
-
import { useEffect as
|
|
13273
|
+
import { useEffect as useEffect19, useRef as useRef16, useState as useState22 } from "react";
|
|
13156
13274
|
import { ChevronDown as ChevronDown5, Download as Download2, FileCode2, FileJson } from "lucide-react";
|
|
13157
13275
|
import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
13158
13276
|
function ToolbarExportMenu({
|
|
@@ -13163,7 +13281,7 @@ function ToolbarExportMenu({
|
|
|
13163
13281
|
}) {
|
|
13164
13282
|
const [open, setOpen] = useState22(false);
|
|
13165
13283
|
const wrapperRef = useRef16(null);
|
|
13166
|
-
|
|
13284
|
+
useEffect19(() => {
|
|
13167
13285
|
if (!open) return;
|
|
13168
13286
|
const close = (event) => {
|
|
13169
13287
|
if (!wrapperRef.current?.contains(event.target)) {
|
|
@@ -13603,8 +13721,6 @@ function CameraPositionPanel({
|
|
|
13603
13721
|
import { useMemo as useMemo15, useState as useState23 } from "react";
|
|
13604
13722
|
|
|
13605
13723
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
13606
|
-
import { GLTFLoader } from "three-stdlib";
|
|
13607
|
-
import { WebGLRenderer as WebGLRenderer2 } from "three";
|
|
13608
13724
|
function slugify(value) {
|
|
13609
13725
|
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
13610
13726
|
}
|
|
@@ -13670,51 +13786,15 @@ function buildBindingsFromNodes(nodeNames) {
|
|
|
13670
13786
|
nodeNames.map((nodeName) => [nodeName, createDefaultBinding(nodeName)])
|
|
13671
13787
|
);
|
|
13672
13788
|
}
|
|
13673
|
-
function
|
|
13674
|
-
const
|
|
13675
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
canvas: document.createElement("canvas"),
|
|
13680
|
-
antialias: false
|
|
13681
|
-
});
|
|
13682
|
-
configureGLTFLoader(loader, {}, renderer);
|
|
13683
|
-
} catch {
|
|
13684
|
-
renderer = null;
|
|
13685
|
-
configureGLTFLoader(loader, {});
|
|
13789
|
+
function collectNodeNames(scene) {
|
|
13790
|
+
const names = /* @__PURE__ */ new Set();
|
|
13791
|
+
scene.traverse((child) => {
|
|
13792
|
+
const mesh = child;
|
|
13793
|
+
if (mesh.isMesh && mesh.geometry && child.name) {
|
|
13794
|
+
names.add(child.name);
|
|
13686
13795
|
}
|
|
13687
|
-
} else {
|
|
13688
|
-
configureGLTFLoader(loader, {});
|
|
13689
|
-
}
|
|
13690
|
-
return new Promise((resolve, reject) => {
|
|
13691
|
-
loader.load(
|
|
13692
|
-
modelUrl,
|
|
13693
|
-
(gltf) => {
|
|
13694
|
-
const names = /* @__PURE__ */ new Set();
|
|
13695
|
-
gltf.scene.traverse((child) => {
|
|
13696
|
-
if (child?.isMesh && child.geometry && child.name) {
|
|
13697
|
-
names.add(child.name);
|
|
13698
|
-
}
|
|
13699
|
-
});
|
|
13700
|
-
resolve({
|
|
13701
|
-
nodeNames: Array.from(names).sort((a, b) => a.localeCompare(b)),
|
|
13702
|
-
animations: resolveAnimationClips(gltf.animations).map(
|
|
13703
|
-
({ sourceName, duration }) => ({
|
|
13704
|
-
sourceName,
|
|
13705
|
-
duration
|
|
13706
|
-
})
|
|
13707
|
-
)
|
|
13708
|
-
});
|
|
13709
|
-
renderer?.dispose();
|
|
13710
|
-
},
|
|
13711
|
-
void 0,
|
|
13712
|
-
(error) => {
|
|
13713
|
-
renderer?.dispose();
|
|
13714
|
-
reject(error);
|
|
13715
|
-
}
|
|
13716
|
-
);
|
|
13717
13796
|
});
|
|
13797
|
+
return Array.from(names).sort((a, b) => a.localeCompare(b));
|
|
13718
13798
|
}
|
|
13719
13799
|
|
|
13720
13800
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
@@ -14260,6 +14340,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14260
14340
|
const uploadedModelRef = useRef18(null);
|
|
14261
14341
|
const previewDragDepthRef = useRef18(0);
|
|
14262
14342
|
const loadRequestRef = useRef18(0);
|
|
14343
|
+
const pendingLoadRef = useRef18(null);
|
|
14344
|
+
const loadedSceneRef = useRef18(null);
|
|
14263
14345
|
const {
|
|
14264
14346
|
cameraState,
|
|
14265
14347
|
resetView,
|
|
@@ -14317,7 +14399,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14317
14399
|
},
|
|
14318
14400
|
[]
|
|
14319
14401
|
);
|
|
14320
|
-
|
|
14402
|
+
useEffect20(() => {
|
|
14321
14403
|
if (!selectedBinding) {
|
|
14322
14404
|
setMetricsDraft("{}");
|
|
14323
14405
|
setMetadataDraft("{}");
|
|
@@ -14330,7 +14412,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14330
14412
|
setMetricsError(null);
|
|
14331
14413
|
setMetadataError(null);
|
|
14332
14414
|
}, [selectedBinding]);
|
|
14333
|
-
|
|
14415
|
+
useEffect20(() => {
|
|
14334
14416
|
const derived = createMaterialPanelEnabledState(selectedMaterial);
|
|
14335
14417
|
if (materialPanelItemRef.current !== selectedItem) {
|
|
14336
14418
|
materialPanelItemRef.current = selectedItem;
|
|
@@ -14349,12 +14431,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14349
14431
|
return changed ? next : prev;
|
|
14350
14432
|
});
|
|
14351
14433
|
}, [selectedItem, selectedMaterial]);
|
|
14352
|
-
|
|
14434
|
+
useEffect20(() => {
|
|
14353
14435
|
return () => {
|
|
14354
14436
|
uploadedModelRef.current?.cleanup();
|
|
14355
14437
|
};
|
|
14356
14438
|
}, []);
|
|
14357
|
-
|
|
14439
|
+
useEffect20(() => {
|
|
14358
14440
|
const handleKeyDown = (event) => {
|
|
14359
14441
|
if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
|
|
14360
14442
|
return;
|
|
@@ -14375,7 +14457,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14375
14457
|
window.addEventListener("keydown", handleKeyDown);
|
|
14376
14458
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
14377
14459
|
}, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
|
|
14378
|
-
|
|
14460
|
+
useEffect20(() => {
|
|
14379
14461
|
if (loading || statusTone === "default" || statusTone === "success") return;
|
|
14380
14462
|
setStatusBannerVisible(true);
|
|
14381
14463
|
const timeout = window.setTimeout(
|
|
@@ -14428,42 +14510,76 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14428
14510
|
});
|
|
14429
14511
|
}
|
|
14430
14512
|
};
|
|
14431
|
-
const
|
|
14513
|
+
const applyBindingsFromScene = useCallback8(
|
|
14514
|
+
(scene, nextModelName) => {
|
|
14515
|
+
const nodeNames = collectNodeNames(scene);
|
|
14516
|
+
bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
|
|
14517
|
+
setSelectedItem(nodeNames[0] ?? null);
|
|
14518
|
+
setSceneConfig((prev) => ({ ...prev, annotations: [] }));
|
|
14519
|
+
setStatusMessage(
|
|
14520
|
+
`Generated ${nodeNames.length} bindings from ${nextModelName}.`
|
|
14521
|
+
);
|
|
14522
|
+
setStatusTone("success");
|
|
14523
|
+
setLoading(false);
|
|
14524
|
+
},
|
|
14525
|
+
[bindingsHistory, setSceneConfig]
|
|
14526
|
+
);
|
|
14527
|
+
const generateBindingsForModel = (nextModelUrl, nextModelName) => {
|
|
14432
14528
|
const requestId = ++loadRequestRef.current;
|
|
14433
14529
|
setLoading(true);
|
|
14434
14530
|
setStatusMessage(
|
|
14435
14531
|
`Reading ${nextModelName} and generating starter bindings...`
|
|
14436
14532
|
);
|
|
14437
14533
|
setStatusTone("default");
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14534
|
+
setModelName(nextModelName);
|
|
14535
|
+
setActiveAnnotation(null);
|
|
14536
|
+
const loaded = loadedSceneRef.current;
|
|
14537
|
+
if (nextModelUrl === modelUrl && loaded?.url === nextModelUrl) {
|
|
14538
|
+
pendingLoadRef.current = null;
|
|
14539
|
+
applyBindingsFromScene(loaded.scene, nextModelName);
|
|
14540
|
+
return;
|
|
14541
|
+
}
|
|
14542
|
+
pendingLoadRef.current = {
|
|
14543
|
+
requestId,
|
|
14544
|
+
modelName: nextModelName,
|
|
14545
|
+
modelUrl: nextModelUrl
|
|
14546
|
+
};
|
|
14547
|
+
setModelUrl(nextModelUrl);
|
|
14548
|
+
bindingsHistory.reset({});
|
|
14549
|
+
setSelectedItem(null);
|
|
14550
|
+
};
|
|
14551
|
+
const handleViewerModelLoaded = useCallback8(
|
|
14552
|
+
(scene) => {
|
|
14553
|
+
const pending = pendingLoadRef.current;
|
|
14554
|
+
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14555
|
+
pendingLoadRef.current = null;
|
|
14556
|
+
loadedSceneRef.current = { url: pending.modelUrl, scene };
|
|
14557
|
+
applyBindingsFromScene(scene, pending.modelName);
|
|
14558
|
+
},
|
|
14559
|
+
[applyBindingsFromScene]
|
|
14560
|
+
);
|
|
14561
|
+
const handleViewerLoadError = useCallback8((error) => {
|
|
14562
|
+
const pending = pendingLoadRef.current;
|
|
14563
|
+
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14564
|
+
pendingLoadRef.current = null;
|
|
14565
|
+
console.error(error);
|
|
14566
|
+
setStatusMessage(
|
|
14567
|
+
`Could not read ${pending.modelName}. Make sure the file is a valid GLB/GLTF asset.`
|
|
14568
|
+
);
|
|
14569
|
+
setStatusTone("error");
|
|
14570
|
+
setLoading(false);
|
|
14571
|
+
}, []);
|
|
14572
|
+
const handleAnimationsReady = useCallback8(
|
|
14573
|
+
(controls) => {
|
|
14574
|
+
viewerAnimations.handleAnimationsReady(controls);
|
|
14575
|
+
const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
|
|
14447
14576
|
setSceneConfig((prev) => ({
|
|
14448
14577
|
...prev,
|
|
14449
|
-
animations: mergeAnimationConfig(prev,
|
|
14450
|
-
annotations: []
|
|
14578
|
+
animations: mergeAnimationConfig(prev, details)
|
|
14451
14579
|
}));
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
setStatusTone("success");
|
|
14456
|
-
} catch (error) {
|
|
14457
|
-
if (requestId !== loadRequestRef.current) return;
|
|
14458
|
-
console.error(error);
|
|
14459
|
-
setStatusMessage(
|
|
14460
|
-
`Could not read ${nextModelName}. Make sure the file is a valid GLB/GLTF asset.`
|
|
14461
|
-
);
|
|
14462
|
-
setStatusTone("error");
|
|
14463
|
-
} finally {
|
|
14464
|
-
if (requestId === loadRequestRef.current) setLoading(false);
|
|
14465
|
-
}
|
|
14466
|
-
};
|
|
14580
|
+
},
|
|
14581
|
+
[viewerAnimations, setSceneConfig]
|
|
14582
|
+
);
|
|
14467
14583
|
const handleLoadDemo = async () => {
|
|
14468
14584
|
uploadedModelRef.current?.cleanup();
|
|
14469
14585
|
uploadedModelRef.current = null;
|
|
@@ -14493,6 +14609,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14493
14609
|
const clearModel = () => {
|
|
14494
14610
|
uploadedModelRef.current?.cleanup();
|
|
14495
14611
|
uploadedModelRef.current = null;
|
|
14612
|
+
loadedSceneRef.current = null;
|
|
14613
|
+
pendingLoadRef.current = null;
|
|
14496
14614
|
previewDragDepthRef.current = 0;
|
|
14497
14615
|
setIsPreviewDragActive(false);
|
|
14498
14616
|
setModelUrl(null);
|
|
@@ -15291,7 +15409,9 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15291
15409
|
onAnnotationsChange: handleAnnotationsChange,
|
|
15292
15410
|
activeAnnotation: activeAnnotation?.annotation ?? null,
|
|
15293
15411
|
onActiveAnnotationChange: handleActiveAnnotationChange,
|
|
15294
|
-
onAnimationsReady:
|
|
15412
|
+
onAnimationsReady: handleAnimationsReady,
|
|
15413
|
+
onModelLoaded: handleViewerModelLoaded,
|
|
15414
|
+
onLoadError: handleViewerLoadError,
|
|
15295
15415
|
licenseKey,
|
|
15296
15416
|
sceneConfig,
|
|
15297
15417
|
disableZoom: false,
|
|
@@ -15374,7 +15494,7 @@ import {
|
|
|
15374
15494
|
useRef as useRef19,
|
|
15375
15495
|
useState as useState25,
|
|
15376
15496
|
useCallback as useCallback9,
|
|
15377
|
-
useEffect as
|
|
15497
|
+
useEffect as useEffect21,
|
|
15378
15498
|
useMemo as useMemo17,
|
|
15379
15499
|
Component as Component3,
|
|
15380
15500
|
Suspense as Suspense3
|
|
@@ -15492,14 +15612,14 @@ function InnerModel({
|
|
|
15492
15612
|
}
|
|
15493
15613
|
return map;
|
|
15494
15614
|
}, [meshMap]);
|
|
15495
|
-
|
|
15615
|
+
useEffect21(
|
|
15496
15616
|
() => () => {
|
|
15497
15617
|
Object.values(perKeyMaterials).forEach((m) => m.dispose());
|
|
15498
15618
|
},
|
|
15499
15619
|
[perKeyMaterials]
|
|
15500
15620
|
);
|
|
15501
15621
|
const notifiedUrl = useRef19(null);
|
|
15502
|
-
|
|
15622
|
+
useEffect21(() => {
|
|
15503
15623
|
if (notifiedUrl.current === url) return;
|
|
15504
15624
|
notifiedUrl.current = url;
|
|
15505
15625
|
scene.updateWorldMatrix(true, true);
|
|
@@ -15507,22 +15627,22 @@ function InnerModel({
|
|
|
15507
15627
|
onMeshesReady(meshMap);
|
|
15508
15628
|
onModelLoaded(scene);
|
|
15509
15629
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
15510
|
-
|
|
15630
|
+
useEffect21(() => {
|
|
15511
15631
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15512
15632
|
nodeRefsOut.current[key] = ref;
|
|
15513
15633
|
}
|
|
15514
15634
|
}, [meshMap, nodeRefsOut]);
|
|
15515
|
-
|
|
15635
|
+
useEffect21(() => {
|
|
15516
15636
|
if (!highlightOnHover && hoveredKey !== null) {
|
|
15517
15637
|
setHoveredKey(null);
|
|
15518
15638
|
}
|
|
15519
15639
|
}, [highlightOnHover, hoveredKey]);
|
|
15520
|
-
|
|
15640
|
+
useEffect21(() => {
|
|
15521
15641
|
return () => {
|
|
15522
15642
|
document.body.style.cursor = "default";
|
|
15523
15643
|
};
|
|
15524
15644
|
}, []);
|
|
15525
|
-
|
|
15645
|
+
useEffect21(() => {
|
|
15526
15646
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15527
15647
|
ref.visible = !hiddenKeys.has(key);
|
|
15528
15648
|
const mat = perKeyMaterials[key];
|
|
@@ -15599,7 +15719,7 @@ function CameraFitter({
|
|
|
15599
15719
|
const invalidate = useThree11((state) => state.invalidate);
|
|
15600
15720
|
const size = useThree11((state) => state.size);
|
|
15601
15721
|
const resizeTimerRef = useRef19(null);
|
|
15602
|
-
|
|
15722
|
+
useEffect21(() => {
|
|
15603
15723
|
fitted.current = false;
|
|
15604
15724
|
invalidate();
|
|
15605
15725
|
}, [invalidate, scene]);
|
|
@@ -15619,7 +15739,7 @@ function CameraFitter({
|
|
|
15619
15739
|
}).catch(() => {
|
|
15620
15740
|
}).finally(onInitialFitComplete);
|
|
15621
15741
|
});
|
|
15622
|
-
|
|
15742
|
+
useEffect21(() => {
|
|
15623
15743
|
if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
|
|
15624
15744
|
return;
|
|
15625
15745
|
}
|
|
@@ -15647,7 +15767,7 @@ function CameraFitter({
|
|
|
15647
15767
|
function ExposureController({ exposure }) {
|
|
15648
15768
|
const gl = useThree11((state) => state.gl);
|
|
15649
15769
|
const invalidate = useThree11((state) => state.invalidate);
|
|
15650
|
-
|
|
15770
|
+
useEffect21(() => {
|
|
15651
15771
|
gl.toneMappingExposure = exposure;
|
|
15652
15772
|
invalidate();
|
|
15653
15773
|
}, [gl, exposure, invalidate]);
|
|
@@ -16150,7 +16270,7 @@ function SimpleModelViewer({
|
|
|
16150
16270
|
},
|
|
16151
16271
|
[handleUploadedModel]
|
|
16152
16272
|
);
|
|
16153
|
-
|
|
16273
|
+
useEffect21(() => {
|
|
16154
16274
|
return () => {
|
|
16155
16275
|
revokeUploadedModelUrl();
|
|
16156
16276
|
};
|
|
@@ -16161,10 +16281,10 @@ function SimpleModelViewer({
|
|
|
16161
16281
|
},
|
|
16162
16282
|
[]
|
|
16163
16283
|
);
|
|
16164
|
-
|
|
16284
|
+
useEffect21(() => {
|
|
16165
16285
|
setEnvSettings((prev) => ({ ...prev, backgroundColor }));
|
|
16166
16286
|
}, [backgroundColor]);
|
|
16167
|
-
|
|
16287
|
+
useEffect21(() => {
|
|
16168
16288
|
resetModelState();
|
|
16169
16289
|
}, [activeModelUrl, resetModelState]);
|
|
16170
16290
|
const handleMeshesReady = useCallback9((meshes) => {
|
|
@@ -16726,7 +16846,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
16726
16846
|
}
|
|
16727
16847
|
|
|
16728
16848
|
// src/hooks/useShareableViewerState.ts
|
|
16729
|
-
import { useCallback as useCallback15, useEffect as
|
|
16849
|
+
import { useCallback as useCallback15, useEffect as useEffect22, useRef as useRef20, useState as useState29 } from "react";
|
|
16730
16850
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
16731
16851
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
16732
16852
|
return {
|
|
@@ -16780,7 +16900,7 @@ function useShareableViewerState({
|
|
|
16780
16900
|
};
|
|
16781
16901
|
const [restoreAttempted, setRestoreAttempted] = useState29(false);
|
|
16782
16902
|
const lastSyncedHashRef = useRef20(null);
|
|
16783
|
-
|
|
16903
|
+
useEffect22(() => {
|
|
16784
16904
|
if (!autoSync || !restoreAttempted) return;
|
|
16785
16905
|
const tick = () => {
|
|
16786
16906
|
const current = latestStateRef.current;
|
|
@@ -16857,7 +16977,7 @@ function useShareableViewerState({
|
|
|
16857
16977
|
setCameraState,
|
|
16858
16978
|
setSelectedObjectBinding
|
|
16859
16979
|
]);
|
|
16860
|
-
|
|
16980
|
+
useEffect22(() => {
|
|
16861
16981
|
const handleHashChange = () => {
|
|
16862
16982
|
void restoreFromUrl();
|
|
16863
16983
|
};
|