@liveroom-tech/react-immersive 1.1.8 → 1.2.1
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 +288 -184
- package/dist/index.mjs +311 -203
- 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,60 @@ 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
|
+
function countTriangles(geometries) {
|
|
2539
|
+
let total = 0;
|
|
2540
|
+
for (const geometry of geometries) {
|
|
2541
|
+
const count = geometry.index ? geometry.index.count : geometry.attributes.position.count;
|
|
2542
|
+
total += count / 3;
|
|
2543
|
+
}
|
|
2544
|
+
return total;
|
|
2545
|
+
}
|
|
2546
|
+
var MAX_BVH_TRIANGLES = 12e6;
|
|
2547
|
+
async function buildSceneBVH(root, signal) {
|
|
2548
|
+
const geometries = collectGeometries(root);
|
|
2549
|
+
if (geometries.length === 0) return;
|
|
2550
|
+
if (countTriangles(geometries) > MAX_BVH_TRIANGLES) return;
|
|
2551
|
+
enableAcceleratedRaycast();
|
|
2552
|
+
for (const geometry of geometries) {
|
|
2553
|
+
if (signal?.aborted) return;
|
|
2554
|
+
const tree = computeBoundsTree.call(geometry);
|
|
2555
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2556
|
+
if (signal?.aborted) return;
|
|
2557
|
+
geometry.boundsTree = tree;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
function disposeSceneBVH(root) {
|
|
2561
|
+
root.traverse((node) => {
|
|
2562
|
+
const geometry = node.geometry;
|
|
2563
|
+
if (!geometry?.boundsTree) return;
|
|
2564
|
+
disposeBoundsTree.call(geometry);
|
|
2565
|
+
geometry.boundsTree = void 0;
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2482
2569
|
// src/components/Model.tsx
|
|
2483
2570
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2484
2571
|
function firstMaterial(node) {
|
|
@@ -2511,14 +2598,14 @@ function Model({
|
|
|
2511
2598
|
const invalidate = useThree7((state) => state.invalidate);
|
|
2512
2599
|
const [hoveredItem, setHoveredItem] = useState3(null);
|
|
2513
2600
|
const groupRef = useRef5(null);
|
|
2514
|
-
|
|
2601
|
+
useEffect7(() => {
|
|
2515
2602
|
if (!highlightOnHover) {
|
|
2516
2603
|
setHoveredItem(null);
|
|
2517
2604
|
onItemHover?.(null);
|
|
2518
2605
|
document.body.style.cursor = "default";
|
|
2519
2606
|
}
|
|
2520
2607
|
}, [highlightOnHover, onItemHover]);
|
|
2521
|
-
|
|
2608
|
+
useEffect7(() => {
|
|
2522
2609
|
return () => {
|
|
2523
2610
|
document.body.style.cursor = "default";
|
|
2524
2611
|
};
|
|
@@ -2554,7 +2641,7 @@ function Model({
|
|
|
2554
2641
|
}
|
|
2555
2642
|
return shell;
|
|
2556
2643
|
}, [nodeMap]);
|
|
2557
|
-
|
|
2644
|
+
useEffect7(() => {
|
|
2558
2645
|
scene.traverse((node) => {
|
|
2559
2646
|
if (!isMeshNode(node)) return;
|
|
2560
2647
|
const mesh = node;
|
|
@@ -2562,9 +2649,17 @@ function Model({
|
|
|
2562
2649
|
mesh.receiveShadow = true;
|
|
2563
2650
|
});
|
|
2564
2651
|
}, [scene, shellNodeNames]);
|
|
2565
|
-
|
|
2652
|
+
useEffect7(() => {
|
|
2566
2653
|
refreshSkinnedBounds(scene);
|
|
2567
2654
|
}, [scene]);
|
|
2655
|
+
useEffect7(() => {
|
|
2656
|
+
const controller = new AbortController();
|
|
2657
|
+
void buildSceneBVH(scene, controller.signal);
|
|
2658
|
+
return () => {
|
|
2659
|
+
controller.abort();
|
|
2660
|
+
disposeSceneBVH(scene);
|
|
2661
|
+
};
|
|
2662
|
+
}, [scene]);
|
|
2568
2663
|
const bindingKeyByNodeName = useMemo7(() => {
|
|
2569
2664
|
const map = {};
|
|
2570
2665
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -2593,10 +2688,10 @@ function Model({
|
|
|
2593
2688
|
}
|
|
2594
2689
|
return map;
|
|
2595
2690
|
}, [nodeMap, nodeBindingSignature]);
|
|
2596
|
-
|
|
2691
|
+
useEffect7(() => {
|
|
2597
2692
|
if (materialRefsOut) materialRefsOut.current = perNodeMaterials;
|
|
2598
2693
|
}, [perNodeMaterials, materialRefsOut]);
|
|
2599
|
-
|
|
2694
|
+
useEffect7(() => {
|
|
2600
2695
|
return () => {
|
|
2601
2696
|
for (const material of Object.values(perNodeMaterials)) {
|
|
2602
2697
|
material.dispose();
|
|
@@ -2647,7 +2742,7 @@ function Model({
|
|
|
2647
2742
|
}
|
|
2648
2743
|
return map;
|
|
2649
2744
|
}, [perNodeMaterials, shadingMode, rendererMode]);
|
|
2650
|
-
|
|
2745
|
+
useEffect7(() => {
|
|
2651
2746
|
return () => {
|
|
2652
2747
|
for (const material of Object.values(perNodeBasicMaterials))
|
|
2653
2748
|
material.dispose();
|
|
@@ -2663,10 +2758,10 @@ function Model({
|
|
|
2663
2758
|
if (rendererMode === "matcap") return perNodeMatcapMaterials[nodeName];
|
|
2664
2759
|
return perNodeMaterials[nodeName];
|
|
2665
2760
|
};
|
|
2666
|
-
|
|
2761
|
+
useEffect7(() => {
|
|
2667
2762
|
onModelLoaded?.(scene);
|
|
2668
2763
|
}, [scene, onModelLoaded]);
|
|
2669
|
-
|
|
2764
|
+
useEffect7(() => {
|
|
2670
2765
|
if (!nodeRefsOut) return;
|
|
2671
2766
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
2672
2767
|
const glbNodeName = binding.modelObjectId ?? nodeName;
|
|
@@ -2675,7 +2770,7 @@ function Model({
|
|
|
2675
2770
|
}
|
|
2676
2771
|
}, [nodeMap, objectBindings, nodeRefsOut]);
|
|
2677
2772
|
const appliedTextureUrls = useRef5({});
|
|
2678
|
-
|
|
2773
|
+
useEffect7(() => {
|
|
2679
2774
|
return () => {
|
|
2680
2775
|
for (const nodeTextures of Object.values(appliedTextureUrls.current)) {
|
|
2681
2776
|
for (const [key, appliedUrl] of Object.entries(nodeTextures)) {
|
|
@@ -2690,7 +2785,7 @@ function Model({
|
|
|
2690
2785
|
appliedTextureUrls.current = {};
|
|
2691
2786
|
};
|
|
2692
2787
|
}, []);
|
|
2693
|
-
|
|
2788
|
+
useEffect7(() => {
|
|
2694
2789
|
const seenNodeNames = /* @__PURE__ */ new Set();
|
|
2695
2790
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
2696
2791
|
seenNodeNames.add(nodeName);
|
|
@@ -2797,7 +2892,7 @@ function Model({
|
|
|
2797
2892
|
rendererMode,
|
|
2798
2893
|
invalidate
|
|
2799
2894
|
]);
|
|
2800
|
-
|
|
2895
|
+
useEffect7(() => {
|
|
2801
2896
|
if (!wireframeEnabled) return;
|
|
2802
2897
|
const added = [];
|
|
2803
2898
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -2931,7 +3026,7 @@ function Model({
|
|
|
2931
3026
|
emitPlaybackState();
|
|
2932
3027
|
invalidate();
|
|
2933
3028
|
};
|
|
2934
|
-
|
|
3029
|
+
useEffect7(() => {
|
|
2935
3030
|
const mixer = new AnimationMixer(scene);
|
|
2936
3031
|
mixerRef.current = mixer;
|
|
2937
3032
|
const actions = {};
|
|
@@ -2986,7 +3081,7 @@ function Model({
|
|
|
2986
3081
|
emitPlaybackState();
|
|
2987
3082
|
};
|
|
2988
3083
|
}, [scene, resolvedAnimationClips]);
|
|
2989
|
-
|
|
3084
|
+
useEffect7(() => {
|
|
2990
3085
|
const currentClip = activeClipNameRef.current;
|
|
2991
3086
|
const activeAction = activeActionRef.current;
|
|
2992
3087
|
if (!currentClip || !activeAction) return;
|
|
@@ -3045,7 +3140,7 @@ function Model({
|
|
|
3045
3140
|
const explodeStrengthRef = useRef5(0);
|
|
3046
3141
|
const currentExplodeRef = useRef5(0);
|
|
3047
3142
|
const explodeTmp = useMemo7(() => new Vector32(), []);
|
|
3048
|
-
|
|
3143
|
+
useEffect7(() => {
|
|
3049
3144
|
for (const part of explodePartsRef.current) {
|
|
3050
3145
|
part.node.position.copy(part.restLocalPos);
|
|
3051
3146
|
}
|
|
@@ -3094,7 +3189,7 @@ function Model({
|
|
|
3094
3189
|
explodePartsRef.current = parts;
|
|
3095
3190
|
invalidate();
|
|
3096
3191
|
}, [scene, nodeMap, nodeBindingSignature, invalidate]);
|
|
3097
|
-
|
|
3192
|
+
useEffect7(() => {
|
|
3098
3193
|
invalidate();
|
|
3099
3194
|
}, [explodeFactor, invalidate]);
|
|
3100
3195
|
useFrame4((_, delta) => {
|
|
@@ -3172,7 +3267,7 @@ import {
|
|
|
3172
3267
|
useMemo as useMemo8,
|
|
3173
3268
|
useRef as useRef6,
|
|
3174
3269
|
useState as useState4,
|
|
3175
|
-
useEffect as
|
|
3270
|
+
useEffect as useEffect8,
|
|
3176
3271
|
useCallback
|
|
3177
3272
|
} from "react";
|
|
3178
3273
|
import { Html } from "@react-three/drei";
|
|
@@ -3202,7 +3297,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3202
3297
|
const worldPosition = useMemo8(() => new Vector33(), []);
|
|
3203
3298
|
const biasedWorldPosition = useMemo8(() => new Vector33(), []);
|
|
3204
3299
|
const toCamera = useMemo8(() => new Vector33(), []);
|
|
3205
|
-
|
|
3300
|
+
useEffect8(() => {
|
|
3206
3301
|
anchorNodeRef.current = null;
|
|
3207
3302
|
}, [annotation.anchorNodeName]);
|
|
3208
3303
|
useFrame5(({ camera }) => {
|
|
@@ -3248,7 +3343,7 @@ function AnnotationMarker3D({ annotation, occluders, worldPositionsRef, onClick,
|
|
|
3248
3343
|
const [zIndex, setZIndex] = useState4(1);
|
|
3249
3344
|
const clickTimeoutRef = useRef6(null);
|
|
3250
3345
|
const markerRef = useRef6(null);
|
|
3251
|
-
|
|
3346
|
+
useEffect8(() => {
|
|
3252
3347
|
return () => {
|
|
3253
3348
|
if (clickTimeoutRef.current) {
|
|
3254
3349
|
clearTimeout(clickTimeoutRef.current);
|
|
@@ -3430,13 +3525,13 @@ function AnnotationsLayer({
|
|
|
3430
3525
|
};
|
|
3431
3526
|
invalidateFrameRef.current = window.requestAnimationFrame(step);
|
|
3432
3527
|
}, [invalidate]);
|
|
3433
|
-
|
|
3528
|
+
useEffect8(() => {
|
|
3434
3529
|
requestAnnotationRefresh();
|
|
3435
3530
|
}, [annotations, requestAnnotationRefresh]);
|
|
3436
|
-
|
|
3531
|
+
useEffect8(() => {
|
|
3437
3532
|
requestAnnotationRefresh();
|
|
3438
3533
|
}, [activeAnnotation, requestAnnotationRefresh]);
|
|
3439
|
-
|
|
3534
|
+
useEffect8(() => {
|
|
3440
3535
|
return () => {
|
|
3441
3536
|
if (invalidateFrameRef.current !== null) {
|
|
3442
3537
|
window.cancelAnimationFrame(invalidateFrameRef.current);
|
|
@@ -3499,7 +3594,7 @@ function AnnotationsLayer({
|
|
|
3499
3594
|
onCloseAnnotation?.();
|
|
3500
3595
|
}
|
|
3501
3596
|
}, [isLocked, onCloseAnnotation]);
|
|
3502
|
-
|
|
3597
|
+
useEffect8(() => {
|
|
3503
3598
|
const canvas = canvasRootRef.current;
|
|
3504
3599
|
if (canvas) {
|
|
3505
3600
|
canvas.addEventListener("click", handleCanvasClick);
|
|
@@ -3568,7 +3663,7 @@ function AnnotationsLayer({
|
|
|
3568
3663
|
}
|
|
3569
3664
|
|
|
3570
3665
|
// src/components/ModelViewer/MeasurementLayer.tsx
|
|
3571
|
-
import { useEffect as
|
|
3666
|
+
import { useEffect as useEffect9, useMemo as useMemo9 } from "react";
|
|
3572
3667
|
import { Html as Html2, Line } from "@react-three/drei";
|
|
3573
3668
|
import { BoxGeometry, EdgesGeometry, Vector3 as Vector34 } from "three";
|
|
3574
3669
|
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -3603,7 +3698,7 @@ function DimensionsBox({ box, unit }) {
|
|
|
3603
3698
|
);
|
|
3604
3699
|
return geometry;
|
|
3605
3700
|
}, [box.size]);
|
|
3606
|
-
|
|
3701
|
+
useEffect9(() => {
|
|
3607
3702
|
return () => {
|
|
3608
3703
|
edges.dispose();
|
|
3609
3704
|
};
|
|
@@ -3659,7 +3754,7 @@ function MeasurementLayer({
|
|
|
3659
3754
|
import {
|
|
3660
3755
|
forwardRef as forwardRef2,
|
|
3661
3756
|
useCallback as useCallback2,
|
|
3662
|
-
useEffect as
|
|
3757
|
+
useEffect as useEffect10,
|
|
3663
3758
|
useImperativeHandle as useImperativeHandle2,
|
|
3664
3759
|
useMemo as useMemo10,
|
|
3665
3760
|
useRef as useRef7
|
|
@@ -3767,7 +3862,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3767
3862
|
}),
|
|
3768
3863
|
[cancelPlacementMode, finishPlacementMode, placeCurrent, startPlacementMode]
|
|
3769
3864
|
);
|
|
3770
|
-
|
|
3865
|
+
useEffect10(() => {
|
|
3771
3866
|
if (!inSession) {
|
|
3772
3867
|
placedPosition.current.set(0, 0, -FORWARD_DISTANCE);
|
|
3773
3868
|
userScale.current = 1;
|
|
@@ -3815,7 +3910,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3815
3910
|
},
|
|
3816
3911
|
[isAR, placeCurrent]
|
|
3817
3912
|
);
|
|
3818
|
-
|
|
3913
|
+
useEffect10(() => {
|
|
3819
3914
|
if (!isAR) return;
|
|
3820
3915
|
const onTouchStart = (event) => {
|
|
3821
3916
|
if (event.touches.length !== 2) return;
|
|
@@ -3900,7 +3995,7 @@ var XRModelPresenter = forwardRef2(
|
|
|
3900
3995
|
var XRModelPresenter_default = XRModelPresenter;
|
|
3901
3996
|
|
|
3902
3997
|
// src/components/ModelViewer/CinematicCamera.tsx
|
|
3903
|
-
import { useEffect as
|
|
3998
|
+
import { useEffect as useEffect11, useMemo as useMemo11, useRef as useRef8 } from "react";
|
|
3904
3999
|
import { useFrame as useFrame7, useThree as useThree9 } from "@react-three/fiber";
|
|
3905
4000
|
import {
|
|
3906
4001
|
Box3 as Box32,
|
|
@@ -3951,7 +4046,7 @@ function CinematicCamera({
|
|
|
3951
4046
|
const orbitRef = useRef8(null);
|
|
3952
4047
|
const tmpPos = useMemo11(() => new Vector36(), []);
|
|
3953
4048
|
const tmpTarget = useMemo11(() => new Vector36(), []);
|
|
3954
|
-
|
|
4049
|
+
useEffect11(() => {
|
|
3955
4050
|
if (!playing) {
|
|
3956
4051
|
startedRef.current = false;
|
|
3957
4052
|
return;
|
|
@@ -3976,7 +4071,7 @@ function CinematicCamera({
|
|
|
3976
4071
|
startedRef.current = true;
|
|
3977
4072
|
invalidate();
|
|
3978
4073
|
}, [playing, hasPath, controlsRef, sceneRef, invalidate]);
|
|
3979
|
-
|
|
4074
|
+
useEffect11(() => {
|
|
3980
4075
|
if (!playing || !onUserInteract) return;
|
|
3981
4076
|
const controls = controlsRef.current;
|
|
3982
4077
|
if (!controls) return;
|
|
@@ -4034,7 +4129,7 @@ function CinematicCamera({
|
|
|
4034
4129
|
}
|
|
4035
4130
|
|
|
4036
4131
|
// src/hooks/useLicense.ts
|
|
4037
|
-
import { useEffect as
|
|
4132
|
+
import { useEffect as useEffect12, useState as useState5 } from "react";
|
|
4038
4133
|
var LOCAL_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
4039
4134
|
var isLocalHostname = (hostname) => LOCAL_HOSTNAMES.includes(hostname) || hostname.endsWith(".local") || hostname.endsWith(".localhost");
|
|
4040
4135
|
var LICENSE_SERVER = "https://react-immersive.liveroom.dev/api/validate-license";
|
|
@@ -4209,7 +4304,7 @@ var useLicense = (licenseKey) => {
|
|
|
4209
4304
|
// window isn't available during SSR — useEffect below recomputes this on mount
|
|
4210
4305
|
isLocalDomain: false
|
|
4211
4306
|
});
|
|
4212
|
-
|
|
4307
|
+
useEffect12(() => {
|
|
4213
4308
|
if (!canUseBrowserAPI()) {
|
|
4214
4309
|
return;
|
|
4215
4310
|
}
|
|
@@ -4260,7 +4355,7 @@ var useLicense = (licenseKey) => {
|
|
|
4260
4355
|
};
|
|
4261
4356
|
|
|
4262
4357
|
// src/components/MouseController.tsx
|
|
4263
|
-
import { useRef as useRef9, useState as useState6, useCallback as useCallback3, useEffect as
|
|
4358
|
+
import { useRef as useRef9, useState as useState6, useCallback as useCallback3, useEffect as useEffect13 } from "react";
|
|
4264
4359
|
|
|
4265
4360
|
// src/styles/MouseController.css
|
|
4266
4361
|
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");
|
|
@@ -4464,7 +4559,7 @@ var MouseController = ({
|
|
|
4464
4559
|
lastRepeatTimeRef.current = 0;
|
|
4465
4560
|
}
|
|
4466
4561
|
}, []);
|
|
4467
|
-
|
|
4562
|
+
useEffect13(() => {
|
|
4468
4563
|
window.addEventListener("mousemove", onJoystickMove);
|
|
4469
4564
|
window.addEventListener("mouseup", onJoystickEnd);
|
|
4470
4565
|
window.addEventListener("touchmove", onJoystickMove, { passive: false });
|
|
@@ -4695,7 +4790,7 @@ var MaterialPicker = ({
|
|
|
4695
4790
|
};
|
|
4696
4791
|
|
|
4697
4792
|
// src/components/ModelViewer/ObjectBindingDataPanel.tsx
|
|
4698
|
-
import { Fragment as Fragment5, useEffect as
|
|
4793
|
+
import { Fragment as Fragment5, useEffect as useEffect14, useRef as useRef10, useState as useState7 } from "react";
|
|
4699
4794
|
|
|
4700
4795
|
// src/styles/ObjectBindingDataPanel.css
|
|
4701
4796
|
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 +4825,7 @@ function ActionExtra({ content }) {
|
|
|
4730
4825
|
const [expanded, setExpanded] = useState7(hasContent);
|
|
4731
4826
|
const cachedRef = useRef10(content);
|
|
4732
4827
|
if (hasContent) cachedRef.current = content;
|
|
4733
|
-
|
|
4828
|
+
useEffect14(() => {
|
|
4734
4829
|
if (hasContent) {
|
|
4735
4830
|
setMounted(true);
|
|
4736
4831
|
return;
|
|
@@ -4741,7 +4836,7 @@ function ActionExtra({ content }) {
|
|
|
4741
4836
|
return () => clearTimeout(timer);
|
|
4742
4837
|
}
|
|
4743
4838
|
}, [hasContent, mounted]);
|
|
4744
|
-
|
|
4839
|
+
useEffect14(() => {
|
|
4745
4840
|
if (mounted && hasContent) {
|
|
4746
4841
|
const frame = requestAnimationFrame(() => setExpanded(true));
|
|
4747
4842
|
return () => cancelAnimationFrame(frame);
|
|
@@ -4765,7 +4860,7 @@ function ObjectBindingDataPanel({
|
|
|
4765
4860
|
renderActionExtra
|
|
4766
4861
|
}) {
|
|
4767
4862
|
const [displayedObject, setDisplayedObject] = useState7(selectedObject);
|
|
4768
|
-
|
|
4863
|
+
useEffect14(() => {
|
|
4769
4864
|
if (selectedObject) setDisplayedObject(selectedObject);
|
|
4770
4865
|
}, [selectedObject]);
|
|
4771
4866
|
const objectToRender = selectedObject ?? displayedObject;
|
|
@@ -5170,7 +5265,7 @@ function GuidedTourCameraFollow({
|
|
|
5170
5265
|
const goal = useMemo13(() => new Vector37(), []);
|
|
5171
5266
|
const nodeName = annotation?.anchorNodeName;
|
|
5172
5267
|
const localPosition = annotation?.anchorLocalPosition;
|
|
5173
|
-
|
|
5268
|
+
useEffect15(() => {
|
|
5174
5269
|
nodeRef.current = null;
|
|
5175
5270
|
hasPrev.current = false;
|
|
5176
5271
|
}, [nodeName, annotation?.id]);
|
|
@@ -5215,6 +5310,14 @@ function computeMeasureBox(scene) {
|
|
|
5215
5310
|
size: [size.x, size.y, size.z]
|
|
5216
5311
|
};
|
|
5217
5312
|
}
|
|
5313
|
+
function computeModelBounds(scene) {
|
|
5314
|
+
const box = new Box33().setFromObject(scene);
|
|
5315
|
+
if (box.isEmpty()) return null;
|
|
5316
|
+
const center = box.getCenter(new Vector37());
|
|
5317
|
+
const radius = box.getSize(new Vector37()).length() / 2;
|
|
5318
|
+
if (!Number.isFinite(radius) || radius <= 0) return null;
|
|
5319
|
+
return { center: [center.x, center.y, center.z], radius };
|
|
5320
|
+
}
|
|
5218
5321
|
function fitClippingPlanesToScene(scene, controls) {
|
|
5219
5322
|
const camera = controls?.camera;
|
|
5220
5323
|
if (!(camera instanceof PerspectiveCamera3)) return;
|
|
@@ -5315,7 +5418,7 @@ function ModelViewer({
|
|
|
5315
5418
|
[performanceProfile]
|
|
5316
5419
|
);
|
|
5317
5420
|
const controlsRef = useRef11(null);
|
|
5318
|
-
|
|
5421
|
+
useEffect15(() => {
|
|
5319
5422
|
if (!controlsRef.current) return;
|
|
5320
5423
|
controlsRef.current.dollySpeed = Math.max(zoomSensitivity, 0);
|
|
5321
5424
|
}, [zoomSensitivity]);
|
|
@@ -5325,7 +5428,7 @@ function ModelViewer({
|
|
|
5325
5428
|
const pendingObjectId = useRef11(null);
|
|
5326
5429
|
const rendererCaptureRef = useRef11(null);
|
|
5327
5430
|
const createdBlobUrls = useRef11({});
|
|
5328
|
-
|
|
5431
|
+
useEffect15(() => {
|
|
5329
5432
|
return () => {
|
|
5330
5433
|
for (const url of Object.values(createdBlobUrls.current)) {
|
|
5331
5434
|
URL.revokeObjectURL(url);
|
|
@@ -5384,13 +5487,14 @@ function ModelViewer({
|
|
|
5384
5487
|
const [measurePoints, setMeasurePoints] = useState10([]);
|
|
5385
5488
|
const [showDimensions, setShowDimensions] = useState10(false);
|
|
5386
5489
|
const [measureBox, setMeasureBox] = useState10(null);
|
|
5490
|
+
const [modelBounds, setModelBounds] = useState10(null);
|
|
5387
5491
|
const measureModeRef = useRef11(false);
|
|
5388
5492
|
measureModeRef.current = measureMode;
|
|
5389
5493
|
const measurePointCounter = useRef11(0);
|
|
5390
5494
|
const measureDownRef = useRef11(null);
|
|
5391
5495
|
const [explodeFactor, setExplodeFactor] = useState10(0);
|
|
5392
5496
|
const [explodeOpen, setExplodeOpen] = useState10(false);
|
|
5393
|
-
|
|
5497
|
+
useEffect15(() => {
|
|
5394
5498
|
setExplodeFactor(0);
|
|
5395
5499
|
setExplodeOpen(false);
|
|
5396
5500
|
}, [modelUrl]);
|
|
@@ -5411,11 +5515,11 @@ function ModelViewer({
|
|
|
5411
5515
|
}, [cinematic, resolvedSceneConfig]);
|
|
5412
5516
|
const [cinematicPlaying, setCinematicPlaying] = useState10(false);
|
|
5413
5517
|
const cinematicAutoStartedRef = useRef11(false);
|
|
5414
|
-
|
|
5518
|
+
useEffect15(() => {
|
|
5415
5519
|
setCinematicPlaying(false);
|
|
5416
5520
|
cinematicAutoStartedRef.current = false;
|
|
5417
5521
|
}, [modelUrl]);
|
|
5418
|
-
|
|
5522
|
+
useEffect15(() => {
|
|
5419
5523
|
if (!cinematicConfig?.autoPlay || cinematicAutoStartedRef.current) return;
|
|
5420
5524
|
if (modelLoaded && cameraPositioned && !isAutoFraming) {
|
|
5421
5525
|
cinematicAutoStartedRef.current = true;
|
|
@@ -5443,7 +5547,7 @@ function ModelViewer({
|
|
|
5443
5547
|
const [isHandheld, setIsHandheld] = useState10(true);
|
|
5444
5548
|
const [resolvedMobileHandoffUrl, setResolvedMobileHandoffUrl] = useState10(null);
|
|
5445
5549
|
const [isMobileHandoffOpen, setIsMobileHandoffOpen] = useState10(false);
|
|
5446
|
-
|
|
5550
|
+
useEffect15(() => {
|
|
5447
5551
|
setIsHandheld(isHandheldDevice());
|
|
5448
5552
|
}, []);
|
|
5449
5553
|
const resolveMobileHandoffUrl = useCallback4(() => {
|
|
@@ -5458,7 +5562,7 @@ function ModelViewer({
|
|
|
5458
5562
|
return null;
|
|
5459
5563
|
}
|
|
5460
5564
|
}, [mobileHandoffUrl]);
|
|
5461
|
-
|
|
5565
|
+
useEffect15(() => {
|
|
5462
5566
|
if (!enableXR || isHandheld) {
|
|
5463
5567
|
setResolvedMobileHandoffUrl(null);
|
|
5464
5568
|
setIsMobileHandoffOpen(false);
|
|
@@ -5477,17 +5581,17 @@ function ModelViewer({
|
|
|
5477
5581
|
DEFAULT_AR_OVERLAY_STATE
|
|
5478
5582
|
);
|
|
5479
5583
|
const [relocatePressed, setRelocatePressed] = useState10(false);
|
|
5480
|
-
|
|
5584
|
+
useEffect15(() => {
|
|
5481
5585
|
if (!xrStore) return;
|
|
5482
5586
|
return xrStore.subscribe(
|
|
5483
5587
|
(state) => setInARSession(state.session != null && state.mode === "immersive-ar")
|
|
5484
5588
|
);
|
|
5485
5589
|
}, [xrStore]);
|
|
5486
|
-
|
|
5590
|
+
useEffect15(() => {
|
|
5487
5591
|
setRelocatePressed(false);
|
|
5488
5592
|
setArOverlayState(DEFAULT_AR_OVERLAY_STATE);
|
|
5489
5593
|
}, [inARSession]);
|
|
5490
|
-
|
|
5594
|
+
useEffect15(() => {
|
|
5491
5595
|
if (!enableXR) return;
|
|
5492
5596
|
const xr = navigator.xr;
|
|
5493
5597
|
if (!xr?.isSessionSupported) return;
|
|
@@ -5522,7 +5626,7 @@ function ModelViewer({
|
|
|
5522
5626
|
setRelocatePressed(true);
|
|
5523
5627
|
xrPresenterRef.current?.startPlacementMode();
|
|
5524
5628
|
}, []);
|
|
5525
|
-
|
|
5629
|
+
useEffect15(() => {
|
|
5526
5630
|
if (!relocatePressed) return;
|
|
5527
5631
|
const onPointerUp = () => finishRelocation(true);
|
|
5528
5632
|
const onPointerCancel = () => finishRelocation(false);
|
|
@@ -5585,7 +5689,7 @@ function ModelViewer({
|
|
|
5585
5689
|
const [annotations, setAnnotations] = useState10(
|
|
5586
5690
|
() => sceneConfig?.annotations ?? []
|
|
5587
5691
|
);
|
|
5588
|
-
|
|
5692
|
+
useEffect15(() => {
|
|
5589
5693
|
if (annotations.length > 0) {
|
|
5590
5694
|
const maxId = annotations.reduce((max, ann) => Math.max(max, ann.id), 0);
|
|
5591
5695
|
annotationCounterRef.current = maxId;
|
|
@@ -5751,23 +5855,23 @@ function ModelViewer({
|
|
|
5751
5855
|
);
|
|
5752
5856
|
}
|
|
5753
5857
|
}, [setActiveAnnotation]);
|
|
5754
|
-
|
|
5858
|
+
useEffect15(() => {
|
|
5755
5859
|
setCurrentAnnotationIndex(-1);
|
|
5756
5860
|
setIsGuidedTourActive(false);
|
|
5757
5861
|
}, [sortedAnnotations.length]);
|
|
5758
|
-
|
|
5862
|
+
useEffect15(() => {
|
|
5759
5863
|
return () => {
|
|
5760
5864
|
if (annotationSmoothRestoreTimeoutRef.current) {
|
|
5761
5865
|
clearTimeout(annotationSmoothRestoreTimeoutRef.current);
|
|
5762
5866
|
}
|
|
5763
5867
|
};
|
|
5764
5868
|
}, []);
|
|
5765
|
-
|
|
5869
|
+
useEffect15(() => {
|
|
5766
5870
|
if (currentAnnotation && isGuidedTourActive) {
|
|
5767
5871
|
setActiveAnnotation(currentAnnotation);
|
|
5768
5872
|
}
|
|
5769
5873
|
}, [currentAnnotation, setActiveAnnotation, isGuidedTourActive]);
|
|
5770
|
-
|
|
5874
|
+
useEffect15(() => {
|
|
5771
5875
|
if (sceneConfig?.annotations === void 0) {
|
|
5772
5876
|
return;
|
|
5773
5877
|
}
|
|
@@ -5796,7 +5900,7 @@ function ModelViewer({
|
|
|
5796
5900
|
);
|
|
5797
5901
|
return annotation ?? null;
|
|
5798
5902
|
}, [activeAnnotation, annotations]);
|
|
5799
|
-
|
|
5903
|
+
useEffect15(() => {
|
|
5800
5904
|
if (activeAnnotation && !activeViewingAnnotation) {
|
|
5801
5905
|
closeViewingAnnotation();
|
|
5802
5906
|
}
|
|
@@ -5848,12 +5952,12 @@ function ModelViewer({
|
|
|
5848
5952
|
);
|
|
5849
5953
|
const isPanelOpen = showObjectBindingDataPanel && currentSelectedObject !== null && currentSelectedObjectKey !== null;
|
|
5850
5954
|
const currentPanelObject = (currentSelectedObjectKey ? currentObjectBindings[currentSelectedObjectKey] : null) ?? currentSelectedObject;
|
|
5851
|
-
|
|
5955
|
+
useEffect15(() => {
|
|
5852
5956
|
if (!isControlledBindings) {
|
|
5853
5957
|
setInternalObjectBindings(objectBindings);
|
|
5854
5958
|
}
|
|
5855
5959
|
}, [isControlledBindings, objectBindings]);
|
|
5856
|
-
|
|
5960
|
+
useEffect15(() => {
|
|
5857
5961
|
onHiddenObjectsChange?.(currentHiddenObjects);
|
|
5858
5962
|
}, [currentHiddenObjects, onHiddenObjectsChange]);
|
|
5859
5963
|
const applySelection = useCallback4(
|
|
@@ -6035,24 +6139,24 @@ function ModelViewer({
|
|
|
6035
6139
|
bindingKeyIndex
|
|
6036
6140
|
]
|
|
6037
6141
|
);
|
|
6038
|
-
|
|
6142
|
+
useEffect15(() => {
|
|
6039
6143
|
if (!colorPicker) return;
|
|
6040
6144
|
window.addEventListener("pointerdown", closeColorPicker);
|
|
6041
6145
|
return () => window.removeEventListener("pointerdown", closeColorPicker);
|
|
6042
6146
|
}, [colorPicker, closeColorPicker]);
|
|
6043
|
-
|
|
6147
|
+
useEffect15(() => {
|
|
6044
6148
|
if (!materialPopup) return;
|
|
6045
6149
|
const close = () => setMaterialPopup(null);
|
|
6046
6150
|
window.addEventListener("pointerdown", close);
|
|
6047
6151
|
return () => window.removeEventListener("pointerdown", close);
|
|
6048
6152
|
}, [materialPopup]);
|
|
6049
|
-
|
|
6153
|
+
useEffect15(() => {
|
|
6050
6154
|
if (!downloadMenuOpen) return;
|
|
6051
6155
|
const close = () => setDownloadMenuOpen(false);
|
|
6052
6156
|
window.addEventListener("pointerdown", close);
|
|
6053
6157
|
return () => window.removeEventListener("pointerdown", close);
|
|
6054
6158
|
}, [downloadMenuOpen]);
|
|
6055
|
-
|
|
6159
|
+
useEffect15(() => {
|
|
6056
6160
|
if (!activeViewingAnnotation) return;
|
|
6057
6161
|
const handleClickOutside = (event) => {
|
|
6058
6162
|
if (viewAnnotationPopupRef.current && !viewAnnotationPopupRef.current.contains(event.target)) {
|
|
@@ -6070,7 +6174,7 @@ function ModelViewer({
|
|
|
6070
6174
|
document.removeEventListener("touchstart", handleClickOutside);
|
|
6071
6175
|
};
|
|
6072
6176
|
}, [activeViewingAnnotation, closeViewingAnnotation]);
|
|
6073
|
-
|
|
6177
|
+
useEffect15(() => {
|
|
6074
6178
|
if (!pendingAnnotation) return;
|
|
6075
6179
|
const handleClickOutside = (event) => {
|
|
6076
6180
|
const target = event.target;
|
|
@@ -6217,7 +6321,7 @@ function ModelViewer({
|
|
|
6217
6321
|
},
|
|
6218
6322
|
[captureInitialCameraState, notifyViewerReady]
|
|
6219
6323
|
);
|
|
6220
|
-
|
|
6324
|
+
useEffect15(() => {
|
|
6221
6325
|
fitAttemptedRef.current = false;
|
|
6222
6326
|
fitInFlightRef.current = false;
|
|
6223
6327
|
initialCameraStateRef.current = null;
|
|
@@ -6289,7 +6393,7 @@ function ModelViewer({
|
|
|
6289
6393
|
notifyViewerReady,
|
|
6290
6394
|
onAutoFit
|
|
6291
6395
|
]);
|
|
6292
|
-
|
|
6396
|
+
useEffect15(() => {
|
|
6293
6397
|
if (!refitOnResize) {
|
|
6294
6398
|
return;
|
|
6295
6399
|
}
|
|
@@ -6398,7 +6502,7 @@ function ModelViewer({
|
|
|
6398
6502
|
},
|
|
6399
6503
|
[applySelection, resolveBindingKey2, disableZoom]
|
|
6400
6504
|
);
|
|
6401
|
-
|
|
6505
|
+
useEffect15(() => {
|
|
6402
6506
|
notifyViewerReady();
|
|
6403
6507
|
}, [notifyViewerReady]);
|
|
6404
6508
|
const handleCameraChangeInternal = useCallback4(
|
|
@@ -6412,6 +6516,7 @@ function ModelViewer({
|
|
|
6412
6516
|
(scene) => {
|
|
6413
6517
|
sceneRef.current = scene;
|
|
6414
6518
|
setMeasureBox(computeMeasureBox(scene));
|
|
6519
|
+
setModelBounds(computeModelBounds(scene));
|
|
6415
6520
|
setModelLoaded(true);
|
|
6416
6521
|
setModelLoadFailed(false);
|
|
6417
6522
|
setMismatchDismissed(false);
|
|
@@ -6429,7 +6534,7 @@ function ModelViewer({
|
|
|
6429
6534
|
},
|
|
6430
6535
|
[onLoadError]
|
|
6431
6536
|
);
|
|
6432
|
-
|
|
6537
|
+
useEffect15(() => {
|
|
6433
6538
|
const scene = sceneRef.current;
|
|
6434
6539
|
if (!modelLoaded || !scene) {
|
|
6435
6540
|
setBindingsMismatch(false);
|
|
@@ -6490,7 +6595,7 @@ function ModelViewer({
|
|
|
6490
6595
|
},
|
|
6491
6596
|
[]
|
|
6492
6597
|
);
|
|
6493
|
-
|
|
6598
|
+
useEffect15(() => {
|
|
6494
6599
|
setMeasurePoints([]);
|
|
6495
6600
|
}, [modelUrl, resolvedSceneConfig.model.rotation]);
|
|
6496
6601
|
const handleDownloadMesh = useCallback4(() => {
|
|
@@ -6767,7 +6872,8 @@ function ModelViewer({
|
|
|
6767
6872
|
shadowConfig: resolvedSceneConfig.shadows,
|
|
6768
6873
|
shadowsEnabled: shadows,
|
|
6769
6874
|
environmentRotation: resolvedSceneConfig.environment.rotation,
|
|
6770
|
-
environmentConfig: resolvedSceneConfig.environment
|
|
6875
|
+
environmentConfig: resolvedSceneConfig.environment,
|
|
6876
|
+
modelBounds
|
|
6771
6877
|
}
|
|
6772
6878
|
),
|
|
6773
6879
|
/* @__PURE__ */ jsx19(
|
|
@@ -7327,7 +7433,7 @@ function ModelViewer({
|
|
|
7327
7433
|
}
|
|
7328
7434
|
|
|
7329
7435
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
7330
|
-
import { useCallback as useCallback8, useEffect as
|
|
7436
|
+
import { useCallback as useCallback8, useEffect as useEffect20, useMemo as useMemo16, useRef as useRef18, useState as useState24 } from "react";
|
|
7331
7437
|
import {
|
|
7332
7438
|
Redo2,
|
|
7333
7439
|
Undo2,
|
|
@@ -7579,7 +7685,7 @@ function useViewerCamera() {
|
|
|
7579
7685
|
}
|
|
7580
7686
|
|
|
7581
7687
|
// src/hooks/useViewerAnimations.ts
|
|
7582
|
-
import { useCallback as useCallback6, useEffect as
|
|
7688
|
+
import { useCallback as useCallback6, useEffect as useEffect16, useRef as useRef13, useState as useState12 } from "react";
|
|
7583
7689
|
function useViewerAnimations() {
|
|
7584
7690
|
const controlsRef = useRef13(null);
|
|
7585
7691
|
const [clips, setClips] = useState12([]);
|
|
@@ -7590,7 +7696,7 @@ function useViewerAnimations() {
|
|
|
7590
7696
|
const [speed, setSpeedState] = useState12(1);
|
|
7591
7697
|
const [time, setTime] = useState12(0);
|
|
7592
7698
|
const [duration, setDuration] = useState12(0);
|
|
7593
|
-
|
|
7699
|
+
useEffect16(
|
|
7594
7700
|
() => () => {
|
|
7595
7701
|
unsubscribeRef.current?.();
|
|
7596
7702
|
},
|
|
@@ -7852,7 +7958,7 @@ function inferMimeType(path) {
|
|
|
7852
7958
|
}
|
|
7853
7959
|
|
|
7854
7960
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
7855
|
-
import { useState as useState19, useEffect as
|
|
7961
|
+
import { useState as useState19, useEffect as useEffect18 } from "react";
|
|
7856
7962
|
|
|
7857
7963
|
// src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
|
|
7858
7964
|
import { useState as useState14 } from "react";
|
|
@@ -7880,9 +7986,9 @@ function getContrastTextColor(hex) {
|
|
|
7880
7986
|
}
|
|
7881
7987
|
|
|
7882
7988
|
// src/hooks/useClickOutside.ts
|
|
7883
|
-
import { useEffect as
|
|
7989
|
+
import { useEffect as useEffect17 } from "react";
|
|
7884
7990
|
function useClickOutside(refs, onOutside, active) {
|
|
7885
|
-
|
|
7991
|
+
useEffect17(() => {
|
|
7886
7992
|
if (!active) return;
|
|
7887
7993
|
const refList = Array.isArray(refs) ? refs : [refs];
|
|
7888
7994
|
const handlePointerDown = (event) => {
|
|
@@ -11664,7 +11770,7 @@ function SceneSettingsTab({
|
|
|
11664
11770
|
const { tier } = useBindingBuilderContext();
|
|
11665
11771
|
const canUseStarterFeatures = hasTier(tier, "starter");
|
|
11666
11772
|
const canUseGrowthFeatures = hasTier(tier, "growth");
|
|
11667
|
-
|
|
11773
|
+
useEffect18(() => {
|
|
11668
11774
|
if (controlledActiveSubTab !== void 0) {
|
|
11669
11775
|
setInternalActiveSubTab(controlledActiveSubTab);
|
|
11670
11776
|
}
|
|
@@ -13152,7 +13258,7 @@ function MaterialPresetGallery({
|
|
|
13152
13258
|
}
|
|
13153
13259
|
|
|
13154
13260
|
// src/components/BindingBuilder/components/ToolbarExportMenu.tsx
|
|
13155
|
-
import { useEffect as
|
|
13261
|
+
import { useEffect as useEffect19, useRef as useRef16, useState as useState22 } from "react";
|
|
13156
13262
|
import { ChevronDown as ChevronDown5, Download as Download2, FileCode2, FileJson } from "lucide-react";
|
|
13157
13263
|
import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
13158
13264
|
function ToolbarExportMenu({
|
|
@@ -13163,7 +13269,7 @@ function ToolbarExportMenu({
|
|
|
13163
13269
|
}) {
|
|
13164
13270
|
const [open, setOpen] = useState22(false);
|
|
13165
13271
|
const wrapperRef = useRef16(null);
|
|
13166
|
-
|
|
13272
|
+
useEffect19(() => {
|
|
13167
13273
|
if (!open) return;
|
|
13168
13274
|
const close = (event) => {
|
|
13169
13275
|
if (!wrapperRef.current?.contains(event.target)) {
|
|
@@ -13603,8 +13709,6 @@ function CameraPositionPanel({
|
|
|
13603
13709
|
import { useMemo as useMemo15, useState as useState23 } from "react";
|
|
13604
13710
|
|
|
13605
13711
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
13606
|
-
import { GLTFLoader } from "three-stdlib";
|
|
13607
|
-
import { WebGLRenderer as WebGLRenderer2 } from "three";
|
|
13608
13712
|
function slugify(value) {
|
|
13609
13713
|
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
13714
|
}
|
|
@@ -13670,51 +13774,15 @@ function buildBindingsFromNodes(nodeNames) {
|
|
|
13670
13774
|
nodeNames.map((nodeName) => [nodeName, createDefaultBinding(nodeName)])
|
|
13671
13775
|
);
|
|
13672
13776
|
}
|
|
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, {});
|
|
13777
|
+
function collectNodeNames(scene) {
|
|
13778
|
+
const names = /* @__PURE__ */ new Set();
|
|
13779
|
+
scene.traverse((child) => {
|
|
13780
|
+
const mesh = child;
|
|
13781
|
+
if (mesh.isMesh && mesh.geometry && child.name) {
|
|
13782
|
+
names.add(child.name);
|
|
13686
13783
|
}
|
|
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
13784
|
});
|
|
13785
|
+
return Array.from(names).sort((a, b) => a.localeCompare(b));
|
|
13718
13786
|
}
|
|
13719
13787
|
|
|
13720
13788
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
@@ -14260,6 +14328,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14260
14328
|
const uploadedModelRef = useRef18(null);
|
|
14261
14329
|
const previewDragDepthRef = useRef18(0);
|
|
14262
14330
|
const loadRequestRef = useRef18(0);
|
|
14331
|
+
const pendingLoadRef = useRef18(null);
|
|
14332
|
+
const loadedSceneRef = useRef18(null);
|
|
14263
14333
|
const {
|
|
14264
14334
|
cameraState,
|
|
14265
14335
|
resetView,
|
|
@@ -14317,7 +14387,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14317
14387
|
},
|
|
14318
14388
|
[]
|
|
14319
14389
|
);
|
|
14320
|
-
|
|
14390
|
+
useEffect20(() => {
|
|
14321
14391
|
if (!selectedBinding) {
|
|
14322
14392
|
setMetricsDraft("{}");
|
|
14323
14393
|
setMetadataDraft("{}");
|
|
@@ -14330,7 +14400,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14330
14400
|
setMetricsError(null);
|
|
14331
14401
|
setMetadataError(null);
|
|
14332
14402
|
}, [selectedBinding]);
|
|
14333
|
-
|
|
14403
|
+
useEffect20(() => {
|
|
14334
14404
|
const derived = createMaterialPanelEnabledState(selectedMaterial);
|
|
14335
14405
|
if (materialPanelItemRef.current !== selectedItem) {
|
|
14336
14406
|
materialPanelItemRef.current = selectedItem;
|
|
@@ -14349,12 +14419,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14349
14419
|
return changed ? next : prev;
|
|
14350
14420
|
});
|
|
14351
14421
|
}, [selectedItem, selectedMaterial]);
|
|
14352
|
-
|
|
14422
|
+
useEffect20(() => {
|
|
14353
14423
|
return () => {
|
|
14354
14424
|
uploadedModelRef.current?.cleanup();
|
|
14355
14425
|
};
|
|
14356
14426
|
}, []);
|
|
14357
|
-
|
|
14427
|
+
useEffect20(() => {
|
|
14358
14428
|
const handleKeyDown = (event) => {
|
|
14359
14429
|
if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
|
|
14360
14430
|
return;
|
|
@@ -14375,7 +14445,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14375
14445
|
window.addEventListener("keydown", handleKeyDown);
|
|
14376
14446
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
14377
14447
|
}, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
|
|
14378
|
-
|
|
14448
|
+
useEffect20(() => {
|
|
14379
14449
|
if (loading || statusTone === "default" || statusTone === "success") return;
|
|
14380
14450
|
setStatusBannerVisible(true);
|
|
14381
14451
|
const timeout = window.setTimeout(
|
|
@@ -14428,42 +14498,76 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14428
14498
|
});
|
|
14429
14499
|
}
|
|
14430
14500
|
};
|
|
14431
|
-
const
|
|
14501
|
+
const applyBindingsFromScene = useCallback8(
|
|
14502
|
+
(scene, nextModelName) => {
|
|
14503
|
+
const nodeNames = collectNodeNames(scene);
|
|
14504
|
+
bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
|
|
14505
|
+
setSelectedItem(nodeNames[0] ?? null);
|
|
14506
|
+
setSceneConfig((prev) => ({ ...prev, annotations: [] }));
|
|
14507
|
+
setStatusMessage(
|
|
14508
|
+
`Generated ${nodeNames.length} bindings from ${nextModelName}.`
|
|
14509
|
+
);
|
|
14510
|
+
setStatusTone("success");
|
|
14511
|
+
setLoading(false);
|
|
14512
|
+
},
|
|
14513
|
+
[bindingsHistory, setSceneConfig]
|
|
14514
|
+
);
|
|
14515
|
+
const generateBindingsForModel = (nextModelUrl, nextModelName) => {
|
|
14432
14516
|
const requestId = ++loadRequestRef.current;
|
|
14433
14517
|
setLoading(true);
|
|
14434
14518
|
setStatusMessage(
|
|
14435
14519
|
`Reading ${nextModelName} and generating starter bindings...`
|
|
14436
14520
|
);
|
|
14437
14521
|
setStatusTone("default");
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14522
|
+
setModelName(nextModelName);
|
|
14523
|
+
setActiveAnnotation(null);
|
|
14524
|
+
const loaded = loadedSceneRef.current;
|
|
14525
|
+
if (nextModelUrl === modelUrl && loaded?.url === nextModelUrl) {
|
|
14526
|
+
pendingLoadRef.current = null;
|
|
14527
|
+
applyBindingsFromScene(loaded.scene, nextModelName);
|
|
14528
|
+
return;
|
|
14529
|
+
}
|
|
14530
|
+
pendingLoadRef.current = {
|
|
14531
|
+
requestId,
|
|
14532
|
+
modelName: nextModelName,
|
|
14533
|
+
modelUrl: nextModelUrl
|
|
14534
|
+
};
|
|
14535
|
+
setModelUrl(nextModelUrl);
|
|
14536
|
+
bindingsHistory.reset({});
|
|
14537
|
+
setSelectedItem(null);
|
|
14538
|
+
};
|
|
14539
|
+
const handleViewerModelLoaded = useCallback8(
|
|
14540
|
+
(scene) => {
|
|
14541
|
+
const pending = pendingLoadRef.current;
|
|
14542
|
+
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14543
|
+
pendingLoadRef.current = null;
|
|
14544
|
+
loadedSceneRef.current = { url: pending.modelUrl, scene };
|
|
14545
|
+
applyBindingsFromScene(scene, pending.modelName);
|
|
14546
|
+
},
|
|
14547
|
+
[applyBindingsFromScene]
|
|
14548
|
+
);
|
|
14549
|
+
const handleViewerLoadError = useCallback8((error) => {
|
|
14550
|
+
const pending = pendingLoadRef.current;
|
|
14551
|
+
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14552
|
+
pendingLoadRef.current = null;
|
|
14553
|
+
console.error(error);
|
|
14554
|
+
setStatusMessage(
|
|
14555
|
+
`Could not read ${pending.modelName}. Make sure the file is a valid GLB/GLTF asset.`
|
|
14556
|
+
);
|
|
14557
|
+
setStatusTone("error");
|
|
14558
|
+
setLoading(false);
|
|
14559
|
+
}, []);
|
|
14560
|
+
const handleAnimationsReady = useCallback8(
|
|
14561
|
+
(controls) => {
|
|
14562
|
+
viewerAnimations.handleAnimationsReady(controls);
|
|
14563
|
+
const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
|
|
14447
14564
|
setSceneConfig((prev) => ({
|
|
14448
14565
|
...prev,
|
|
14449
|
-
animations: mergeAnimationConfig(prev,
|
|
14450
|
-
annotations: []
|
|
14566
|
+
animations: mergeAnimationConfig(prev, details)
|
|
14451
14567
|
}));
|
|
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
|
-
};
|
|
14568
|
+
},
|
|
14569
|
+
[viewerAnimations, setSceneConfig]
|
|
14570
|
+
);
|
|
14467
14571
|
const handleLoadDemo = async () => {
|
|
14468
14572
|
uploadedModelRef.current?.cleanup();
|
|
14469
14573
|
uploadedModelRef.current = null;
|
|
@@ -14493,6 +14597,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14493
14597
|
const clearModel = () => {
|
|
14494
14598
|
uploadedModelRef.current?.cleanup();
|
|
14495
14599
|
uploadedModelRef.current = null;
|
|
14600
|
+
loadedSceneRef.current = null;
|
|
14601
|
+
pendingLoadRef.current = null;
|
|
14496
14602
|
previewDragDepthRef.current = 0;
|
|
14497
14603
|
setIsPreviewDragActive(false);
|
|
14498
14604
|
setModelUrl(null);
|
|
@@ -15291,7 +15397,9 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15291
15397
|
onAnnotationsChange: handleAnnotationsChange,
|
|
15292
15398
|
activeAnnotation: activeAnnotation?.annotation ?? null,
|
|
15293
15399
|
onActiveAnnotationChange: handleActiveAnnotationChange,
|
|
15294
|
-
onAnimationsReady:
|
|
15400
|
+
onAnimationsReady: handleAnimationsReady,
|
|
15401
|
+
onModelLoaded: handleViewerModelLoaded,
|
|
15402
|
+
onLoadError: handleViewerLoadError,
|
|
15295
15403
|
licenseKey,
|
|
15296
15404
|
sceneConfig,
|
|
15297
15405
|
disableZoom: false,
|
|
@@ -15374,7 +15482,7 @@ import {
|
|
|
15374
15482
|
useRef as useRef19,
|
|
15375
15483
|
useState as useState25,
|
|
15376
15484
|
useCallback as useCallback9,
|
|
15377
|
-
useEffect as
|
|
15485
|
+
useEffect as useEffect21,
|
|
15378
15486
|
useMemo as useMemo17,
|
|
15379
15487
|
Component as Component3,
|
|
15380
15488
|
Suspense as Suspense3
|
|
@@ -15492,14 +15600,14 @@ function InnerModel({
|
|
|
15492
15600
|
}
|
|
15493
15601
|
return map;
|
|
15494
15602
|
}, [meshMap]);
|
|
15495
|
-
|
|
15603
|
+
useEffect21(
|
|
15496
15604
|
() => () => {
|
|
15497
15605
|
Object.values(perKeyMaterials).forEach((m) => m.dispose());
|
|
15498
15606
|
},
|
|
15499
15607
|
[perKeyMaterials]
|
|
15500
15608
|
);
|
|
15501
15609
|
const notifiedUrl = useRef19(null);
|
|
15502
|
-
|
|
15610
|
+
useEffect21(() => {
|
|
15503
15611
|
if (notifiedUrl.current === url) return;
|
|
15504
15612
|
notifiedUrl.current = url;
|
|
15505
15613
|
scene.updateWorldMatrix(true, true);
|
|
@@ -15507,22 +15615,22 @@ function InnerModel({
|
|
|
15507
15615
|
onMeshesReady(meshMap);
|
|
15508
15616
|
onModelLoaded(scene);
|
|
15509
15617
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
15510
|
-
|
|
15618
|
+
useEffect21(() => {
|
|
15511
15619
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15512
15620
|
nodeRefsOut.current[key] = ref;
|
|
15513
15621
|
}
|
|
15514
15622
|
}, [meshMap, nodeRefsOut]);
|
|
15515
|
-
|
|
15623
|
+
useEffect21(() => {
|
|
15516
15624
|
if (!highlightOnHover && hoveredKey !== null) {
|
|
15517
15625
|
setHoveredKey(null);
|
|
15518
15626
|
}
|
|
15519
15627
|
}, [highlightOnHover, hoveredKey]);
|
|
15520
|
-
|
|
15628
|
+
useEffect21(() => {
|
|
15521
15629
|
return () => {
|
|
15522
15630
|
document.body.style.cursor = "default";
|
|
15523
15631
|
};
|
|
15524
15632
|
}, []);
|
|
15525
|
-
|
|
15633
|
+
useEffect21(() => {
|
|
15526
15634
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15527
15635
|
ref.visible = !hiddenKeys.has(key);
|
|
15528
15636
|
const mat = perKeyMaterials[key];
|
|
@@ -15599,7 +15707,7 @@ function CameraFitter({
|
|
|
15599
15707
|
const invalidate = useThree11((state) => state.invalidate);
|
|
15600
15708
|
const size = useThree11((state) => state.size);
|
|
15601
15709
|
const resizeTimerRef = useRef19(null);
|
|
15602
|
-
|
|
15710
|
+
useEffect21(() => {
|
|
15603
15711
|
fitted.current = false;
|
|
15604
15712
|
invalidate();
|
|
15605
15713
|
}, [invalidate, scene]);
|
|
@@ -15619,7 +15727,7 @@ function CameraFitter({
|
|
|
15619
15727
|
}).catch(() => {
|
|
15620
15728
|
}).finally(onInitialFitComplete);
|
|
15621
15729
|
});
|
|
15622
|
-
|
|
15730
|
+
useEffect21(() => {
|
|
15623
15731
|
if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
|
|
15624
15732
|
return;
|
|
15625
15733
|
}
|
|
@@ -15647,7 +15755,7 @@ function CameraFitter({
|
|
|
15647
15755
|
function ExposureController({ exposure }) {
|
|
15648
15756
|
const gl = useThree11((state) => state.gl);
|
|
15649
15757
|
const invalidate = useThree11((state) => state.invalidate);
|
|
15650
|
-
|
|
15758
|
+
useEffect21(() => {
|
|
15651
15759
|
gl.toneMappingExposure = exposure;
|
|
15652
15760
|
invalidate();
|
|
15653
15761
|
}, [gl, exposure, invalidate]);
|
|
@@ -16150,7 +16258,7 @@ function SimpleModelViewer({
|
|
|
16150
16258
|
},
|
|
16151
16259
|
[handleUploadedModel]
|
|
16152
16260
|
);
|
|
16153
|
-
|
|
16261
|
+
useEffect21(() => {
|
|
16154
16262
|
return () => {
|
|
16155
16263
|
revokeUploadedModelUrl();
|
|
16156
16264
|
};
|
|
@@ -16161,10 +16269,10 @@ function SimpleModelViewer({
|
|
|
16161
16269
|
},
|
|
16162
16270
|
[]
|
|
16163
16271
|
);
|
|
16164
|
-
|
|
16272
|
+
useEffect21(() => {
|
|
16165
16273
|
setEnvSettings((prev) => ({ ...prev, backgroundColor }));
|
|
16166
16274
|
}, [backgroundColor]);
|
|
16167
|
-
|
|
16275
|
+
useEffect21(() => {
|
|
16168
16276
|
resetModelState();
|
|
16169
16277
|
}, [activeModelUrl, resetModelState]);
|
|
16170
16278
|
const handleMeshesReady = useCallback9((meshes) => {
|
|
@@ -16726,7 +16834,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
16726
16834
|
}
|
|
16727
16835
|
|
|
16728
16836
|
// src/hooks/useShareableViewerState.ts
|
|
16729
|
-
import { useCallback as useCallback15, useEffect as
|
|
16837
|
+
import { useCallback as useCallback15, useEffect as useEffect22, useRef as useRef20, useState as useState29 } from "react";
|
|
16730
16838
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
16731
16839
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
16732
16840
|
return {
|
|
@@ -16780,7 +16888,7 @@ function useShareableViewerState({
|
|
|
16780
16888
|
};
|
|
16781
16889
|
const [restoreAttempted, setRestoreAttempted] = useState29(false);
|
|
16782
16890
|
const lastSyncedHashRef = useRef20(null);
|
|
16783
|
-
|
|
16891
|
+
useEffect22(() => {
|
|
16784
16892
|
if (!autoSync || !restoreAttempted) return;
|
|
16785
16893
|
const tick = () => {
|
|
16786
16894
|
const current = latestStateRef.current;
|
|
@@ -16857,7 +16965,7 @@ function useShareableViewerState({
|
|
|
16857
16965
|
setCameraState,
|
|
16858
16966
|
setSelectedObjectBinding
|
|
16859
16967
|
]);
|
|
16860
|
-
|
|
16968
|
+
useEffect22(() => {
|
|
16861
16969
|
const handleHashChange = () => {
|
|
16862
16970
|
void restoreFromUrl();
|
|
16863
16971
|
};
|