@liveroom-tech/react-immersive 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -28
- package/dist/index.d.mts +386 -76
- package/dist/index.d.ts +386 -76
- package/dist/index.js +1793 -490
- package/dist/index.mjs +1616 -313
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -174,7 +174,7 @@ import {
|
|
|
174
174
|
Raycaster,
|
|
175
175
|
Sphere as Sphere3,
|
|
176
176
|
Vector2 as Vector26,
|
|
177
|
-
Vector3 as
|
|
177
|
+
Vector3 as Vector39
|
|
178
178
|
} from "three";
|
|
179
179
|
|
|
180
180
|
// #style-inject:#style-inject
|
|
@@ -2053,6 +2053,28 @@ function Controls({ controlsRef, onCameraChange }) {
|
|
|
2053
2053
|
// src/components/Lights.tsx
|
|
2054
2054
|
import { useRef as useRef4, useMemo as useMemo5, useEffect as useEffect6 } from "react";
|
|
2055
2055
|
import { useFrame as useFrame3, useThree as useThree5 } from "@react-three/fiber";
|
|
2056
|
+
import {
|
|
2057
|
+
Vector3 as Vector32
|
|
2058
|
+
} from "three";
|
|
2059
|
+
|
|
2060
|
+
// src/utils/resolveBindingKey.ts
|
|
2061
|
+
function resolveBindingKey(objectBindings, objectId) {
|
|
2062
|
+
if (objectBindings[objectId]) {
|
|
2063
|
+
return objectId;
|
|
2064
|
+
}
|
|
2065
|
+
const idMatch = Object.entries(objectBindings).find(
|
|
2066
|
+
([, binding]) => binding.id === objectId
|
|
2067
|
+
)?.[0];
|
|
2068
|
+
if (idMatch) {
|
|
2069
|
+
return idMatch;
|
|
2070
|
+
}
|
|
2071
|
+
const modelObjectMatch = Object.entries(objectBindings).find(
|
|
2072
|
+
([, binding]) => binding.modelObjectId === objectId
|
|
2073
|
+
)?.[0];
|
|
2074
|
+
return modelObjectMatch ?? null;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/components/Lights.tsx
|
|
2056
2078
|
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2057
2079
|
function rotatePositionY(pos, deg) {
|
|
2058
2080
|
if (!deg) return pos;
|
|
@@ -2132,21 +2154,42 @@ function LightInstance({
|
|
|
2132
2154
|
shadows,
|
|
2133
2155
|
shadowsEnabled,
|
|
2134
2156
|
environmentRotation,
|
|
2135
|
-
modelBounds
|
|
2157
|
+
modelBounds,
|
|
2158
|
+
objectBindings,
|
|
2159
|
+
nodeRefs
|
|
2136
2160
|
}) {
|
|
2137
2161
|
const dirRef = useRef4(null);
|
|
2138
2162
|
const pointRef = useRef4(null);
|
|
2139
2163
|
const spotRef = useRef4(null);
|
|
2140
2164
|
const camera = useThree5((s) => s.camera);
|
|
2165
|
+
const rootScene = useThree5((s) => s.scene);
|
|
2166
|
+
const attachedPosition = useMemo5(() => new Vector32(), []);
|
|
2141
2167
|
const position = useMemo5(
|
|
2142
|
-
() => light.attachedToCamera ? light.position : rotatePositionY(light.position, environmentRotation),
|
|
2143
|
-
[
|
|
2168
|
+
() => light.attachedToCamera || light.attachment ? light.position : rotatePositionY(light.position, environmentRotation),
|
|
2169
|
+
[
|
|
2170
|
+
light.position,
|
|
2171
|
+
light.attachedToCamera,
|
|
2172
|
+
light.attachment,
|
|
2173
|
+
environmentRotation
|
|
2174
|
+
]
|
|
2144
2175
|
);
|
|
2145
2176
|
useFrame3(() => {
|
|
2146
|
-
|
|
2147
|
-
if (
|
|
2148
|
-
if (
|
|
2149
|
-
|
|
2177
|
+
const instance = dirRef.current ?? pointRef.current ?? spotRef.current;
|
|
2178
|
+
if (!instance) return;
|
|
2179
|
+
if (light.attachedToCamera) {
|
|
2180
|
+
instance.position.copy(camera.position);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const attachment = light.attachment;
|
|
2184
|
+
if (!attachment) return;
|
|
2185
|
+
const bindingKey = objectBindings ? resolveBindingKey(objectBindings, attachment.objectId) : null;
|
|
2186
|
+
const binding = bindingKey ? objectBindings?.[bindingKey] : void 0;
|
|
2187
|
+
const modelObjectId = binding?.modelObjectId ?? attachment.objectId;
|
|
2188
|
+
const target = (bindingKey ? nodeRefs?.current[bindingKey] : void 0) ?? rootScene.getObjectByName(modelObjectId);
|
|
2189
|
+
if (!target) return;
|
|
2190
|
+
attachedPosition.fromArray(attachment.offset);
|
|
2191
|
+
target.localToWorld(attachedPosition);
|
|
2192
|
+
instance.position.copy(attachedPosition);
|
|
2150
2193
|
});
|
|
2151
2194
|
const castShadow = shadowsEnabled && shadows.enabled && light.castShadow;
|
|
2152
2195
|
const fit = useMemo5(
|
|
@@ -2192,6 +2235,8 @@ function LightInstance({
|
|
|
2192
2235
|
color: light.color,
|
|
2193
2236
|
intensity: light.intensity,
|
|
2194
2237
|
position,
|
|
2238
|
+
distance: light.distance,
|
|
2239
|
+
decay: light.decay,
|
|
2195
2240
|
castShadow,
|
|
2196
2241
|
"shadow-mapSize-width": shadows.mapSize,
|
|
2197
2242
|
"shadow-mapSize-height": shadows.mapSize,
|
|
@@ -2217,7 +2262,9 @@ function Lights({
|
|
|
2217
2262
|
shadowsEnabled = true,
|
|
2218
2263
|
environmentRotation = 0,
|
|
2219
2264
|
environmentConfig,
|
|
2220
|
-
modelBounds
|
|
2265
|
+
modelBounds,
|
|
2266
|
+
objectBindings,
|
|
2267
|
+
nodeRefs
|
|
2221
2268
|
}) {
|
|
2222
2269
|
const lighting = lightingConfig ?? DEFAULT_LIGHTING;
|
|
2223
2270
|
const shadows = shadowConfig ?? DEFAULT_SHADOWS;
|
|
@@ -2238,7 +2285,9 @@ function Lights({
|
|
|
2238
2285
|
shadows,
|
|
2239
2286
|
shadowsEnabled,
|
|
2240
2287
|
environmentRotation,
|
|
2241
|
-
modelBounds
|
|
2288
|
+
modelBounds,
|
|
2289
|
+
objectBindings,
|
|
2290
|
+
nodeRefs
|
|
2242
2291
|
},
|
|
2243
2292
|
light.id
|
|
2244
2293
|
)),
|
|
@@ -2259,7 +2308,9 @@ function Lights({
|
|
|
2259
2308
|
shadows,
|
|
2260
2309
|
shadowsEnabled,
|
|
2261
2310
|
environmentRotation: 0,
|
|
2262
|
-
modelBounds
|
|
2311
|
+
modelBounds,
|
|
2312
|
+
objectBindings,
|
|
2313
|
+
nodeRefs
|
|
2263
2314
|
}
|
|
2264
2315
|
)
|
|
2265
2316
|
] });
|
|
@@ -2368,7 +2419,7 @@ import {
|
|
|
2368
2419
|
LoopOnce,
|
|
2369
2420
|
LoopRepeat,
|
|
2370
2421
|
Box3 as Box32,
|
|
2371
|
-
Vector3 as
|
|
2422
|
+
Vector3 as Vector33,
|
|
2372
2423
|
Matrix4,
|
|
2373
2424
|
MathUtils as MathUtils2
|
|
2374
2425
|
} from "three";
|
|
@@ -2984,6 +3035,24 @@ function refreshSkinnedBounds(root) {
|
|
|
2984
3035
|
|
|
2985
3036
|
// src/components/Model.tsx
|
|
2986
3037
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3038
|
+
function applyBindingTransform(node, transform, original) {
|
|
3039
|
+
if (!original) return;
|
|
3040
|
+
if (transform?.position) node.position.fromArray(transform.position);
|
|
3041
|
+
else node.position.copy(original.position);
|
|
3042
|
+
if (transform?.rotation) {
|
|
3043
|
+
node.rotation.set(
|
|
3044
|
+
transform.rotation[0],
|
|
3045
|
+
transform.rotation[1],
|
|
3046
|
+
transform.rotation[2],
|
|
3047
|
+
original.rotation.order
|
|
3048
|
+
);
|
|
3049
|
+
} else {
|
|
3050
|
+
node.rotation.copy(original.rotation);
|
|
3051
|
+
}
|
|
3052
|
+
if (transform?.scale) node.scale.fromArray(transform.scale);
|
|
3053
|
+
else node.scale.copy(original.scale);
|
|
3054
|
+
node.updateMatrix();
|
|
3055
|
+
}
|
|
2987
3056
|
function firstMaterial(node) {
|
|
2988
3057
|
const mat = node.material;
|
|
2989
3058
|
if (!mat) return void 0;
|
|
@@ -3037,6 +3106,17 @@ function Model({
|
|
|
3037
3106
|
});
|
|
3038
3107
|
return map;
|
|
3039
3108
|
}, [scene]);
|
|
3109
|
+
const originalNodeTransforms = useMemo7(() => {
|
|
3110
|
+
const transforms = /* @__PURE__ */ new WeakMap();
|
|
3111
|
+
scene.traverse((node) => {
|
|
3112
|
+
transforms.set(node, {
|
|
3113
|
+
position: node.position.clone(),
|
|
3114
|
+
rotation: node.rotation.clone(),
|
|
3115
|
+
scale: node.scale.clone()
|
|
3116
|
+
});
|
|
3117
|
+
});
|
|
3118
|
+
return transforms;
|
|
3119
|
+
}, [scene]);
|
|
3040
3120
|
const shellNodeNames = useMemo7(() => {
|
|
3041
3121
|
const shell = /* @__PURE__ */ new Set();
|
|
3042
3122
|
const sceneBox = new Box32();
|
|
@@ -3048,8 +3128,8 @@ function Model({
|
|
|
3048
3128
|
meshNodes.push({ name, box });
|
|
3049
3129
|
sceneBox.union(box);
|
|
3050
3130
|
}
|
|
3051
|
-
const sceneSize = sceneBox.getSize(new
|
|
3052
|
-
const size = new
|
|
3131
|
+
const sceneSize = sceneBox.getSize(new Vector33());
|
|
3132
|
+
const size = new Vector33();
|
|
3053
3133
|
for (const { name, box } of meshNodes) {
|
|
3054
3134
|
box.getSize(size);
|
|
3055
3135
|
const isShell = size.x > sceneSize.x * 0.7 && size.z > sceneSize.z * 0.7 && size.y > sceneSize.y * 0.4;
|
|
@@ -3089,6 +3169,43 @@ function Model({
|
|
|
3089
3169
|
).sort().join(""),
|
|
3090
3170
|
[objectBindings]
|
|
3091
3171
|
);
|
|
3172
|
+
const bindingTransformSignature = useMemo7(
|
|
3173
|
+
() => Object.entries(objectBindings).map(
|
|
3174
|
+
([nodeName, binding]) => JSON.stringify([
|
|
3175
|
+
nodeName,
|
|
3176
|
+
binding.modelObjectId ?? nodeName,
|
|
3177
|
+
binding.transform ?? null
|
|
3178
|
+
])
|
|
3179
|
+
).sort().join(""),
|
|
3180
|
+
[objectBindings]
|
|
3181
|
+
);
|
|
3182
|
+
const transformedNodesRef = useRef5(/* @__PURE__ */ new Set());
|
|
3183
|
+
useEffect7(() => {
|
|
3184
|
+
for (const node of transformedNodesRef.current) {
|
|
3185
|
+
applyBindingTransform(node, void 0, originalNodeTransforms.get(node));
|
|
3186
|
+
}
|
|
3187
|
+
const nextTransformedNodes = /* @__PURE__ */ new Set();
|
|
3188
|
+
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
3189
|
+
if (!binding.transform) continue;
|
|
3190
|
+
const node = nodeMap[binding.modelObjectId ?? nodeName];
|
|
3191
|
+
if (!node) continue;
|
|
3192
|
+
applyBindingTransform(
|
|
3193
|
+
node,
|
|
3194
|
+
binding.transform,
|
|
3195
|
+
originalNodeTransforms.get(node)
|
|
3196
|
+
);
|
|
3197
|
+
nextTransformedNodes.add(node);
|
|
3198
|
+
}
|
|
3199
|
+
transformedNodesRef.current = nextTransformedNodes;
|
|
3200
|
+
scene.updateMatrixWorld(true);
|
|
3201
|
+
invalidate();
|
|
3202
|
+
}, [
|
|
3203
|
+
scene,
|
|
3204
|
+
nodeMap,
|
|
3205
|
+
originalNodeTransforms,
|
|
3206
|
+
bindingTransformSignature,
|
|
3207
|
+
invalidate
|
|
3208
|
+
]);
|
|
3092
3209
|
const perNodeMaterials = useMemo7(() => {
|
|
3093
3210
|
const map = {};
|
|
3094
3211
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -3592,27 +3709,30 @@ function Model({
|
|
|
3592
3709
|
const explodePartsRef = useRef5([]);
|
|
3593
3710
|
const explodeStrengthRef = useRef5(0);
|
|
3594
3711
|
const currentExplodeRef = useRef5(0);
|
|
3595
|
-
const explodeTmp = useMemo7(() => new
|
|
3712
|
+
const explodeTmp = useMemo7(() => new Vector33(), []);
|
|
3596
3713
|
useEffect7(() => {
|
|
3714
|
+
const bindingByNode = /* @__PURE__ */ new Map();
|
|
3715
|
+
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
3716
|
+
const node = nodeMap[binding.modelObjectId ?? nodeName];
|
|
3717
|
+
if (node) bindingByNode.set(node, binding);
|
|
3718
|
+
}
|
|
3597
3719
|
for (const part of explodePartsRef.current) {
|
|
3598
|
-
|
|
3720
|
+
applyBindingTransform(
|
|
3721
|
+
part.node,
|
|
3722
|
+
bindingByNode.get(part.node)?.transform,
|
|
3723
|
+
originalNodeTransforms.get(part.node)
|
|
3724
|
+
);
|
|
3599
3725
|
}
|
|
3600
3726
|
currentExplodeRef.current = 0;
|
|
3601
3727
|
scene.updateWorldMatrix(true, true);
|
|
3602
3728
|
const sceneBox = new Box32().setFromObject(scene);
|
|
3603
|
-
const modelCenter = sceneBox.getCenter(new
|
|
3604
|
-
const radius = sceneBox.getSize(new
|
|
3729
|
+
const modelCenter = sceneBox.getCenter(new Vector33());
|
|
3730
|
+
const radius = sceneBox.getSize(new Vector33()).length() * 0.5;
|
|
3605
3731
|
explodeStrengthRef.current = Number.isFinite(radius) ? radius : 0;
|
|
3606
|
-
const targets = [];
|
|
3607
|
-
const targetSet =
|
|
3608
|
-
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
3609
|
-
const node = nodeMap[binding.modelObjectId ?? nodeName];
|
|
3610
|
-
if (!node || targetSet.has(node)) continue;
|
|
3611
|
-
targetSet.add(node);
|
|
3612
|
-
targets.push(node);
|
|
3613
|
-
}
|
|
3732
|
+
const targets = [...bindingByNode.keys()];
|
|
3733
|
+
const targetSet = new Set(targets);
|
|
3614
3734
|
const parts = [];
|
|
3615
|
-
const partCenter = new
|
|
3735
|
+
const partCenter = new Vector33();
|
|
3616
3736
|
for (const node of targets) {
|
|
3617
3737
|
if (!node.parent) continue;
|
|
3618
3738
|
let ancestor = node.parent;
|
|
@@ -3634,14 +3754,21 @@ function Model({
|
|
|
3634
3754
|
parts.push({
|
|
3635
3755
|
node,
|
|
3636
3756
|
restLocalPos: node.position.clone(),
|
|
3637
|
-
restWorldPos: node.getWorldPosition(new
|
|
3757
|
+
restWorldPos: node.getWorldPosition(new Vector33()),
|
|
3638
3758
|
worldDir,
|
|
3639
3759
|
parentInverse: new Matrix4().copy(node.parent.matrixWorld).invert()
|
|
3640
3760
|
});
|
|
3641
3761
|
}
|
|
3642
3762
|
explodePartsRef.current = parts;
|
|
3643
3763
|
invalidate();
|
|
3644
|
-
}, [
|
|
3764
|
+
}, [
|
|
3765
|
+
scene,
|
|
3766
|
+
nodeMap,
|
|
3767
|
+
nodeBindingSignature,
|
|
3768
|
+
bindingTransformSignature,
|
|
3769
|
+
originalNodeTransforms,
|
|
3770
|
+
invalidate
|
|
3771
|
+
]);
|
|
3645
3772
|
useEffect7(() => {
|
|
3646
3773
|
invalidate();
|
|
3647
3774
|
}, [explodeFactor, invalidate]);
|
|
@@ -3733,7 +3860,7 @@ import {
|
|
|
3733
3860
|
} from "react";
|
|
3734
3861
|
import { Html } from "@react-three/drei";
|
|
3735
3862
|
import { useFrame as useFrame5, useThree as useThree8 } from "@react-three/fiber";
|
|
3736
|
-
import { MathUtils as MathUtils3, Vector3 as
|
|
3863
|
+
import { MathUtils as MathUtils3, Vector3 as Vector34 } from "three";
|
|
3737
3864
|
|
|
3738
3865
|
// src/styles/AnnotationsLayer.css
|
|
3739
3866
|
styleInject(".ri-annotationMarker--hovered {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n");
|
|
@@ -3746,7 +3873,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3746
3873
|
const scene = useThree8((state) => state.scene);
|
|
3747
3874
|
const anchorNodeRef = useRef6(null);
|
|
3748
3875
|
const baseLocalPosition = useMemo8(
|
|
3749
|
-
() => new
|
|
3876
|
+
() => new Vector34(
|
|
3750
3877
|
...annotation.anchorLocalPosition ?? annotation.localPosition ?? annotation.worldPosition
|
|
3751
3878
|
),
|
|
3752
3879
|
[
|
|
@@ -3755,9 +3882,9 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3755
3882
|
annotation.worldPosition
|
|
3756
3883
|
]
|
|
3757
3884
|
);
|
|
3758
|
-
const worldPosition = useMemo8(() => new
|
|
3759
|
-
const biasedWorldPosition = useMemo8(() => new
|
|
3760
|
-
const toCamera = useMemo8(() => new
|
|
3885
|
+
const worldPosition = useMemo8(() => new Vector34(), []);
|
|
3886
|
+
const biasedWorldPosition = useMemo8(() => new Vector34(), []);
|
|
3887
|
+
const toCamera = useMemo8(() => new Vector34(), []);
|
|
3761
3888
|
useEffect8(() => {
|
|
3762
3889
|
anchorNodeRef.current = null;
|
|
3763
3890
|
}, [annotation.anchorNodeName]);
|
|
@@ -3780,7 +3907,7 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3780
3907
|
if (worldPositionsRef) {
|
|
3781
3908
|
let stored = worldPositionsRef.current.get(annotation.id);
|
|
3782
3909
|
if (!stored) {
|
|
3783
|
-
stored = new
|
|
3910
|
+
stored = new Vector34();
|
|
3784
3911
|
worldPositionsRef.current.set(annotation.id, stored);
|
|
3785
3912
|
}
|
|
3786
3913
|
stored.copy(worldPosition);
|
|
@@ -4178,7 +4305,7 @@ function AnnotationsLayer({
|
|
|
4178
4305
|
// src/components/ModelViewer/MeasurementLayer.tsx
|
|
4179
4306
|
import { useEffect as useEffect9, useMemo as useMemo9 } from "react";
|
|
4180
4307
|
import { Html as Html2, Line } from "@react-three/drei";
|
|
4181
|
-
import { BoxGeometry, EdgesGeometry, Vector3 as
|
|
4308
|
+
import { BoxGeometry, EdgesGeometry, Vector3 as Vector35 } from "three";
|
|
4182
4309
|
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4183
4310
|
var LINE_COLOR = "#22d3ee";
|
|
4184
4311
|
var BOX_COLOR = "#facc15";
|
|
@@ -4253,7 +4380,7 @@ function MeasurementLayer({
|
|
|
4253
4380
|
points.slice(1).map((point, index) => {
|
|
4254
4381
|
const a = points[index].position;
|
|
4255
4382
|
const b = point.position;
|
|
4256
|
-
const distance = new
|
|
4383
|
+
const distance = new Vector35(...a).distanceTo(new Vector35(...b));
|
|
4257
4384
|
return /* @__PURE__ */ jsxs6("group", { children: [
|
|
4258
4385
|
/* @__PURE__ */ jsx10(Line, { points: [a, b], color: LINE_COLOR, lineWidth: 2 }),
|
|
4259
4386
|
/* @__PURE__ */ jsx10(MeasureLabel, { position: midpoint(a, b), children: formatMeasurement(distance, unit) })
|
|
@@ -4274,7 +4401,7 @@ import {
|
|
|
4274
4401
|
} from "react";
|
|
4275
4402
|
import { useXR as useXR2, useXRHitTest, useXRInputSourceEvent } from "@react-three/xr";
|
|
4276
4403
|
import { useFrame as useFrame6 } from "@react-three/fiber";
|
|
4277
|
-
import { Matrix4 as Matrix42, Vector3 as
|
|
4404
|
+
import { Matrix4 as Matrix42, Vector3 as Vector36 } from "three";
|
|
4278
4405
|
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4279
4406
|
var TARGET_SIZE = 1;
|
|
4280
4407
|
var FORWARD_DISTANCE = 1;
|
|
@@ -4311,13 +4438,13 @@ var XRModelPresenter = forwardRef2(
|
|
|
4311
4438
|
() => box ? computeNormalization(box, scaleMode) : null,
|
|
4312
4439
|
[box, scaleMode]
|
|
4313
4440
|
);
|
|
4314
|
-
const placedPosition = useRef7(new
|
|
4441
|
+
const placedPosition = useRef7(new Vector36(0, 0, -FORWARD_DISTANCE));
|
|
4315
4442
|
const userScale = useRef7(1);
|
|
4316
4443
|
const rotationY = useRef7(0);
|
|
4317
4444
|
const placementMode = useRef7(true);
|
|
4318
4445
|
const modelPlaced = useRef7(false);
|
|
4319
4446
|
const hitMatrix = useMemo10(() => new Matrix42(), []);
|
|
4320
|
-
const hitPosition = useRef7(new
|
|
4447
|
+
const hitPosition = useRef7(new Vector36());
|
|
4321
4448
|
const hasHit = useRef7(false);
|
|
4322
4449
|
const lastARState = useRef7(null);
|
|
4323
4450
|
const gesturing = useRef7(false);
|
|
@@ -4515,7 +4642,7 @@ import {
|
|
|
4515
4642
|
CatmullRomCurve3,
|
|
4516
4643
|
Sphere,
|
|
4517
4644
|
Spherical,
|
|
4518
|
-
Vector3 as
|
|
4645
|
+
Vector3 as Vector37
|
|
4519
4646
|
} from "three";
|
|
4520
4647
|
var ENTER_BLEND_SECONDS = 1.2;
|
|
4521
4648
|
function CinematicCamera({
|
|
@@ -4596,13 +4723,13 @@ function CinematicCamera({
|
|
|
4596
4723
|
if (!hasPath) return null;
|
|
4597
4724
|
return {
|
|
4598
4725
|
position: new CatmullRomCurve3(
|
|
4599
|
-
waypoints.map((w) => new
|
|
4726
|
+
waypoints.map((w) => new Vector37(...w.position)),
|
|
4600
4727
|
loop,
|
|
4601
4728
|
"catmullrom",
|
|
4602
4729
|
0.5
|
|
4603
4730
|
),
|
|
4604
4731
|
target: new CatmullRomCurve3(
|
|
4605
|
-
waypoints.map((w) => new
|
|
4732
|
+
waypoints.map((w) => new Vector37(...w.target)),
|
|
4606
4733
|
loop,
|
|
4607
4734
|
"catmullrom",
|
|
4608
4735
|
0.5
|
|
@@ -4618,8 +4745,8 @@ function CinematicCamera({
|
|
|
4618
4745
|
const points = waypoints.map((waypoint) => ({
|
|
4619
4746
|
azimuth: waypoint.azimuthAngle,
|
|
4620
4747
|
polar: waypoint.polarAngle,
|
|
4621
|
-
distance: new
|
|
4622
|
-
new
|
|
4748
|
+
distance: new Vector37(...waypoint.position).distanceTo(
|
|
4749
|
+
new Vector37(...waypoint.target)
|
|
4623
4750
|
)
|
|
4624
4751
|
}));
|
|
4625
4752
|
for (let index = 1; index < points.length; index += 1) {
|
|
@@ -4631,14 +4758,14 @@ function CinematicCamera({
|
|
|
4631
4758
|
}, [signature, hasPath]);
|
|
4632
4759
|
const elapsedRef = useRef8(0);
|
|
4633
4760
|
const enterElapsedRef = useRef8(0);
|
|
4634
|
-
const enterPos = useRef8(new
|
|
4635
|
-
const enterTarget = useRef8(new
|
|
4761
|
+
const enterPos = useRef8(new Vector37());
|
|
4762
|
+
const enterTarget = useRef8(new Vector37());
|
|
4636
4763
|
const startedRef = useRef8(false);
|
|
4637
4764
|
const pausedElapsedRef = useRef8(0);
|
|
4638
4765
|
const hasFinishedRef = useRef8(false);
|
|
4639
4766
|
const orbitRef = useRef8(null);
|
|
4640
|
-
const tmpPos = useMemo11(() => new
|
|
4641
|
-
const tmpTarget = useMemo11(() => new
|
|
4767
|
+
const tmpPos = useMemo11(() => new Vector37(), []);
|
|
4768
|
+
const tmpTarget = useMemo11(() => new Vector37(), []);
|
|
4642
4769
|
const tmpSpherical = useMemo11(() => new Spherical(), []);
|
|
4643
4770
|
useEffect11(() => {
|
|
4644
4771
|
elapsedRef.current = 0;
|
|
@@ -5877,27 +6004,35 @@ import { useRef as useRef11, useEffect as useEffect15, useMemo as useMemo13, use
|
|
|
5877
6004
|
import { useThree as useThree10, useFrame as useFrame8 } from "@react-three/fiber";
|
|
5878
6005
|
import { Sphere as Sphere2, Line as Line2 } from "@react-three/drei";
|
|
5879
6006
|
import {
|
|
5880
|
-
Vector3 as
|
|
6007
|
+
Vector3 as Vector38,
|
|
5881
6008
|
Vector2 as Vector25,
|
|
5882
6009
|
AdditiveBlending as AdditiveBlending2,
|
|
5883
6010
|
Plane,
|
|
5884
6011
|
TextureLoader as TextureLoader2
|
|
5885
6012
|
} from "three";
|
|
6013
|
+
|
|
6014
|
+
// src/assets/light-gizmos/sun.png
|
|
6015
|
+
var sun_default = "data:image/png;base64,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";
|
|
6016
|
+
|
|
6017
|
+
// src/assets/light-gizmos/bulb.png
|
|
6018
|
+
var bulb_default = "data:image/png;base64,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";
|
|
6019
|
+
|
|
6020
|
+
// src/components/BindingBuilder/components/LightHelpers.tsx
|
|
5886
6021
|
import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
5887
6022
|
var POSITION_MIN = -100;
|
|
5888
6023
|
var POSITION_MAX = 100;
|
|
5889
6024
|
var SUN_SCREEN_SIZE = 0.03;
|
|
5890
6025
|
var BULB_SCREEN_SIZE = 0.03;
|
|
5891
6026
|
var MIN_LINE_DISTANCE = 0.5;
|
|
5892
|
-
var SUN_ICON_PATH =
|
|
5893
|
-
var BULB_ICON_PATH =
|
|
6027
|
+
var SUN_ICON_PATH = sun_default;
|
|
6028
|
+
var BULB_ICON_PATH = bulb_default;
|
|
5894
6029
|
var RING_RADIUS = 0.4;
|
|
5895
6030
|
var RING_SEGMENTS = 64;
|
|
5896
6031
|
var RING_SCREEN_SIZE = SUN_SCREEN_SIZE * 3;
|
|
5897
6032
|
var RING_COLOR = "#4d7dff";
|
|
5898
6033
|
var LINE_COLOR2 = "#ffcc00";
|
|
5899
6034
|
var LINE_GLOW_RADIUS = 0.02;
|
|
5900
|
-
var UP_AXIS = new
|
|
6035
|
+
var UP_AXIS = new Vector38(0, 1, 0);
|
|
5901
6036
|
var AXIS_ARROW_LENGTH = 1.4;
|
|
5902
6037
|
var AXIS_CONE_LENGTH = 0.35;
|
|
5903
6038
|
var AXIS_CONE_RADIUS = 0.13;
|
|
@@ -5909,17 +6044,17 @@ var AXIS_COLORS = {
|
|
|
5909
6044
|
z: "#4444ff"
|
|
5910
6045
|
};
|
|
5911
6046
|
function axisVector(axis) {
|
|
5912
|
-
return new
|
|
6047
|
+
return new Vector38(axis === "x" ? 1 : 0, axis === "y" ? 1 : 0, axis === "z" ? 1 : 0);
|
|
5913
6048
|
}
|
|
5914
6049
|
function computeAxisDragPlaneNormal(axis, eye) {
|
|
5915
6050
|
const axisVec = axisVector(axis);
|
|
5916
6051
|
if (eye.lengthSq() < 1e-8) {
|
|
5917
6052
|
eye = axisVec.clone();
|
|
5918
6053
|
}
|
|
5919
|
-
let normal = new
|
|
6054
|
+
let normal = new Vector38().crossVectors(axisVec, new Vector38().crossVectors(eye, axisVec));
|
|
5920
6055
|
if (normal.lengthSq() < 1e-8) {
|
|
5921
|
-
const helper = Math.abs(axisVec.y) < 0.9 ? UP_AXIS : new
|
|
5922
|
-
normal = new
|
|
6056
|
+
const helper = Math.abs(axisVec.y) < 0.9 ? UP_AXIS : new Vector38(1, 0, 0);
|
|
6057
|
+
normal = new Vector38().crossVectors(axisVec, helper);
|
|
5923
6058
|
}
|
|
5924
6059
|
return normal.normalize();
|
|
5925
6060
|
}
|
|
@@ -5992,17 +6127,17 @@ function LightHelpers({
|
|
|
5992
6127
|
const shaftZRef = useRef11(null);
|
|
5993
6128
|
const axisDragRef = useRef11({ dragging: false, axis: null });
|
|
5994
6129
|
const axisDragPlaneRef = useRef11(new Plane());
|
|
5995
|
-
const dragStartPosRef = useRef11(new
|
|
6130
|
+
const dragStartPosRef = useRef11(new Vector38());
|
|
5996
6131
|
const dragLineRef = useRef11(null);
|
|
5997
6132
|
const dragLineGeomRef = useRef11(null);
|
|
5998
|
-
const currentPosRef = useRef11(new
|
|
6133
|
+
const currentPosRef = useRef11(new Vector38());
|
|
5999
6134
|
const isDraggingRef = useRef11(false);
|
|
6000
6135
|
const dragPlaneRef = useRef11(new Plane());
|
|
6001
6136
|
const onDraggingChangeRef = useRef11(onDraggingChange);
|
|
6002
6137
|
useEffect15(() => {
|
|
6003
6138
|
onDraggingChangeRef.current = onDraggingChange;
|
|
6004
6139
|
}, [onDraggingChange]);
|
|
6005
|
-
const lightPos = useMemo13(() => new
|
|
6140
|
+
const lightPos = useMemo13(() => new Vector38(position[0], position[1], position[2]), [position]);
|
|
6006
6141
|
const linePositions = useMemo13(() => new Float32Array(6), []);
|
|
6007
6142
|
const dragLinePositions = useMemo13(() => new Float32Array(6), []);
|
|
6008
6143
|
const ringXPositions = useMemo13(() => makeRingPositions("x"), []);
|
|
@@ -6161,12 +6296,12 @@ function LightHelpers({
|
|
|
6161
6296
|
const axis = axisDragRef.current.axis;
|
|
6162
6297
|
const currentPos = currentPosRef.current;
|
|
6163
6298
|
const axisVec = axisVector(axis);
|
|
6164
|
-
const eye = new
|
|
6299
|
+
const eye = new Vector38().subVectors(camera.position, currentPos);
|
|
6165
6300
|
const planeNormal = computeAxisDragPlaneNormal(axis, eye);
|
|
6166
6301
|
axisDragPlaneRef.current.setFromNormalAndCoplanarPoint(planeNormal, currentPos);
|
|
6167
6302
|
const mouse2 = getMouseNdc(event);
|
|
6168
6303
|
raycaster.setFromCamera(mouse2, camera);
|
|
6169
|
-
const intersection2 = new
|
|
6304
|
+
const intersection2 = new Vector38();
|
|
6170
6305
|
const hitPoint2 = raycaster.ray.intersectPlane(axisDragPlaneRef.current, intersection2);
|
|
6171
6306
|
if (!hitPoint2) return;
|
|
6172
6307
|
const toPoint = hitPoint2.clone().sub(currentPos);
|
|
@@ -6185,7 +6320,7 @@ function LightHelpers({
|
|
|
6185
6320
|
if (!isDraggingRef.current) return;
|
|
6186
6321
|
const mouse = getMouseNdc(event);
|
|
6187
6322
|
raycaster.setFromCamera(mouse, camera);
|
|
6188
|
-
const intersection = new
|
|
6323
|
+
const intersection = new Vector38();
|
|
6189
6324
|
const hitPoint = raycaster.ray.intersectPlane(dragPlaneRef.current, intersection);
|
|
6190
6325
|
if (!hitPoint) return;
|
|
6191
6326
|
const newPos = [
|
|
@@ -6232,7 +6367,7 @@ function LightHelpers({
|
|
|
6232
6367
|
setActiveTab?.("scene");
|
|
6233
6368
|
setActiveSubTab?.("Lighting");
|
|
6234
6369
|
}
|
|
6235
|
-
const cameraDir = new
|
|
6370
|
+
const cameraDir = new Vector38();
|
|
6236
6371
|
camera.getWorldDirection(cameraDir);
|
|
6237
6372
|
dragPlaneRef.current.setFromNormalAndCoplanarPoint(cameraDir, currentPosRef.current);
|
|
6238
6373
|
isDraggingRef.current = true;
|
|
@@ -6365,17 +6500,17 @@ function LightHelpers({
|
|
|
6365
6500
|
] }),
|
|
6366
6501
|
gizmoVisible && /* @__PURE__ */ jsx19("group", { ref: axisGroupRef, children: axisArrows.map((axis) => {
|
|
6367
6502
|
const axisColor = AXIS_COLORS[axis];
|
|
6368
|
-
const end = new
|
|
6503
|
+
const end = new Vector38(
|
|
6369
6504
|
axis === "x" ? AXIS_ARROW_LENGTH : 0,
|
|
6370
6505
|
axis === "y" ? AXIS_ARROW_LENGTH : 0,
|
|
6371
6506
|
axis === "z" ? AXIS_ARROW_LENGTH : 0
|
|
6372
6507
|
);
|
|
6373
|
-
const conePos = new
|
|
6508
|
+
const conePos = new Vector38(
|
|
6374
6509
|
axis === "x" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0,
|
|
6375
6510
|
axis === "y" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0,
|
|
6376
6511
|
axis === "z" ? AXIS_ARROW_LENGTH - AXIS_CONE_LENGTH / 2 : 0
|
|
6377
6512
|
);
|
|
6378
|
-
const shaftPos = new
|
|
6513
|
+
const shaftPos = new Vector38(
|
|
6379
6514
|
axis === "x" ? AXIS_ARROW_LENGTH / 2 : 0,
|
|
6380
6515
|
axis === "y" ? AXIS_ARROW_LENGTH / 2 : 0,
|
|
6381
6516
|
axis === "z" ? AXIS_ARROW_LENGTH / 2 : 0
|
|
@@ -6387,7 +6522,7 @@ function LightHelpers({
|
|
|
6387
6522
|
/* @__PURE__ */ jsx19(
|
|
6388
6523
|
Line2,
|
|
6389
6524
|
{
|
|
6390
|
-
points: [new
|
|
6525
|
+
points: [new Vector38(0, 0, 0), end],
|
|
6391
6526
|
color: axisColor,
|
|
6392
6527
|
transparent: true,
|
|
6393
6528
|
opacity: 0.9,
|
|
@@ -6476,7 +6611,7 @@ function findInfluencingBone(hit) {
|
|
|
6476
6611
|
if (!skinIndex || !skinWeight || !position) return null;
|
|
6477
6612
|
const localHit = mesh.worldToLocal(hit.point.clone());
|
|
6478
6613
|
const vertices = [hit.face.a, hit.face.b, hit.face.c];
|
|
6479
|
-
const vertex = new
|
|
6614
|
+
const vertex = new Vector39();
|
|
6480
6615
|
let nearest = vertices[0];
|
|
6481
6616
|
let nearestDist = Infinity;
|
|
6482
6617
|
for (const vi of vertices) {
|
|
@@ -6526,9 +6661,9 @@ function GuidedTourCameraFollow({
|
|
|
6526
6661
|
const scene = useThree11((state) => state.scene);
|
|
6527
6662
|
const nodeRef = useRef12(null);
|
|
6528
6663
|
const hasPrev = useRef12(false);
|
|
6529
|
-
const live = useMemo14(() => new
|
|
6530
|
-
const prev = useMemo14(() => new
|
|
6531
|
-
const goal = useMemo14(() => new
|
|
6664
|
+
const live = useMemo14(() => new Vector39(), []);
|
|
6665
|
+
const prev = useMemo14(() => new Vector39(), []);
|
|
6666
|
+
const goal = useMemo14(() => new Vector39(), []);
|
|
6532
6667
|
const nodeName = annotation?.anchorNodeName;
|
|
6533
6668
|
const localPosition = annotation?.anchorLocalPosition;
|
|
6534
6669
|
useEffect16(() => {
|
|
@@ -6568,7 +6703,7 @@ var DEFAULT_AR_OVERLAY_STATE = {
|
|
|
6568
6703
|
};
|
|
6569
6704
|
function computeMeasureBox(scene) {
|
|
6570
6705
|
const box = new Box34().setFromObject(scene);
|
|
6571
|
-
const size = new
|
|
6706
|
+
const size = new Vector39();
|
|
6572
6707
|
box.getSize(size);
|
|
6573
6708
|
return {
|
|
6574
6709
|
min: [box.min.x, box.min.y, box.min.z],
|
|
@@ -6589,8 +6724,8 @@ function computeUvMeshStats(scene) {
|
|
|
6589
6724
|
function computeModelBounds(scene) {
|
|
6590
6725
|
const box = new Box34().setFromObject(scene);
|
|
6591
6726
|
if (box.isEmpty()) return null;
|
|
6592
|
-
const center = box.getCenter(new
|
|
6593
|
-
const radius = box.getSize(new
|
|
6727
|
+
const center = box.getCenter(new Vector39());
|
|
6728
|
+
const radius = box.getSize(new Vector39()).length() / 2;
|
|
6594
6729
|
if (!Number.isFinite(radius) || radius <= 0) return null;
|
|
6595
6730
|
return { center: [center.x, center.y, center.z], radius };
|
|
6596
6731
|
}
|
|
@@ -6598,7 +6733,7 @@ function fitClippingPlanesToScene(scene, controls) {
|
|
|
6598
6733
|
const camera = controls?.camera;
|
|
6599
6734
|
if (!(camera instanceof PerspectiveCamera3)) return;
|
|
6600
6735
|
const box = new Box34().setFromObject(scene);
|
|
6601
|
-
const size = box.getSize(new
|
|
6736
|
+
const size = box.getSize(new Vector39()).length();
|
|
6602
6737
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
6603
6738
|
camera.near = size / 100;
|
|
6604
6739
|
camera.far = size * 100;
|
|
@@ -6676,7 +6811,8 @@ function ModelViewer({
|
|
|
6676
6811
|
showAnnotationNavigation = true,
|
|
6677
6812
|
showAnnotationOnHover = false
|
|
6678
6813
|
}) {
|
|
6679
|
-
const { activeTab, activeSubTab } = useBindingBuilderContext();
|
|
6814
|
+
const { isInsideEditor, activeTab, activeSubTab } = useBindingBuilderContext();
|
|
6815
|
+
const showLightHelpers = isInsideEditor && activeTab === "scene" && activeSubTab === "Lighting";
|
|
6680
6816
|
const license = useLicense(licenseKey);
|
|
6681
6817
|
const resolvedSceneConfig = useMemo14(
|
|
6682
6818
|
() => sceneConfig ?? DEFAULT_SCENE_CONFIG,
|
|
@@ -6782,8 +6918,8 @@ function ModelViewer({
|
|
|
6782
6918
|
if (!controls || initialCameraStateRef.current) {
|
|
6783
6919
|
return;
|
|
6784
6920
|
}
|
|
6785
|
-
const position = controls.getPosition(new
|
|
6786
|
-
const target = controls.getTarget(new
|
|
6921
|
+
const position = controls.getPosition(new Vector39());
|
|
6922
|
+
const target = controls.getTarget(new Vector39());
|
|
6787
6923
|
initialCameraStateRef.current = {
|
|
6788
6924
|
position: [position.x, position.y, position.z],
|
|
6789
6925
|
target: [target.x, target.y, target.z]
|
|
@@ -7041,7 +7177,7 @@ function ModelViewer({
|
|
|
7041
7177
|
const controls = controlsRef.current;
|
|
7042
7178
|
const scene = sceneRef.current;
|
|
7043
7179
|
if (!controls) return;
|
|
7044
|
-
const target = new
|
|
7180
|
+
const target = new Vector39();
|
|
7045
7181
|
const livePosition = annotationWorldPositionsRef.current.get(
|
|
7046
7182
|
annotation.id
|
|
7047
7183
|
);
|
|
@@ -7060,14 +7196,14 @@ function ModelViewer({
|
|
|
7060
7196
|
const storedNormal = annotation.worldNormal;
|
|
7061
7197
|
if (scene) {
|
|
7062
7198
|
const box = new Box34().setFromObject(scene);
|
|
7063
|
-
const modelCenter = new
|
|
7199
|
+
const modelCenter = new Vector39();
|
|
7064
7200
|
box.getCenter(modelCenter);
|
|
7065
|
-
const modelSize = new
|
|
7201
|
+
const modelSize = new Vector39();
|
|
7066
7202
|
box.getSize(modelSize);
|
|
7067
7203
|
const modelRadius = modelSize.length() * 0.2;
|
|
7068
7204
|
let outwardDir;
|
|
7069
7205
|
if (storedNormal) {
|
|
7070
|
-
outwardDir = new
|
|
7206
|
+
outwardDir = new Vector39(
|
|
7071
7207
|
storedNormal[0],
|
|
7072
7208
|
storedNormal[1],
|
|
7073
7209
|
storedNormal[2]
|
|
@@ -7218,6 +7354,16 @@ function ModelViewer({
|
|
|
7218
7354
|
const isControlledBindings = onObjectBindingsChange !== void 0;
|
|
7219
7355
|
const currentObjectBindings = isControlledBindings ? objectBindings : internalObjectBindings;
|
|
7220
7356
|
currentObjectBindingsRef.current = currentObjectBindings;
|
|
7357
|
+
const currentBindingTransformSignature = useMemo14(
|
|
7358
|
+
() => Object.entries(currentObjectBindings).map(
|
|
7359
|
+
([bindingKey, binding]) => JSON.stringify([
|
|
7360
|
+
bindingKey,
|
|
7361
|
+
binding.modelObjectId,
|
|
7362
|
+
binding.transform ?? null
|
|
7363
|
+
])
|
|
7364
|
+
).sort().join(""),
|
|
7365
|
+
[currentObjectBindings]
|
|
7366
|
+
);
|
|
7221
7367
|
const currentHiddenObjects = useMemo14(
|
|
7222
7368
|
() => Object.fromEntries(
|
|
7223
7369
|
Object.entries(currentObjectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
|
|
@@ -7357,7 +7503,7 @@ function ModelViewer({
|
|
|
7357
7503
|
const fallbackY = Math.max(minOffset, (rect.height - height) / 2);
|
|
7358
7504
|
const objectKey = resolveObjectKey(objectId, binding);
|
|
7359
7505
|
const object = objectKey ? nodeRefsMap.current[objectKey] : null;
|
|
7360
|
-
const
|
|
7506
|
+
const clamp3 = (value, max) => {
|
|
7361
7507
|
if (max <= minOffset) {
|
|
7362
7508
|
return minOffset;
|
|
7363
7509
|
}
|
|
@@ -7365,23 +7511,23 @@ function ModelViewer({
|
|
|
7365
7511
|
};
|
|
7366
7512
|
if (canvasContainer && cameraControls && object) {
|
|
7367
7513
|
const canvasRect = canvasContainer.getBoundingClientRect();
|
|
7368
|
-
const objectCenter = new
|
|
7514
|
+
const objectCenter = new Vector39();
|
|
7369
7515
|
new Box34().setFromObject(object).getCenter(objectCenter);
|
|
7370
7516
|
objectCenter.project(cameraControls);
|
|
7371
7517
|
const projectedX = (objectCenter.x + 1) / 2 * canvasRect.width + (canvasRect.left - rect.left);
|
|
7372
7518
|
const projectedY = (1 - objectCenter.y) / 2 * canvasRect.height + (canvasRect.top - rect.top);
|
|
7373
7519
|
return {
|
|
7374
7520
|
objectId,
|
|
7375
|
-
x:
|
|
7376
|
-
y:
|
|
7521
|
+
x: clamp3(projectedX + 24, maxX),
|
|
7522
|
+
y: clamp3(projectedY - height / 2, maxY)
|
|
7377
7523
|
};
|
|
7378
7524
|
}
|
|
7379
7525
|
const rawX = typeof fallbackCoords?.screenX === "number" ? fallbackCoords.screenX - rect.left : fallbackX;
|
|
7380
7526
|
const rawY = typeof fallbackCoords?.screenY === "number" ? fallbackCoords.screenY - rect.top : fallbackY;
|
|
7381
7527
|
return {
|
|
7382
7528
|
objectId,
|
|
7383
|
-
x:
|
|
7384
|
-
y:
|
|
7529
|
+
x: clamp3(rawX, maxX),
|
|
7530
|
+
y: clamp3(rawY, maxY)
|
|
7385
7531
|
};
|
|
7386
7532
|
},
|
|
7387
7533
|
[resolveObjectKey]
|
|
@@ -7390,7 +7536,6 @@ function ModelViewer({
|
|
|
7390
7536
|
(event) => {
|
|
7391
7537
|
const { objectId, action, binding } = event;
|
|
7392
7538
|
setCurrentAction(event);
|
|
7393
|
-
onAction?.(event);
|
|
7394
7539
|
switch (action.id) {
|
|
7395
7540
|
case "toggle-visibility": {
|
|
7396
7541
|
let targetKey = objectId;
|
|
@@ -7438,6 +7583,7 @@ function ModelViewer({
|
|
|
7438
7583
|
default:
|
|
7439
7584
|
break;
|
|
7440
7585
|
}
|
|
7586
|
+
onAction?.(event);
|
|
7441
7587
|
},
|
|
7442
7588
|
[
|
|
7443
7589
|
applyObjectBindings,
|
|
@@ -7578,7 +7724,8 @@ function ModelViewer({
|
|
|
7578
7724
|
objectBindings: currentObjectBindingsRef.current,
|
|
7579
7725
|
nodeRefs: nodeRefsMap.current,
|
|
7580
7726
|
homeCameraState: initialCameraStateRef.current,
|
|
7581
|
-
captureImage: (options) => captureImage(rendererCaptureRef.current, options)
|
|
7727
|
+
captureImage: (options) => captureImage(rendererCaptureRef.current, options),
|
|
7728
|
+
invalidate: () => rendererCaptureRef.current?.invalidate()
|
|
7582
7729
|
});
|
|
7583
7730
|
}, []);
|
|
7584
7731
|
const fitScene = useCallback4(
|
|
@@ -7590,8 +7737,8 @@ function ModelViewer({
|
|
|
7590
7737
|
}
|
|
7591
7738
|
try {
|
|
7592
7739
|
if (flyIn) {
|
|
7593
|
-
const target = controls.getTarget(new
|
|
7594
|
-
const position = controls.getPosition(new
|
|
7740
|
+
const target = controls.getTarget(new Vector39());
|
|
7741
|
+
const position = controls.getPosition(new Vector39());
|
|
7595
7742
|
const direction = position.clone().sub(target);
|
|
7596
7743
|
if (direction.lengthSq() < 1e-6) {
|
|
7597
7744
|
direction.set(0, 0, 1);
|
|
@@ -7909,6 +8056,13 @@ function ModelViewer({
|
|
|
7909
8056
|
);
|
|
7910
8057
|
setBindingsMismatch(!hasAnyMatch);
|
|
7911
8058
|
}, [modelLoaded, currentObjectBindings]);
|
|
8059
|
+
useEffect16(() => {
|
|
8060
|
+
const scene = sceneRef.current;
|
|
8061
|
+
if (!modelLoaded || !scene) return;
|
|
8062
|
+
scene.updateMatrixWorld(true);
|
|
8063
|
+
setMeasureBox(computeMeasureBox(scene));
|
|
8064
|
+
setModelBounds(computeModelBounds(scene));
|
|
8065
|
+
}, [modelLoaded, currentBindingTransformSignature]);
|
|
7912
8066
|
const clearMeasurements = useCallback4(() => setMeasurePoints([]), []);
|
|
7913
8067
|
const handleMeasurePointerDown = useCallback4(
|
|
7914
8068
|
(event) => {
|
|
@@ -7952,7 +8106,11 @@ function ModelViewer({
|
|
|
7952
8106
|
);
|
|
7953
8107
|
useEffect16(() => {
|
|
7954
8108
|
setMeasurePoints([]);
|
|
7955
|
-
}, [
|
|
8109
|
+
}, [
|
|
8110
|
+
modelUrl,
|
|
8111
|
+
resolvedSceneConfig.model.rotation,
|
|
8112
|
+
currentBindingTransformSignature
|
|
8113
|
+
]);
|
|
7956
8114
|
const handleDownloadMesh = useCallback4(() => {
|
|
7957
8115
|
const matRefs = materialRefsOut.current;
|
|
7958
8116
|
const scene = sceneRef.current;
|
|
@@ -8295,33 +8453,28 @@ function ModelViewer({
|
|
|
8295
8453
|
shadowsEnabled: shadows,
|
|
8296
8454
|
environmentRotation: resolvedSceneConfig.environment.rotation,
|
|
8297
8455
|
environmentConfig: resolvedSceneConfig.environment,
|
|
8298
|
-
modelBounds
|
|
8456
|
+
modelBounds,
|
|
8457
|
+
objectBindings: currentObjectBindings,
|
|
8458
|
+
nodeRefs: nodeRefsMap
|
|
8299
8459
|
}
|
|
8300
8460
|
),
|
|
8301
|
-
(() => {
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8461
|
+
showLightHelpers && resolvedSceneConfig.lighting.lights.map((light) => {
|
|
8462
|
+
if (!light.visible || light.type === "none" || light.type === "hemisphere" || light.attachment) {
|
|
8463
|
+
return null;
|
|
8464
|
+
}
|
|
8465
|
+
return /* @__PURE__ */ jsx20(
|
|
8466
|
+
LightHelpers,
|
|
8467
|
+
{
|
|
8468
|
+
light,
|
|
8469
|
+
position: light.position,
|
|
8470
|
+
color: light.color,
|
|
8471
|
+
intensity: light.intensity,
|
|
8472
|
+
visible: light.visible,
|
|
8473
|
+
onPositionChange: (newPosition) => handleLightPositionChange(light.id, newPosition)
|
|
8474
|
+
},
|
|
8475
|
+
light.id
|
|
8305
8476
|
);
|
|
8306
|
-
|
|
8307
|
-
if (light.type === "none" || light.type === "hemisphere") {
|
|
8308
|
-
return null;
|
|
8309
|
-
}
|
|
8310
|
-
const uniqueKey = light.id;
|
|
8311
|
-
return /* @__PURE__ */ jsx20(
|
|
8312
|
-
LightHelpers,
|
|
8313
|
-
{
|
|
8314
|
-
light,
|
|
8315
|
-
position: light.position,
|
|
8316
|
-
color: light.color,
|
|
8317
|
-
intensity: light.intensity,
|
|
8318
|
-
visible: light.visible,
|
|
8319
|
-
onPositionChange: (newPos) => handleLightPositionChange(light.id, newPos)
|
|
8320
|
-
},
|
|
8321
|
-
uniqueKey
|
|
8322
|
-
);
|
|
8323
|
-
});
|
|
8324
|
-
})()
|
|
8477
|
+
})
|
|
8325
8478
|
] }),
|
|
8326
8479
|
/* @__PURE__ */ jsx20(
|
|
8327
8480
|
Controls,
|
|
@@ -8948,7 +9101,7 @@ function ModelViewer({
|
|
|
8948
9101
|
}
|
|
8949
9102
|
|
|
8950
9103
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
8951
|
-
import { useCallback as useCallback9, useEffect as
|
|
9104
|
+
import { useCallback as useCallback9, useEffect as useEffect23, useMemo as useMemo17, useRef as useRef19, useState as useState27 } from "react";
|
|
8952
9105
|
import {
|
|
8953
9106
|
Redo2,
|
|
8954
9107
|
Undo2,
|
|
@@ -8963,32 +9116,13 @@ styleInject('.bb-root {\n --bb-font-family:\n Inter,\n "Segoe UI",\n H
|
|
|
8963
9116
|
|
|
8964
9117
|
// src/hooks/useViewerCamera.ts
|
|
8965
9118
|
import { useCallback as useCallback5, useRef as useRef13, useState as useState11 } from "react";
|
|
8966
|
-
import { Vector3 as
|
|
8967
|
-
|
|
8968
|
-
// src/utils/resolveBindingKey.ts
|
|
8969
|
-
function resolveBindingKey(objectBindings, objectId) {
|
|
8970
|
-
if (objectBindings[objectId]) {
|
|
8971
|
-
return objectId;
|
|
8972
|
-
}
|
|
8973
|
-
const idMatch = Object.entries(objectBindings).find(
|
|
8974
|
-
([, binding]) => binding.id === objectId
|
|
8975
|
-
)?.[0];
|
|
8976
|
-
if (idMatch) {
|
|
8977
|
-
return idMatch;
|
|
8978
|
-
}
|
|
8979
|
-
const modelObjectMatch = Object.entries(objectBindings).find(
|
|
8980
|
-
([, binding]) => binding.modelObjectId === objectId
|
|
8981
|
-
)?.[0];
|
|
8982
|
-
return modelObjectMatch ?? null;
|
|
8983
|
-
}
|
|
8984
|
-
|
|
8985
|
-
// src/hooks/useViewerCamera.ts
|
|
9119
|
+
import { Vector3 as Vector310 } from "three";
|
|
8986
9120
|
function toTuple(vector) {
|
|
8987
9121
|
return [vector.x, vector.y, vector.z];
|
|
8988
9122
|
}
|
|
8989
9123
|
function buildCameraState(camera, controls) {
|
|
8990
|
-
const position = new
|
|
8991
|
-
const target = new
|
|
9124
|
+
const position = new Vector310();
|
|
9125
|
+
const target = new Vector310();
|
|
8992
9126
|
controls.getPosition(position);
|
|
8993
9127
|
controls.getTarget(target);
|
|
8994
9128
|
return {
|
|
@@ -9015,11 +9149,16 @@ async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
|
|
|
9015
9149
|
controls.smoothTime = originalSmoothTime;
|
|
9016
9150
|
}
|
|
9017
9151
|
}
|
|
9018
|
-
function useViewerCamera() {
|
|
9152
|
+
function useViewerCamera(options = {}) {
|
|
9153
|
+
const optionsRef = useRef13(options);
|
|
9154
|
+
optionsRef.current = options;
|
|
9019
9155
|
const viewerRef = useRef13(null);
|
|
9020
9156
|
const [cameraState, setCameraStateValue] = useState11(
|
|
9021
9157
|
null
|
|
9022
9158
|
);
|
|
9159
|
+
const [cameraPresets, setCameraPresets] = useState11(() => ({ ...options.presets }));
|
|
9160
|
+
const cameraPresetsRef = useRef13(cameraPresets);
|
|
9161
|
+
cameraPresetsRef.current = cameraPresets;
|
|
9023
9162
|
const updateCameraState = useCallback5((nextState) => {
|
|
9024
9163
|
setCameraStateValue(
|
|
9025
9164
|
(previousState) => isSameCameraState(previousState, nextState) ? previousState : nextState
|
|
@@ -9033,16 +9172,56 @@ function useViewerCamera() {
|
|
|
9033
9172
|
);
|
|
9034
9173
|
const homeCameraStateRef = useRef13(null);
|
|
9035
9174
|
const homeSceneRef = useRef13(null);
|
|
9175
|
+
const initializedControlsRef = useRef13(null);
|
|
9036
9176
|
const handleViewerReady = useCallback5((viewer) => {
|
|
9037
9177
|
viewerRef.current = viewer;
|
|
9038
|
-
|
|
9178
|
+
const currentState = buildCameraState(
|
|
9179
|
+
viewer.controls.camera,
|
|
9180
|
+
viewer.controls
|
|
9181
|
+
);
|
|
9182
|
+
updateCameraState(currentState);
|
|
9039
9183
|
if (viewer.scene !== homeSceneRef.current) {
|
|
9040
9184
|
homeSceneRef.current = viewer.scene;
|
|
9041
9185
|
homeCameraStateRef.current = null;
|
|
9186
|
+
initializedControlsRef.current = null;
|
|
9042
9187
|
}
|
|
9043
9188
|
if (viewer.homeCameraState && !homeCameraStateRef.current) {
|
|
9044
9189
|
homeCameraStateRef.current = viewer.homeCameraState;
|
|
9045
9190
|
}
|
|
9191
|
+
const { initialPosition, initialTarget, initialTransition = false } = optionsRef.current;
|
|
9192
|
+
if ((initialPosition || initialTarget) && viewer.scene && // A target-only setup should preserve ModelViewer's fitted or authored
|
|
9193
|
+
// camera position. Wait until that initial framing has settled instead
|
|
9194
|
+
// of cancelling auto-fit while the camera is still at its fallback.
|
|
9195
|
+
(initialPosition || viewer.homeCameraState) && initializedControlsRef.current !== viewer.controls) {
|
|
9196
|
+
initializedControlsRef.current = viewer.controls;
|
|
9197
|
+
if (!viewer.homeCameraState) {
|
|
9198
|
+
markInitialAutoFitOverride(viewer.controls);
|
|
9199
|
+
}
|
|
9200
|
+
const position = initialPosition ?? currentState.position;
|
|
9201
|
+
const target = initialTarget ?? currentState.target;
|
|
9202
|
+
void viewer.controls.setLookAt(
|
|
9203
|
+
position[0],
|
|
9204
|
+
position[1],
|
|
9205
|
+
position[2],
|
|
9206
|
+
target[0],
|
|
9207
|
+
target[1],
|
|
9208
|
+
target[2],
|
|
9209
|
+
initialTransition
|
|
9210
|
+
).then(() => {
|
|
9211
|
+
if (viewerRef.current?.controls !== viewer.controls) {
|
|
9212
|
+
return;
|
|
9213
|
+
}
|
|
9214
|
+
const nextState = buildCameraState(
|
|
9215
|
+
viewer.controls.camera,
|
|
9216
|
+
viewer.controls
|
|
9217
|
+
);
|
|
9218
|
+
homeCameraStateRef.current = {
|
|
9219
|
+
position: nextState.position,
|
|
9220
|
+
target: nextState.target
|
|
9221
|
+
};
|
|
9222
|
+
updateCameraState(nextState);
|
|
9223
|
+
}).catch(() => void 0);
|
|
9224
|
+
}
|
|
9046
9225
|
}, [updateCameraState]);
|
|
9047
9226
|
const resetView = useCallback5(async () => {
|
|
9048
9227
|
const viewer = viewerRef.current;
|
|
@@ -9138,27 +9317,33 @@ function useViewerCamera() {
|
|
|
9138
9317
|
return false;
|
|
9139
9318
|
}
|
|
9140
9319
|
try {
|
|
9320
|
+
if (!viewerRef.current?.homeCameraState) {
|
|
9321
|
+
markInitialAutoFitOverride(controls);
|
|
9322
|
+
}
|
|
9141
9323
|
await controls.setTarget(
|
|
9142
9324
|
target[0],
|
|
9143
9325
|
target[1],
|
|
9144
9326
|
target[2],
|
|
9145
9327
|
enableTransition
|
|
9146
9328
|
);
|
|
9329
|
+
updateCameraState(buildCameraState(controls.camera, controls));
|
|
9147
9330
|
return true;
|
|
9148
9331
|
} catch {
|
|
9149
9332
|
return false;
|
|
9150
9333
|
}
|
|
9151
9334
|
},
|
|
9152
|
-
[]
|
|
9335
|
+
[updateCameraState]
|
|
9153
9336
|
);
|
|
9154
|
-
const
|
|
9337
|
+
const applyCameraPreset = useCallback5(
|
|
9155
9338
|
async (state, enableTransition = true) => {
|
|
9156
9339
|
const controls = viewerRef.current?.controls;
|
|
9157
9340
|
if (!controls) {
|
|
9158
9341
|
return false;
|
|
9159
9342
|
}
|
|
9160
9343
|
try {
|
|
9161
|
-
|
|
9344
|
+
if (!viewerRef.current?.homeCameraState) {
|
|
9345
|
+
markInitialAutoFitOverride(controls);
|
|
9346
|
+
}
|
|
9162
9347
|
const transitions = [
|
|
9163
9348
|
controls.setLookAt(
|
|
9164
9349
|
state.position[0],
|
|
@@ -9182,11 +9367,11 @@ function useViewerCamera() {
|
|
|
9182
9367
|
await Promise.all(transitions);
|
|
9183
9368
|
const camera = controls.camera;
|
|
9184
9369
|
let projectionChanged = false;
|
|
9185
|
-
if (state.fov
|
|
9370
|
+
if (state.fov != null && "fov" in camera && typeof camera.fov === "number") {
|
|
9186
9371
|
camera.fov = state.fov;
|
|
9187
9372
|
projectionChanged = true;
|
|
9188
9373
|
}
|
|
9189
|
-
if ("zoom" in camera && typeof camera.zoom === "number") {
|
|
9374
|
+
if (state.zoom != null && "zoom" in camera && typeof camera.zoom === "number") {
|
|
9190
9375
|
camera.zoom = state.zoom;
|
|
9191
9376
|
projectionChanged = true;
|
|
9192
9377
|
}
|
|
@@ -9201,13 +9386,94 @@ function useViewerCamera() {
|
|
|
9201
9386
|
},
|
|
9202
9387
|
[updateCameraState]
|
|
9203
9388
|
);
|
|
9389
|
+
const setCameraState = useCallback5(
|
|
9390
|
+
(state, enableTransition = true) => applyCameraPreset(state, enableTransition),
|
|
9391
|
+
[applyCameraPreset]
|
|
9392
|
+
);
|
|
9393
|
+
const lookAt = useCallback5(
|
|
9394
|
+
(position, target, enableTransition = true) => applyCameraPreset({ position, target }, enableTransition),
|
|
9395
|
+
[applyCameraPreset]
|
|
9396
|
+
);
|
|
9397
|
+
const orbitTo = useCallback5(
|
|
9398
|
+
async (azimuthAngle, polarAngle, enableTransition = true) => {
|
|
9399
|
+
const controls = viewerRef.current?.controls;
|
|
9400
|
+
if (!controls) {
|
|
9401
|
+
return false;
|
|
9402
|
+
}
|
|
9403
|
+
try {
|
|
9404
|
+
if (!viewerRef.current?.homeCameraState) {
|
|
9405
|
+
markInitialAutoFitOverride(controls);
|
|
9406
|
+
}
|
|
9407
|
+
await controls.rotateTo(
|
|
9408
|
+
azimuthAngle,
|
|
9409
|
+
polarAngle,
|
|
9410
|
+
enableTransition
|
|
9411
|
+
);
|
|
9412
|
+
updateCameraState(buildCameraState(controls.camera, controls));
|
|
9413
|
+
return true;
|
|
9414
|
+
} catch {
|
|
9415
|
+
return false;
|
|
9416
|
+
}
|
|
9417
|
+
},
|
|
9418
|
+
[updateCameraState]
|
|
9419
|
+
);
|
|
9420
|
+
const dollyTo = useCallback5(
|
|
9421
|
+
async (distance, enableTransition = true) => {
|
|
9422
|
+
const controls = viewerRef.current?.controls;
|
|
9423
|
+
if (!controls) {
|
|
9424
|
+
return false;
|
|
9425
|
+
}
|
|
9426
|
+
try {
|
|
9427
|
+
if (!viewerRef.current?.homeCameraState) {
|
|
9428
|
+
markInitialAutoFitOverride(controls);
|
|
9429
|
+
}
|
|
9430
|
+
await controls.dollyTo(distance, enableTransition);
|
|
9431
|
+
updateCameraState(buildCameraState(controls.camera, controls));
|
|
9432
|
+
return true;
|
|
9433
|
+
} catch {
|
|
9434
|
+
return false;
|
|
9435
|
+
}
|
|
9436
|
+
},
|
|
9437
|
+
[updateCameraState]
|
|
9438
|
+
);
|
|
9439
|
+
const savePreset = useCallback5((name) => {
|
|
9440
|
+
const controls = viewerRef.current?.controls;
|
|
9441
|
+
const trimmedName = name.trim();
|
|
9442
|
+
if (!controls || !trimmedName) {
|
|
9443
|
+
return null;
|
|
9444
|
+
}
|
|
9445
|
+
const nextState = buildCameraState(controls.camera, controls);
|
|
9446
|
+
cameraPresetsRef.current = {
|
|
9447
|
+
...cameraPresetsRef.current,
|
|
9448
|
+
[trimmedName]: nextState
|
|
9449
|
+
};
|
|
9450
|
+
setCameraPresets((current) => ({
|
|
9451
|
+
...current,
|
|
9452
|
+
[trimmedName]: nextState
|
|
9453
|
+
}));
|
|
9454
|
+
updateCameraState(nextState);
|
|
9455
|
+
return nextState;
|
|
9456
|
+
}, [updateCameraState]);
|
|
9457
|
+
const goToPreset = useCallback5(
|
|
9458
|
+
(name, enableTransition = true) => {
|
|
9459
|
+
const preset = cameraPresetsRef.current[name];
|
|
9460
|
+
return preset ? applyCameraPreset(preset, enableTransition) : Promise.resolve(false);
|
|
9461
|
+
},
|
|
9462
|
+
[applyCameraPreset]
|
|
9463
|
+
);
|
|
9204
9464
|
return {
|
|
9205
9465
|
cameraState,
|
|
9466
|
+
cameraPresets,
|
|
9206
9467
|
resetView,
|
|
9207
9468
|
focusObject,
|
|
9208
9469
|
fitScene,
|
|
9209
9470
|
setCameraTarget,
|
|
9210
9471
|
setCameraState,
|
|
9472
|
+
lookAt,
|
|
9473
|
+
orbitTo,
|
|
9474
|
+
dollyTo,
|
|
9475
|
+
savePreset,
|
|
9476
|
+
goToPreset,
|
|
9211
9477
|
handleCameraChange,
|
|
9212
9478
|
handleViewerReady
|
|
9213
9479
|
};
|
|
@@ -15076,6 +15342,9 @@ function IdentityPanel({
|
|
|
15076
15342
|
] }) });
|
|
15077
15343
|
}
|
|
15078
15344
|
|
|
15345
|
+
// src/components/BindingBuilder/components/BasicsPanel.tsx
|
|
15346
|
+
import { useEffect as useEffect21, useState as useState24 } from "react";
|
|
15347
|
+
|
|
15079
15348
|
// src/components/BindingBuilder/utils/constants.ts
|
|
15080
15349
|
var typeSuggestions = [
|
|
15081
15350
|
"other",
|
|
@@ -15115,6 +15384,31 @@ function BasicsPanel({
|
|
|
15115
15384
|
onToggle,
|
|
15116
15385
|
updateBinding
|
|
15117
15386
|
}) {
|
|
15387
|
+
const [groupDraft, setGroupDraft] = useState24(selectedBinding.group ?? "");
|
|
15388
|
+
const [tagsDraft, setTagsDraft] = useState24(
|
|
15389
|
+
selectedBinding.tags?.join(", ") ?? ""
|
|
15390
|
+
);
|
|
15391
|
+
useEffect21(() => {
|
|
15392
|
+
setGroupDraft(selectedBinding.group ?? "");
|
|
15393
|
+
setTagsDraft(selectedBinding.tags?.join(", ") ?? "");
|
|
15394
|
+
}, [selectedItem, selectedBinding.group, selectedBinding.tags]);
|
|
15395
|
+
const commitGroup = () => {
|
|
15396
|
+
updateBinding(selectedItem, (binding) => ({
|
|
15397
|
+
...binding,
|
|
15398
|
+
group: groupDraft.trim() || void 0
|
|
15399
|
+
}));
|
|
15400
|
+
};
|
|
15401
|
+
const commitTags = () => {
|
|
15402
|
+
const tags = [
|
|
15403
|
+
...new Set(
|
|
15404
|
+
tagsDraft.split(",").map((tag) => tag.trim()).filter(Boolean)
|
|
15405
|
+
)
|
|
15406
|
+
];
|
|
15407
|
+
updateBinding(selectedItem, (binding) => ({
|
|
15408
|
+
...binding,
|
|
15409
|
+
tags: tags.length > 0 ? tags : void 0
|
|
15410
|
+
}));
|
|
15411
|
+
};
|
|
15118
15412
|
return /* @__PURE__ */ jsxs36(BBSection, { title: "Basics", open, onToggle, children: [
|
|
15119
15413
|
/* @__PURE__ */ jsxs36("div", { className: "bb-grid", children: [
|
|
15120
15414
|
/* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
|
|
@@ -15161,6 +15455,38 @@ function BasicsPanel({
|
|
|
15161
15455
|
children: statusOptions.map((option) => /* @__PURE__ */ jsx41("option", { value: option, children: option }, option))
|
|
15162
15456
|
}
|
|
15163
15457
|
)
|
|
15458
|
+
] }),
|
|
15459
|
+
/* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
|
|
15460
|
+
/* @__PURE__ */ jsx41("label", { className: "bb-label", children: "Group" }),
|
|
15461
|
+
/* @__PURE__ */ jsx41(
|
|
15462
|
+
"input",
|
|
15463
|
+
{
|
|
15464
|
+
className: "bb-input",
|
|
15465
|
+
placeholder: "paint",
|
|
15466
|
+
value: groupDraft,
|
|
15467
|
+
onChange: (event) => setGroupDraft(event.target.value),
|
|
15468
|
+
onBlur: commitGroup,
|
|
15469
|
+
onKeyDown: (event) => {
|
|
15470
|
+
if (event.key === "Enter") event.currentTarget.blur();
|
|
15471
|
+
}
|
|
15472
|
+
}
|
|
15473
|
+
)
|
|
15474
|
+
] }),
|
|
15475
|
+
/* @__PURE__ */ jsxs36("div", { className: "bb-field", children: [
|
|
15476
|
+
/* @__PURE__ */ jsx41("label", { className: "bb-label", children: "Tags" }),
|
|
15477
|
+
/* @__PURE__ */ jsx41(
|
|
15478
|
+
"input",
|
|
15479
|
+
{
|
|
15480
|
+
className: "bb-input",
|
|
15481
|
+
placeholder: "exterior, configurable",
|
|
15482
|
+
value: tagsDraft,
|
|
15483
|
+
onChange: (event) => setTagsDraft(event.target.value),
|
|
15484
|
+
onBlur: commitTags,
|
|
15485
|
+
onKeyDown: (event) => {
|
|
15486
|
+
if (event.key === "Enter") event.currentTarget.blur();
|
|
15487
|
+
}
|
|
15488
|
+
}
|
|
15489
|
+
)
|
|
15164
15490
|
] })
|
|
15165
15491
|
] }),
|
|
15166
15492
|
/* @__PURE__ */ jsxs36("div", { className: "bb-checkboxRow", style: { marginTop: 12 }, children: [
|
|
@@ -15211,7 +15537,7 @@ function BasicsPanel({
|
|
|
15211
15537
|
}
|
|
15212
15538
|
|
|
15213
15539
|
// src/components/BindingBuilder/components/ActionsPanel.tsx
|
|
15214
|
-
import { useEffect as
|
|
15540
|
+
import { useEffect as useEffect22, useState as useState25 } from "react";
|
|
15215
15541
|
|
|
15216
15542
|
// src/components/BindingBuilder/utils/actionCatalog.ts
|
|
15217
15543
|
var ACTION_CATALOG = [
|
|
@@ -15237,19 +15563,19 @@ var ACTION_CATALOG = [
|
|
|
15237
15563
|
id: "toggle-light",
|
|
15238
15564
|
label: "Toggle Light",
|
|
15239
15565
|
handling: "host",
|
|
15240
|
-
description: "Seeded on light bindings.
|
|
15566
|
+
description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
|
|
15241
15567
|
},
|
|
15242
15568
|
{
|
|
15243
15569
|
id: "change-light-color",
|
|
15244
15570
|
label: "Change Light Color",
|
|
15245
15571
|
handling: "host",
|
|
15246
|
-
description: "Seeded on light bindings.
|
|
15572
|
+
description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
|
|
15247
15573
|
},
|
|
15248
15574
|
{
|
|
15249
15575
|
id: "set-brightness",
|
|
15250
15576
|
label: "Set Brightness",
|
|
15251
15577
|
handling: "host",
|
|
15252
|
-
description: "Seeded on light bindings.
|
|
15578
|
+
description: "Seeded on light bindings. Add declarative effects for useViewerActions, or handle it in onAction."
|
|
15253
15579
|
}
|
|
15254
15580
|
];
|
|
15255
15581
|
var CUSTOM_ACTION_ID = "__custom__";
|
|
@@ -15310,8 +15636,8 @@ function ActionsPanel({
|
|
|
15310
15636
|
onToggle,
|
|
15311
15637
|
updateBinding
|
|
15312
15638
|
}) {
|
|
15313
|
-
const [customRows, setCustomRows] =
|
|
15314
|
-
|
|
15639
|
+
const [customRows, setCustomRows] = useState25(/* @__PURE__ */ new Set());
|
|
15640
|
+
useEffect22(() => {
|
|
15315
15641
|
setCustomRows(/* @__PURE__ */ new Set());
|
|
15316
15642
|
}, [selectedItem]);
|
|
15317
15643
|
const actions = selectedBinding.actions ?? [];
|
|
@@ -15427,8 +15753,8 @@ function ActionsPanel({
|
|
|
15427
15753
|
}
|
|
15428
15754
|
)
|
|
15429
15755
|
] }),
|
|
15430
|
-
catalogEntry ? /* @__PURE__ */ jsx42("span", { className: "bb-note", children: catalogEntry.description }) : trimmedId.length === 0 ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fca5a5" }, children: "An action ID is required
|
|
15431
|
-
"Unrecognised ID
|
|
15756
|
+
catalogEntry ? /* @__PURE__ */ jsx42("span", { className: "bb-note", children: catalogEntry.description }) : trimmedId.length === 0 ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fca5a5" }, children: "An action ID is required, the button renders but can never be identified in onAction." }) : /* @__PURE__ */ jsxs37("span", { className: "bb-note", style: { color: "#fcd34d" }, children: [
|
|
15757
|
+
"Unrecognised ID, the viewer has no built-in behavior for it. Add declarative effects and execute them with useViewerActions, or handle it in onAction.",
|
|
15432
15758
|
suggestion ? /* @__PURE__ */ jsxs37(Fragment13, { children: [
|
|
15433
15759
|
" Did you mean ",
|
|
15434
15760
|
/* @__PURE__ */ jsx42(
|
|
@@ -15451,7 +15777,13 @@ function ActionsPanel({
|
|
|
15451
15777
|
"?"
|
|
15452
15778
|
] }) : null
|
|
15453
15779
|
] }),
|
|
15454
|
-
|
|
15780
|
+
action.effects?.length ? /* @__PURE__ */ jsxs37("span", { className: "bb-note", children: [
|
|
15781
|
+
action.effects.length,
|
|
15782
|
+
" declarative effect",
|
|
15783
|
+
action.effects.length === 1 ? "" : "s",
|
|
15784
|
+
", execute with useViewerActions."
|
|
15785
|
+
] }) : null,
|
|
15786
|
+
isDuplicate ? /* @__PURE__ */ jsx42("span", { className: "bb-note", style: { color: "#fcd34d" }, children: "Another action on this binding uses the same ID, they will render as two identical buttons." }) : null
|
|
15455
15787
|
] }),
|
|
15456
15788
|
/* @__PURE__ */ jsx42("div", { className: "bb-actionFooter", children: /* @__PURE__ */ jsx42(
|
|
15457
15789
|
"button",
|
|
@@ -15600,7 +15932,7 @@ function CameraPositionPanel({
|
|
|
15600
15932
|
}
|
|
15601
15933
|
|
|
15602
15934
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
15603
|
-
import { useMemo as useMemo16, useState as
|
|
15935
|
+
import { useMemo as useMemo16, useState as useState26 } from "react";
|
|
15604
15936
|
|
|
15605
15937
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
15606
15938
|
function slugify2(value) {
|
|
@@ -15689,7 +16021,7 @@ function SceneNodesPanel({
|
|
|
15689
16021
|
isOpen = true,
|
|
15690
16022
|
onToggle
|
|
15691
16023
|
}) {
|
|
15692
|
-
const [search, setSearch] =
|
|
16024
|
+
const [search, setSearch] = useState26("");
|
|
15693
16025
|
const nodeKeys = useMemo16(() => Object.keys(bindings), [bindings]);
|
|
15694
16026
|
const filteredNodeKeys = useMemo16(() => {
|
|
15695
16027
|
const query = search.trim().toLowerCase();
|
|
@@ -15920,9 +16252,9 @@ function toBindingsJson(bindings) {
|
|
|
15920
16252
|
}
|
|
15921
16253
|
function toBindingsTypeScript(bindings) {
|
|
15922
16254
|
return [
|
|
15923
|
-
'import
|
|
16255
|
+
'import { defineObjectBindings } from "@liveroom-tech/react-immersive";',
|
|
15924
16256
|
"",
|
|
15925
|
-
`export const objectBindings
|
|
16257
|
+
`export const objectBindings = defineObjectBindings(${JSON.stringify(bindings, null, 2)});`,
|
|
15926
16258
|
""
|
|
15927
16259
|
].join("\n");
|
|
15928
16260
|
}
|
|
@@ -16145,27 +16477,27 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16145
16477
|
const modelInputRef = useRef19(null);
|
|
16146
16478
|
const bindingsInputRef = useRef19(null);
|
|
16147
16479
|
const sceneConfigInputRef = useRef19(null);
|
|
16148
|
-
const [modelUrl, setModelUrl] =
|
|
16149
|
-
const [modelName, setModelName] =
|
|
16150
|
-
const [selectedItem, setSelectedItem] =
|
|
16480
|
+
const [modelUrl, setModelUrl] = useState27(null);
|
|
16481
|
+
const [modelName, setModelName] = useState27("");
|
|
16482
|
+
const [selectedItem, setSelectedItem] = useState27(null);
|
|
16151
16483
|
const bindingsHistory = useUndoableState({});
|
|
16152
16484
|
const bindings = bindingsHistory.value;
|
|
16153
16485
|
const setBindings = bindingsHistory.setValue;
|
|
16154
|
-
const [loading, setLoading] =
|
|
16155
|
-
const [isExporting, setIsExporting] =
|
|
16156
|
-
const [statusMessage, setStatusMessage] =
|
|
16486
|
+
const [loading, setLoading] = useState27(false);
|
|
16487
|
+
const [isExporting, setIsExporting] = useState27(false);
|
|
16488
|
+
const [statusMessage, setStatusMessage] = useState27(
|
|
16157
16489
|
"Load the demo model or upload a GLB/GLTF model to generate bindings."
|
|
16158
16490
|
);
|
|
16159
|
-
const [statusTone, setStatusTone] =
|
|
16160
|
-
const [statusBannerVisible, setStatusBannerVisible] =
|
|
16161
|
-
const [metricsDraft, setMetricsDraft] =
|
|
16162
|
-
const [metadataDraft, setMetadataDraft] =
|
|
16163
|
-
const [metricsError, setMetricsError] =
|
|
16164
|
-
const [metadataError, setMetadataError] =
|
|
16165
|
-
const [isPreviewDragActive, setIsPreviewDragActive] =
|
|
16166
|
-
const [isEditorCollapsed, setIsEditorCollapsed] =
|
|
16167
|
-
const [isNodesCollapsed, setIsNodesCollapsed] =
|
|
16168
|
-
const [openObjectSections, setOpenObjectSections] =
|
|
16491
|
+
const [statusTone, setStatusTone] = useState27("default");
|
|
16492
|
+
const [statusBannerVisible, setStatusBannerVisible] = useState27(false);
|
|
16493
|
+
const [metricsDraft, setMetricsDraft] = useState27("{}");
|
|
16494
|
+
const [metadataDraft, setMetadataDraft] = useState27("{}");
|
|
16495
|
+
const [metricsError, setMetricsError] = useState27(null);
|
|
16496
|
+
const [metadataError, setMetadataError] = useState27(null);
|
|
16497
|
+
const [isPreviewDragActive, setIsPreviewDragActive] = useState27(false);
|
|
16498
|
+
const [isEditorCollapsed, setIsEditorCollapsed] = useState27(false);
|
|
16499
|
+
const [isNodesCollapsed, setIsNodesCollapsed] = useState27(false);
|
|
16500
|
+
const [openObjectSections, setOpenObjectSections] = useState27({
|
|
16169
16501
|
identity: true,
|
|
16170
16502
|
basics: true,
|
|
16171
16503
|
material: false,
|
|
@@ -16174,7 +16506,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16174
16506
|
metrics: false,
|
|
16175
16507
|
metadata: false
|
|
16176
16508
|
});
|
|
16177
|
-
const [openMaterialPanels, setOpenMaterialPanels] =
|
|
16509
|
+
const [openMaterialPanels, setOpenMaterialPanels] = useState27({
|
|
16178
16510
|
pbr: true,
|
|
16179
16511
|
roughness: false,
|
|
16180
16512
|
emission: false,
|
|
@@ -16190,24 +16522,24 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16190
16522
|
});
|
|
16191
16523
|
const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
16192
16524
|
const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
16193
|
-
const [activeEditorTab, setActiveEditorTab] =
|
|
16525
|
+
const [activeEditorTab, setActiveEditorTab] = useState27(
|
|
16194
16526
|
"object"
|
|
16195
16527
|
);
|
|
16196
|
-
const [activeSubTab, setActiveSubTab] =
|
|
16197
|
-
const [pbrWorkflow, setPbrWorkflow] =
|
|
16198
|
-
const [materialPanelEnabled, setMaterialPanelEnabled] =
|
|
16528
|
+
const [activeSubTab, setActiveSubTab] = useState27("General");
|
|
16529
|
+
const [pbrWorkflow, setPbrWorkflow] = useState27("metalness");
|
|
16530
|
+
const [materialPanelEnabled, setMaterialPanelEnabled] = useState27(createMaterialPanelEnabledState());
|
|
16199
16531
|
const materialPanelItemRef = useRef19(null);
|
|
16200
|
-
const [zoomOnSelected, setZoomOnSelected] =
|
|
16201
|
-
const [highlightOnHover, setHighlightOnHover] =
|
|
16202
|
-
const [resetDialogOpen, setResetDialogOpen] =
|
|
16203
|
-
const [resetTarget, setResetTarget] =
|
|
16532
|
+
const [zoomOnSelected, setZoomOnSelected] = useState27(false);
|
|
16533
|
+
const [highlightOnHover, setHighlightOnHover] = useState27(true);
|
|
16534
|
+
const [resetDialogOpen, setResetDialogOpen] = useState27(false);
|
|
16535
|
+
const [resetTarget, setResetTarget] = useState27(
|
|
16204
16536
|
null
|
|
16205
16537
|
);
|
|
16206
16538
|
const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
|
|
16207
16539
|
const sceneConfig = sceneConfigHistory.value;
|
|
16208
16540
|
const setSceneConfig = sceneConfigHistory.setValue;
|
|
16209
|
-
const [activeAnnotation, setActiveAnnotation] =
|
|
16210
|
-
const [panelHoveredBinding, setPanelHoveredBinding] =
|
|
16541
|
+
const [activeAnnotation, setActiveAnnotation] = useState27(null);
|
|
16542
|
+
const [panelHoveredBinding, setPanelHoveredBinding] = useState27(null);
|
|
16211
16543
|
const license = useLicense(licenseKey);
|
|
16212
16544
|
const tier = normalizeTier(license.tier);
|
|
16213
16545
|
const bindingBuilderContextValue = useMemo17(
|
|
@@ -16285,7 +16617,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16285
16617
|
},
|
|
16286
16618
|
[]
|
|
16287
16619
|
);
|
|
16288
|
-
|
|
16620
|
+
useEffect23(() => {
|
|
16289
16621
|
if (!selectedBinding) {
|
|
16290
16622
|
setMetricsDraft("{}");
|
|
16291
16623
|
setMetadataDraft("{}");
|
|
@@ -16298,7 +16630,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16298
16630
|
setMetricsError(null);
|
|
16299
16631
|
setMetadataError(null);
|
|
16300
16632
|
}, [selectedBinding]);
|
|
16301
|
-
|
|
16633
|
+
useEffect23(() => {
|
|
16302
16634
|
const derived = createMaterialPanelEnabledState(selectedMaterial);
|
|
16303
16635
|
if (materialPanelItemRef.current !== selectedItem) {
|
|
16304
16636
|
materialPanelItemRef.current = selectedItem;
|
|
@@ -16317,12 +16649,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16317
16649
|
return changed ? next : prev;
|
|
16318
16650
|
});
|
|
16319
16651
|
}, [selectedItem, selectedMaterial]);
|
|
16320
|
-
|
|
16652
|
+
useEffect23(() => {
|
|
16321
16653
|
return () => {
|
|
16322
16654
|
uploadedModelRef.current?.cleanup();
|
|
16323
16655
|
};
|
|
16324
16656
|
}, []);
|
|
16325
|
-
|
|
16657
|
+
useEffect23(() => {
|
|
16326
16658
|
const handleKeyDown = (event) => {
|
|
16327
16659
|
if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
|
|
16328
16660
|
return;
|
|
@@ -16343,7 +16675,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16343
16675
|
window.addEventListener("keydown", handleKeyDown);
|
|
16344
16676
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
16345
16677
|
}, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
|
|
16346
|
-
|
|
16678
|
+
useEffect23(() => {
|
|
16347
16679
|
if (loading || statusTone === "default" || statusTone === "success") return;
|
|
16348
16680
|
setStatusBannerVisible(true);
|
|
16349
16681
|
const timeout = window.setTimeout(
|
|
@@ -17373,7 +17705,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
17373
17705
|
/* @__PURE__ */ jsx46("strong", { children: resetTarget === "bindings" ? "Object Bindings" : "Scene Config" }),
|
|
17374
17706
|
"?"
|
|
17375
17707
|
] }),
|
|
17376
|
-
/* @__PURE__ */ jsx46("p", { className: "bb-reset-details", children: resetTarget === "bindings" ? "This will remove all your custom changes to object properties, materials, actions, camera positions, metrics, and metadata." : "This will restore all scene settings (camera, lighting, environment, background, animations, annotations, etc.) to their default values." }),
|
|
17708
|
+
/* @__PURE__ */ jsx46("p", { className: "bb-reset-details", children: resetTarget === "bindings" ? "This will remove all your custom changes to object properties, groups, tags, transforms, materials, actions, camera positions, metrics, and metadata." : "This will restore all scene settings (camera, lighting, environment, background, animations, annotations, etc.) to their default values." }),
|
|
17377
17709
|
/* @__PURE__ */ jsx46("p", { className: "bb-reset-warning", children: "This action cannot be undone!" })
|
|
17378
17710
|
] }),
|
|
17379
17711
|
/* @__PURE__ */ jsxs41("div", { className: "bb-reset-footer", children: [
|
|
@@ -17387,9 +17719,9 @@ function BindingBuilder({ licenseKey }) {
|
|
|
17387
17719
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
17388
17720
|
import {
|
|
17389
17721
|
useRef as useRef20,
|
|
17390
|
-
useState as
|
|
17722
|
+
useState as useState28,
|
|
17391
17723
|
useCallback as useCallback10,
|
|
17392
|
-
useEffect as
|
|
17724
|
+
useEffect as useEffect24,
|
|
17393
17725
|
useMemo as useMemo18,
|
|
17394
17726
|
Component as Component3,
|
|
17395
17727
|
Suspense as Suspense3
|
|
@@ -17400,7 +17732,7 @@ import { Canvas as Canvas3 } from "@react-three/fiber";
|
|
|
17400
17732
|
import {
|
|
17401
17733
|
Box3 as Box35,
|
|
17402
17734
|
PerspectiveCamera as PerspectiveCamera4,
|
|
17403
|
-
Vector3 as
|
|
17735
|
+
Vector3 as Vector311
|
|
17404
17736
|
} from "three";
|
|
17405
17737
|
import { SkeletonUtils as SkeletonUtils2 } from "three-stdlib";
|
|
17406
17738
|
|
|
@@ -17439,7 +17771,7 @@ function applyClippingPlanes(scene, controls) {
|
|
|
17439
17771
|
if (!controls) return;
|
|
17440
17772
|
const camera = controls.camera;
|
|
17441
17773
|
if (!(camera instanceof PerspectiveCamera4)) return;
|
|
17442
|
-
const size = new Box35().setFromObject(scene).getSize(new
|
|
17774
|
+
const size = new Box35().setFromObject(scene).getSize(new Vector311()).length();
|
|
17443
17775
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
17444
17776
|
camera.near = size / 100;
|
|
17445
17777
|
camera.far = size * 100;
|
|
@@ -17478,7 +17810,7 @@ function InnerModel({
|
|
|
17478
17810
|
meshopt: true
|
|
17479
17811
|
});
|
|
17480
17812
|
const invalidate = useThree12((state) => state.invalidate);
|
|
17481
|
-
const [hoveredKey, setHoveredKey] =
|
|
17813
|
+
const [hoveredKey, setHoveredKey] = useState28(null);
|
|
17482
17814
|
const scene = useMemo18(
|
|
17483
17815
|
() => SkeletonUtils2.clone(rawScene),
|
|
17484
17816
|
[rawScene]
|
|
@@ -17508,14 +17840,14 @@ function InnerModel({
|
|
|
17508
17840
|
}
|
|
17509
17841
|
return map;
|
|
17510
17842
|
}, [meshMap]);
|
|
17511
|
-
|
|
17843
|
+
useEffect24(
|
|
17512
17844
|
() => () => {
|
|
17513
17845
|
Object.values(perKeyMaterials).forEach((m) => m.dispose());
|
|
17514
17846
|
},
|
|
17515
17847
|
[perKeyMaterials]
|
|
17516
17848
|
);
|
|
17517
17849
|
const notifiedUrl = useRef20(null);
|
|
17518
|
-
|
|
17850
|
+
useEffect24(() => {
|
|
17519
17851
|
if (notifiedUrl.current === url) return;
|
|
17520
17852
|
notifiedUrl.current = url;
|
|
17521
17853
|
scene.updateWorldMatrix(true, true);
|
|
@@ -17523,7 +17855,7 @@ function InnerModel({
|
|
|
17523
17855
|
onMeshesReady(meshMap);
|
|
17524
17856
|
onModelLoaded(scene);
|
|
17525
17857
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
17526
|
-
|
|
17858
|
+
useEffect24(() => {
|
|
17527
17859
|
const controller = new AbortController();
|
|
17528
17860
|
void buildSceneBVH(scene, controller.signal);
|
|
17529
17861
|
return () => {
|
|
@@ -17535,22 +17867,22 @@ function InnerModel({
|
|
|
17535
17867
|
() => !exceedsBvhBudget(scene),
|
|
17536
17868
|
[scene]
|
|
17537
17869
|
);
|
|
17538
|
-
|
|
17870
|
+
useEffect24(() => {
|
|
17539
17871
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
17540
17872
|
nodeRefsOut.current[key] = ref;
|
|
17541
17873
|
}
|
|
17542
17874
|
}, [meshMap, nodeRefsOut]);
|
|
17543
|
-
|
|
17875
|
+
useEffect24(() => {
|
|
17544
17876
|
if (!highlightOnHover && hoveredKey !== null) {
|
|
17545
17877
|
setHoveredKey(null);
|
|
17546
17878
|
}
|
|
17547
17879
|
}, [highlightOnHover, hoveredKey]);
|
|
17548
|
-
|
|
17880
|
+
useEffect24(() => {
|
|
17549
17881
|
return () => {
|
|
17550
17882
|
document.body.style.cursor = "default";
|
|
17551
17883
|
};
|
|
17552
17884
|
}, []);
|
|
17553
|
-
|
|
17885
|
+
useEffect24(() => {
|
|
17554
17886
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
17555
17887
|
ref.visible = !hiddenKeys.has(key);
|
|
17556
17888
|
const mat = perKeyMaterials[key];
|
|
@@ -17624,7 +17956,7 @@ function CameraFitter({
|
|
|
17624
17956
|
const invalidate = useThree12((state) => state.invalidate);
|
|
17625
17957
|
const size = useThree12((state) => state.size);
|
|
17626
17958
|
const resizeTimerRef = useRef20(null);
|
|
17627
|
-
|
|
17959
|
+
useEffect24(() => {
|
|
17628
17960
|
fitted.current = false;
|
|
17629
17961
|
invalidate();
|
|
17630
17962
|
}, [invalidate, scene]);
|
|
@@ -17644,7 +17976,7 @@ function CameraFitter({
|
|
|
17644
17976
|
}).catch(() => {
|
|
17645
17977
|
}).finally(onInitialFitComplete);
|
|
17646
17978
|
});
|
|
17647
|
-
|
|
17979
|
+
useEffect24(() => {
|
|
17648
17980
|
if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
|
|
17649
17981
|
return;
|
|
17650
17982
|
}
|
|
@@ -17672,7 +18004,7 @@ function CameraFitter({
|
|
|
17672
18004
|
function ExposureController({ exposure }) {
|
|
17673
18005
|
const gl = useThree12((state) => state.gl);
|
|
17674
18006
|
const invalidate = useThree12((state) => state.invalidate);
|
|
17675
|
-
|
|
18007
|
+
useEffect24(() => {
|
|
17676
18008
|
gl.toneMappingExposure = exposure;
|
|
17677
18009
|
invalidate();
|
|
17678
18010
|
}, [gl, exposure, invalidate]);
|
|
@@ -17780,7 +18112,7 @@ function EnvironmentSection({
|
|
|
17780
18112
|
title,
|
|
17781
18113
|
children
|
|
17782
18114
|
}) {
|
|
17783
|
-
const [collapsed, setCollapsed] =
|
|
18115
|
+
const [collapsed, setCollapsed] = useState28(false);
|
|
17784
18116
|
return /* @__PURE__ */ jsxs42("div", { className: "env-section", children: [
|
|
17785
18117
|
/* @__PURE__ */ jsxs42(
|
|
17786
18118
|
"button",
|
|
@@ -17945,8 +18277,8 @@ function SceneObjectsPanel2({
|
|
|
17945
18277
|
onHover,
|
|
17946
18278
|
onToggleVisibility
|
|
17947
18279
|
}) {
|
|
17948
|
-
const [search, setSearch] =
|
|
17949
|
-
const [rowHovered, setRowHovered] =
|
|
18280
|
+
const [search, setSearch] = useState28("");
|
|
18281
|
+
const [rowHovered, setRowHovered] = useState28(null);
|
|
17950
18282
|
const entries = Object.values(meshKeys).filter(
|
|
17951
18283
|
(m) => m.label.toLowerCase().includes(search.toLowerCase())
|
|
17952
18284
|
);
|
|
@@ -18075,30 +18407,30 @@ function SimpleModelViewer({
|
|
|
18075
18407
|
const fileInputRef = useRef20(null);
|
|
18076
18408
|
const uploadedModelRef = useRef20(null);
|
|
18077
18409
|
const dragDepthRef = useRef20(0);
|
|
18078
|
-
const [loadedScene, setLoadedScene] =
|
|
18079
|
-
const [largeModelTriangles, setLargeModelTriangles] =
|
|
18410
|
+
const [loadedScene, setLoadedScene] = useState28(null);
|
|
18411
|
+
const [largeModelTriangles, setLargeModelTriangles] = useState28(
|
|
18080
18412
|
null
|
|
18081
18413
|
);
|
|
18082
|
-
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] =
|
|
18083
|
-
const [meshKeys, setMeshKeys] =
|
|
18084
|
-
const [hiddenKeys, setHiddenKeys] =
|
|
18085
|
-
const [selectedKey, setSelectedKey] =
|
|
18086
|
-
const [panelHoveredKey, setPanelHoveredKey] =
|
|
18087
|
-
const [uploadedModelUrl, setUploadedModelUrl] =
|
|
18088
|
-
const [uploadedModelName, setUploadedModelName] =
|
|
18414
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState28(false);
|
|
18415
|
+
const [meshKeys, setMeshKeys] = useState28({});
|
|
18416
|
+
const [hiddenKeys, setHiddenKeys] = useState28(/* @__PURE__ */ new Set());
|
|
18417
|
+
const [selectedKey, setSelectedKey] = useState28(null);
|
|
18418
|
+
const [panelHoveredKey, setPanelHoveredKey] = useState28(null);
|
|
18419
|
+
const [uploadedModelUrl, setUploadedModelUrl] = useState28(null);
|
|
18420
|
+
const [uploadedModelName, setUploadedModelName] = useState28(
|
|
18089
18421
|
null
|
|
18090
18422
|
);
|
|
18091
|
-
const [modelLoadFailed, setModelLoadFailed] =
|
|
18092
|
-
const [isResolvingUpload, setIsResolvingUpload] =
|
|
18093
|
-
const [initialFitComplete, setInitialFitComplete] =
|
|
18094
|
-
const [isDragActive, setIsDragActive] =
|
|
18095
|
-
const [isPanelOpen, setIsPanelOpen] =
|
|
18423
|
+
const [modelLoadFailed, setModelLoadFailed] = useState28(false);
|
|
18424
|
+
const [isResolvingUpload, setIsResolvingUpload] = useState28(false);
|
|
18425
|
+
const [initialFitComplete, setInitialFitComplete] = useState28(false);
|
|
18426
|
+
const [isDragActive, setIsDragActive] = useState28(false);
|
|
18427
|
+
const [isPanelOpen, setIsPanelOpen] = useState28(
|
|
18096
18428
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
18097
18429
|
);
|
|
18098
|
-
const [isEnvPanelOpen, setIsEnvPanelOpen] =
|
|
18430
|
+
const [isEnvPanelOpen, setIsEnvPanelOpen] = useState28(
|
|
18099
18431
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
18100
18432
|
);
|
|
18101
|
-
const [envSettings, setEnvSettings] =
|
|
18433
|
+
const [envSettings, setEnvSettings] = useState28({
|
|
18102
18434
|
backgroundEnabled,
|
|
18103
18435
|
backgroundColor,
|
|
18104
18436
|
autoRotate,
|
|
@@ -18181,7 +18513,7 @@ function SimpleModelViewer({
|
|
|
18181
18513
|
},
|
|
18182
18514
|
[handleUploadedModel]
|
|
18183
18515
|
);
|
|
18184
|
-
|
|
18516
|
+
useEffect24(() => {
|
|
18185
18517
|
return () => {
|
|
18186
18518
|
revokeUploadedModelUrl();
|
|
18187
18519
|
};
|
|
@@ -18192,10 +18524,10 @@ function SimpleModelViewer({
|
|
|
18192
18524
|
},
|
|
18193
18525
|
[]
|
|
18194
18526
|
);
|
|
18195
|
-
|
|
18527
|
+
useEffect24(() => {
|
|
18196
18528
|
setEnvSettings((prev) => ({ ...prev, backgroundColor }));
|
|
18197
18529
|
}, [backgroundColor]);
|
|
18198
|
-
|
|
18530
|
+
useEffect24(() => {
|
|
18199
18531
|
resetModelState();
|
|
18200
18532
|
}, [activeModelUrl, resetModelState]);
|
|
18201
18533
|
const handleMeshesReady = useCallback10((meshes) => {
|
|
@@ -18511,9 +18843,9 @@ function SimpleModelViewer({
|
|
|
18511
18843
|
}
|
|
18512
18844
|
|
|
18513
18845
|
// src/hooks/useViewerSelection.ts
|
|
18514
|
-
import { useCallback as useCallback11, useState as
|
|
18846
|
+
import { useCallback as useCallback11, useState as useState29 } from "react";
|
|
18515
18847
|
function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
18516
|
-
const [selectedObjectBinding, setSelectedObjectBinding] =
|
|
18848
|
+
const [selectedObjectBinding, setSelectedObjectBinding] = useState29(
|
|
18517
18849
|
initialSelectedObjectBinding
|
|
18518
18850
|
);
|
|
18519
18851
|
const handleObjectSelect = useCallback11(
|
|
@@ -18560,7 +18892,7 @@ function useObjectBindingIds(objectBindings) {
|
|
|
18560
18892
|
}
|
|
18561
18893
|
|
|
18562
18894
|
// src/hooks/useObjectBindings.ts
|
|
18563
|
-
import { useCallback as useCallback12, useMemo as useMemo21, useState as
|
|
18895
|
+
import { useCallback as useCallback12, useMemo as useMemo21, useState as useState30 } from "react";
|
|
18564
18896
|
|
|
18565
18897
|
// src/utils/deepMerge.ts
|
|
18566
18898
|
function isPlainObject2(value) {
|
|
@@ -18578,6 +18910,27 @@ function deepMerge(base, patch) {
|
|
|
18578
18910
|
return target;
|
|
18579
18911
|
}
|
|
18580
18912
|
|
|
18913
|
+
// src/utils/selectBindings.ts
|
|
18914
|
+
function isSelector(target) {
|
|
18915
|
+
return typeof target === "object" && !Array.isArray(target);
|
|
18916
|
+
}
|
|
18917
|
+
function matchesSelector(binding, selector) {
|
|
18918
|
+
if ("group" in selector) return binding.group === selector.group;
|
|
18919
|
+
if ("tag" in selector) return binding.tags?.includes(selector.tag) ?? false;
|
|
18920
|
+
return binding.type === selector.type;
|
|
18921
|
+
}
|
|
18922
|
+
function selectBindingKeys(bindings, target) {
|
|
18923
|
+
if (isSelector(target)) {
|
|
18924
|
+
return Object.entries(bindings).filter(([, binding]) => matchesSelector(binding, target)).map(([bindingKey]) => bindingKey);
|
|
18925
|
+
}
|
|
18926
|
+
const ids = typeof target === "string" ? [target] : target;
|
|
18927
|
+
const keys = ids.map((id) => resolveBindingKey(bindings, id)).filter((key) => key !== null);
|
|
18928
|
+
return [...new Set(keys)];
|
|
18929
|
+
}
|
|
18930
|
+
function selectBindings(bindings, target) {
|
|
18931
|
+
return selectBindingKeys(bindings, target).map((key) => bindings[key]).filter((binding) => binding !== void 0);
|
|
18932
|
+
}
|
|
18933
|
+
|
|
18581
18934
|
// src/utils/patchBindings.ts
|
|
18582
18935
|
function isBindingEditArray(value) {
|
|
18583
18936
|
return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
|
|
@@ -18586,10 +18939,7 @@ function patchBindings(bindings, objectIdOrEdits, patch) {
|
|
|
18586
18939
|
const edits = isBindingEditArray(objectIdOrEdits) ? objectIdOrEdits : [{ ids: objectIdOrEdits, patch: patch ?? {} }];
|
|
18587
18940
|
let next = bindings;
|
|
18588
18941
|
for (const edit of edits) {
|
|
18589
|
-
const
|
|
18590
|
-
for (const id of ids) {
|
|
18591
|
-
const bindingKey = resolveBindingKey(next, id);
|
|
18592
|
-
if (!bindingKey) continue;
|
|
18942
|
+
for (const bindingKey of selectBindingKeys(next, edit.ids)) {
|
|
18593
18943
|
const binding = next[bindingKey];
|
|
18594
18944
|
if (!binding) continue;
|
|
18595
18945
|
const patchedBinding = deepMerge(binding, edit.patch);
|
|
@@ -18604,13 +18954,43 @@ function material(value) {
|
|
|
18604
18954
|
}
|
|
18605
18955
|
|
|
18606
18956
|
// src/hooks/useObjectBindings.ts
|
|
18957
|
+
var TEXTURE_PROPERTY_BY_CHANNEL = {
|
|
18958
|
+
baseColor: "texture",
|
|
18959
|
+
metalness: "metalnessMap",
|
|
18960
|
+
roughness: "roughnessMap",
|
|
18961
|
+
emissive: "emissiveMap",
|
|
18962
|
+
alpha: "alphaMap",
|
|
18963
|
+
normal: "normalMap",
|
|
18964
|
+
bump: "bumpMap",
|
|
18965
|
+
ao: "aoMap",
|
|
18966
|
+
displacement: "displacementMap",
|
|
18967
|
+
clearcoat: "clearcoatMap",
|
|
18968
|
+
clearcoatRoughness: "clearcoatRoughnessMap",
|
|
18969
|
+
clearcoatNormal: "clearcoatNormalMap",
|
|
18970
|
+
anisotropy: "anisotropyMap",
|
|
18971
|
+
transmission: "transmissionMap",
|
|
18972
|
+
thickness: "thicknessMap",
|
|
18973
|
+
sheenColor: "sheenColorMap",
|
|
18974
|
+
sheenRoughness: "sheenRoughnessMap",
|
|
18975
|
+
specularIntensity: "specularIntensityMap",
|
|
18976
|
+
specularColor: "specularColorMap"
|
|
18977
|
+
};
|
|
18978
|
+
function cloneMaterial(value) {
|
|
18979
|
+
if (!value) return void 0;
|
|
18980
|
+
return Object.fromEntries(
|
|
18981
|
+
Object.entries(value).map(([key, field]) => [
|
|
18982
|
+
key,
|
|
18983
|
+
field && typeof field === "object" ? { ...field } : field
|
|
18984
|
+
])
|
|
18985
|
+
);
|
|
18986
|
+
}
|
|
18607
18987
|
function getHiddenObjects(objectBindings) {
|
|
18608
18988
|
return Object.fromEntries(
|
|
18609
18989
|
Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
|
|
18610
18990
|
);
|
|
18611
18991
|
}
|
|
18612
18992
|
function useObjectBindings(initialBindings) {
|
|
18613
|
-
const [objectBindings, setObjectBindings] =
|
|
18993
|
+
const [objectBindings, setObjectBindings] = useState30(initialBindings);
|
|
18614
18994
|
const updateObjectBindings = useCallback12(
|
|
18615
18995
|
(objectIdOrEdits, patchValue) => {
|
|
18616
18996
|
setObjectBindings(
|
|
@@ -18624,17 +19004,86 @@ function useObjectBindings(initialBindings) {
|
|
|
18624
19004
|
[]
|
|
18625
19005
|
);
|
|
18626
19006
|
const setMaterial = useCallback12(
|
|
18627
|
-
(
|
|
19007
|
+
(target, value) => updateObjectBindings(target, material(value)),
|
|
18628
19008
|
[updateObjectBindings]
|
|
18629
19009
|
);
|
|
18630
19010
|
const setBaseColor = useCallback12(
|
|
18631
|
-
(
|
|
19011
|
+
(target, color) => setMaterial(target, { baseColor: color }),
|
|
18632
19012
|
[setMaterial]
|
|
18633
19013
|
);
|
|
19014
|
+
const setTexture = useCallback12(
|
|
19015
|
+
(target, texture, channel = "baseColor") => {
|
|
19016
|
+
const property = TEXTURE_PROPERTY_BY_CHANNEL[channel];
|
|
19017
|
+
const slot = typeof texture === "string" ? { path: texture } : texture;
|
|
19018
|
+
updateObjectBindings(target, {
|
|
19019
|
+
style: { material: { [property]: slot } }
|
|
19020
|
+
});
|
|
19021
|
+
},
|
|
19022
|
+
[updateObjectBindings]
|
|
19023
|
+
);
|
|
19024
|
+
const clearTexture = useCallback12(
|
|
19025
|
+
(target, channel = "baseColor") => {
|
|
19026
|
+
const property = TEXTURE_PROPERTY_BY_CHANNEL[channel];
|
|
19027
|
+
updateObjectBindings(target, {
|
|
19028
|
+
style: { material: { [property]: void 0 } }
|
|
19029
|
+
});
|
|
19030
|
+
},
|
|
19031
|
+
[updateObjectBindings]
|
|
19032
|
+
);
|
|
19033
|
+
const copyMaterial = useCallback12(
|
|
19034
|
+
(source, target) => {
|
|
19035
|
+
setObjectBindings((current) => {
|
|
19036
|
+
const sourceKey = resolveBindingKey(current, source);
|
|
19037
|
+
if (!sourceKey) return current;
|
|
19038
|
+
const sourceMaterial = cloneMaterial(
|
|
19039
|
+
current[sourceKey]?.style?.material
|
|
19040
|
+
);
|
|
19041
|
+
const targetKeys = selectBindingKeys(current, target);
|
|
19042
|
+
if (targetKeys.length === 0) return current;
|
|
19043
|
+
let next = current;
|
|
19044
|
+
for (const targetKey of targetKeys) {
|
|
19045
|
+
const binding = current[targetKey];
|
|
19046
|
+
if (!binding) continue;
|
|
19047
|
+
if (next === current) next = { ...current };
|
|
19048
|
+
next[targetKey] = {
|
|
19049
|
+
...binding,
|
|
19050
|
+
style: {
|
|
19051
|
+
...binding.style,
|
|
19052
|
+
material: cloneMaterial(sourceMaterial)
|
|
19053
|
+
}
|
|
19054
|
+
};
|
|
19055
|
+
}
|
|
19056
|
+
return next;
|
|
19057
|
+
});
|
|
19058
|
+
},
|
|
19059
|
+
[]
|
|
19060
|
+
);
|
|
18634
19061
|
const resetMaterial = useCallback12(
|
|
18635
|
-
(
|
|
19062
|
+
(target) => updateObjectBindings(target, { style: { material: void 0 } }),
|
|
19063
|
+
[updateObjectBindings]
|
|
19064
|
+
);
|
|
19065
|
+
const setTransform = useCallback12(
|
|
19066
|
+
(target, transform) => updateObjectBindings(target, { transform }),
|
|
19067
|
+
[updateObjectBindings]
|
|
19068
|
+
);
|
|
19069
|
+
const resetTransform = useCallback12(
|
|
19070
|
+
(target) => updateObjectBindings(target, { transform: void 0 }),
|
|
19071
|
+
[updateObjectBindings]
|
|
19072
|
+
);
|
|
19073
|
+
const updateMetadata = useCallback12(
|
|
19074
|
+
(target, metadata) => updateObjectBindings(target, { metadata }),
|
|
18636
19075
|
[updateObjectBindings]
|
|
18637
19076
|
);
|
|
19077
|
+
const selectObjectBindings = useCallback12(
|
|
19078
|
+
(target) => selectBindings(objectBindings, target),
|
|
19079
|
+
[objectBindings]
|
|
19080
|
+
);
|
|
19081
|
+
const selectObjectBindingIds = useCallback12(
|
|
19082
|
+
(target) => selectBindingKeys(objectBindings, target).map(
|
|
19083
|
+
(bindingKey) => objectBindings[bindingKey]?.id ?? bindingKey
|
|
19084
|
+
),
|
|
19085
|
+
[objectBindings]
|
|
19086
|
+
);
|
|
18638
19087
|
const getMaterial = useCallback12(
|
|
18639
19088
|
(objectId) => {
|
|
18640
19089
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
@@ -18643,27 +19092,28 @@ function useObjectBindings(initialBindings) {
|
|
|
18643
19092
|
[objectBindings]
|
|
18644
19093
|
);
|
|
18645
19094
|
const hideObject = useCallback12(
|
|
18646
|
-
(
|
|
19095
|
+
(target) => updateObjectBindings(target, { visible: false }),
|
|
18647
19096
|
[updateObjectBindings]
|
|
18648
19097
|
);
|
|
18649
19098
|
const showObject = useCallback12(
|
|
18650
|
-
(
|
|
19099
|
+
(target) => updateObjectBindings(target, { visible: true }),
|
|
18651
19100
|
[updateObjectBindings]
|
|
18652
19101
|
);
|
|
18653
19102
|
const toggleObjectVisibility = useCallback12(
|
|
18654
|
-
(
|
|
19103
|
+
(target) => {
|
|
18655
19104
|
setObjectBindings((current) => {
|
|
18656
|
-
const
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
19105
|
+
const edits = selectBindingKeys(current, target).reduce(
|
|
19106
|
+
(acc, bindingKey) => {
|
|
19107
|
+
const binding = current[bindingKey];
|
|
19108
|
+
if (!binding) return acc;
|
|
19109
|
+
acc.push({
|
|
19110
|
+
ids: bindingKey,
|
|
19111
|
+
patch: { visible: binding.visible === false }
|
|
19112
|
+
});
|
|
19113
|
+
return acc;
|
|
19114
|
+
},
|
|
19115
|
+
[]
|
|
19116
|
+
);
|
|
18667
19117
|
return patchBindings(current, edits);
|
|
18668
19118
|
});
|
|
18669
19119
|
},
|
|
@@ -18697,8 +19147,16 @@ function useObjectBindings(initialBindings) {
|
|
|
18697
19147
|
updateObjectBindings,
|
|
18698
19148
|
setMaterial,
|
|
18699
19149
|
setBaseColor,
|
|
19150
|
+
setTexture,
|
|
19151
|
+
clearTexture,
|
|
19152
|
+
copyMaterial,
|
|
18700
19153
|
resetMaterial,
|
|
18701
19154
|
getMaterial,
|
|
19155
|
+
setTransform,
|
|
19156
|
+
resetTransform,
|
|
19157
|
+
updateMetadata,
|
|
19158
|
+
selectObjectBindings,
|
|
19159
|
+
selectObjectBindingIds,
|
|
18702
19160
|
hiddenObjects,
|
|
18703
19161
|
hiddenObjectIds,
|
|
18704
19162
|
hideObject,
|
|
@@ -18709,8 +19167,13 @@ function useObjectBindings(initialBindings) {
|
|
|
18709
19167
|
};
|
|
18710
19168
|
}
|
|
18711
19169
|
|
|
19170
|
+
// src/utils/defineObjectBindings.ts
|
|
19171
|
+
function defineObjectBindings(bindings) {
|
|
19172
|
+
return bindings;
|
|
19173
|
+
}
|
|
19174
|
+
|
|
18712
19175
|
// src/hooks/useSceneConfig.ts
|
|
18713
|
-
import { useCallback as useCallback13, useRef as useRef21, useState as
|
|
19176
|
+
import { useCallback as useCallback13, useMemo as useMemo22, useRef as useRef21, useState as useState31 } from "react";
|
|
18714
19177
|
|
|
18715
19178
|
// src/utils/patchSceneConfig.ts
|
|
18716
19179
|
function patchSceneConfig(sceneConfig, patch) {
|
|
@@ -18718,9 +19181,21 @@ function patchSceneConfig(sceneConfig, patch) {
|
|
|
18718
19181
|
}
|
|
18719
19182
|
|
|
18720
19183
|
// src/hooks/useSceneConfig.ts
|
|
19184
|
+
var DEFAULT_LIGHT = {
|
|
19185
|
+
type: "point",
|
|
19186
|
+
color: "#ffffff",
|
|
19187
|
+
intensity: 1,
|
|
19188
|
+
position: [0, 0, 0],
|
|
19189
|
+
distance: 0,
|
|
19190
|
+
decay: 2,
|
|
19191
|
+
attachedToCamera: false,
|
|
19192
|
+
castShadow: false,
|
|
19193
|
+
shadowBias: -5e-4,
|
|
19194
|
+
visible: true
|
|
19195
|
+
};
|
|
18721
19196
|
function useSceneConfig(initialSceneConfig) {
|
|
18722
19197
|
const initialSceneConfigRef = useRef21(null);
|
|
18723
|
-
const [sceneConfig, setSceneConfig] =
|
|
19198
|
+
const [sceneConfig, setSceneConfig] = useState31(() => {
|
|
18724
19199
|
const initial = typeof initialSceneConfig === "function" ? initialSceneConfig() : initialSceneConfig;
|
|
18725
19200
|
initialSceneConfigRef.current = initial;
|
|
18726
19201
|
return initial;
|
|
@@ -18735,18 +19210,124 @@ function useSceneConfig(initialSceneConfig) {
|
|
|
18735
19210
|
const initial = initialSceneConfigRef.current;
|
|
18736
19211
|
if (initial) setSceneConfig(initial);
|
|
18737
19212
|
}, []);
|
|
19213
|
+
const updateLight = useCallback13(
|
|
19214
|
+
(id, patch) => {
|
|
19215
|
+
updateSceneConfig((current) => ({
|
|
19216
|
+
lighting: {
|
|
19217
|
+
lights: current.lighting.lights.map(
|
|
19218
|
+
(light) => light.id === id ? { ...light, ...patch } : light
|
|
19219
|
+
)
|
|
19220
|
+
}
|
|
19221
|
+
}));
|
|
19222
|
+
},
|
|
19223
|
+
[updateSceneConfig]
|
|
19224
|
+
);
|
|
19225
|
+
const addLight = useCallback13(
|
|
19226
|
+
(light) => {
|
|
19227
|
+
updateSceneConfig((current) => {
|
|
19228
|
+
if (current.lighting.lights.some((item) => item.id === light.id)) {
|
|
19229
|
+
return {};
|
|
19230
|
+
}
|
|
19231
|
+
return {
|
|
19232
|
+
lighting: {
|
|
19233
|
+
lights: [
|
|
19234
|
+
...current.lighting.lights,
|
|
19235
|
+
{ ...DEFAULT_LIGHT, ...light }
|
|
19236
|
+
]
|
|
19237
|
+
}
|
|
19238
|
+
};
|
|
19239
|
+
});
|
|
19240
|
+
},
|
|
19241
|
+
[updateSceneConfig]
|
|
19242
|
+
);
|
|
19243
|
+
const removeLight = useCallback13(
|
|
19244
|
+
(id) => {
|
|
19245
|
+
updateSceneConfig((current) => ({
|
|
19246
|
+
lighting: {
|
|
19247
|
+
lights: current.lighting.lights.filter((light) => light.id !== id)
|
|
19248
|
+
}
|
|
19249
|
+
}));
|
|
19250
|
+
},
|
|
19251
|
+
[updateSceneConfig]
|
|
19252
|
+
);
|
|
19253
|
+
const setLightIntensity = useCallback13(
|
|
19254
|
+
(id, intensity) => updateLight(id, { intensity }),
|
|
19255
|
+
[updateLight]
|
|
19256
|
+
);
|
|
19257
|
+
const setLightColor = useCallback13(
|
|
19258
|
+
(id, color) => updateLight(id, { color }),
|
|
19259
|
+
[updateLight]
|
|
19260
|
+
);
|
|
19261
|
+
const showLight = useCallback13(
|
|
19262
|
+
(id) => updateLight(id, { visible: true }),
|
|
19263
|
+
[updateLight]
|
|
19264
|
+
);
|
|
19265
|
+
const hideLight = useCallback13(
|
|
19266
|
+
(id) => updateLight(id, { visible: false }),
|
|
19267
|
+
[updateLight]
|
|
19268
|
+
);
|
|
19269
|
+
const toggleLight = useCallback13(
|
|
19270
|
+
(id) => {
|
|
19271
|
+
updateSceneConfig((current) => ({
|
|
19272
|
+
lighting: {
|
|
19273
|
+
lights: current.lighting.lights.map(
|
|
19274
|
+
(light) => light.id === id ? { ...light, visible: !light.visible } : light
|
|
19275
|
+
)
|
|
19276
|
+
}
|
|
19277
|
+
}));
|
|
19278
|
+
},
|
|
19279
|
+
[updateSceneConfig]
|
|
19280
|
+
);
|
|
19281
|
+
const attachLight = useCallback13(
|
|
19282
|
+
(id, attachment) => updateLight(id, { attachment, attachedToCamera: false }),
|
|
19283
|
+
[updateLight]
|
|
19284
|
+
);
|
|
19285
|
+
const detachLight = useCallback13(
|
|
19286
|
+
(id) => updateLight(id, { attachment: void 0 }),
|
|
19287
|
+
[updateLight]
|
|
19288
|
+
);
|
|
19289
|
+
const lights = useMemo22(
|
|
19290
|
+
() => ({
|
|
19291
|
+
items: sceneConfig.lighting.lights,
|
|
19292
|
+
get: (id) => sceneConfig.lighting.lights.find((light) => light.id === id),
|
|
19293
|
+
add: addLight,
|
|
19294
|
+
update: updateLight,
|
|
19295
|
+
remove: removeLight,
|
|
19296
|
+
setIntensity: setLightIntensity,
|
|
19297
|
+
setColor: setLightColor,
|
|
19298
|
+
show: showLight,
|
|
19299
|
+
hide: hideLight,
|
|
19300
|
+
toggle: toggleLight,
|
|
19301
|
+
attach: attachLight,
|
|
19302
|
+
detach: detachLight
|
|
19303
|
+
}),
|
|
19304
|
+
[
|
|
19305
|
+
sceneConfig.lighting.lights,
|
|
19306
|
+
addLight,
|
|
19307
|
+
updateLight,
|
|
19308
|
+
removeLight,
|
|
19309
|
+
setLightIntensity,
|
|
19310
|
+
setLightColor,
|
|
19311
|
+
showLight,
|
|
19312
|
+
hideLight,
|
|
19313
|
+
toggleLight,
|
|
19314
|
+
attachLight,
|
|
19315
|
+
detachLight
|
|
19316
|
+
]
|
|
19317
|
+
);
|
|
18738
19318
|
return {
|
|
18739
19319
|
sceneConfig,
|
|
18740
19320
|
setSceneConfig,
|
|
18741
19321
|
updateSceneConfig,
|
|
18742
|
-
resetSceneConfig
|
|
19322
|
+
resetSceneConfig,
|
|
19323
|
+
lights
|
|
18743
19324
|
};
|
|
18744
19325
|
}
|
|
18745
19326
|
|
|
18746
19327
|
// src/hooks/useViewerHover.ts
|
|
18747
|
-
import { useCallback as useCallback14, useState as
|
|
19328
|
+
import { useCallback as useCallback14, useState as useState32 } from "react";
|
|
18748
19329
|
function useViewerHover() {
|
|
18749
|
-
const [hoveredObjectBinding, setHoveredObjectBinding] =
|
|
19330
|
+
const [hoveredObjectBinding, setHoveredObjectBinding] = useState32(null);
|
|
18750
19331
|
const handleHoveredObject = useCallback14(
|
|
18751
19332
|
(binding) => {
|
|
18752
19333
|
setHoveredObjectBinding(binding);
|
|
@@ -18760,23 +19341,23 @@ function useViewerHover() {
|
|
|
18760
19341
|
}
|
|
18761
19342
|
|
|
18762
19343
|
// src/hooks/useViewerModel.ts
|
|
18763
|
-
import { useCallback as useCallback15, useState as
|
|
18764
|
-
import { Box3 as Box36, Vector3 as
|
|
19344
|
+
import { useCallback as useCallback15, useState as useState33 } from "react";
|
|
19345
|
+
import { Box3 as Box36, Vector3 as Vector312 } from "three";
|
|
18765
19346
|
function toTuple2(vector) {
|
|
18766
19347
|
return [vector.x, vector.y, vector.z];
|
|
18767
19348
|
}
|
|
18768
19349
|
function useViewerModel() {
|
|
18769
|
-
const [isLoading, setIsLoading] =
|
|
18770
|
-
const [isReady, setIsReady] =
|
|
18771
|
-
const [error, setError] =
|
|
18772
|
-
const [bounds, setBounds] =
|
|
18773
|
-
const [objectCount, setObjectCount] =
|
|
19350
|
+
const [isLoading, setIsLoading] = useState33(true);
|
|
19351
|
+
const [isReady, setIsReady] = useState33(false);
|
|
19352
|
+
const [error, setError] = useState33(null);
|
|
19353
|
+
const [bounds, setBounds] = useState33(null);
|
|
19354
|
+
const [objectCount, setObjectCount] = useState33(0);
|
|
18774
19355
|
const handleModelLoaded = useCallback15((scene) => {
|
|
18775
19356
|
const box = new Box36().setFromObject(scene);
|
|
18776
19357
|
const min = box.min.clone();
|
|
18777
19358
|
const max = box.max.clone();
|
|
18778
|
-
const center = new
|
|
18779
|
-
const size = new
|
|
19359
|
+
const center = new Vector312();
|
|
19360
|
+
const size = new Vector312();
|
|
18780
19361
|
let nextObjectCount = 0;
|
|
18781
19362
|
scene.traverse((child) => {
|
|
18782
19363
|
if ("isMesh" in child && child.isMesh) {
|
|
@@ -18824,61 +19405,777 @@ function useViewerModel() {
|
|
|
18824
19405
|
|
|
18825
19406
|
// src/hooks/useViewerActions.ts
|
|
18826
19407
|
import { useCallback as useCallback16 } from "react";
|
|
18827
|
-
function useViewerActions(
|
|
18828
|
-
|
|
19408
|
+
function useViewerActions({
|
|
19409
|
+
bindings,
|
|
19410
|
+
scene,
|
|
19411
|
+
camera,
|
|
19412
|
+
animations,
|
|
19413
|
+
onAction
|
|
19414
|
+
}) {
|
|
19415
|
+
const {
|
|
19416
|
+
objectBindings,
|
|
19417
|
+
updateObjectBindings,
|
|
19418
|
+
showObject,
|
|
19419
|
+
hideObject,
|
|
19420
|
+
toggleObjectVisibility
|
|
19421
|
+
} = bindings;
|
|
19422
|
+
const updateSceneConfig = scene?.updateSceneConfig;
|
|
19423
|
+
const focusObject = camera?.focusObject;
|
|
19424
|
+
const fitScene = camera?.fitScene;
|
|
19425
|
+
const resetView = camera?.resetView;
|
|
19426
|
+
const setCameraTarget = camera?.setCameraTarget;
|
|
19427
|
+
const setCameraState = camera?.setCameraState;
|
|
19428
|
+
const playAnimation = animations?.play;
|
|
19429
|
+
const pauseAnimation = animations?.pause;
|
|
19430
|
+
const stopAnimation = animations?.stop;
|
|
19431
|
+
const setAnimationSpeed = animations?.setSpeed;
|
|
19432
|
+
const seekAnimation = animations?.seek;
|
|
18829
19433
|
const getActionsForObject = useCallback16(
|
|
18830
19434
|
(objectId) => {
|
|
18831
19435
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
18832
|
-
|
|
18833
|
-
return [];
|
|
18834
|
-
}
|
|
18835
|
-
return objectBindings[bindingKey]?.actions ?? [];
|
|
19436
|
+
return bindingKey ? objectBindings[bindingKey]?.actions ?? [] : [];
|
|
18836
19437
|
},
|
|
18837
19438
|
[objectBindings]
|
|
18838
19439
|
);
|
|
19440
|
+
const handleAction = useCallback16(
|
|
19441
|
+
async (event) => {
|
|
19442
|
+
for (const effect of event.action.effects ?? []) {
|
|
19443
|
+
if ("objectBindings" in effect) {
|
|
19444
|
+
updateObjectBindings(effect.target, effect.objectBindings);
|
|
19445
|
+
continue;
|
|
19446
|
+
}
|
|
19447
|
+
if ("sceneConfig" in effect) {
|
|
19448
|
+
updateSceneConfig?.(effect.sceneConfig);
|
|
19449
|
+
continue;
|
|
19450
|
+
}
|
|
19451
|
+
if ("visibility" in effect) {
|
|
19452
|
+
if (effect.visibility === "show") showObject(effect.target);
|
|
19453
|
+
else if (effect.visibility === "hide") hideObject(effect.target);
|
|
19454
|
+
else toggleObjectVisibility(effect.target);
|
|
19455
|
+
continue;
|
|
19456
|
+
}
|
|
19457
|
+
if ("camera" in effect) {
|
|
19458
|
+
const action2 = effect.camera;
|
|
19459
|
+
if (action2.type === "focus-object") {
|
|
19460
|
+
await focusObject?.(action2.objectId ?? event.objectId);
|
|
19461
|
+
} else if (action2.type === "fit-scene") {
|
|
19462
|
+
await fitScene?.();
|
|
19463
|
+
} else if (action2.type === "reset") {
|
|
19464
|
+
await resetView?.();
|
|
19465
|
+
} else if (action2.type === "set-target") {
|
|
19466
|
+
await setCameraTarget?.(action2.target, action2.transition ?? true);
|
|
19467
|
+
} else {
|
|
19468
|
+
await setCameraState?.(action2.state, action2.transition ?? true);
|
|
19469
|
+
}
|
|
19470
|
+
continue;
|
|
19471
|
+
}
|
|
19472
|
+
const action = effect.animation;
|
|
19473
|
+
if (action.type === "play") playAnimation?.(action.clip);
|
|
19474
|
+
else if (action.type === "pause") pauseAnimation?.();
|
|
19475
|
+
else if (action.type === "stop") stopAnimation?.();
|
|
19476
|
+
else if (action.type === "set-speed") {
|
|
19477
|
+
setAnimationSpeed?.(action.speed);
|
|
19478
|
+
} else {
|
|
19479
|
+
seekAnimation?.(action.time);
|
|
19480
|
+
}
|
|
19481
|
+
}
|
|
19482
|
+
await onAction?.(event);
|
|
19483
|
+
},
|
|
19484
|
+
[
|
|
19485
|
+
fitScene,
|
|
19486
|
+
focusObject,
|
|
19487
|
+
hideObject,
|
|
19488
|
+
onAction,
|
|
19489
|
+
pauseAnimation,
|
|
19490
|
+
playAnimation,
|
|
19491
|
+
resetView,
|
|
19492
|
+
seekAnimation,
|
|
19493
|
+
setAnimationSpeed,
|
|
19494
|
+
setCameraState,
|
|
19495
|
+
setCameraTarget,
|
|
19496
|
+
showObject,
|
|
19497
|
+
stopAnimation,
|
|
19498
|
+
toggleObjectVisibility,
|
|
19499
|
+
updateObjectBindings,
|
|
19500
|
+
updateSceneConfig
|
|
19501
|
+
]
|
|
19502
|
+
);
|
|
18839
19503
|
const runAction = useCallback16(
|
|
18840
|
-
(objectId, actionId) => {
|
|
19504
|
+
async (objectId, actionId) => {
|
|
18841
19505
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
18842
|
-
if (!bindingKey)
|
|
18843
|
-
return null;
|
|
18844
|
-
}
|
|
19506
|
+
if (!bindingKey) return null;
|
|
18845
19507
|
const binding = objectBindings[bindingKey];
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
}
|
|
18849
|
-
const action = (binding.actions ?? []).find(
|
|
18850
|
-
(entry) => entry.id === actionId
|
|
18851
|
-
);
|
|
18852
|
-
if (!action) {
|
|
18853
|
-
return null;
|
|
18854
|
-
}
|
|
19508
|
+
const action = binding?.actions?.find((entry) => entry.id === actionId);
|
|
19509
|
+
if (!binding || !action) return null;
|
|
18855
19510
|
const event = {
|
|
18856
19511
|
objectId: bindingKey,
|
|
18857
19512
|
action,
|
|
18858
19513
|
binding
|
|
18859
19514
|
};
|
|
18860
|
-
|
|
18861
|
-
onObjectBindingsChange({
|
|
18862
|
-
...objectBindings,
|
|
18863
|
-
[bindingKey]: {
|
|
18864
|
-
...binding,
|
|
18865
|
-
visible: binding.visible === false
|
|
18866
|
-
}
|
|
18867
|
-
});
|
|
18868
|
-
}
|
|
18869
|
-
onAction?.(event);
|
|
19515
|
+
await handleAction(event);
|
|
18870
19516
|
return event;
|
|
18871
19517
|
},
|
|
18872
|
-
[
|
|
19518
|
+
[handleAction, objectBindings]
|
|
18873
19519
|
);
|
|
18874
19520
|
return {
|
|
18875
19521
|
getActionsForObject,
|
|
19522
|
+
handleAction,
|
|
18876
19523
|
runAction
|
|
18877
19524
|
};
|
|
18878
19525
|
}
|
|
18879
19526
|
|
|
19527
|
+
// src/hooks/useViewerConnection.ts
|
|
19528
|
+
import { useCallback as useCallback17, useRef as useRef22 } from "react";
|
|
19529
|
+
function getReadyHandler(consumer) {
|
|
19530
|
+
if (typeof consumer === "function") {
|
|
19531
|
+
return consumer;
|
|
19532
|
+
}
|
|
19533
|
+
return consumer?.handleViewerReady ?? null;
|
|
19534
|
+
}
|
|
19535
|
+
function useViewerConnection(...consumers) {
|
|
19536
|
+
const viewerRef = useRef22(null);
|
|
19537
|
+
const consumersRef = useRef22(consumers);
|
|
19538
|
+
consumersRef.current = consumers;
|
|
19539
|
+
const handleViewerReady = useCallback17((viewer) => {
|
|
19540
|
+
viewerRef.current = viewer;
|
|
19541
|
+
const invoked = /* @__PURE__ */ new Set();
|
|
19542
|
+
for (const consumer of consumersRef.current) {
|
|
19543
|
+
const handler = getReadyHandler(consumer);
|
|
19544
|
+
if (handler && !invoked.has(handler)) {
|
|
19545
|
+
invoked.add(handler);
|
|
19546
|
+
handler(viewer);
|
|
19547
|
+
}
|
|
19548
|
+
}
|
|
19549
|
+
}, []);
|
|
19550
|
+
const getViewer = useCallback17(() => viewerRef.current, []);
|
|
19551
|
+
const captureImage2 = useCallback17((options) => {
|
|
19552
|
+
const viewer = viewerRef.current;
|
|
19553
|
+
if (!viewer) {
|
|
19554
|
+
return Promise.reject(new Error("ModelViewer is not ready yet."));
|
|
19555
|
+
}
|
|
19556
|
+
return viewer.captureImage(options);
|
|
19557
|
+
}, []);
|
|
19558
|
+
const invalidate = useCallback17(() => {
|
|
19559
|
+
const viewer = viewerRef.current;
|
|
19560
|
+
if (!viewer) {
|
|
19561
|
+
return false;
|
|
19562
|
+
}
|
|
19563
|
+
viewer.invalidate();
|
|
19564
|
+
return true;
|
|
19565
|
+
}, []);
|
|
19566
|
+
return {
|
|
19567
|
+
handleViewerReady,
|
|
19568
|
+
getViewer,
|
|
19569
|
+
captureImage: captureImage2,
|
|
19570
|
+
invalidate
|
|
19571
|
+
};
|
|
19572
|
+
}
|
|
19573
|
+
|
|
19574
|
+
// src/hooks/useViewerEffects.ts
|
|
19575
|
+
import { useCallback as useCallback18, useEffect as useEffect25, useRef as useRef23 } from "react";
|
|
19576
|
+
import {
|
|
19577
|
+
Material as Material3,
|
|
19578
|
+
PointLight,
|
|
19579
|
+
Vector3 as Vector313
|
|
19580
|
+
} from "three";
|
|
19581
|
+
var NUMERIC_MATERIAL_KEYS = [
|
|
19582
|
+
"emissiveIntensity",
|
|
19583
|
+
"opacity",
|
|
19584
|
+
"metalness",
|
|
19585
|
+
"roughness",
|
|
19586
|
+
"transmission",
|
|
19587
|
+
"clearcoat",
|
|
19588
|
+
"clearcoatRoughness",
|
|
19589
|
+
"ior",
|
|
19590
|
+
"thickness"
|
|
19591
|
+
];
|
|
19592
|
+
var nextEffectId = 1;
|
|
19593
|
+
function stringList(target) {
|
|
19594
|
+
return typeof target === "string" ? [target] : target;
|
|
19595
|
+
}
|
|
19596
|
+
function clamp2(value, min = 0, max = 1) {
|
|
19597
|
+
return Math.min(max, Math.max(min, value));
|
|
19598
|
+
}
|
|
19599
|
+
function randomBetween(min, max) {
|
|
19600
|
+
return min + Math.random() * (max - min);
|
|
19601
|
+
}
|
|
19602
|
+
function interpolate([from, to], level) {
|
|
19603
|
+
return from + (to - from) * level;
|
|
19604
|
+
}
|
|
19605
|
+
function interpolateVector([from, to], level) {
|
|
19606
|
+
return [
|
|
19607
|
+
from[0] + (to[0] - from[0]) * level,
|
|
19608
|
+
from[1] + (to[1] - from[1]) * level,
|
|
19609
|
+
from[2] + (to[2] - from[2]) * level
|
|
19610
|
+
];
|
|
19611
|
+
}
|
|
19612
|
+
function ease(value, easing) {
|
|
19613
|
+
const t = clamp2(value);
|
|
19614
|
+
switch (easing) {
|
|
19615
|
+
case "ease-in":
|
|
19616
|
+
return t * t;
|
|
19617
|
+
case "ease-out":
|
|
19618
|
+
return 1 - (1 - t) * (1 - t);
|
|
19619
|
+
case "ease-in-out":
|
|
19620
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
19621
|
+
default:
|
|
19622
|
+
return t;
|
|
19623
|
+
}
|
|
19624
|
+
}
|
|
19625
|
+
function materialList(node) {
|
|
19626
|
+
const value = node.material;
|
|
19627
|
+
if (!value) return [];
|
|
19628
|
+
return (Array.isArray(value) ? value : [value]).filter(
|
|
19629
|
+
(entry) => entry instanceof Material3
|
|
19630
|
+
);
|
|
19631
|
+
}
|
|
19632
|
+
function readMaterial(material2) {
|
|
19633
|
+
const snapshot = {
|
|
19634
|
+
opacity: material2.opacity,
|
|
19635
|
+
transparent: material2.transparent
|
|
19636
|
+
};
|
|
19637
|
+
if (material2.color) snapshot.baseColor = `#${material2.color.getHexString()}`;
|
|
19638
|
+
if (material2.emissive) snapshot.emissive = `#${material2.emissive.getHexString()}`;
|
|
19639
|
+
for (const key of NUMERIC_MATERIAL_KEYS) {
|
|
19640
|
+
const value = material2[key];
|
|
19641
|
+
if (typeof value === "number") snapshot[key] = value;
|
|
19642
|
+
}
|
|
19643
|
+
return snapshot;
|
|
19644
|
+
}
|
|
19645
|
+
function applyMaterial(material2, patch) {
|
|
19646
|
+
if (patch.baseColor !== void 0 && material2.color) {
|
|
19647
|
+
material2.color.set(patch.baseColor);
|
|
19648
|
+
}
|
|
19649
|
+
if (patch.emissive !== void 0 && material2.emissive) {
|
|
19650
|
+
material2.emissive.set(patch.emissive);
|
|
19651
|
+
}
|
|
19652
|
+
for (const key of NUMERIC_MATERIAL_KEYS) {
|
|
19653
|
+
const value = patch[key];
|
|
19654
|
+
if (value !== void 0 && typeof material2[key] === "number") {
|
|
19655
|
+
material2[key] = value;
|
|
19656
|
+
}
|
|
19657
|
+
}
|
|
19658
|
+
if (patch.transparent !== void 0) material2.transparent = patch.transparent;
|
|
19659
|
+
material2.needsUpdate = true;
|
|
19660
|
+
}
|
|
19661
|
+
function readTransform(node) {
|
|
19662
|
+
return {
|
|
19663
|
+
position: [node.position.x, node.position.y, node.position.z],
|
|
19664
|
+
rotation: [node.rotation.x, node.rotation.y, node.rotation.z],
|
|
19665
|
+
scale: [node.scale.x, node.scale.y, node.scale.z],
|
|
19666
|
+
visible: node.visible
|
|
19667
|
+
};
|
|
19668
|
+
}
|
|
19669
|
+
function applyTransform(node, patch) {
|
|
19670
|
+
if (patch.position) node.position.set(...patch.position);
|
|
19671
|
+
if (patch.rotation) node.rotation.set(...patch.rotation);
|
|
19672
|
+
if (patch.scale !== void 0) {
|
|
19673
|
+
const scale = typeof patch.scale === "number" ? [patch.scale, patch.scale, patch.scale] : patch.scale;
|
|
19674
|
+
node.scale.set(...scale);
|
|
19675
|
+
}
|
|
19676
|
+
if (patch.visible !== void 0) node.visible = patch.visible;
|
|
19677
|
+
node.updateMatrix();
|
|
19678
|
+
node.updateMatrixWorld(true);
|
|
19679
|
+
}
|
|
19680
|
+
function useViewerEffects(options = {}) {
|
|
19681
|
+
const optionsRef = useRef23(options);
|
|
19682
|
+
optionsRef.current = options;
|
|
19683
|
+
const viewerRef = useRef23(null);
|
|
19684
|
+
const sceneRef = useRef23(null);
|
|
19685
|
+
const frameRef = useRef23(null);
|
|
19686
|
+
const effectsRef = useRef23(/* @__PURE__ */ new Map());
|
|
19687
|
+
const lightsRef = useRef23(/* @__PURE__ */ new Map());
|
|
19688
|
+
const materialSnapshotsRef = useRef23(
|
|
19689
|
+
/* @__PURE__ */ new Map()
|
|
19690
|
+
);
|
|
19691
|
+
const transformSnapshotsRef = useRef23(/* @__PURE__ */ new Map());
|
|
19692
|
+
const resolveObject = useCallback18((target) => {
|
|
19693
|
+
const viewer = viewerRef.current;
|
|
19694
|
+
if (!viewer) return null;
|
|
19695
|
+
const bindingKey = resolveBindingKey(viewer.objectBindings, target);
|
|
19696
|
+
return viewer.nodeRefs[target] ?? (bindingKey ? viewer.nodeRefs[bindingKey] : null) ?? viewer.scene?.getObjectByName(target) ?? null;
|
|
19697
|
+
}, []);
|
|
19698
|
+
const resolveTargets = useCallback18(
|
|
19699
|
+
(target) => {
|
|
19700
|
+
const viewer = viewerRef.current;
|
|
19701
|
+
if (!viewer) {
|
|
19702
|
+
return typeof target === "string" ? [target] : Array.isArray(target) ? target : [];
|
|
19703
|
+
}
|
|
19704
|
+
if (typeof target === "object" && !Array.isArray(target)) {
|
|
19705
|
+
return selectBindingKeys(viewer.objectBindings, target);
|
|
19706
|
+
}
|
|
19707
|
+
return stringList(target).map(
|
|
19708
|
+
(id) => resolveBindingKey(viewer.objectBindings, id) ?? id
|
|
19709
|
+
);
|
|
19710
|
+
},
|
|
19711
|
+
[]
|
|
19712
|
+
);
|
|
19713
|
+
const invalidate = useCallback18(() => {
|
|
19714
|
+
viewerRef.current?.invalidate();
|
|
19715
|
+
}, []);
|
|
19716
|
+
const setMaterialInternal = useCallback18(
|
|
19717
|
+
(target, patch) => {
|
|
19718
|
+
let changed = false;
|
|
19719
|
+
for (const id of resolveTargets(target)) {
|
|
19720
|
+
const node = resolveObject(id);
|
|
19721
|
+
if (!node) continue;
|
|
19722
|
+
for (const entry of materialList(node)) {
|
|
19723
|
+
if (!materialSnapshotsRef.current.has(entry)) {
|
|
19724
|
+
materialSnapshotsRef.current.set(entry, readMaterial(entry));
|
|
19725
|
+
}
|
|
19726
|
+
applyMaterial(entry, patch);
|
|
19727
|
+
changed = true;
|
|
19728
|
+
}
|
|
19729
|
+
}
|
|
19730
|
+
return changed;
|
|
19731
|
+
},
|
|
19732
|
+
[resolveObject, resolveTargets]
|
|
19733
|
+
);
|
|
19734
|
+
const setMaterial = useCallback18(
|
|
19735
|
+
(target, patch) => {
|
|
19736
|
+
const changed = setMaterialInternal(target, patch);
|
|
19737
|
+
if (changed) invalidate();
|
|
19738
|
+
return changed;
|
|
19739
|
+
},
|
|
19740
|
+
[invalidate, setMaterialInternal]
|
|
19741
|
+
);
|
|
19742
|
+
const setTransformInternal = useCallback18(
|
|
19743
|
+
(target, patch) => {
|
|
19744
|
+
let changed = false;
|
|
19745
|
+
for (const id of resolveTargets(target)) {
|
|
19746
|
+
const node = resolveObject(id);
|
|
19747
|
+
if (!node) continue;
|
|
19748
|
+
if (!transformSnapshotsRef.current.has(node)) {
|
|
19749
|
+
transformSnapshotsRef.current.set(node, readTransform(node));
|
|
19750
|
+
}
|
|
19751
|
+
applyTransform(node, patch);
|
|
19752
|
+
changed = true;
|
|
19753
|
+
}
|
|
19754
|
+
return changed;
|
|
19755
|
+
},
|
|
19756
|
+
[resolveObject, resolveTargets]
|
|
19757
|
+
);
|
|
19758
|
+
const setTransform = useCallback18(
|
|
19759
|
+
(target, patch) => {
|
|
19760
|
+
const changed = setTransformInternal(target, patch);
|
|
19761
|
+
if (changed) invalidate();
|
|
19762
|
+
return changed;
|
|
19763
|
+
},
|
|
19764
|
+
[invalidate, setTransformInternal]
|
|
19765
|
+
);
|
|
19766
|
+
const getWorldPosition = useCallback18(
|
|
19767
|
+
(target) => {
|
|
19768
|
+
const node = resolveObject(target);
|
|
19769
|
+
if (!node) return null;
|
|
19770
|
+
node.updateWorldMatrix(true, false);
|
|
19771
|
+
const position = node.getWorldPosition(new Vector313());
|
|
19772
|
+
return [position.x, position.y, position.z];
|
|
19773
|
+
},
|
|
19774
|
+
[resolveObject]
|
|
19775
|
+
);
|
|
19776
|
+
const removePointLight = useCallback18(
|
|
19777
|
+
(id) => {
|
|
19778
|
+
const runtime = lightsRef.current.get(id);
|
|
19779
|
+
if (!runtime) return false;
|
|
19780
|
+
runtime.light.removeFromParent();
|
|
19781
|
+
runtime.light.dispose();
|
|
19782
|
+
lightsRef.current.delete(id);
|
|
19783
|
+
invalidate();
|
|
19784
|
+
return true;
|
|
19785
|
+
},
|
|
19786
|
+
[invalidate]
|
|
19787
|
+
);
|
|
19788
|
+
const addPointLight = useCallback18(
|
|
19789
|
+
(lightOptions) => {
|
|
19790
|
+
const target = resolveObject(lightOptions.target);
|
|
19791
|
+
if (!target) return false;
|
|
19792
|
+
const existing = lightsRef.current.get(lightOptions.id);
|
|
19793
|
+
const light = existing?.light ?? new PointLight();
|
|
19794
|
+
if (existing && existing.target !== lightOptions.target) {
|
|
19795
|
+
light.removeFromParent();
|
|
19796
|
+
}
|
|
19797
|
+
light.name = `react-immersive-runtime-light:${lightOptions.id}`;
|
|
19798
|
+
light.color.set(lightOptions.color ?? "#ffffff");
|
|
19799
|
+
light.intensity = lightOptions.intensity ?? 1;
|
|
19800
|
+
light.distance = lightOptions.distance ?? 0;
|
|
19801
|
+
light.decay = lightOptions.decay ?? 2;
|
|
19802
|
+
light.castShadow = lightOptions.castShadow ?? false;
|
|
19803
|
+
light.position.set(...lightOptions.offset ?? [0, 0, 0]);
|
|
19804
|
+
if (light.parent !== target) target.add(light);
|
|
19805
|
+
lightsRef.current.set(lightOptions.id, {
|
|
19806
|
+
light,
|
|
19807
|
+
target: lightOptions.target
|
|
19808
|
+
});
|
|
19809
|
+
invalidate();
|
|
19810
|
+
return true;
|
|
19811
|
+
},
|
|
19812
|
+
[invalidate, resolveObject]
|
|
19813
|
+
);
|
|
19814
|
+
const updatePointLight = useCallback18(
|
|
19815
|
+
(id, patch) => {
|
|
19816
|
+
const runtime = lightsRef.current.get(id);
|
|
19817
|
+
if (!runtime) return false;
|
|
19818
|
+
if (patch.target && patch.target !== runtime.target) {
|
|
19819
|
+
const target = resolveObject(patch.target);
|
|
19820
|
+
if (!target) return false;
|
|
19821
|
+
runtime.light.removeFromParent();
|
|
19822
|
+
target.add(runtime.light);
|
|
19823
|
+
runtime.target = patch.target;
|
|
19824
|
+
}
|
|
19825
|
+
if (patch.color !== void 0) runtime.light.color.set(patch.color);
|
|
19826
|
+
if (patch.intensity !== void 0) {
|
|
19827
|
+
runtime.light.intensity = patch.intensity;
|
|
19828
|
+
}
|
|
19829
|
+
if (patch.distance !== void 0) runtime.light.distance = patch.distance;
|
|
19830
|
+
if (patch.decay !== void 0) runtime.light.decay = patch.decay;
|
|
19831
|
+
if (patch.offset !== void 0) {
|
|
19832
|
+
runtime.light.position.set(...patch.offset);
|
|
19833
|
+
}
|
|
19834
|
+
if (patch.castShadow !== void 0) {
|
|
19835
|
+
runtime.light.castShadow = patch.castShadow;
|
|
19836
|
+
}
|
|
19837
|
+
invalidate();
|
|
19838
|
+
return true;
|
|
19839
|
+
},
|
|
19840
|
+
[invalidate, resolveObject]
|
|
19841
|
+
);
|
|
19842
|
+
const applyChannels = useCallback18(
|
|
19843
|
+
(channels, levels) => {
|
|
19844
|
+
const levelFor = (index) => levels[index % levels.length] ?? 0;
|
|
19845
|
+
for (const channel of channels.materials ?? []) {
|
|
19846
|
+
resolveTargets(channel.targets).forEach((target, index) => {
|
|
19847
|
+
const level = levelFor(index);
|
|
19848
|
+
const patch = {};
|
|
19849
|
+
for (const key of NUMERIC_MATERIAL_KEYS) {
|
|
19850
|
+
const range = channel.values[key];
|
|
19851
|
+
if (range) patch[key] = interpolate(range, level);
|
|
19852
|
+
}
|
|
19853
|
+
setMaterialInternal(target, patch);
|
|
19854
|
+
});
|
|
19855
|
+
}
|
|
19856
|
+
for (const channel of channels.transforms ?? []) {
|
|
19857
|
+
resolveTargets(channel.targets).forEach((target, index) => {
|
|
19858
|
+
const level = levelFor(index);
|
|
19859
|
+
const patch = {};
|
|
19860
|
+
if (channel.values.position) {
|
|
19861
|
+
patch.position = interpolateVector(channel.values.position, level);
|
|
19862
|
+
}
|
|
19863
|
+
if (channel.values.rotation) {
|
|
19864
|
+
patch.rotation = interpolateVector(channel.values.rotation, level);
|
|
19865
|
+
}
|
|
19866
|
+
if (channel.values.scale) {
|
|
19867
|
+
patch.scale = interpolateVector(channel.values.scale, level);
|
|
19868
|
+
}
|
|
19869
|
+
setTransformInternal(target, patch);
|
|
19870
|
+
});
|
|
19871
|
+
}
|
|
19872
|
+
for (const channel of channels.lights ?? []) {
|
|
19873
|
+
stringList(channel.lights).forEach((id, index) => {
|
|
19874
|
+
const runtime = lightsRef.current.get(id);
|
|
19875
|
+
if (runtime) {
|
|
19876
|
+
runtime.light.intensity = interpolate(
|
|
19877
|
+
channel.intensity,
|
|
19878
|
+
levelFor(index)
|
|
19879
|
+
);
|
|
19880
|
+
}
|
|
19881
|
+
});
|
|
19882
|
+
}
|
|
19883
|
+
},
|
|
19884
|
+
[resolveTargets, setMaterialInternal, setTransformInternal]
|
|
19885
|
+
);
|
|
19886
|
+
const scheduleFrameRef = useRef23(() => void 0);
|
|
19887
|
+
const runFrameRef = useRef23(() => void 0);
|
|
19888
|
+
runFrameRef.current = (now) => {
|
|
19889
|
+
frameRef.current = null;
|
|
19890
|
+
let rendered = false;
|
|
19891
|
+
for (const [id, effect] of effectsRef.current) {
|
|
19892
|
+
if (effect.startedAt === null) effect.startedAt = now;
|
|
19893
|
+
const previous = effect.lastFrameAt ?? now;
|
|
19894
|
+
effect.lastFrameAt = now;
|
|
19895
|
+
const override = effect.override && effect.override.until > now ? effect.override.level : null;
|
|
19896
|
+
if (effect.override && override === null) effect.override = null;
|
|
19897
|
+
const keepRunning = effect.update(
|
|
19898
|
+
now,
|
|
19899
|
+
now - effect.startedAt,
|
|
19900
|
+
Math.min(100, now - previous),
|
|
19901
|
+
override
|
|
19902
|
+
);
|
|
19903
|
+
rendered = true;
|
|
19904
|
+
if (!keepRunning) {
|
|
19905
|
+
effectsRef.current.delete(id);
|
|
19906
|
+
effect.onComplete?.();
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
if (rendered) invalidate();
|
|
19910
|
+
if (effectsRef.current.size > 0) scheduleFrameRef.current();
|
|
19911
|
+
};
|
|
19912
|
+
scheduleFrameRef.current = () => {
|
|
19913
|
+
if (frameRef.current !== null || typeof requestAnimationFrame === "undefined") {
|
|
19914
|
+
return;
|
|
19915
|
+
}
|
|
19916
|
+
frameRef.current = requestAnimationFrame((now) => runFrameRef.current(now));
|
|
19917
|
+
};
|
|
19918
|
+
const registerEffect = useCallback18((effect) => {
|
|
19919
|
+
effectsRef.current.set(effect.id, effect);
|
|
19920
|
+
scheduleFrameRef.current();
|
|
19921
|
+
return effect.id;
|
|
19922
|
+
}, []);
|
|
19923
|
+
const transition = useCallback18(
|
|
19924
|
+
(channels, transitionOptions = {}) => {
|
|
19925
|
+
const id = transitionOptions.id ?? `transition-${nextEffectId++}`;
|
|
19926
|
+
const duration = Math.max(1, transitionOptions.duration ?? 400);
|
|
19927
|
+
const easing = transitionOptions.easing ?? "ease-in-out";
|
|
19928
|
+
return registerEffect({
|
|
19929
|
+
id,
|
|
19930
|
+
startedAt: null,
|
|
19931
|
+
lastFrameAt: null,
|
|
19932
|
+
override: null,
|
|
19933
|
+
onComplete: transitionOptions.onComplete,
|
|
19934
|
+
update: (_now, elapsed, _delta, override) => {
|
|
19935
|
+
const progress = override ?? ease(elapsed / duration, easing);
|
|
19936
|
+
applyChannels(channels, [progress]);
|
|
19937
|
+
return elapsed < duration;
|
|
19938
|
+
}
|
|
19939
|
+
});
|
|
19940
|
+
},
|
|
19941
|
+
[applyChannels, registerEffect]
|
|
19942
|
+
);
|
|
19943
|
+
const pulse = useCallback18(
|
|
19944
|
+
(channels, pulseOptions = {}) => {
|
|
19945
|
+
const id = pulseOptions.id ?? `pulse-${nextEffectId++}`;
|
|
19946
|
+
const duration = Math.max(1, pulseOptions.duration ?? 1e3);
|
|
19947
|
+
const minLevel = pulseOptions.minLevel ?? 0;
|
|
19948
|
+
const maxLevel = pulseOptions.maxLevel ?? 1;
|
|
19949
|
+
const repeat = pulseOptions.repeat ?? Infinity;
|
|
19950
|
+
const synchronized = pulseOptions.synchronized ?? true;
|
|
19951
|
+
const unitCount = Math.max(
|
|
19952
|
+
1,
|
|
19953
|
+
...[...channels.materials ?? [], ...channels.transforms ?? []].map(
|
|
19954
|
+
(channel) => resolveTargets(channel.targets).length
|
|
19955
|
+
),
|
|
19956
|
+
...(channels.lights ?? []).map(
|
|
19957
|
+
(channel) => stringList(channel.lights).length
|
|
19958
|
+
)
|
|
19959
|
+
);
|
|
19960
|
+
return registerEffect({
|
|
19961
|
+
id,
|
|
19962
|
+
startedAt: null,
|
|
19963
|
+
lastFrameAt: null,
|
|
19964
|
+
override: null,
|
|
19965
|
+
onComplete: pulseOptions.onComplete,
|
|
19966
|
+
update: (_now, elapsed, _delta, override) => {
|
|
19967
|
+
const cycles = elapsed / duration;
|
|
19968
|
+
const levels = Array.from(
|
|
19969
|
+
{ length: synchronized ? 1 : unitCount },
|
|
19970
|
+
(_, index) => {
|
|
19971
|
+
if (override !== null) return override;
|
|
19972
|
+
const phase = cycles + (synchronized ? 0 : index / unitCount);
|
|
19973
|
+
const wave = 0.5 - Math.cos(phase % 1 * Math.PI * 2) / 2;
|
|
19974
|
+
return minLevel + (maxLevel - minLevel) * wave;
|
|
19975
|
+
}
|
|
19976
|
+
);
|
|
19977
|
+
applyChannels(channels, levels);
|
|
19978
|
+
return repeat === Infinity || cycles < repeat;
|
|
19979
|
+
}
|
|
19980
|
+
});
|
|
19981
|
+
},
|
|
19982
|
+
[applyChannels, registerEffect, resolveTargets]
|
|
19983
|
+
);
|
|
19984
|
+
const timeline = useCallback18(
|
|
19985
|
+
(steps, timelineOptions = {}) => {
|
|
19986
|
+
const id = timelineOptions.id ?? `timeline-${nextEffectId++}`;
|
|
19987
|
+
const repeat = timelineOptions.repeat ?? 1;
|
|
19988
|
+
const normalizedSteps = steps.map((step) => ({
|
|
19989
|
+
...step,
|
|
19990
|
+
duration: Math.max(1, step.duration),
|
|
19991
|
+
hold: Math.max(0, step.hold ?? 0)
|
|
19992
|
+
}));
|
|
19993
|
+
const cycleDuration = Math.max(
|
|
19994
|
+
1,
|
|
19995
|
+
normalizedSteps.reduce(
|
|
19996
|
+
(total, step) => total + step.duration + step.hold,
|
|
19997
|
+
0
|
|
19998
|
+
)
|
|
19999
|
+
);
|
|
20000
|
+
return registerEffect({
|
|
20001
|
+
id,
|
|
20002
|
+
startedAt: null,
|
|
20003
|
+
lastFrameAt: null,
|
|
20004
|
+
override: null,
|
|
20005
|
+
onComplete: timelineOptions.onComplete,
|
|
20006
|
+
update: (_now, elapsed, _delta, override) => {
|
|
20007
|
+
if (normalizedSteps.length === 0 || repeat <= 0) return false;
|
|
20008
|
+
const cycles = elapsed / cycleDuration;
|
|
20009
|
+
if (repeat !== Infinity && cycles >= repeat) {
|
|
20010
|
+
for (const step of normalizedSteps) {
|
|
20011
|
+
applyChannels(step.channels, [1]);
|
|
20012
|
+
}
|
|
20013
|
+
return false;
|
|
20014
|
+
}
|
|
20015
|
+
const cycleTime = elapsed % cycleDuration;
|
|
20016
|
+
let cursor = 0;
|
|
20017
|
+
for (const step of normalizedSteps) {
|
|
20018
|
+
const transitionEnd = cursor + step.duration;
|
|
20019
|
+
const stepEnd = transitionEnd + step.hold;
|
|
20020
|
+
if (cycleTime >= transitionEnd) {
|
|
20021
|
+
applyChannels(step.channels, [override ?? 1]);
|
|
20022
|
+
cursor = stepEnd;
|
|
20023
|
+
continue;
|
|
20024
|
+
}
|
|
20025
|
+
if (cycleTime >= cursor) {
|
|
20026
|
+
const progress = override ?? ease(
|
|
20027
|
+
(cycleTime - cursor) / step.duration,
|
|
20028
|
+
step.easing ?? "ease-in-out"
|
|
20029
|
+
);
|
|
20030
|
+
applyChannels(step.channels, [progress]);
|
|
20031
|
+
break;
|
|
20032
|
+
}
|
|
20033
|
+
}
|
|
20034
|
+
return true;
|
|
20035
|
+
}
|
|
20036
|
+
});
|
|
20037
|
+
},
|
|
20038
|
+
[applyChannels, registerEffect]
|
|
20039
|
+
);
|
|
20040
|
+
const flicker = useCallback18(
|
|
20041
|
+
(channels, flickerOptions = {}) => {
|
|
20042
|
+
const id = flickerOptions.id ?? `flicker-${nextEffectId++}`;
|
|
20043
|
+
const synchronized = flickerOptions.synchronized ?? true;
|
|
20044
|
+
const minLevel = flickerOptions.minLevel ?? 0.15;
|
|
20045
|
+
const maxLevel = flickerOptions.maxLevel ?? 1;
|
|
20046
|
+
const interval = flickerOptions.interval ?? [45, 160];
|
|
20047
|
+
const rawIntervalRange = typeof interval === "number" ? [interval, interval] : interval;
|
|
20048
|
+
const intervalRange = [
|
|
20049
|
+
Math.max(1, Math.min(...rawIntervalRange)),
|
|
20050
|
+
Math.max(1, Math.max(...rawIntervalRange))
|
|
20051
|
+
];
|
|
20052
|
+
const response = clamp2(flickerOptions.response ?? 0.7, 0.01, 1);
|
|
20053
|
+
const blackoutChance = clamp2(flickerOptions.blackoutChance ?? 0.08);
|
|
20054
|
+
const flashChance = clamp2(flickerOptions.flashChance ?? 0.12);
|
|
20055
|
+
const flashLevel = flickerOptions.flashLevel ?? 1.15;
|
|
20056
|
+
const unitCount = Math.max(
|
|
20057
|
+
1,
|
|
20058
|
+
...[...channels.materials ?? [], ...channels.transforms ?? []].map(
|
|
20059
|
+
(channel) => resolveTargets(channel.targets).length
|
|
20060
|
+
),
|
|
20061
|
+
...(channels.lights ?? []).map(
|
|
20062
|
+
(channel) => stringList(channel.lights).length
|
|
20063
|
+
)
|
|
20064
|
+
);
|
|
20065
|
+
const count = synchronized ? 1 : unitCount;
|
|
20066
|
+
const levels = Array.from({ length: count }, () => minLevel);
|
|
20067
|
+
const targets = Array.from({ length: count }, () => minLevel);
|
|
20068
|
+
const nextChanges = Array.from({ length: count }, () => 0);
|
|
20069
|
+
return registerEffect({
|
|
20070
|
+
id,
|
|
20071
|
+
startedAt: null,
|
|
20072
|
+
lastFrameAt: null,
|
|
20073
|
+
override: null,
|
|
20074
|
+
onComplete: flickerOptions.onComplete,
|
|
20075
|
+
update: (now, elapsed, delta, override) => {
|
|
20076
|
+
for (let index = 0; index < count; index += 1) {
|
|
20077
|
+
if (override !== null) {
|
|
20078
|
+
targets[index] = override;
|
|
20079
|
+
} else if (now >= nextChanges[index]) {
|
|
20080
|
+
const roll = Math.random();
|
|
20081
|
+
targets[index] = roll < blackoutChance ? randomBetween(0, Math.min(0.04, minLevel)) : roll < blackoutChance + flashChance ? flashLevel : randomBetween(minLevel, maxLevel);
|
|
20082
|
+
nextChanges[index] = now + randomBetween(...intervalRange);
|
|
20083
|
+
}
|
|
20084
|
+
const frameResponse = 1 - Math.pow(1 - response, Math.max(1, delta) / 16.67);
|
|
20085
|
+
levels[index] += (targets[index] - levels[index]) * frameResponse;
|
|
20086
|
+
}
|
|
20087
|
+
applyChannels(channels, levels);
|
|
20088
|
+
return flickerOptions.duration === void 0 || elapsed < flickerOptions.duration;
|
|
20089
|
+
}
|
|
20090
|
+
});
|
|
20091
|
+
},
|
|
20092
|
+
[applyChannels, registerEffect, resolveTargets]
|
|
20093
|
+
);
|
|
20094
|
+
const overrideEffect = useCallback18(
|
|
20095
|
+
(id, level, duration) => {
|
|
20096
|
+
const effect = effectsRef.current.get(id);
|
|
20097
|
+
if (!effect) return false;
|
|
20098
|
+
const now = typeof performance === "undefined" ? Date.now() : performance.now();
|
|
20099
|
+
effect.override = { level, until: now + Math.max(0, duration) };
|
|
20100
|
+
scheduleFrameRef.current();
|
|
20101
|
+
return true;
|
|
20102
|
+
},
|
|
20103
|
+
[]
|
|
20104
|
+
);
|
|
20105
|
+
const stopEffect = useCallback18((id) => {
|
|
20106
|
+
return effectsRef.current.delete(id);
|
|
20107
|
+
}, []);
|
|
20108
|
+
const stopAllEffects = useCallback18(() => {
|
|
20109
|
+
effectsRef.current.clear();
|
|
20110
|
+
if (frameRef.current !== null && typeof cancelAnimationFrame !== "undefined") {
|
|
20111
|
+
cancelAnimationFrame(frameRef.current);
|
|
20112
|
+
frameRef.current = null;
|
|
20113
|
+
}
|
|
20114
|
+
}, []);
|
|
20115
|
+
const restoreRuntimeValues = useCallback18(() => {
|
|
20116
|
+
for (const [entry, snapshot] of materialSnapshotsRef.current) {
|
|
20117
|
+
applyMaterial(entry, snapshot);
|
|
20118
|
+
}
|
|
20119
|
+
materialSnapshotsRef.current.clear();
|
|
20120
|
+
for (const [node, snapshot] of transformSnapshotsRef.current) {
|
|
20121
|
+
node.position.set(...snapshot.position);
|
|
20122
|
+
node.rotation.set(...snapshot.rotation);
|
|
20123
|
+
node.scale.set(...snapshot.scale);
|
|
20124
|
+
node.visible = snapshot.visible;
|
|
20125
|
+
node.updateMatrix();
|
|
20126
|
+
node.updateMatrixWorld(true);
|
|
20127
|
+
}
|
|
20128
|
+
transformSnapshotsRef.current.clear();
|
|
20129
|
+
for (const runtime of lightsRef.current.values()) {
|
|
20130
|
+
runtime.light.removeFromParent();
|
|
20131
|
+
runtime.light.dispose();
|
|
20132
|
+
}
|
|
20133
|
+
lightsRef.current.clear();
|
|
20134
|
+
invalidate();
|
|
20135
|
+
}, [invalidate]);
|
|
20136
|
+
const resetRuntime = useCallback18(() => {
|
|
20137
|
+
stopAllEffects();
|
|
20138
|
+
restoreRuntimeValues();
|
|
20139
|
+
}, [restoreRuntimeValues, stopAllEffects]);
|
|
20140
|
+
const handleViewerReady = useCallback18(
|
|
20141
|
+
(viewer) => {
|
|
20142
|
+
if (sceneRef.current && sceneRef.current !== viewer.scene) {
|
|
20143
|
+
restoreRuntimeValues();
|
|
20144
|
+
}
|
|
20145
|
+
viewerRef.current = viewer;
|
|
20146
|
+
sceneRef.current = viewer.scene;
|
|
20147
|
+
optionsRef.current.onViewerReady?.(viewer);
|
|
20148
|
+
},
|
|
20149
|
+
[restoreRuntimeValues]
|
|
20150
|
+
);
|
|
20151
|
+
useEffect25(() => {
|
|
20152
|
+
return () => {
|
|
20153
|
+
resetRuntime();
|
|
20154
|
+
viewerRef.current = null;
|
|
20155
|
+
sceneRef.current = null;
|
|
20156
|
+
};
|
|
20157
|
+
}, [resetRuntime]);
|
|
20158
|
+
return {
|
|
20159
|
+
handleViewerReady,
|
|
20160
|
+
setMaterial,
|
|
20161
|
+
setTransform,
|
|
20162
|
+
getWorldPosition,
|
|
20163
|
+
addPointLight,
|
|
20164
|
+
updatePointLight,
|
|
20165
|
+
removePointLight,
|
|
20166
|
+
transition,
|
|
20167
|
+
timeline,
|
|
20168
|
+
pulse,
|
|
20169
|
+
flicker,
|
|
20170
|
+
overrideEffect,
|
|
20171
|
+
stopEffect,
|
|
20172
|
+
stopAllEffects,
|
|
20173
|
+
resetRuntime
|
|
20174
|
+
};
|
|
20175
|
+
}
|
|
20176
|
+
|
|
18880
20177
|
// src/hooks/useShareableViewerState.ts
|
|
18881
|
-
import { useCallback as
|
|
20178
|
+
import { useCallback as useCallback19, useEffect as useEffect26, useRef as useRef24, useState as useState34 } from "react";
|
|
18882
20179
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
18883
20180
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
18884
20181
|
return {
|
|
@@ -18898,8 +20195,8 @@ function useShareableViewerState({
|
|
|
18898
20195
|
debounceMs = 400,
|
|
18899
20196
|
autoSync = true
|
|
18900
20197
|
}) {
|
|
18901
|
-
const lastWrittenRawRef =
|
|
18902
|
-
const buildHash =
|
|
20198
|
+
const lastWrittenRawRef = useRef24(null);
|
|
20199
|
+
const buildHash = useCallback19(() => {
|
|
18903
20200
|
const state = serializeState(
|
|
18904
20201
|
cameraState,
|
|
18905
20202
|
objectBindings,
|
|
@@ -18907,12 +20204,12 @@ function useShareableViewerState({
|
|
|
18907
20204
|
);
|
|
18908
20205
|
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
18909
20206
|
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
18910
|
-
const getShareUrl =
|
|
20207
|
+
const getShareUrl = useCallback19(() => {
|
|
18911
20208
|
const url = new URL(window.location.href);
|
|
18912
20209
|
url.hash = buildHash();
|
|
18913
20210
|
return url.toString();
|
|
18914
20211
|
}, [buildHash]);
|
|
18915
|
-
const syncUrl =
|
|
20212
|
+
const syncUrl = useCallback19(() => {
|
|
18916
20213
|
const url = new URL(window.location.href);
|
|
18917
20214
|
url.hash = buildHash();
|
|
18918
20215
|
window.history.replaceState(null, "", url.toString());
|
|
@@ -18920,7 +20217,7 @@ function useShareableViewerState({
|
|
|
18920
20217
|
paramKey
|
|
18921
20218
|
);
|
|
18922
20219
|
}, [buildHash, paramKey]);
|
|
18923
|
-
const latestStateRef =
|
|
20220
|
+
const latestStateRef = useRef24({
|
|
18924
20221
|
cameraState,
|
|
18925
20222
|
objectBindings,
|
|
18926
20223
|
selectedObjectBinding
|
|
@@ -18930,9 +20227,9 @@ function useShareableViewerState({
|
|
|
18930
20227
|
objectBindings,
|
|
18931
20228
|
selectedObjectBinding
|
|
18932
20229
|
};
|
|
18933
|
-
const [restoreAttempted, setRestoreAttempted] =
|
|
18934
|
-
const lastSyncedHashRef =
|
|
18935
|
-
|
|
20230
|
+
const [restoreAttempted, setRestoreAttempted] = useState34(false);
|
|
20231
|
+
const lastSyncedHashRef = useRef24(null);
|
|
20232
|
+
useEffect26(() => {
|
|
18936
20233
|
if (!autoSync || !restoreAttempted) return;
|
|
18937
20234
|
const tick = () => {
|
|
18938
20235
|
const current = latestStateRef.current;
|
|
@@ -18954,9 +20251,9 @@ function useShareableViewerState({
|
|
|
18954
20251
|
const id = window.setInterval(tick, debounceMs);
|
|
18955
20252
|
return () => window.clearInterval(id);
|
|
18956
20253
|
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
18957
|
-
const lastRestoredRawRef =
|
|
18958
|
-
const restoringRawRef =
|
|
18959
|
-
const restoreFromUrl =
|
|
20254
|
+
const lastRestoredRawRef = useRef24(null);
|
|
20255
|
+
const restoringRawRef = useRef24(null);
|
|
20256
|
+
const restoreFromUrl = useCallback19(async () => {
|
|
18960
20257
|
setRestoreAttempted(true);
|
|
18961
20258
|
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
18962
20259
|
const raw = new URLSearchParams(rawHash).get(paramKey);
|
|
@@ -19009,7 +20306,7 @@ function useShareableViewerState({
|
|
|
19009
20306
|
setCameraState,
|
|
19010
20307
|
setSelectedObjectBinding
|
|
19011
20308
|
]);
|
|
19012
|
-
|
|
20309
|
+
useEffect26(() => {
|
|
19013
20310
|
const handleHashChange = () => {
|
|
19014
20311
|
void restoreFromUrl();
|
|
19015
20312
|
};
|
|
@@ -19022,13 +20319,17 @@ function useShareableViewerState({
|
|
|
19022
20319
|
}
|
|
19023
20320
|
export {
|
|
19024
20321
|
BindingBuilder,
|
|
20322
|
+
DEFAULT_SCENE_CONFIG,
|
|
19025
20323
|
MATERIAL_BLENDING_MODES,
|
|
19026
20324
|
MATERIAL_SIDES,
|
|
19027
20325
|
ModelViewer,
|
|
19028
20326
|
SimpleModelViewer,
|
|
20327
|
+
defineObjectBindings,
|
|
19029
20328
|
material,
|
|
19030
20329
|
patchBindings,
|
|
19031
20330
|
patchSceneConfig,
|
|
20331
|
+
selectBindingKeys,
|
|
20332
|
+
selectBindings,
|
|
19032
20333
|
useObjectBinding,
|
|
19033
20334
|
useObjectBindingIds,
|
|
19034
20335
|
useObjectBindings,
|
|
@@ -19037,6 +20338,8 @@ export {
|
|
|
19037
20338
|
useViewerActions,
|
|
19038
20339
|
useViewerAnimations,
|
|
19039
20340
|
useViewerCamera,
|
|
20341
|
+
useViewerConnection,
|
|
20342
|
+
useViewerEffects,
|
|
19040
20343
|
useViewerHover,
|
|
19041
20344
|
useViewerModel,
|
|
19042
20345
|
useViewerSelection
|