@ericchen1990/pano-view 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -8
- package/dist/index.cjs +2383 -645
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +389 -9
- package/dist/index.d.ts +389 -9
- package/dist/index.mjs +2400 -667
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,16 +4,54 @@
|
|
|
4
4
|
import { Canvas } from "@react-three/fiber";
|
|
5
5
|
import {
|
|
6
6
|
forwardRef as forwardRef2,
|
|
7
|
-
|
|
7
|
+
useCallback as useCallback3,
|
|
8
|
+
useEffect as useEffect10,
|
|
8
9
|
useImperativeHandle as useImperativeHandle2,
|
|
9
|
-
useMemo as
|
|
10
|
-
useRef as
|
|
11
|
-
useState as
|
|
10
|
+
useMemo as useMemo4,
|
|
11
|
+
useRef as useRef9,
|
|
12
|
+
useState as useState3
|
|
12
13
|
} from "react";
|
|
13
14
|
|
|
14
|
-
// src/
|
|
15
|
+
// src/auto-rotate.tsx
|
|
16
|
+
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
17
|
+
import { useContext, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
18
|
+
|
|
19
|
+
// src/pano-event-bus.ts
|
|
20
|
+
import { createContext } from "react";
|
|
21
|
+
function createPanoEventBus() {
|
|
22
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
23
|
+
return {
|
|
24
|
+
subscribe(type, listener) {
|
|
25
|
+
let set = listeners.get(type);
|
|
26
|
+
if (!set) {
|
|
27
|
+
set = /* @__PURE__ */ new Set();
|
|
28
|
+
listeners.set(type, set);
|
|
29
|
+
}
|
|
30
|
+
set.add(listener);
|
|
31
|
+
return () => {
|
|
32
|
+
set?.delete(listener);
|
|
33
|
+
if (set && set.size === 0) {
|
|
34
|
+
listeners.delete(type);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
emit(type, payload) {
|
|
39
|
+
const set = listeners.get(type);
|
|
40
|
+
if (!set || set.size === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
for (const listener of [...set]) {
|
|
44
|
+
listener(payload);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var PanoEventBusContext = createContext(null);
|
|
50
|
+
|
|
51
|
+
// src/panorama-view-runtime.tsx
|
|
15
52
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
16
53
|
import {
|
|
54
|
+
createContext as createContext2,
|
|
17
55
|
forwardRef,
|
|
18
56
|
useEffect,
|
|
19
57
|
useImperativeHandle,
|
|
@@ -21,13 +59,15 @@ import {
|
|
|
21
59
|
} from "react";
|
|
22
60
|
import { Euler, MathUtils, PerspectiveCamera } from "three";
|
|
23
61
|
var MAX_PITCH = 90;
|
|
62
|
+
var BOUNCE_OVERSHOOT_PITCH = 12;
|
|
63
|
+
var BOUNCE_OVERSHOOT_FOV_RATIO = 0.15;
|
|
24
64
|
var INERTIA_ROTATION_DAMPING = 8;
|
|
25
65
|
var INERTIA_ZOOM_DAMPING = 12;
|
|
26
66
|
var DEFAULT_ROTATE_DAMPING = 14;
|
|
27
67
|
var DEFAULT_ZOOM_DAMPING = 16;
|
|
28
|
-
var
|
|
29
|
-
var MIN_ZOOM_VELOCITY = 0.01;
|
|
68
|
+
var DEFAULT_FRICTION_STOP = 0.01;
|
|
30
69
|
var VIEW_SETTLE_EPSILON = 1e-3;
|
|
70
|
+
var PanoramaViewContext = createContext2(null);
|
|
31
71
|
function clamp(value, min, max) {
|
|
32
72
|
return Math.max(min, Math.min(max, value));
|
|
33
73
|
}
|
|
@@ -43,54 +83,104 @@ function dampingFactor(damping, deltaSeconds) {
|
|
|
43
83
|
function resolveDamping(value, fallback) {
|
|
44
84
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
45
85
|
}
|
|
46
|
-
function
|
|
47
|
-
return
|
|
86
|
+
function resolveFrictionStop(value) {
|
|
87
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_FRICTION_STOP;
|
|
48
88
|
}
|
|
49
89
|
function viewsEqual(a, b) {
|
|
50
90
|
return a.yaw === b.yaw && a.pitch === b.pitch && a.fov === b.fov;
|
|
51
91
|
}
|
|
52
|
-
var
|
|
53
|
-
const { camera
|
|
92
|
+
var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView, minFov, maxFov, options, eventBus }, ref) {
|
|
93
|
+
const { camera } = useThree();
|
|
54
94
|
const viewRef = useRef({ ...initialView });
|
|
55
95
|
const targetViewRef = useRef({ ...initialView });
|
|
56
96
|
const initialViewRef = useRef({ ...initialView });
|
|
57
|
-
const pointersRef = useRef(/* @__PURE__ */ new Map());
|
|
58
|
-
const pinchDistanceRef = useRef(null);
|
|
59
97
|
const yawVelocityRef = useRef(0);
|
|
60
98
|
const pitchVelocityRef = useRef(0);
|
|
61
99
|
const zoomVelocityRef = useRef(0);
|
|
62
100
|
const dirtyRef = useRef(true);
|
|
101
|
+
const settledRef = useRef(false);
|
|
63
102
|
const interactingRef = useRef(false);
|
|
103
|
+
const mouseActiveRef = useRef(false);
|
|
104
|
+
const touchActiveRef = useRef(false);
|
|
64
105
|
const interactionLockCountRef = useRef(0);
|
|
106
|
+
const keyboardActiveRef = useRef(false);
|
|
107
|
+
const lastVelocitySampleAtRef = useRef(null);
|
|
65
108
|
const optionsRef = useRef(options);
|
|
66
|
-
const
|
|
109
|
+
const eventBusRef = useRef(eventBus);
|
|
67
110
|
const lastEmittedViewRef = useRef(null);
|
|
68
111
|
const eulerRef = useRef(new Euler(0, 0, 0, "YXZ"));
|
|
112
|
+
const minFovRef = useRef(minFov);
|
|
113
|
+
const maxFovRef = useRef(maxFov);
|
|
69
114
|
optionsRef.current = options;
|
|
70
|
-
|
|
71
|
-
|
|
115
|
+
eventBusRef.current = eventBus;
|
|
116
|
+
minFovRef.current = minFov;
|
|
117
|
+
maxFovRef.current = maxFov;
|
|
118
|
+
const hasInertia = () => yawVelocityRef.current !== 0 || pitchVelocityRef.current !== 0 || zoomVelocityRef.current !== 0;
|
|
119
|
+
const isUserInteracting = () => mouseActiveRef.current || touchActiveRef.current || keyboardActiveRef.current || interactionLockCountRef.current > 0 || hasInertia();
|
|
120
|
+
const syncInteracting = () => {
|
|
121
|
+
interactingRef.current = mouseActiveRef.current || touchActiveRef.current;
|
|
122
|
+
};
|
|
123
|
+
const hardConstrainView = (view, current = targetViewRef.current) => {
|
|
72
124
|
return {
|
|
73
125
|
yaw: normalizeYaw(view.yaw ?? current.yaw),
|
|
74
126
|
pitch: clamp(view.pitch ?? current.pitch, -MAX_PITCH, MAX_PITCH),
|
|
75
|
-
fov: clamp(
|
|
127
|
+
fov: clamp(
|
|
128
|
+
view.fov ?? current.fov,
|
|
129
|
+
minFovRef.current,
|
|
130
|
+
maxFovRef.current
|
|
131
|
+
)
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const softConstrainView = (view, current = targetViewRef.current) => {
|
|
135
|
+
const bouncing = optionsRef.current.bouncingLimits === true;
|
|
136
|
+
if (!bouncing || !interactingRef.current) {
|
|
137
|
+
return hardConstrainView(view, current);
|
|
138
|
+
}
|
|
139
|
+
const fovRange = Math.max(maxFovRef.current - minFovRef.current, 1);
|
|
140
|
+
const fovOvershoot = fovRange * BOUNCE_OVERSHOOT_FOV_RATIO;
|
|
141
|
+
return {
|
|
142
|
+
yaw: normalizeYaw(view.yaw ?? current.yaw),
|
|
143
|
+
pitch: clamp(
|
|
144
|
+
view.pitch ?? current.pitch,
|
|
145
|
+
-MAX_PITCH - BOUNCE_OVERSHOOT_PITCH,
|
|
146
|
+
MAX_PITCH + BOUNCE_OVERSHOOT_PITCH
|
|
147
|
+
),
|
|
148
|
+
fov: clamp(
|
|
149
|
+
view.fov ?? current.fov,
|
|
150
|
+
minFovRef.current - fovOvershoot,
|
|
151
|
+
maxFovRef.current + fovOvershoot
|
|
152
|
+
)
|
|
76
153
|
};
|
|
77
154
|
};
|
|
78
155
|
const stopVelocity = () => {
|
|
79
156
|
yawVelocityRef.current = 0;
|
|
80
157
|
pitchVelocityRef.current = 0;
|
|
81
158
|
zoomVelocityRef.current = 0;
|
|
159
|
+
lastVelocitySampleAtRef.current = null;
|
|
160
|
+
};
|
|
161
|
+
const markDirty = () => {
|
|
162
|
+
dirtyRef.current = true;
|
|
163
|
+
settledRef.current = false;
|
|
164
|
+
};
|
|
165
|
+
const snapTargetToHardLimits = () => {
|
|
166
|
+
const next = hardConstrainView(targetViewRef.current);
|
|
167
|
+
if (next.yaw !== targetViewRef.current.yaw || next.pitch !== targetViewRef.current.pitch || next.fov !== targetViewRef.current.fov) {
|
|
168
|
+
targetViewRef.current = next;
|
|
169
|
+
markDirty();
|
|
170
|
+
}
|
|
82
171
|
};
|
|
83
172
|
const acquireInteractionLock = () => {
|
|
84
173
|
interactionLockCountRef.current += 1;
|
|
85
174
|
stopVelocity();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (gl.domElement.hasPointerCapture?.(pointerId)) {
|
|
90
|
-
gl.domElement.releasePointerCapture?.(pointerId);
|
|
91
|
-
}
|
|
175
|
+
if (mouseActiveRef.current) {
|
|
176
|
+
mouseActiveRef.current = false;
|
|
177
|
+
eventBusRef.current.emit("viewinteractionend", { source: "mouse" });
|
|
92
178
|
}
|
|
93
|
-
|
|
179
|
+
if (touchActiveRef.current) {
|
|
180
|
+
touchActiveRef.current = false;
|
|
181
|
+
eventBusRef.current.emit("viewinteractionend", { source: "touch" });
|
|
182
|
+
}
|
|
183
|
+
syncInteracting();
|
|
94
184
|
let released = false;
|
|
95
185
|
return () => {
|
|
96
186
|
if (released) {
|
|
@@ -104,233 +194,145 @@ var PanoramaControls = forwardRef(function PanoramaControls2({ enabled, initialV
|
|
|
104
194
|
};
|
|
105
195
|
};
|
|
106
196
|
const setView = (view, setOptions) => {
|
|
107
|
-
const nextView =
|
|
197
|
+
const nextView = hardConstrainView(view);
|
|
108
198
|
targetViewRef.current = nextView;
|
|
109
199
|
viewRef.current = nextView;
|
|
110
200
|
if (setOptions?.immediate !== false) {
|
|
111
201
|
stopVelocity();
|
|
112
202
|
}
|
|
113
|
-
|
|
203
|
+
markDirty();
|
|
204
|
+
};
|
|
205
|
+
const applyViewDelta = (delta, applyOptions) => {
|
|
206
|
+
if (interactionLockCountRef.current > 0) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const current = targetViewRef.current;
|
|
210
|
+
const nextYaw = delta.yaw !== void 0 && Number.isFinite(delta.yaw) ? current.yaw + delta.yaw : current.yaw;
|
|
211
|
+
const nextPitch = delta.pitch !== void 0 && Number.isFinite(delta.pitch) ? current.pitch + delta.pitch : current.pitch;
|
|
212
|
+
const nextFov = delta.fov !== void 0 && Number.isFinite(delta.fov) ? current.fov + delta.fov : current.fov;
|
|
213
|
+
targetViewRef.current = softConstrainView({
|
|
214
|
+
yaw: nextYaw,
|
|
215
|
+
pitch: nextPitch,
|
|
216
|
+
fov: nextFov
|
|
217
|
+
});
|
|
218
|
+
markDirty();
|
|
219
|
+
if (applyOptions?.recordVelocity) {
|
|
220
|
+
const now = performance.now();
|
|
221
|
+
const previousAt = lastVelocitySampleAtRef.current;
|
|
222
|
+
const elapsed = previousAt === null ? 1 / 60 : Math.max((now - previousAt) / 1e3, 1 / 120);
|
|
223
|
+
lastVelocitySampleAtRef.current = now;
|
|
224
|
+
if (delta.yaw !== void 0 && Number.isFinite(delta.yaw)) {
|
|
225
|
+
yawVelocityRef.current = delta.yaw / elapsed;
|
|
226
|
+
}
|
|
227
|
+
if (delta.pitch !== void 0 && Number.isFinite(delta.pitch)) {
|
|
228
|
+
pitchVelocityRef.current = delta.pitch / elapsed;
|
|
229
|
+
}
|
|
230
|
+
if (delta.fov !== void 0 && Number.isFinite(delta.fov)) {
|
|
231
|
+
zoomVelocityRef.current = clamp(delta.fov / elapsed, -180, 180);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
114
235
|
};
|
|
115
236
|
useImperativeHandle(
|
|
116
237
|
ref,
|
|
117
238
|
() => ({
|
|
118
239
|
getView: () => ({ ...viewRef.current }),
|
|
240
|
+
getTargetView: () => ({ ...targetViewRef.current }),
|
|
119
241
|
setView,
|
|
120
242
|
reset: () => setView(initialViewRef.current),
|
|
243
|
+
applyViewDelta,
|
|
244
|
+
setKeyboardActive: (active) => {
|
|
245
|
+
const wasActive = keyboardActiveRef.current;
|
|
246
|
+
keyboardActiveRef.current = active;
|
|
247
|
+
if (active && !wasActive) {
|
|
248
|
+
eventBusRef.current.emit("viewinteractionstart", {
|
|
249
|
+
source: "keyboard"
|
|
250
|
+
});
|
|
251
|
+
} else if (!active && wasActive) {
|
|
252
|
+
eventBusRef.current.emit("viewinteractionend", {
|
|
253
|
+
source: "keyboard"
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
setPointerActive: (source, active) => {
|
|
258
|
+
const wasActive = source === "mouse" ? mouseActiveRef.current : touchActiveRef.current;
|
|
259
|
+
if (source === "mouse") {
|
|
260
|
+
mouseActiveRef.current = active;
|
|
261
|
+
} else {
|
|
262
|
+
touchActiveRef.current = active;
|
|
263
|
+
}
|
|
264
|
+
syncInteracting();
|
|
265
|
+
if (active && !wasActive) {
|
|
266
|
+
eventBusRef.current.emit("viewinteractionstart", { source });
|
|
267
|
+
} else if (!active && wasActive) {
|
|
268
|
+
eventBusRef.current.emit("viewinteractionend", { source });
|
|
269
|
+
}
|
|
270
|
+
if (active) {
|
|
271
|
+
stopVelocity();
|
|
272
|
+
} else if (!mouseActiveRef.current && !touchActiveRef.current) {
|
|
273
|
+
lastVelocitySampleAtRef.current = null;
|
|
274
|
+
snapTargetToHardLimits();
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
isInteractionLocked: () => interactionLockCountRef.current > 0,
|
|
278
|
+
isUserInteracting,
|
|
121
279
|
acquireInteractionLock,
|
|
122
280
|
applyAutoRotation: (yawDelta) => {
|
|
123
281
|
const inertiaFinished = yawVelocityRef.current === 0 && pitchVelocityRef.current === 0 && zoomVelocityRef.current === 0;
|
|
124
|
-
if (!Number.isFinite(yawDelta) || interactionLockCountRef.current > 0 || interactingRef.current || !inertiaFinished) {
|
|
282
|
+
if (!Number.isFinite(yawDelta) || interactionLockCountRef.current > 0 || interactingRef.current || keyboardActiveRef.current || !inertiaFinished) {
|
|
125
283
|
return false;
|
|
126
284
|
}
|
|
127
285
|
targetViewRef.current.yaw += yawDelta;
|
|
128
|
-
|
|
286
|
+
markDirty();
|
|
129
287
|
return true;
|
|
130
288
|
}
|
|
131
289
|
}),
|
|
132
|
-
[
|
|
290
|
+
[]
|
|
133
291
|
);
|
|
134
292
|
useEffect(() => {
|
|
135
293
|
initialViewRef.current = { ...initialView };
|
|
136
294
|
setView(initialView);
|
|
137
295
|
}, [initialView.fov, initialView.pitch, initialView.yaw]);
|
|
138
|
-
useEffect(() => {
|
|
139
|
-
const element = gl.domElement;
|
|
140
|
-
if (!enabled) {
|
|
141
|
-
pointersRef.current.clear();
|
|
142
|
-
interactingRef.current = false;
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const updatePointer = (event) => {
|
|
146
|
-
const previous = pointersRef.current.get(event.pointerId);
|
|
147
|
-
const now = performance.now();
|
|
148
|
-
const next = { x: event.clientX, y: event.clientY, at: now };
|
|
149
|
-
pointersRef.current.set(event.pointerId, next);
|
|
150
|
-
return { previous, next };
|
|
151
|
-
};
|
|
152
|
-
const onPointerDown = (event) => {
|
|
153
|
-
if (interactionLockCountRef.current > 0) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
event.preventDefault();
|
|
157
|
-
interactingRef.current = true;
|
|
158
|
-
stopVelocity();
|
|
159
|
-
pointersRef.current.set(event.pointerId, {
|
|
160
|
-
x: event.clientX,
|
|
161
|
-
y: event.clientY,
|
|
162
|
-
at: performance.now()
|
|
163
|
-
});
|
|
164
|
-
element.setPointerCapture?.(event.pointerId);
|
|
165
|
-
if (pointersRef.current.size === 2) {
|
|
166
|
-
const [first, second] = Array.from(pointersRef.current.values());
|
|
167
|
-
pinchDistanceRef.current = pointerDistance(first, second);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
const onPointerMove = (event) => {
|
|
171
|
-
if (interactionLockCountRef.current > 0) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!pointersRef.current.has(event.pointerId)) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
event.preventDefault();
|
|
178
|
-
const { previous, next } = updatePointer(event);
|
|
179
|
-
if (!previous) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const pointers = Array.from(pointersRef.current.values());
|
|
183
|
-
if (pointers.length >= 2) {
|
|
184
|
-
const distance = pointerDistance(pointers[0], pointers[1]);
|
|
185
|
-
const previousDistance = pinchDistanceRef.current;
|
|
186
|
-
pinchDistanceRef.current = distance;
|
|
187
|
-
if (previousDistance && distance > 0) {
|
|
188
|
-
const currentFov = targetViewRef.current.fov;
|
|
189
|
-
const nextFov = clamp(
|
|
190
|
-
currentFov * (previousDistance / distance),
|
|
191
|
-
minFov,
|
|
192
|
-
maxFov
|
|
193
|
-
);
|
|
194
|
-
const elapsed2 = Math.max((next.at - previous.at) / 1e3, 1 / 120);
|
|
195
|
-
zoomVelocityRef.current = (nextFov - currentFov) / elapsed2;
|
|
196
|
-
targetViewRef.current = {
|
|
197
|
-
...targetViewRef.current,
|
|
198
|
-
fov: nextFov
|
|
199
|
-
};
|
|
200
|
-
dirtyRef.current = true;
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const rect = element.getBoundingClientRect();
|
|
205
|
-
const rotateSpeed = optionsRef.current.rotateSpeed ?? 0.35;
|
|
206
|
-
const deltaYaw = -(next.x - previous.x) * 360 * rotateSpeed / Math.max(rect.width, 1);
|
|
207
|
-
const deltaPitch = (next.y - previous.y) * 180 * rotateSpeed / Math.max(rect.height, 1);
|
|
208
|
-
const elapsed = Math.max((next.at - previous.at) / 1e3, 1 / 120);
|
|
209
|
-
targetViewRef.current = constrainView({
|
|
210
|
-
yaw: targetViewRef.current.yaw + deltaYaw,
|
|
211
|
-
pitch: targetViewRef.current.pitch + deltaPitch
|
|
212
|
-
});
|
|
213
|
-
yawVelocityRef.current = deltaYaw / elapsed;
|
|
214
|
-
pitchVelocityRef.current = deltaPitch / elapsed;
|
|
215
|
-
dirtyRef.current = true;
|
|
216
|
-
};
|
|
217
|
-
const releasePointer = (event) => {
|
|
218
|
-
pointersRef.current.delete(event.pointerId);
|
|
219
|
-
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
220
|
-
element.releasePointerCapture?.(event.pointerId);
|
|
221
|
-
}
|
|
222
|
-
if (pointersRef.current.size < 2) {
|
|
223
|
-
pinchDistanceRef.current = null;
|
|
224
|
-
}
|
|
225
|
-
interactingRef.current = pointersRef.current.size > 0;
|
|
226
|
-
};
|
|
227
|
-
const onWheel = (event) => {
|
|
228
|
-
if (interactionLockCountRef.current > 0) {
|
|
229
|
-
event.preventDefault();
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
event.preventDefault();
|
|
233
|
-
const zoomSpeed = optionsRef.current.zoomSpeed ?? 0.08;
|
|
234
|
-
const delta = event.deltaY * zoomSpeed;
|
|
235
|
-
const nextFov = clamp(
|
|
236
|
-
targetViewRef.current.fov + delta,
|
|
237
|
-
minFov,
|
|
238
|
-
maxFov
|
|
239
|
-
);
|
|
240
|
-
zoomVelocityRef.current = clamp(
|
|
241
|
-
zoomVelocityRef.current + delta * 12,
|
|
242
|
-
-180,
|
|
243
|
-
180
|
|
244
|
-
);
|
|
245
|
-
targetViewRef.current = {
|
|
246
|
-
...targetViewRef.current,
|
|
247
|
-
fov: nextFov
|
|
248
|
-
};
|
|
249
|
-
dirtyRef.current = true;
|
|
250
|
-
};
|
|
251
|
-
const onKeyDown = (event) => {
|
|
252
|
-
if (interactionLockCountRef.current > 0) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (optionsRef.current.keyboard === false) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const step = event.shiftKey ? 10 : 3;
|
|
259
|
-
let next = null;
|
|
260
|
-
if (event.key === "ArrowLeft") {
|
|
261
|
-
next = { yaw: targetViewRef.current.yaw - step };
|
|
262
|
-
} else if (event.key === "ArrowRight") {
|
|
263
|
-
next = { yaw: targetViewRef.current.yaw + step };
|
|
264
|
-
} else if (event.key === "ArrowUp") {
|
|
265
|
-
next = { pitch: targetViewRef.current.pitch + step };
|
|
266
|
-
} else if (event.key === "ArrowDown") {
|
|
267
|
-
next = { pitch: targetViewRef.current.pitch - step };
|
|
268
|
-
} else if (event.key === "+" || event.key === "=") {
|
|
269
|
-
next = { fov: targetViewRef.current.fov - step };
|
|
270
|
-
} else if (event.key === "-" || event.key === "_") {
|
|
271
|
-
next = { fov: targetViewRef.current.fov + step };
|
|
272
|
-
} else if (event.key === "0") {
|
|
273
|
-
setView(initialViewRef.current);
|
|
274
|
-
event.preventDefault();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (next) {
|
|
278
|
-
event.preventDefault();
|
|
279
|
-
targetViewRef.current = constrainView(next);
|
|
280
|
-
dirtyRef.current = true;
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
element.addEventListener("pointerdown", onPointerDown);
|
|
284
|
-
element.addEventListener("pointermove", onPointerMove);
|
|
285
|
-
element.addEventListener("pointerup", releasePointer);
|
|
286
|
-
element.addEventListener("pointercancel", releasePointer);
|
|
287
|
-
element.addEventListener("wheel", onWheel, { passive: false });
|
|
288
|
-
element.addEventListener("keydown", onKeyDown);
|
|
289
|
-
return () => {
|
|
290
|
-
element.removeEventListener("pointerdown", onPointerDown);
|
|
291
|
-
element.removeEventListener("pointermove", onPointerMove);
|
|
292
|
-
element.removeEventListener("pointerup", releasePointer);
|
|
293
|
-
element.removeEventListener("pointercancel", releasePointer);
|
|
294
|
-
element.removeEventListener("wheel", onWheel);
|
|
295
|
-
element.removeEventListener("keydown", onKeyDown);
|
|
296
|
-
pointersRef.current.clear();
|
|
297
|
-
};
|
|
298
|
-
}, [enabled, gl, maxFov, minFov]);
|
|
299
296
|
useFrame((_, deltaSeconds) => {
|
|
300
297
|
if (!(camera instanceof PerspectiveCamera)) {
|
|
301
298
|
return;
|
|
302
299
|
}
|
|
300
|
+
const frictionStop = resolveFrictionStop(optionsRef.current.frictionStop);
|
|
303
301
|
const inertiaEnabled = optionsRef.current.inertia !== false;
|
|
304
302
|
if (!interactingRef.current && inertiaEnabled) {
|
|
305
303
|
const rotationDamping = Math.exp(
|
|
306
304
|
-INERTIA_ROTATION_DAMPING * deltaSeconds
|
|
307
305
|
);
|
|
308
306
|
const zoomDamping2 = Math.exp(-INERTIA_ZOOM_DAMPING * deltaSeconds);
|
|
309
|
-
if (Math.abs(yawVelocityRef.current) >=
|
|
307
|
+
if (Math.abs(yawVelocityRef.current) >= frictionStop) {
|
|
310
308
|
targetViewRef.current.yaw += yawVelocityRef.current * deltaSeconds;
|
|
311
309
|
yawVelocityRef.current *= rotationDamping;
|
|
312
|
-
|
|
310
|
+
markDirty();
|
|
313
311
|
} else {
|
|
314
312
|
yawVelocityRef.current = 0;
|
|
315
313
|
}
|
|
316
|
-
if (Math.abs(pitchVelocityRef.current) >=
|
|
314
|
+
if (Math.abs(pitchVelocityRef.current) >= frictionStop) {
|
|
317
315
|
targetViewRef.current.pitch += pitchVelocityRef.current * deltaSeconds;
|
|
318
316
|
pitchVelocityRef.current *= rotationDamping;
|
|
319
|
-
|
|
317
|
+
markDirty();
|
|
320
318
|
} else {
|
|
321
319
|
pitchVelocityRef.current = 0;
|
|
322
320
|
}
|
|
323
|
-
if (Math.abs(zoomVelocityRef.current) >=
|
|
321
|
+
if (Math.abs(zoomVelocityRef.current) >= frictionStop) {
|
|
324
322
|
targetViewRef.current.fov += zoomVelocityRef.current * deltaSeconds;
|
|
325
323
|
zoomVelocityRef.current *= zoomDamping2;
|
|
326
|
-
|
|
324
|
+
markDirty();
|
|
327
325
|
} else {
|
|
328
326
|
zoomVelocityRef.current = 0;
|
|
329
327
|
}
|
|
330
328
|
} else if (!inertiaEnabled) {
|
|
331
329
|
stopVelocity();
|
|
332
330
|
}
|
|
333
|
-
|
|
331
|
+
if (!interactingRef.current) {
|
|
332
|
+
targetViewRef.current = hardConstrainView(targetViewRef.current);
|
|
333
|
+
} else {
|
|
334
|
+
targetViewRef.current = softConstrainView(targetViewRef.current);
|
|
335
|
+
}
|
|
334
336
|
if (!dirtyRef.current) {
|
|
335
337
|
return;
|
|
336
338
|
}
|
|
@@ -367,7 +369,12 @@ var PanoramaControls = forwardRef(function PanoramaControls2({ enabled, initialV
|
|
|
367
369
|
const lastEmitted = lastEmittedViewRef.current;
|
|
368
370
|
if (!lastEmitted || !viewsEqual(lastEmitted, nextView)) {
|
|
369
371
|
lastEmittedViewRef.current = nextView;
|
|
370
|
-
|
|
372
|
+
eventBusRef.current.emit("viewchange", nextView);
|
|
373
|
+
}
|
|
374
|
+
const wasSettled = settledRef.current;
|
|
375
|
+
settledRef.current = hasSettled;
|
|
376
|
+
if (hasSettled && !wasSettled) {
|
|
377
|
+
eventBusRef.current.emit("viewsettled", nextView);
|
|
371
378
|
}
|
|
372
379
|
dirtyRef.current = !hasSettled;
|
|
373
380
|
});
|
|
@@ -375,17 +382,9 @@ var PanoramaControls = forwardRef(function PanoramaControls2({ enabled, initialV
|
|
|
375
382
|
});
|
|
376
383
|
|
|
377
384
|
// src/auto-rotate.tsx
|
|
378
|
-
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
379
|
-
import {
|
|
380
|
-
createContext,
|
|
381
|
-
useContext,
|
|
382
|
-
useEffect as useEffect2,
|
|
383
|
-
useRef as useRef2
|
|
384
|
-
} from "react";
|
|
385
385
|
var DEFAULT_AUTO_ROTATE_SPEED = 18;
|
|
386
386
|
var DEFAULT_AUTO_ROTATE_ACCELERATION = 18;
|
|
387
387
|
var DEFAULT_AUTO_ROTATE_START_DELAY = 2;
|
|
388
|
-
var PanoramaControlsContext = createContext(null);
|
|
389
388
|
function resolveStartDelay(value) {
|
|
390
389
|
return Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
391
390
|
}
|
|
@@ -398,19 +397,31 @@ function AutoRotate({
|
|
|
398
397
|
acceleration = DEFAULT_AUTO_ROTATE_ACCELERATION,
|
|
399
398
|
startDelay = DEFAULT_AUTO_ROTATE_START_DELAY
|
|
400
399
|
}) {
|
|
401
|
-
const controlsRef = useContext(
|
|
400
|
+
const controlsRef = useContext(PanoramaViewContext);
|
|
401
|
+
const eventBus = useContext(PanoEventBusContext);
|
|
402
402
|
const elapsedRef = useRef2(0);
|
|
403
403
|
const rotationSpeedRef = useRef2(0);
|
|
404
|
+
const rotatingRef = useRef2(false);
|
|
405
|
+
const yawAccumulatedRef = useRef2(0);
|
|
404
406
|
if (!controlsRef) {
|
|
405
407
|
throw new Error("<AutoRotate> must be rendered inside <PanoView>.");
|
|
406
408
|
}
|
|
407
409
|
useEffect2(() => {
|
|
408
410
|
elapsedRef.current = 0;
|
|
409
411
|
rotationSpeedRef.current = 0;
|
|
410
|
-
|
|
412
|
+
yawAccumulatedRef.current = 0;
|
|
413
|
+
if (rotatingRef.current) {
|
|
414
|
+
rotatingRef.current = false;
|
|
415
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
416
|
+
}
|
|
417
|
+
}, [enabled, eventBus, startDelay]);
|
|
411
418
|
useFrame2((_, deltaSeconds) => {
|
|
412
419
|
if (!enabled || !Number.isFinite(speed) || speed === 0) {
|
|
413
420
|
rotationSpeedRef.current = 0;
|
|
421
|
+
if (rotatingRef.current) {
|
|
422
|
+
rotatingRef.current = false;
|
|
423
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
424
|
+
}
|
|
414
425
|
return;
|
|
415
426
|
}
|
|
416
427
|
const delaySeconds = resolveStartDelay(startDelay) / 1e3;
|
|
@@ -429,8 +440,21 @@ function AutoRotate({
|
|
|
429
440
|
const yawDelta = Math.sign(speed) * averageSpeed * deltaSeconds;
|
|
430
441
|
if (!controlsRef.current?.applyAutoRotation(yawDelta)) {
|
|
431
442
|
rotationSpeedRef.current = 0;
|
|
443
|
+
if (rotatingRef.current) {
|
|
444
|
+
rotatingRef.current = false;
|
|
445
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
446
|
+
}
|
|
432
447
|
return;
|
|
433
448
|
}
|
|
449
|
+
if (!rotatingRef.current) {
|
|
450
|
+
rotatingRef.current = true;
|
|
451
|
+
eventBus?.emit("autorotatestart", void 0);
|
|
452
|
+
}
|
|
453
|
+
yawAccumulatedRef.current += Math.abs(yawDelta);
|
|
454
|
+
while (yawAccumulatedRef.current >= 360) {
|
|
455
|
+
yawAccumulatedRef.current -= 360;
|
|
456
|
+
eventBus?.emit("autorotateoneround", void 0);
|
|
457
|
+
}
|
|
434
458
|
rotationSpeedRef.current = nextSpeed;
|
|
435
459
|
});
|
|
436
460
|
return null;
|
|
@@ -438,7 +462,7 @@ function AutoRotate({
|
|
|
438
462
|
|
|
439
463
|
// src/hotspot/accessibility.tsx
|
|
440
464
|
import {
|
|
441
|
-
createContext as
|
|
465
|
+
createContext as createContext3,
|
|
442
466
|
useCallback,
|
|
443
467
|
useContext as useContext2,
|
|
444
468
|
useEffect as useEffect3,
|
|
@@ -447,7 +471,7 @@ import {
|
|
|
447
471
|
useState
|
|
448
472
|
} from "react";
|
|
449
473
|
import { jsx } from "react/jsx-runtime";
|
|
450
|
-
var HotspotAccessibilityContext =
|
|
474
|
+
var HotspotAccessibilityContext = createContext3(null);
|
|
451
475
|
var VISUALLY_HIDDEN_STYLE = {
|
|
452
476
|
border: 0,
|
|
453
477
|
clip: "rect(0 0 0 0)",
|
|
@@ -552,116 +576,1454 @@ function useHotspotAccessibilityLayer() {
|
|
|
552
576
|
return { controls, registry };
|
|
553
577
|
}
|
|
554
578
|
|
|
555
|
-
// src/
|
|
556
|
-
import {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
579
|
+
// src/keyboard-controls.tsx
|
|
580
|
+
import { useFrame as useFrame3, useThree as useThree2 } from "@react-three/fiber";
|
|
581
|
+
import { useContext as useContext3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4 } from "react";
|
|
582
|
+
var DEFAULT_ROTATE_SPEED = 60;
|
|
583
|
+
var DEFAULT_ZOOM_SPEED = 30;
|
|
584
|
+
var DEFAULT_SHIFT_MULTIPLIER = 3;
|
|
585
|
+
var DEFAULT_KEYS = {
|
|
586
|
+
left: ["ArrowLeft"],
|
|
587
|
+
right: ["ArrowRight"],
|
|
588
|
+
up: ["ArrowUp"],
|
|
589
|
+
down: ["ArrowDown"],
|
|
590
|
+
zoomIn: ["+", "="],
|
|
591
|
+
zoomOut: ["-", "_"],
|
|
592
|
+
previousScene: ["[", "PageUp"],
|
|
593
|
+
nextScene: ["]", "PageDown"],
|
|
594
|
+
reset: ["0"]
|
|
595
|
+
};
|
|
596
|
+
var CONTINUOUS_ACTIONS = /* @__PURE__ */ new Set([
|
|
597
|
+
"left",
|
|
598
|
+
"right",
|
|
599
|
+
"up",
|
|
600
|
+
"down",
|
|
601
|
+
"zoomIn",
|
|
602
|
+
"zoomOut"
|
|
603
|
+
]);
|
|
604
|
+
function normalizeBinding(value) {
|
|
605
|
+
if (value === void 0) {
|
|
606
|
+
return [];
|
|
569
607
|
}
|
|
570
|
-
return
|
|
608
|
+
return (typeof value === "string" ? [value] : [...value]).filter(
|
|
609
|
+
(entry) => entry.length > 0
|
|
610
|
+
);
|
|
571
611
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
574
|
-
|
|
612
|
+
function resolveKeyMap(overrides) {
|
|
613
|
+
const map = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const action of Object.keys(DEFAULT_KEYS)) {
|
|
615
|
+
const bindings = overrides && action in overrides ? normalizeBinding(overrides[action]) : [...DEFAULT_KEYS[action]];
|
|
616
|
+
for (const binding of bindings) {
|
|
617
|
+
map.set(binding.toLowerCase(), action);
|
|
618
|
+
}
|
|
575
619
|
}
|
|
576
|
-
|
|
577
|
-
pitch,
|
|
578
|
-
-MAX_HOTSPOT_PITCH,
|
|
579
|
-
MAX_HOTSPOT_PITCH
|
|
580
|
-
);
|
|
581
|
-
return Math.abs(clamped) >= MAX_HOTSPOT_PITCH - POLE_EPSILON_DEGREES ? Math.sign(clamped) * MAX_HOTSPOT_PITCH : clamped;
|
|
620
|
+
return map;
|
|
582
621
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
return {
|
|
586
|
-
yaw: Math.abs(pitch) === MAX_HOTSPOT_PITCH ? 0 : normalizePanoYaw(position.yaw),
|
|
587
|
-
pitch
|
|
588
|
-
};
|
|
622
|
+
function matchAction(event, keyMap) {
|
|
623
|
+
return keyMap.get(event.key.toLowerCase()) ?? keyMap.get(event.code.toLowerCase()) ?? null;
|
|
589
624
|
}
|
|
590
|
-
function
|
|
591
|
-
|
|
592
|
-
const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 1;
|
|
593
|
-
const yaw = MathUtils2.degToRad(normalized.yaw);
|
|
594
|
-
const pitch = MathUtils2.degToRad(normalized.pitch);
|
|
595
|
-
const cosPitch = Math.cos(pitch);
|
|
596
|
-
return new Vector3(
|
|
597
|
-
cosPitch * Math.sin(yaw) * safeRadius,
|
|
598
|
-
Math.sin(pitch) * safeRadius,
|
|
599
|
-
-cosPitch * Math.cos(yaw) * safeRadius
|
|
600
|
-
);
|
|
625
|
+
function resolveSpeed(value, fallback) {
|
|
626
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
601
627
|
}
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
return { yaw: 0, pitch: 0 };
|
|
628
|
+
function cycleSceneId(scenes, activeSceneId, step) {
|
|
629
|
+
if (scenes.length === 0) {
|
|
630
|
+
return null;
|
|
606
631
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
Math.asin(MathUtils2.clamp(vector.y / length, -1, 1))
|
|
611
|
-
)
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// src/panorama-radius.ts
|
|
616
|
-
var DEFAULT_PANORAMA_RADIUS = 1e3;
|
|
617
|
-
var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
|
|
618
|
-
var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
|
|
619
|
-
|
|
620
|
-
// src/panorama-event-surface.tsx
|
|
621
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
622
|
-
var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
|
|
623
|
-
function makePanoramaPointerEvent(event) {
|
|
624
|
-
const nativeEvent = event.nativeEvent;
|
|
625
|
-
return {
|
|
626
|
-
position: vector3ToPanoPosition(event.ray.direction),
|
|
627
|
-
nativeEvent,
|
|
628
|
-
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
629
|
-
button: nativeEvent.button,
|
|
630
|
-
buttons: nativeEvent.buttons,
|
|
631
|
-
clientX: nativeEvent.clientX,
|
|
632
|
-
clientY: nativeEvent.clientY
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
function emitPanoramaEvent(event, callback) {
|
|
636
|
-
if (!callback) {
|
|
637
|
-
return;
|
|
632
|
+
const index = scenes.findIndex((scene) => scene.id === activeSceneId);
|
|
633
|
+
if (index < 0) {
|
|
634
|
+
return null;
|
|
638
635
|
}
|
|
639
|
-
|
|
640
|
-
|
|
636
|
+
const nextIndex = (index + step + scenes.length) % scenes.length;
|
|
637
|
+
return scenes[nextIndex]?.id ?? null;
|
|
641
638
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
639
|
+
function KeyboardControls({
|
|
640
|
+
enabled = true,
|
|
641
|
+
keys,
|
|
642
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED,
|
|
643
|
+
zoomSpeed = DEFAULT_ZOOM_SPEED,
|
|
644
|
+
shiftMultiplier = DEFAULT_SHIFT_MULTIPLIER,
|
|
645
|
+
invert = false,
|
|
646
|
+
fovSpeed,
|
|
647
|
+
onPreviousScene,
|
|
648
|
+
onNextScene
|
|
646
649
|
}) {
|
|
647
|
-
|
|
648
|
-
|
|
650
|
+
const controlsRef = useContext3(PanoramaViewContext);
|
|
651
|
+
const { gl } = useThree2();
|
|
652
|
+
const pressedRef = useRef4(/* @__PURE__ */ new Set());
|
|
653
|
+
const shiftHeldRef = useRef4(false);
|
|
654
|
+
const invertRef = useRef4(invert);
|
|
655
|
+
const fovSpeedRef = useRef4(fovSpeed);
|
|
656
|
+
const onPreviousSceneRef = useRef4(onPreviousScene);
|
|
657
|
+
const onNextSceneRef = useRef4(onNextScene);
|
|
658
|
+
const keyMap = useMemo2(() => resolveKeyMap(keys), [keys]);
|
|
659
|
+
if (!controlsRef) {
|
|
660
|
+
throw new Error("<KeyboardControls> must be rendered inside <PanoView>.");
|
|
649
661
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
662
|
+
invertRef.current = invert;
|
|
663
|
+
fovSpeedRef.current = fovSpeed;
|
|
664
|
+
onPreviousSceneRef.current = onPreviousScene;
|
|
665
|
+
onNextSceneRef.current = onNextScene;
|
|
666
|
+
useEffect4(() => {
|
|
667
|
+
const element = gl.domElement;
|
|
668
|
+
if (!enabled) {
|
|
669
|
+
pressedRef.current.clear();
|
|
670
|
+
shiftHeldRef.current = false;
|
|
671
|
+
controlsRef.current?.setKeyboardActive(false);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const syncKeyboardActive = () => {
|
|
675
|
+
let continuous = false;
|
|
676
|
+
for (const action of pressedRef.current) {
|
|
677
|
+
if (CONTINUOUS_ACTIONS.has(action)) {
|
|
678
|
+
continuous = true;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
controlsRef.current?.setKeyboardActive(continuous);
|
|
683
|
+
};
|
|
684
|
+
const clearPressed = () => {
|
|
685
|
+
pressedRef.current.clear();
|
|
686
|
+
shiftHeldRef.current = false;
|
|
687
|
+
controlsRef.current?.setKeyboardActive(false);
|
|
688
|
+
};
|
|
689
|
+
const onKeyDown = (event) => {
|
|
690
|
+
if (event.key === "Shift") {
|
|
691
|
+
shiftHeldRef.current = true;
|
|
692
|
+
}
|
|
693
|
+
const action = matchAction(event, keyMap);
|
|
694
|
+
if (!action) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
event.preventDefault();
|
|
701
|
+
if (action === "reset") {
|
|
702
|
+
if (!event.repeat) {
|
|
703
|
+
controlsRef.current?.reset();
|
|
704
|
+
}
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (action === "previousScene") {
|
|
708
|
+
if (!event.repeat) {
|
|
709
|
+
onPreviousSceneRef.current?.();
|
|
710
|
+
}
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (action === "nextScene") {
|
|
714
|
+
if (!event.repeat) {
|
|
715
|
+
onNextSceneRef.current?.();
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
pressedRef.current.add(action);
|
|
720
|
+
syncKeyboardActive();
|
|
721
|
+
};
|
|
722
|
+
const onKeyUp = (event) => {
|
|
723
|
+
if (event.key === "Shift") {
|
|
724
|
+
shiftHeldRef.current = false;
|
|
725
|
+
}
|
|
726
|
+
const action = matchAction(event, keyMap);
|
|
727
|
+
if (!action || !CONTINUOUS_ACTIONS.has(action)) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
pressedRef.current.delete(action);
|
|
731
|
+
syncKeyboardActive();
|
|
732
|
+
};
|
|
733
|
+
const onBlur = () => {
|
|
734
|
+
clearPressed();
|
|
735
|
+
};
|
|
736
|
+
element.addEventListener("keydown", onKeyDown);
|
|
737
|
+
element.addEventListener("keyup", onKeyUp);
|
|
738
|
+
element.addEventListener("blur", onBlur);
|
|
739
|
+
window.addEventListener("blur", onBlur);
|
|
740
|
+
return () => {
|
|
741
|
+
element.removeEventListener("keydown", onKeyDown);
|
|
742
|
+
element.removeEventListener("keyup", onKeyUp);
|
|
743
|
+
element.removeEventListener("blur", onBlur);
|
|
744
|
+
window.removeEventListener("blur", onBlur);
|
|
745
|
+
clearPressed();
|
|
746
|
+
};
|
|
747
|
+
}, [controlsRef, enabled, gl, keyMap]);
|
|
748
|
+
useFrame3((_, deltaSeconds) => {
|
|
749
|
+
if (!enabled || pressedRef.current.size === 0) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const controls = controlsRef.current;
|
|
753
|
+
if (!controls) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const multiplier = shiftHeldRef.current ? resolveSpeed(shiftMultiplier, DEFAULT_SHIFT_MULTIPLIER) : 1;
|
|
757
|
+
const rotation = resolveSpeed(rotateSpeed, DEFAULT_ROTATE_SPEED) * multiplier * deltaSeconds;
|
|
758
|
+
let zoom = resolveSpeed(zoomSpeed, DEFAULT_ZOOM_SPEED) * multiplier * deltaSeconds;
|
|
759
|
+
const fovCap = fovSpeedRef.current;
|
|
760
|
+
if (Number.isFinite(fovCap) && fovCap >= 0) {
|
|
761
|
+
zoom = Math.min(zoom, fovCap * deltaSeconds);
|
|
762
|
+
}
|
|
763
|
+
const pitchSign = invertRef.current ? -1 : 1;
|
|
764
|
+
let yaw = 0;
|
|
765
|
+
let pitch = 0;
|
|
766
|
+
let fov = 0;
|
|
767
|
+
for (const action of pressedRef.current) {
|
|
768
|
+
switch (action) {
|
|
769
|
+
case "left":
|
|
770
|
+
yaw -= rotation;
|
|
771
|
+
break;
|
|
772
|
+
case "right":
|
|
773
|
+
yaw += rotation;
|
|
774
|
+
break;
|
|
775
|
+
case "up":
|
|
776
|
+
pitch += pitchSign * rotation;
|
|
777
|
+
break;
|
|
778
|
+
case "down":
|
|
779
|
+
pitch -= pitchSign * rotation;
|
|
780
|
+
break;
|
|
781
|
+
case "zoomIn":
|
|
782
|
+
fov -= zoom;
|
|
783
|
+
break;
|
|
784
|
+
case "zoomOut":
|
|
785
|
+
fov += zoom;
|
|
786
|
+
break;
|
|
787
|
+
case "previousScene":
|
|
788
|
+
case "nextScene":
|
|
789
|
+
case "reset":
|
|
790
|
+
break;
|
|
791
|
+
default: {
|
|
792
|
+
const _exhaustive = action;
|
|
793
|
+
void _exhaustive;
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (yaw !== 0 || pitch !== 0 || fov !== 0) {
|
|
799
|
+
controls.applyViewDelta({ yaw, pitch, fov });
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/mouse-controls.tsx
|
|
806
|
+
import { useThree as useThree3 } from "@react-three/fiber";
|
|
807
|
+
import { useContext as useContext4, useEffect as useEffect5, useRef as useRef5 } from "react";
|
|
808
|
+
var DEFAULT_ROTATE_SPEED2 = 0.35;
|
|
809
|
+
var DEFAULT_ZOOM_SPEED2 = 0.08;
|
|
810
|
+
var BUTTON_FLAG = {
|
|
811
|
+
left: 0,
|
|
812
|
+
middle: 1,
|
|
813
|
+
right: 2
|
|
814
|
+
};
|
|
815
|
+
function resolveSpeed2(value, fallback) {
|
|
816
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
817
|
+
}
|
|
818
|
+
function resolveButtons(buttons) {
|
|
819
|
+
const list = buttons && buttons.length > 0 ? buttons : ["left"];
|
|
820
|
+
return new Set(list.map((button) => BUTTON_FLAG[button]));
|
|
821
|
+
}
|
|
822
|
+
function MouseControls({
|
|
823
|
+
enabled = true,
|
|
824
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED2,
|
|
825
|
+
zoomSpeed = DEFAULT_ZOOM_SPEED2,
|
|
826
|
+
wheel = true,
|
|
827
|
+
invert = false,
|
|
828
|
+
buttons,
|
|
829
|
+
fovSpeed
|
|
830
|
+
}) {
|
|
831
|
+
const controlsRef = useContext4(PanoramaViewContext);
|
|
832
|
+
const { gl } = useThree3();
|
|
833
|
+
const pointersRef = useRef5(
|
|
834
|
+
/* @__PURE__ */ new Map()
|
|
835
|
+
);
|
|
836
|
+
const optionsRef = useRef5({
|
|
837
|
+
rotateSpeed,
|
|
838
|
+
zoomSpeed,
|
|
839
|
+
wheel,
|
|
840
|
+
invert,
|
|
841
|
+
fovSpeed
|
|
842
|
+
});
|
|
843
|
+
if (!controlsRef) {
|
|
844
|
+
throw new Error("<MouseControls> must be rendered inside <PanoView>.");
|
|
845
|
+
}
|
|
846
|
+
optionsRef.current = {
|
|
847
|
+
rotateSpeed,
|
|
848
|
+
zoomSpeed,
|
|
849
|
+
wheel,
|
|
850
|
+
invert,
|
|
851
|
+
fovSpeed
|
|
852
|
+
};
|
|
853
|
+
useEffect5(() => {
|
|
854
|
+
const element = gl.domElement;
|
|
855
|
+
const allowedButtons = resolveButtons(buttons);
|
|
856
|
+
const clearPointers = () => {
|
|
857
|
+
for (const pointerId of pointersRef.current.keys()) {
|
|
858
|
+
if (element.hasPointerCapture?.(pointerId)) {
|
|
859
|
+
element.releasePointerCapture?.(pointerId);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
pointersRef.current.clear();
|
|
863
|
+
controlsRef.current?.setPointerActive("mouse", false);
|
|
864
|
+
};
|
|
865
|
+
if (!enabled) {
|
|
866
|
+
clearPointers();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const isMouseLike = (event) => event.pointerType === "mouse" || event.pointerType === "pen";
|
|
870
|
+
const syncActive = () => {
|
|
871
|
+
controlsRef.current?.setPointerActive(
|
|
872
|
+
"mouse",
|
|
873
|
+
pointersRef.current.size > 0
|
|
874
|
+
);
|
|
875
|
+
};
|
|
876
|
+
const onPointerDown = (event) => {
|
|
877
|
+
if (!isMouseLike(event)) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (!allowedButtons.has(event.button)) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
event.preventDefault();
|
|
887
|
+
pointersRef.current.set(event.pointerId, {
|
|
888
|
+
x: event.clientX,
|
|
889
|
+
y: event.clientY,
|
|
890
|
+
at: performance.now()
|
|
891
|
+
});
|
|
892
|
+
element.setPointerCapture?.(event.pointerId);
|
|
893
|
+
syncActive();
|
|
894
|
+
};
|
|
895
|
+
const onPointerMove = (event) => {
|
|
896
|
+
if (!isMouseLike(event)) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const previous = pointersRef.current.get(event.pointerId);
|
|
903
|
+
if (!previous) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
event.preventDefault();
|
|
907
|
+
const next = {
|
|
908
|
+
x: event.clientX,
|
|
909
|
+
y: event.clientY,
|
|
910
|
+
at: performance.now()
|
|
911
|
+
};
|
|
912
|
+
pointersRef.current.set(event.pointerId, next);
|
|
913
|
+
const rect = element.getBoundingClientRect();
|
|
914
|
+
const speed = resolveSpeed2(
|
|
915
|
+
optionsRef.current.rotateSpeed,
|
|
916
|
+
DEFAULT_ROTATE_SPEED2
|
|
917
|
+
);
|
|
918
|
+
const sign = optionsRef.current.invert ? -1 : 1;
|
|
919
|
+
const deltaYaw = sign * -(next.x - previous.x) * 360 * speed / Math.max(rect.width, 1);
|
|
920
|
+
const deltaPitch = sign * (next.y - previous.y) * 180 * speed / Math.max(rect.height, 1);
|
|
921
|
+
controlsRef.current?.applyViewDelta(
|
|
922
|
+
{ yaw: deltaYaw, pitch: deltaPitch },
|
|
923
|
+
{ recordVelocity: true }
|
|
924
|
+
);
|
|
925
|
+
};
|
|
926
|
+
const releasePointer = (event) => {
|
|
927
|
+
if (!pointersRef.current.has(event.pointerId)) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
pointersRef.current.delete(event.pointerId);
|
|
931
|
+
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
932
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
933
|
+
}
|
|
934
|
+
syncActive();
|
|
935
|
+
};
|
|
936
|
+
const onWheel = (event) => {
|
|
937
|
+
if (!optionsRef.current.wheel) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
941
|
+
event.preventDefault();
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
event.preventDefault();
|
|
945
|
+
const speed = resolveSpeed2(
|
|
946
|
+
optionsRef.current.zoomSpeed,
|
|
947
|
+
DEFAULT_ZOOM_SPEED2
|
|
948
|
+
);
|
|
949
|
+
let delta = event.deltaY * speed;
|
|
950
|
+
const cap = optionsRef.current.fovSpeed;
|
|
951
|
+
if (Number.isFinite(cap) && cap > 0) {
|
|
952
|
+
delta = Math.max(-cap, Math.min(cap, delta));
|
|
953
|
+
}
|
|
954
|
+
controlsRef.current?.applyViewDelta(
|
|
955
|
+
{ fov: delta },
|
|
956
|
+
{ recordVelocity: true }
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
960
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
961
|
+
element.addEventListener("pointerup", releasePointer);
|
|
962
|
+
element.addEventListener("pointercancel", releasePointer);
|
|
963
|
+
element.addEventListener("wheel", onWheel, { passive: false });
|
|
964
|
+
return () => {
|
|
965
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
966
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
967
|
+
element.removeEventListener("pointerup", releasePointer);
|
|
968
|
+
element.removeEventListener("pointercancel", releasePointer);
|
|
969
|
+
element.removeEventListener("wheel", onWheel);
|
|
970
|
+
clearPointers();
|
|
971
|
+
};
|
|
972
|
+
}, [buttons, controlsRef, enabled, gl]);
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/pano-context-menu.tsx
|
|
977
|
+
import {
|
|
978
|
+
createContext as createContext4,
|
|
979
|
+
useCallback as useCallback2,
|
|
980
|
+
useContext as useContext5,
|
|
981
|
+
useEffect as useEffect6,
|
|
982
|
+
useId,
|
|
983
|
+
useLayoutEffect,
|
|
984
|
+
useMemo as useMemo3,
|
|
985
|
+
useRef as useRef6,
|
|
986
|
+
useState as useState2
|
|
987
|
+
} from "react";
|
|
988
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
989
|
+
var DEFAULT_ICON_SIZE = 16;
|
|
990
|
+
var MENU_EDGE_PADDING = 8;
|
|
991
|
+
var MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY = 0.4;
|
|
992
|
+
var PanoContextMenuOverlayContext = createContext4(null);
|
|
993
|
+
var DEFAULT_APPEARANCE = {
|
|
994
|
+
background: "#161616",
|
|
995
|
+
color: "#f4f4f4",
|
|
996
|
+
border: "1px solid #2e2e2e",
|
|
997
|
+
borderRadius: 8,
|
|
998
|
+
shadow: "0 8px 24px rgba(0, 0, 0, 0.45)",
|
|
999
|
+
opacity: 1,
|
|
1000
|
+
fontSize: 13,
|
|
1001
|
+
minWidth: 180,
|
|
1002
|
+
padding: 4,
|
|
1003
|
+
iconSize: DEFAULT_ICON_SIZE,
|
|
1004
|
+
itemPadding: "6px 10px",
|
|
1005
|
+
itemHoverBackground: "#2a2a2a",
|
|
1006
|
+
itemDisabledColor: "#777777",
|
|
1007
|
+
separatorColor: "#333333",
|
|
1008
|
+
separatorMargin: "4px 6px"
|
|
1009
|
+
};
|
|
1010
|
+
function clampBackgroundOpacity(value) {
|
|
1011
|
+
if (!Number.isFinite(value)) {
|
|
1012
|
+
return DEFAULT_APPEARANCE.opacity;
|
|
1013
|
+
}
|
|
1014
|
+
return Math.min(
|
|
1015
|
+
1,
|
|
1016
|
+
Math.max(MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY, value)
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
function isActionItem(item) {
|
|
1020
|
+
return item.type !== "separator";
|
|
1021
|
+
}
|
|
1022
|
+
function DefaultContextMenuPanel({
|
|
1023
|
+
items,
|
|
1024
|
+
appearance,
|
|
1025
|
+
className,
|
|
1026
|
+
style,
|
|
1027
|
+
itemClassName,
|
|
1028
|
+
separatorClassName,
|
|
1029
|
+
selectContext,
|
|
1030
|
+
onRequestClose
|
|
1031
|
+
}) {
|
|
1032
|
+
const menuRef = useRef6(null);
|
|
1033
|
+
const [activeIndex, setActiveIndex] = useState2(0);
|
|
1034
|
+
const resolved = { ...DEFAULT_APPEARANCE, ...appearance };
|
|
1035
|
+
const iconSize = Number.isFinite(resolved.iconSize) ? Math.max(0, resolved.iconSize) : DEFAULT_ICON_SIZE;
|
|
1036
|
+
const visibleItems = useMemo3(
|
|
1037
|
+
() => items.filter((item) => {
|
|
1038
|
+
if (!isActionItem(item)) {
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1041
|
+
return !item.hidden;
|
|
1042
|
+
}),
|
|
1043
|
+
[items]
|
|
1044
|
+
);
|
|
1045
|
+
const focusableIndices = useMemo3(() => {
|
|
1046
|
+
const indices = [];
|
|
1047
|
+
visibleItems.forEach((item, index) => {
|
|
1048
|
+
if (isActionItem(item) && !item.disabled) {
|
|
1049
|
+
indices.push(index);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
return indices;
|
|
1053
|
+
}, [visibleItems]);
|
|
1054
|
+
useEffect6(() => {
|
|
1055
|
+
const first = focusableIndices[0];
|
|
1056
|
+
setActiveIndex(first ?? 0);
|
|
1057
|
+
const frame = requestAnimationFrame(() => {
|
|
1058
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1059
|
+
'[role="menuitem"]:not([aria-disabled="true"])'
|
|
1060
|
+
);
|
|
1061
|
+
buttons?.[0]?.focus();
|
|
1062
|
+
});
|
|
1063
|
+
return () => cancelAnimationFrame(frame);
|
|
1064
|
+
}, [focusableIndices]);
|
|
1065
|
+
const activateItem = useCallback2(
|
|
1066
|
+
(item) => {
|
|
1067
|
+
if (item.disabled) {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
item.onSelect(selectContext);
|
|
1071
|
+
onRequestClose();
|
|
1072
|
+
},
|
|
1073
|
+
[onRequestClose, selectContext]
|
|
1074
|
+
);
|
|
1075
|
+
const moveFocus = (direction) => {
|
|
1076
|
+
if (focusableIndices.length === 0) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
const currentSlot = focusableIndices.indexOf(activeIndex);
|
|
1080
|
+
const nextSlot = currentSlot === -1 ? 0 : (currentSlot + direction + focusableIndices.length) % focusableIndices.length;
|
|
1081
|
+
const nextIndex = focusableIndices[nextSlot];
|
|
1082
|
+
setActiveIndex(nextIndex);
|
|
1083
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1084
|
+
'[role="menuitem"]'
|
|
1085
|
+
);
|
|
1086
|
+
buttons?.[nextIndex]?.focus();
|
|
1087
|
+
};
|
|
1088
|
+
const handleKeyDown = (event) => {
|
|
1089
|
+
switch (event.key) {
|
|
1090
|
+
case "ArrowDown":
|
|
1091
|
+
event.preventDefault();
|
|
1092
|
+
moveFocus(1);
|
|
1093
|
+
break;
|
|
1094
|
+
case "ArrowUp":
|
|
1095
|
+
event.preventDefault();
|
|
1096
|
+
moveFocus(-1);
|
|
1097
|
+
break;
|
|
1098
|
+
case "Home": {
|
|
1099
|
+
event.preventDefault();
|
|
1100
|
+
const first = focusableIndices[0];
|
|
1101
|
+
if (first !== void 0) {
|
|
1102
|
+
setActiveIndex(first);
|
|
1103
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1104
|
+
'[role="menuitem"]'
|
|
1105
|
+
);
|
|
1106
|
+
buttons?.[first]?.focus();
|
|
1107
|
+
}
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
case "End": {
|
|
1111
|
+
event.preventDefault();
|
|
1112
|
+
const last = focusableIndices[focusableIndices.length - 1];
|
|
1113
|
+
if (last !== void 0) {
|
|
1114
|
+
setActiveIndex(last);
|
|
1115
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1116
|
+
'[role="menuitem"]'
|
|
1117
|
+
);
|
|
1118
|
+
buttons?.[last]?.focus();
|
|
1119
|
+
}
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
case "Escape":
|
|
1123
|
+
event.preventDefault();
|
|
1124
|
+
onRequestClose();
|
|
1125
|
+
break;
|
|
1126
|
+
case "Enter":
|
|
1127
|
+
case " ": {
|
|
1128
|
+
event.preventDefault();
|
|
1129
|
+
const item = visibleItems[activeIndex];
|
|
1130
|
+
if (item && isActionItem(item)) {
|
|
1131
|
+
activateItem(item);
|
|
1132
|
+
}
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
default: {
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
const backgroundOpacity = clampBackgroundOpacity(resolved.opacity);
|
|
1141
|
+
const panelStyle = {
|
|
1142
|
+
color: resolved.color,
|
|
1143
|
+
border: resolved.border,
|
|
1144
|
+
borderRadius: resolved.borderRadius,
|
|
1145
|
+
boxShadow: resolved.shadow,
|
|
1146
|
+
fontSize: resolved.fontSize,
|
|
1147
|
+
minWidth: resolved.minWidth,
|
|
1148
|
+
padding: resolved.padding,
|
|
1149
|
+
pointerEvents: "auto",
|
|
1150
|
+
position: "relative",
|
|
1151
|
+
userSelect: "none",
|
|
1152
|
+
...style
|
|
1153
|
+
};
|
|
1154
|
+
return /* @__PURE__ */ jsxs(
|
|
1155
|
+
"div",
|
|
1156
|
+
{
|
|
1157
|
+
ref: menuRef,
|
|
1158
|
+
"aria-label": "Panorama context menu",
|
|
1159
|
+
className,
|
|
1160
|
+
onKeyDown: handleKeyDown,
|
|
1161
|
+
role: "menu",
|
|
1162
|
+
style: panelStyle,
|
|
1163
|
+
tabIndex: -1,
|
|
1164
|
+
children: [
|
|
1165
|
+
/* @__PURE__ */ jsx2(
|
|
1166
|
+
"div",
|
|
1167
|
+
{
|
|
1168
|
+
"aria-hidden": true,
|
|
1169
|
+
style: {
|
|
1170
|
+
background: resolved.background,
|
|
1171
|
+
borderRadius: resolved.borderRadius,
|
|
1172
|
+
inset: 0,
|
|
1173
|
+
opacity: backgroundOpacity,
|
|
1174
|
+
pointerEvents: "none",
|
|
1175
|
+
position: "absolute",
|
|
1176
|
+
zIndex: 0
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
/* @__PURE__ */ jsx2("div", { style: { position: "relative", zIndex: 1 }, children: visibleItems.map((item, index) => {
|
|
1181
|
+
if (!isActionItem(item)) {
|
|
1182
|
+
return /* @__PURE__ */ jsx2(
|
|
1183
|
+
"div",
|
|
1184
|
+
{
|
|
1185
|
+
"aria-hidden": true,
|
|
1186
|
+
className: [separatorClassName, item.className].filter(Boolean).join(" ") || void 0,
|
|
1187
|
+
role: "separator",
|
|
1188
|
+
style: {
|
|
1189
|
+
background: resolved.separatorColor,
|
|
1190
|
+
height: 1,
|
|
1191
|
+
margin: resolved.separatorMargin,
|
|
1192
|
+
...item.style
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
item.id ?? `separator-${index}`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
const disabled = item.disabled === true;
|
|
1199
|
+
const iconNode = item.icon ?? (item.image ? /* @__PURE__ */ jsx2(
|
|
1200
|
+
"img",
|
|
1201
|
+
{
|
|
1202
|
+
alt: item.imageAlt ?? "",
|
|
1203
|
+
draggable: false,
|
|
1204
|
+
src: item.image,
|
|
1205
|
+
style: {
|
|
1206
|
+
display: "block",
|
|
1207
|
+
height: iconSize,
|
|
1208
|
+
objectFit: "contain",
|
|
1209
|
+
width: iconSize
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
) : null);
|
|
1213
|
+
return /* @__PURE__ */ jsxs(
|
|
1214
|
+
"button",
|
|
1215
|
+
{
|
|
1216
|
+
"aria-disabled": disabled || void 0,
|
|
1217
|
+
className: [itemClassName, item.className].filter(Boolean).join(" ") || void 0,
|
|
1218
|
+
disabled,
|
|
1219
|
+
onClick: (event) => {
|
|
1220
|
+
event.preventDefault();
|
|
1221
|
+
event.stopPropagation();
|
|
1222
|
+
activateItem(item);
|
|
1223
|
+
},
|
|
1224
|
+
onMouseEnter: (event) => {
|
|
1225
|
+
if (!disabled) {
|
|
1226
|
+
setActiveIndex(index);
|
|
1227
|
+
event.currentTarget.focus();
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
role: "menuitem",
|
|
1231
|
+
style: {
|
|
1232
|
+
alignItems: "center",
|
|
1233
|
+
background: !disabled && activeIndex === index ? resolved.itemHoverBackground : "transparent",
|
|
1234
|
+
border: "none",
|
|
1235
|
+
borderRadius: 4,
|
|
1236
|
+
color: disabled ? resolved.itemDisabledColor : "inherit",
|
|
1237
|
+
cursor: disabled ? "default" : "pointer",
|
|
1238
|
+
display: "flex",
|
|
1239
|
+
font: "inherit",
|
|
1240
|
+
gap: 8,
|
|
1241
|
+
padding: resolved.itemPadding,
|
|
1242
|
+
textAlign: "left",
|
|
1243
|
+
width: "100%",
|
|
1244
|
+
...item.style
|
|
1245
|
+
},
|
|
1246
|
+
tabIndex: disabled ? -1 : activeIndex === index ? 0 : -1,
|
|
1247
|
+
type: "button",
|
|
1248
|
+
children: [
|
|
1249
|
+
/* @__PURE__ */ jsx2(
|
|
1250
|
+
"span",
|
|
1251
|
+
{
|
|
1252
|
+
"aria-hidden": true,
|
|
1253
|
+
style: {
|
|
1254
|
+
alignItems: "center",
|
|
1255
|
+
display: "inline-flex",
|
|
1256
|
+
flexShrink: 0,
|
|
1257
|
+
height: iconSize,
|
|
1258
|
+
justifyContent: "center",
|
|
1259
|
+
width: iconSize
|
|
1260
|
+
},
|
|
1261
|
+
children: iconNode
|
|
1262
|
+
}
|
|
1263
|
+
),
|
|
1264
|
+
/* @__PURE__ */ jsx2("span", { style: { flex: 1 }, children: item.label })
|
|
1265
|
+
]
|
|
1266
|
+
},
|
|
1267
|
+
item.id
|
|
1268
|
+
);
|
|
1269
|
+
}) })
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
function PositionedMenuShell({
|
|
1275
|
+
clientX,
|
|
1276
|
+
clientY,
|
|
1277
|
+
overlayElement,
|
|
1278
|
+
children
|
|
1279
|
+
}) {
|
|
1280
|
+
const shellRef = useRef6(null);
|
|
1281
|
+
const [offset, setOffset] = useState2({ left: 0, top: 0 });
|
|
1282
|
+
useLayoutEffect(() => {
|
|
1283
|
+
const shell = shellRef.current;
|
|
1284
|
+
if (!overlayElement || !shell) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const overlayRect = overlayElement.getBoundingClientRect();
|
|
1288
|
+
const menuRect = shell.getBoundingClientRect();
|
|
1289
|
+
const rawLeft = clientX - overlayRect.left;
|
|
1290
|
+
const rawTop = clientY - overlayRect.top;
|
|
1291
|
+
const maxLeft = Math.max(
|
|
1292
|
+
MENU_EDGE_PADDING,
|
|
1293
|
+
overlayRect.width - menuRect.width - MENU_EDGE_PADDING
|
|
1294
|
+
);
|
|
1295
|
+
const maxTop = Math.max(
|
|
1296
|
+
MENU_EDGE_PADDING,
|
|
1297
|
+
overlayRect.height - menuRect.height - MENU_EDGE_PADDING
|
|
1298
|
+
);
|
|
1299
|
+
setOffset({
|
|
1300
|
+
left: Math.min(Math.max(rawLeft, MENU_EDGE_PADDING), maxLeft),
|
|
1301
|
+
top: Math.min(Math.max(rawTop, MENU_EDGE_PADDING), maxTop)
|
|
1302
|
+
});
|
|
1303
|
+
}, [clientX, clientY, overlayElement, children]);
|
|
1304
|
+
return /* @__PURE__ */ jsx2(
|
|
1305
|
+
"div",
|
|
1306
|
+
{
|
|
1307
|
+
ref: shellRef,
|
|
1308
|
+
style: {
|
|
1309
|
+
left: offset.left,
|
|
1310
|
+
position: "absolute",
|
|
1311
|
+
top: offset.top,
|
|
1312
|
+
zIndex: 20
|
|
1313
|
+
},
|
|
1314
|
+
children
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
function usePanoContextMenuOverlay() {
|
|
1319
|
+
const [session, setSession] = useState2(null);
|
|
1320
|
+
const overlayRef = useRef6(null);
|
|
1321
|
+
const [overlayElement, setOverlayElement] = useState2(
|
|
1322
|
+
null
|
|
1323
|
+
);
|
|
1324
|
+
const api = useMemo3(
|
|
1325
|
+
() => ({ setSession, overlayRef }),
|
|
1326
|
+
[]
|
|
1327
|
+
);
|
|
1328
|
+
const setOverlayNode = useCallback2((node) => {
|
|
1329
|
+
overlayRef.current = node;
|
|
1330
|
+
setOverlayElement(node);
|
|
1331
|
+
}, []);
|
|
1332
|
+
useEffect6(() => {
|
|
1333
|
+
if (!session) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
const handlePointerDown = (event) => {
|
|
1337
|
+
const overlay2 = overlayRef.current;
|
|
1338
|
+
if (!overlay2) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
const target = event.target;
|
|
1342
|
+
if (target instanceof Node && overlay2.contains(target)) {
|
|
1343
|
+
const menuRoot = overlay2.querySelector(
|
|
1344
|
+
'[role="menu"], [data-pano-context-menu]'
|
|
1345
|
+
);
|
|
1346
|
+
if (menuRoot && menuRoot.contains(target)) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
setSession(null);
|
|
1351
|
+
};
|
|
1352
|
+
const handleKeyDown = (event) => {
|
|
1353
|
+
if (event.key === "Escape") {
|
|
1354
|
+
setSession(null);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
1358
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1359
|
+
return () => {
|
|
1360
|
+
document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
1361
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
1362
|
+
};
|
|
1363
|
+
}, [session]);
|
|
1364
|
+
const overlay = /* @__PURE__ */ jsx2(
|
|
1365
|
+
"div",
|
|
1366
|
+
{
|
|
1367
|
+
ref: setOverlayNode,
|
|
1368
|
+
"aria-hidden": session ? void 0 : true,
|
|
1369
|
+
style: {
|
|
1370
|
+
inset: 0,
|
|
1371
|
+
pointerEvents: "none",
|
|
1372
|
+
position: "absolute",
|
|
1373
|
+
zIndex: 20
|
|
1374
|
+
},
|
|
1375
|
+
children: session ? /* @__PURE__ */ jsx2(
|
|
1376
|
+
PositionedMenuShell,
|
|
1377
|
+
{
|
|
1378
|
+
clientX: session.event.clientX,
|
|
1379
|
+
clientY: session.event.clientY,
|
|
1380
|
+
overlayElement,
|
|
1381
|
+
children: session.render()
|
|
1382
|
+
},
|
|
1383
|
+
session.key
|
|
1384
|
+
) : null
|
|
1385
|
+
}
|
|
1386
|
+
);
|
|
1387
|
+
return { api, overlay };
|
|
1388
|
+
}
|
|
1389
|
+
function PanoContextMenu({
|
|
1390
|
+
items,
|
|
1391
|
+
prepend,
|
|
1392
|
+
append,
|
|
1393
|
+
appearance,
|
|
1394
|
+
className,
|
|
1395
|
+
style,
|
|
1396
|
+
itemClassName,
|
|
1397
|
+
separatorClassName,
|
|
1398
|
+
children
|
|
1399
|
+
}) {
|
|
1400
|
+
const eventBus = useContext5(PanoEventBusContext);
|
|
1401
|
+
const controlsRef = useContext5(PanoramaViewContext);
|
|
1402
|
+
const overlayApi = useContext5(PanoContextMenuOverlayContext);
|
|
1403
|
+
const actions = useContext5(PanoContextMenuActionsContext);
|
|
1404
|
+
const instanceId = useId();
|
|
1405
|
+
const sessionKeyRef = useRef6(0);
|
|
1406
|
+
const propsRef = useRef6({
|
|
1407
|
+
items,
|
|
1408
|
+
prepend,
|
|
1409
|
+
append,
|
|
1410
|
+
appearance,
|
|
1411
|
+
className,
|
|
1412
|
+
style,
|
|
1413
|
+
itemClassName,
|
|
1414
|
+
separatorClassName,
|
|
1415
|
+
children
|
|
1416
|
+
});
|
|
1417
|
+
propsRef.current = {
|
|
1418
|
+
items,
|
|
1419
|
+
prepend,
|
|
1420
|
+
append,
|
|
1421
|
+
appearance,
|
|
1422
|
+
className,
|
|
1423
|
+
style,
|
|
1424
|
+
itemClassName,
|
|
1425
|
+
separatorClassName,
|
|
1426
|
+
children
|
|
1427
|
+
};
|
|
1428
|
+
if (!eventBus || !controlsRef) {
|
|
1429
|
+
throw new Error("<PanoContextMenu> must be rendered inside <PanoView>.");
|
|
1430
|
+
}
|
|
1431
|
+
if (!overlayApi) {
|
|
1432
|
+
throw new Error(
|
|
1433
|
+
"<PanoContextMenu> requires the PanoView context-menu overlay host."
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
const close = useCallback2(() => {
|
|
1437
|
+
overlayApi.setSession(null);
|
|
1438
|
+
}, [overlayApi]);
|
|
1439
|
+
const openMenu = useCallback2(
|
|
1440
|
+
(event) => {
|
|
1441
|
+
event.nativeEvent.preventDefault();
|
|
1442
|
+
const view = controlsRef.current?.getView() ?? {
|
|
1443
|
+
yaw: 0,
|
|
1444
|
+
pitch: 0,
|
|
1445
|
+
fov: 75
|
|
1446
|
+
};
|
|
1447
|
+
const selectContext = {
|
|
1448
|
+
position: event.position,
|
|
1449
|
+
view,
|
|
1450
|
+
nativeEvent: event.nativeEvent,
|
|
1451
|
+
close
|
|
1452
|
+
};
|
|
1453
|
+
const current = propsRef.current;
|
|
1454
|
+
sessionKeyRef.current += 1;
|
|
1455
|
+
const key = `${instanceId}-${sessionKeyRef.current}`;
|
|
1456
|
+
const hasCustomChildren = typeof current.children === "function";
|
|
1457
|
+
const presets = actions ? createPanoContextMenuPresets(actions) : null;
|
|
1458
|
+
const resolvedItems = presets ? composePanoContextMenuItems(current, presets) : current.items?.filter(isActionEntry) ?? [];
|
|
1459
|
+
if (!hasCustomChildren && resolvedItems.length === 0) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
overlayApi.setSession({
|
|
1463
|
+
key,
|
|
1464
|
+
event,
|
|
1465
|
+
view,
|
|
1466
|
+
render: () => {
|
|
1467
|
+
if (hasCustomChildren && current.children) {
|
|
1468
|
+
return /* @__PURE__ */ jsx2("div", { "data-pano-context-menu": true, style: { pointerEvents: "auto" }, children: current.children({ event, view, close }) });
|
|
1469
|
+
}
|
|
1470
|
+
return /* @__PURE__ */ jsx2(
|
|
1471
|
+
DefaultContextMenuPanel,
|
|
1472
|
+
{
|
|
1473
|
+
appearance: current.appearance,
|
|
1474
|
+
className: current.className,
|
|
1475
|
+
itemClassName: current.itemClassName,
|
|
1476
|
+
items: resolvedItems,
|
|
1477
|
+
onRequestClose: close,
|
|
1478
|
+
selectContext,
|
|
1479
|
+
separatorClassName: current.separatorClassName,
|
|
1480
|
+
style: current.style
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
},
|
|
1486
|
+
[actions, close, controlsRef, instanceId, overlayApi]
|
|
1487
|
+
);
|
|
1488
|
+
useEffect6(() => {
|
|
1489
|
+
const unsubscribeContextMenu = eventBus.subscribe(
|
|
1490
|
+
"contextmenu",
|
|
1491
|
+
openMenu
|
|
1492
|
+
);
|
|
1493
|
+
const unsubscribeInteraction = eventBus.subscribe(
|
|
1494
|
+
"viewinteractionstart",
|
|
1495
|
+
() => {
|
|
1496
|
+
close();
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
return () => {
|
|
1500
|
+
unsubscribeContextMenu();
|
|
1501
|
+
unsubscribeInteraction();
|
|
1502
|
+
close();
|
|
1503
|
+
};
|
|
1504
|
+
}, [close, eventBus, openMenu]);
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
function ResetViewIcon() {
|
|
1508
|
+
return /* @__PURE__ */ jsxs(
|
|
1509
|
+
"svg",
|
|
1510
|
+
{
|
|
1511
|
+
"aria-hidden": true,
|
|
1512
|
+
fill: "none",
|
|
1513
|
+
height: "100%",
|
|
1514
|
+
stroke: "currentColor",
|
|
1515
|
+
strokeLinecap: "round",
|
|
1516
|
+
strokeLinejoin: "round",
|
|
1517
|
+
strokeWidth: "1.75",
|
|
1518
|
+
viewBox: "0 0 16 16",
|
|
1519
|
+
width: "100%",
|
|
1520
|
+
children: [
|
|
1521
|
+
/* @__PURE__ */ jsx2("path", { d: "M3.5 6.5A4.5 4.5 0 1 1 4 10" }),
|
|
1522
|
+
/* @__PURE__ */ jsx2("path", { d: "M3.5 3.5v3h3" })
|
|
1523
|
+
]
|
|
1524
|
+
}
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
function FullscreenIcon() {
|
|
1528
|
+
return /* @__PURE__ */ jsx2(
|
|
1529
|
+
"svg",
|
|
1530
|
+
{
|
|
1531
|
+
"aria-hidden": true,
|
|
1532
|
+
fill: "none",
|
|
1533
|
+
height: "100%",
|
|
1534
|
+
stroke: "currentColor",
|
|
1535
|
+
strokeLinecap: "round",
|
|
1536
|
+
strokeLinejoin: "round",
|
|
1537
|
+
strokeWidth: "1.75",
|
|
1538
|
+
viewBox: "0 0 16 16",
|
|
1539
|
+
width: "100%",
|
|
1540
|
+
children: /* @__PURE__ */ jsx2("path", { d: "M3 6V3h3M10 3h3v3M13 10v3h-3M6 13H3v-3" })
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
function ExitFullscreenIcon() {
|
|
1545
|
+
return /* @__PURE__ */ jsx2(
|
|
1546
|
+
"svg",
|
|
1547
|
+
{
|
|
1548
|
+
"aria-hidden": true,
|
|
1549
|
+
fill: "none",
|
|
1550
|
+
height: "100%",
|
|
1551
|
+
stroke: "currentColor",
|
|
1552
|
+
strokeLinecap: "round",
|
|
1553
|
+
strokeLinejoin: "round",
|
|
1554
|
+
strokeWidth: "1.75",
|
|
1555
|
+
viewBox: "0 0 16 16",
|
|
1556
|
+
width: "100%",
|
|
1557
|
+
children: /* @__PURE__ */ jsx2("path", { d: "M6 3v3H3M10 3v3h3M3 10h3v3M10 10h3v3" })
|
|
1558
|
+
}
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
function createPanoContextMenuPresets(actions) {
|
|
1562
|
+
const isFullscreen = actions.isFullscreen === true;
|
|
1563
|
+
const resetView = {
|
|
1564
|
+
id: "pano-reset-view",
|
|
1565
|
+
label: "Reset view",
|
|
1566
|
+
icon: /* @__PURE__ */ jsx2(ResetViewIcon, {}),
|
|
1567
|
+
onSelect: () => {
|
|
1568
|
+
actions.reset();
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
const fullscreen = {
|
|
1572
|
+
id: "pano-fullscreen",
|
|
1573
|
+
label: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
1574
|
+
icon: isFullscreen ? /* @__PURE__ */ jsx2(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx2(FullscreenIcon, {}),
|
|
1575
|
+
onSelect: () => {
|
|
1576
|
+
actions.toggleFullscreen();
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
const separator = (id = "pano-separator") => ({
|
|
1580
|
+
type: "separator",
|
|
1581
|
+
id
|
|
1582
|
+
});
|
|
1583
|
+
return {
|
|
1584
|
+
resetView,
|
|
1585
|
+
fullscreen,
|
|
1586
|
+
separator,
|
|
1587
|
+
defaults: [resetView, separator("pano-default-separator"), fullscreen]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
function createDefaultPanoContextMenuItems(actions) {
|
|
1591
|
+
return createPanoContextMenuPresets(actions).defaults;
|
|
1592
|
+
}
|
|
1593
|
+
function isPresetId(value) {
|
|
1594
|
+
return value === "resetView" || value === "fullscreen" || value === "separator";
|
|
1595
|
+
}
|
|
1596
|
+
function isPresetRef(value) {
|
|
1597
|
+
return typeof value === "object" && value !== null && "preset" in value && isPresetId(value.preset);
|
|
1598
|
+
}
|
|
1599
|
+
function isActionEntry(value) {
|
|
1600
|
+
if (typeof value === "string") {
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
if (isPresetRef(value)) {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
function applyPresetOverrides(item, overrides) {
|
|
1609
|
+
if (item.type === "separator") {
|
|
1610
|
+
return {
|
|
1611
|
+
...item,
|
|
1612
|
+
id: overrides.id ?? item.id,
|
|
1613
|
+
className: overrides.className ?? item.className,
|
|
1614
|
+
style: overrides.style ?? item.style
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
...item,
|
|
1619
|
+
id: overrides.id ?? item.id,
|
|
1620
|
+
label: overrides.label ?? item.label,
|
|
1621
|
+
className: overrides.className ?? item.className,
|
|
1622
|
+
style: overrides.style ?? item.style,
|
|
1623
|
+
disabled: overrides.disabled ?? item.disabled,
|
|
1624
|
+
hidden: overrides.hidden ?? item.hidden
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
function resolvePresetEntry(entry, presets) {
|
|
1628
|
+
const presetId = typeof entry === "string" ? entry : entry.preset;
|
|
1629
|
+
const overrides = typeof entry === "string" ? {} : entry;
|
|
1630
|
+
switch (presetId) {
|
|
1631
|
+
case "resetView":
|
|
1632
|
+
return applyPresetOverrides(presets.resetView, overrides);
|
|
1633
|
+
case "fullscreen":
|
|
1634
|
+
return applyPresetOverrides(presets.fullscreen, overrides);
|
|
1635
|
+
case "separator":
|
|
1636
|
+
return applyPresetOverrides(
|
|
1637
|
+
presets.separator(overrides.id ?? "pano-separator"),
|
|
1638
|
+
overrides
|
|
1639
|
+
);
|
|
1640
|
+
default: {
|
|
1641
|
+
const _exhaustive = presetId;
|
|
1642
|
+
return _exhaustive;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function resolvePanoContextMenuEntries(entries, presets) {
|
|
1647
|
+
const resolved = [];
|
|
1648
|
+
for (const entry of entries) {
|
|
1649
|
+
if (isActionEntry(entry)) {
|
|
1650
|
+
resolved.push(entry);
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
resolved.push(resolvePresetEntry(entry, presets));
|
|
1654
|
+
}
|
|
1655
|
+
return resolved;
|
|
1656
|
+
}
|
|
1657
|
+
function composePanoContextMenuItems(props, presets) {
|
|
1658
|
+
if (props.items) {
|
|
1659
|
+
return resolvePanoContextMenuEntries(props.items, presets);
|
|
1660
|
+
}
|
|
1661
|
+
return [
|
|
1662
|
+
...resolvePanoContextMenuEntries(props.prepend ?? [], presets),
|
|
1663
|
+
...presets.defaults,
|
|
1664
|
+
...resolvePanoContextMenuEntries(props.append ?? [], presets)
|
|
1665
|
+
];
|
|
1666
|
+
}
|
|
1667
|
+
var PanoContextMenuActionsContext = createContext4(null);
|
|
1668
|
+
|
|
1669
|
+
// src/pano-events.tsx
|
|
1670
|
+
import { useFrame as useFrame4, useThree as useThree4 } from "@react-three/fiber";
|
|
1671
|
+
import { useContext as useContext6, useEffect as useEffect7, useRef as useRef7 } from "react";
|
|
1672
|
+
var DEFAULT_IDLE_TIME_MS = 2e3;
|
|
1673
|
+
function resolveIdleTime(value) {
|
|
1674
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIME_MS;
|
|
1675
|
+
}
|
|
1676
|
+
function usePanoEvents({
|
|
1677
|
+
idleTime = DEFAULT_IDLE_TIME_MS,
|
|
1678
|
+
onViewChange,
|
|
1679
|
+
onViewSettled,
|
|
1680
|
+
onViewInteractionStart,
|
|
1681
|
+
onViewInteractionEnd,
|
|
1682
|
+
onClick,
|
|
1683
|
+
onDoubleClick,
|
|
1684
|
+
onPointerDown,
|
|
1685
|
+
onPointerUp,
|
|
1686
|
+
onPointerMove,
|
|
1687
|
+
onContextMenu,
|
|
1688
|
+
onWheel,
|
|
1689
|
+
onIdle,
|
|
1690
|
+
onIdleEnd,
|
|
1691
|
+
onEnterFullscreen,
|
|
1692
|
+
onExitFullscreen,
|
|
1693
|
+
onResize,
|
|
1694
|
+
onAutoRotateStart,
|
|
1695
|
+
onAutoRotateStop,
|
|
1696
|
+
onAutoRotateOneRound
|
|
1697
|
+
}) {
|
|
1698
|
+
const eventBus = useContext6(PanoEventBusContext);
|
|
1699
|
+
const controlsRef = useContext6(PanoramaViewContext);
|
|
1700
|
+
const { gl } = useThree4();
|
|
1701
|
+
if (!eventBus || !controlsRef) {
|
|
1702
|
+
throw new Error("usePanoEvents must be used inside <PanoView>.");
|
|
1703
|
+
}
|
|
1704
|
+
const callbacksRef = useRef7({
|
|
1705
|
+
onViewChange,
|
|
1706
|
+
onViewSettled,
|
|
1707
|
+
onViewInteractionStart,
|
|
1708
|
+
onViewInteractionEnd,
|
|
1709
|
+
onClick,
|
|
1710
|
+
onDoubleClick,
|
|
1711
|
+
onPointerDown,
|
|
1712
|
+
onPointerUp,
|
|
1713
|
+
onPointerMove,
|
|
1714
|
+
onContextMenu,
|
|
1715
|
+
onWheel,
|
|
1716
|
+
onIdle,
|
|
1717
|
+
onIdleEnd,
|
|
1718
|
+
onEnterFullscreen,
|
|
1719
|
+
onExitFullscreen,
|
|
1720
|
+
onResize,
|
|
1721
|
+
onAutoRotateStart,
|
|
1722
|
+
onAutoRotateStop,
|
|
1723
|
+
onAutoRotateOneRound
|
|
1724
|
+
});
|
|
1725
|
+
callbacksRef.current = {
|
|
1726
|
+
onViewChange,
|
|
1727
|
+
onViewSettled,
|
|
1728
|
+
onViewInteractionStart,
|
|
1729
|
+
onViewInteractionEnd,
|
|
1730
|
+
onClick,
|
|
1731
|
+
onDoubleClick,
|
|
1732
|
+
onPointerDown,
|
|
1733
|
+
onPointerUp,
|
|
1734
|
+
onPointerMove,
|
|
1735
|
+
onContextMenu,
|
|
1736
|
+
onWheel,
|
|
1737
|
+
onIdle,
|
|
1738
|
+
onIdleEnd,
|
|
1739
|
+
onEnterFullscreen,
|
|
1740
|
+
onExitFullscreen,
|
|
1741
|
+
onResize,
|
|
1742
|
+
onAutoRotateStart,
|
|
1743
|
+
onAutoRotateStop,
|
|
1744
|
+
onAutoRotateOneRound
|
|
1745
|
+
};
|
|
1746
|
+
const idleElapsedRef = useRef7(0);
|
|
1747
|
+
const isIdleRef = useRef7(false);
|
|
1748
|
+
const idleTimeRef = useRef7(resolveIdleTime(idleTime));
|
|
1749
|
+
idleTimeRef.current = resolveIdleTime(idleTime);
|
|
1750
|
+
useEffect7(() => {
|
|
1751
|
+
idleElapsedRef.current = 0;
|
|
1752
|
+
isIdleRef.current = false;
|
|
1753
|
+
}, [idleTime]);
|
|
1754
|
+
useEffect7(() => {
|
|
1755
|
+
const unsubscribers = [
|
|
1756
|
+
eventBus.subscribe("viewchange", (view) => {
|
|
1757
|
+
callbacksRef.current.onViewChange?.(view);
|
|
1758
|
+
}),
|
|
1759
|
+
eventBus.subscribe("viewsettled", (view) => {
|
|
1760
|
+
callbacksRef.current.onViewSettled?.(view);
|
|
1761
|
+
}),
|
|
1762
|
+
eventBus.subscribe("viewinteractionstart", (event) => {
|
|
1763
|
+
callbacksRef.current.onViewInteractionStart?.(event);
|
|
1764
|
+
}),
|
|
1765
|
+
eventBus.subscribe("viewinteractionend", (event) => {
|
|
1766
|
+
callbacksRef.current.onViewInteractionEnd?.(event);
|
|
1767
|
+
}),
|
|
1768
|
+
eventBus.subscribe("click", (event) => {
|
|
1769
|
+
callbacksRef.current.onClick?.(event);
|
|
1770
|
+
}),
|
|
1771
|
+
eventBus.subscribe("doubleclick", (event) => {
|
|
1772
|
+
callbacksRef.current.onDoubleClick?.(event);
|
|
1773
|
+
}),
|
|
1774
|
+
eventBus.subscribe("pointerdown", (event) => {
|
|
1775
|
+
callbacksRef.current.onPointerDown?.(event);
|
|
1776
|
+
}),
|
|
1777
|
+
eventBus.subscribe("pointerup", (event) => {
|
|
1778
|
+
callbacksRef.current.onPointerUp?.(event);
|
|
1779
|
+
}),
|
|
1780
|
+
eventBus.subscribe("pointermove", (event) => {
|
|
1781
|
+
callbacksRef.current.onPointerMove?.(event);
|
|
1782
|
+
}),
|
|
1783
|
+
eventBus.subscribe("contextmenu", (event) => {
|
|
1784
|
+
callbacksRef.current.onContextMenu?.(event);
|
|
1785
|
+
}),
|
|
1786
|
+
eventBus.subscribe("autorotatestart", () => {
|
|
1787
|
+
callbacksRef.current.onAutoRotateStart?.();
|
|
1788
|
+
}),
|
|
1789
|
+
eventBus.subscribe("autorotatestop", () => {
|
|
1790
|
+
callbacksRef.current.onAutoRotateStop?.();
|
|
1791
|
+
}),
|
|
1792
|
+
eventBus.subscribe("autorotateoneround", () => {
|
|
1793
|
+
callbacksRef.current.onAutoRotateOneRound?.();
|
|
1794
|
+
})
|
|
1795
|
+
];
|
|
1796
|
+
return () => {
|
|
1797
|
+
for (const unsubscribe of unsubscribers) {
|
|
1798
|
+
unsubscribe();
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
}, [eventBus]);
|
|
1802
|
+
useEffect7(() => {
|
|
1803
|
+
const element = gl.domElement;
|
|
1804
|
+
const handleWheel = (nativeEvent) => {
|
|
1805
|
+
callbacksRef.current.onWheel?.({
|
|
1806
|
+
delta: nativeEvent.deltaY,
|
|
1807
|
+
nativeEvent
|
|
1808
|
+
});
|
|
1809
|
+
};
|
|
1810
|
+
element.addEventListener("wheel", handleWheel, { passive: true });
|
|
1811
|
+
return () => {
|
|
1812
|
+
element.removeEventListener("wheel", handleWheel);
|
|
1813
|
+
};
|
|
1814
|
+
}, [gl]);
|
|
1815
|
+
useEffect7(() => {
|
|
1816
|
+
if (typeof document === "undefined") {
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
const wasFullscreenRef = { current: false };
|
|
1820
|
+
const isViewerFullscreen = () => {
|
|
1821
|
+
const fullscreenElement = document.fullscreenElement;
|
|
1822
|
+
if (!fullscreenElement) {
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
const canvas = gl.domElement;
|
|
1826
|
+
return fullscreenElement === canvas || fullscreenElement.contains(canvas) || canvas.contains(fullscreenElement);
|
|
1827
|
+
};
|
|
1828
|
+
const handleFullscreenChange = () => {
|
|
1829
|
+
const isOurs = isViewerFullscreen();
|
|
1830
|
+
if (isOurs && !wasFullscreenRef.current) {
|
|
1831
|
+
wasFullscreenRef.current = true;
|
|
1832
|
+
callbacksRef.current.onEnterFullscreen?.();
|
|
1833
|
+
} else if (!isOurs && wasFullscreenRef.current) {
|
|
1834
|
+
wasFullscreenRef.current = false;
|
|
1835
|
+
callbacksRef.current.onExitFullscreen?.();
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
wasFullscreenRef.current = isViewerFullscreen();
|
|
1839
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
1840
|
+
return () => {
|
|
1841
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
1842
|
+
};
|
|
1843
|
+
}, [gl]);
|
|
1844
|
+
useEffect7(() => {
|
|
1845
|
+
const element = gl.domElement;
|
|
1846
|
+
if (typeof ResizeObserver === "undefined") {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
const observer = new ResizeObserver((entries) => {
|
|
1850
|
+
const entry = entries[0];
|
|
1851
|
+
if (!entry) {
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
const { width, height } = entry.contentRect;
|
|
1855
|
+
callbacksRef.current.onResize?.({ width, height });
|
|
1856
|
+
});
|
|
1857
|
+
observer.observe(element);
|
|
1858
|
+
return () => {
|
|
1859
|
+
observer.disconnect();
|
|
1860
|
+
};
|
|
1861
|
+
}, [gl]);
|
|
1862
|
+
useFrame4((_, deltaSeconds) => {
|
|
1863
|
+
const interacting = controlsRef.current?.isUserInteracting() ?? false;
|
|
1864
|
+
if (interacting) {
|
|
1865
|
+
idleElapsedRef.current = 0;
|
|
1866
|
+
if (isIdleRef.current) {
|
|
1867
|
+
isIdleRef.current = false;
|
|
1868
|
+
callbacksRef.current.onIdleEnd?.();
|
|
1869
|
+
}
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
idleElapsedRef.current += deltaSeconds * 1e3;
|
|
1873
|
+
if (!isIdleRef.current && idleElapsedRef.current >= idleTimeRef.current) {
|
|
1874
|
+
isIdleRef.current = true;
|
|
1875
|
+
callbacksRef.current.onIdle?.();
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
function PanoEvents(props) {
|
|
1880
|
+
usePanoEvents(props);
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// src/panorama-event-surface.tsx
|
|
1885
|
+
import { useThree as useThree5 } from "@react-three/fiber";
|
|
1886
|
+
import { useContext as useContext7, useEffect as useEffect8 } from "react";
|
|
1887
|
+
import { BackSide, Raycaster, Vector2, Vector3 as Vector32 } from "three";
|
|
1888
|
+
|
|
1889
|
+
// src/hotspot/coordinates.ts
|
|
1890
|
+
import { MathUtils as MathUtils2, Vector3 } from "three";
|
|
1891
|
+
|
|
1892
|
+
// src/hotspot/types.ts
|
|
1893
|
+
var MAX_HOTSPOT_PITCH = 90;
|
|
1894
|
+
|
|
1895
|
+
// src/hotspot/coordinates.ts
|
|
1896
|
+
var POLE_EPSILON_DEGREES = 1e-6;
|
|
1897
|
+
function normalizePanoYaw(yaw) {
|
|
1898
|
+
if (!Number.isFinite(yaw)) {
|
|
1899
|
+
return 0;
|
|
1900
|
+
}
|
|
1901
|
+
return MathUtils2.euclideanModulo(yaw + 180, 360) - 180;
|
|
1902
|
+
}
|
|
1903
|
+
function clampPanoPitch(pitch) {
|
|
1904
|
+
if (!Number.isFinite(pitch)) {
|
|
1905
|
+
return 0;
|
|
1906
|
+
}
|
|
1907
|
+
const clamped = MathUtils2.clamp(
|
|
1908
|
+
pitch,
|
|
1909
|
+
-MAX_HOTSPOT_PITCH,
|
|
1910
|
+
MAX_HOTSPOT_PITCH
|
|
1911
|
+
);
|
|
1912
|
+
return Math.abs(clamped) >= MAX_HOTSPOT_PITCH - POLE_EPSILON_DEGREES ? Math.sign(clamped) * MAX_HOTSPOT_PITCH : clamped;
|
|
1913
|
+
}
|
|
1914
|
+
function normalizePanoPosition(position) {
|
|
1915
|
+
const pitch = clampPanoPitch(position.pitch);
|
|
1916
|
+
return {
|
|
1917
|
+
yaw: Math.abs(pitch) === MAX_HOTSPOT_PITCH ? 0 : normalizePanoYaw(position.yaw),
|
|
1918
|
+
pitch
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function panoPositionToVector3(position, radius = 1) {
|
|
1922
|
+
const normalized = normalizePanoPosition(position);
|
|
1923
|
+
const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 1;
|
|
1924
|
+
const yaw = MathUtils2.degToRad(normalized.yaw);
|
|
1925
|
+
const pitch = MathUtils2.degToRad(normalized.pitch);
|
|
1926
|
+
const cosPitch = Math.cos(pitch);
|
|
1927
|
+
return new Vector3(
|
|
1928
|
+
cosPitch * Math.sin(yaw) * safeRadius,
|
|
1929
|
+
Math.sin(pitch) * safeRadius,
|
|
1930
|
+
-cosPitch * Math.cos(yaw) * safeRadius
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
function vector3ToPanoPosition(vector) {
|
|
1934
|
+
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
1935
|
+
if (!Number.isFinite(length) || length === 0) {
|
|
1936
|
+
return { yaw: 0, pitch: 0 };
|
|
1937
|
+
}
|
|
1938
|
+
return normalizePanoPosition({
|
|
1939
|
+
yaw: MathUtils2.radToDeg(Math.atan2(vector.x, -vector.z)),
|
|
1940
|
+
pitch: MathUtils2.radToDeg(
|
|
1941
|
+
Math.asin(MathUtils2.clamp(vector.y / length, -1, 1))
|
|
1942
|
+
)
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/panorama-radius.ts
|
|
1947
|
+
var DEFAULT_PANORAMA_RADIUS = 1e3;
|
|
1948
|
+
var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
|
|
1949
|
+
var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
|
|
1950
|
+
|
|
1951
|
+
// src/panorama-event-surface.tsx
|
|
1952
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1953
|
+
var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
|
|
1954
|
+
function makePanoramaPointerEvent(event) {
|
|
1955
|
+
const nativeEvent = event.nativeEvent;
|
|
1956
|
+
return {
|
|
1957
|
+
position: vector3ToPanoPosition(event.ray.direction),
|
|
1958
|
+
nativeEvent,
|
|
1959
|
+
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
1960
|
+
button: nativeEvent.button,
|
|
1961
|
+
buttons: nativeEvent.buttons,
|
|
1962
|
+
clientX: nativeEvent.clientX,
|
|
1963
|
+
clientY: nativeEvent.clientY
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function PanoramaEventSurface() {
|
|
1967
|
+
const eventBus = useContext7(PanoEventBusContext);
|
|
1968
|
+
const { camera, gl } = useThree5();
|
|
1969
|
+
useEffect8(() => {
|
|
1970
|
+
if (!eventBus) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const element = gl.domElement;
|
|
1974
|
+
const ndc = new Vector2();
|
|
1975
|
+
const raycaster = new Raycaster();
|
|
1976
|
+
const direction = new Vector32();
|
|
1977
|
+
const handleContextMenu = (nativeEvent) => {
|
|
1978
|
+
const rect = element.getBoundingClientRect();
|
|
1979
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
ndc.set(
|
|
1983
|
+
(nativeEvent.clientX - rect.left) / rect.width * 2 - 1,
|
|
1984
|
+
-((nativeEvent.clientY - rect.top) / rect.height) * 2 + 1
|
|
1985
|
+
);
|
|
1986
|
+
raycaster.setFromCamera(ndc, camera);
|
|
1987
|
+
direction.copy(raycaster.ray.direction);
|
|
1988
|
+
eventBus.emit("contextmenu", {
|
|
1989
|
+
position: vector3ToPanoPosition(direction),
|
|
1990
|
+
nativeEvent,
|
|
1991
|
+
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
1992
|
+
button: nativeEvent.button,
|
|
1993
|
+
buttons: nativeEvent.buttons,
|
|
1994
|
+
clientX: nativeEvent.clientX,
|
|
1995
|
+
clientY: nativeEvent.clientY
|
|
1996
|
+
});
|
|
1997
|
+
};
|
|
1998
|
+
element.addEventListener("contextmenu", handleContextMenu);
|
|
1999
|
+
return () => {
|
|
2000
|
+
element.removeEventListener("contextmenu", handleContextMenu);
|
|
2001
|
+
};
|
|
2002
|
+
}, [camera, eventBus, gl]);
|
|
2003
|
+
if (!eventBus) {
|
|
2004
|
+
return null;
|
|
2005
|
+
}
|
|
2006
|
+
const emit = (type, event) => {
|
|
2007
|
+
event.stopPropagation();
|
|
2008
|
+
eventBus.emit(type, makePanoramaPointerEvent(event));
|
|
2009
|
+
};
|
|
2010
|
+
return /* @__PURE__ */ jsxs2(
|
|
2011
|
+
"mesh",
|
|
2012
|
+
{
|
|
2013
|
+
renderOrder: -200,
|
|
2014
|
+
onClick: (event) => emit("click", event),
|
|
2015
|
+
onDoubleClick: (event) => emit("doubleclick", event),
|
|
2016
|
+
onPointerDown: (event) => emit("pointerdown", event),
|
|
2017
|
+
onPointerUp: (event) => emit("pointerup", event),
|
|
2018
|
+
onPointerMove: (event) => emit("pointermove", event),
|
|
2019
|
+
children: [
|
|
2020
|
+
/* @__PURE__ */ jsx3("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
|
|
2021
|
+
/* @__PURE__ */ jsx3(
|
|
2022
|
+
"meshBasicMaterial",
|
|
2023
|
+
{
|
|
2024
|
+
colorWrite: false,
|
|
2025
|
+
depthTest: false,
|
|
2026
|
+
depthWrite: false,
|
|
665
2027
|
opacity: 0,
|
|
666
2028
|
side: BackSide,
|
|
667
2029
|
transparent: true
|
|
@@ -669,21 +2031,194 @@ function PanoramaEventSurface({
|
|
|
669
2031
|
)
|
|
670
2032
|
]
|
|
671
2033
|
}
|
|
672
|
-
);
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// src/touch-controls.tsx
|
|
2038
|
+
import { useThree as useThree6 } from "@react-three/fiber";
|
|
2039
|
+
import { useContext as useContext8, useEffect as useEffect9, useRef as useRef8 } from "react";
|
|
2040
|
+
var DEFAULT_ROTATE_SPEED3 = 0.35;
|
|
2041
|
+
function resolveSpeed3(value, fallback) {
|
|
2042
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
2043
|
+
}
|
|
2044
|
+
function pointerDistance(a, b) {
|
|
2045
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
2046
|
+
}
|
|
2047
|
+
function TouchControls({
|
|
2048
|
+
enabled = true,
|
|
2049
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED3,
|
|
2050
|
+
invert = false,
|
|
2051
|
+
pinchZoom = true
|
|
2052
|
+
}) {
|
|
2053
|
+
const controlsRef = useContext8(PanoramaViewContext);
|
|
2054
|
+
const { gl } = useThree6();
|
|
2055
|
+
const pointersRef = useRef8(/* @__PURE__ */ new Map());
|
|
2056
|
+
const pinchDistanceRef = useRef8(null);
|
|
2057
|
+
const optionsRef = useRef8({ rotateSpeed, invert, pinchZoom });
|
|
2058
|
+
if (!controlsRef) {
|
|
2059
|
+
throw new Error("<TouchControls> must be rendered inside <PanoView>.");
|
|
2060
|
+
}
|
|
2061
|
+
optionsRef.current = { rotateSpeed, invert, pinchZoom };
|
|
2062
|
+
useEffect9(() => {
|
|
2063
|
+
const element = gl.domElement;
|
|
2064
|
+
const clearPointers = () => {
|
|
2065
|
+
for (const pointerId of pointersRef.current.keys()) {
|
|
2066
|
+
if (element.hasPointerCapture?.(pointerId)) {
|
|
2067
|
+
element.releasePointerCapture?.(pointerId);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
pointersRef.current.clear();
|
|
2071
|
+
pinchDistanceRef.current = null;
|
|
2072
|
+
controlsRef.current?.setPointerActive("touch", false);
|
|
2073
|
+
};
|
|
2074
|
+
if (!enabled) {
|
|
2075
|
+
clearPointers();
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const syncActive = () => {
|
|
2079
|
+
controlsRef.current?.setPointerActive(
|
|
2080
|
+
"touch",
|
|
2081
|
+
pointersRef.current.size > 0
|
|
2082
|
+
);
|
|
2083
|
+
};
|
|
2084
|
+
const onPointerDown = (event) => {
|
|
2085
|
+
if (event.pointerType !== "touch") {
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
event.preventDefault();
|
|
2092
|
+
pointersRef.current.set(event.pointerId, {
|
|
2093
|
+
x: event.clientX,
|
|
2094
|
+
y: event.clientY,
|
|
2095
|
+
at: performance.now()
|
|
2096
|
+
});
|
|
2097
|
+
element.setPointerCapture?.(event.pointerId);
|
|
2098
|
+
syncActive();
|
|
2099
|
+
if (pointersRef.current.size === 2) {
|
|
2100
|
+
const [first, second] = Array.from(pointersRef.current.values());
|
|
2101
|
+
pinchDistanceRef.current = pointerDistance(first, second);
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
const onPointerMove = (event) => {
|
|
2105
|
+
if (event.pointerType !== "touch") {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const previous = pointersRef.current.get(event.pointerId);
|
|
2112
|
+
if (!previous) {
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
event.preventDefault();
|
|
2116
|
+
const next = {
|
|
2117
|
+
x: event.clientX,
|
|
2118
|
+
y: event.clientY,
|
|
2119
|
+
at: performance.now()
|
|
2120
|
+
};
|
|
2121
|
+
pointersRef.current.set(event.pointerId, next);
|
|
2122
|
+
const pointers = Array.from(pointersRef.current.values());
|
|
2123
|
+
if (pointers.length >= 2) {
|
|
2124
|
+
if (!optionsRef.current.pinchZoom) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
const distance = pointerDistance(pointers[0], pointers[1]);
|
|
2128
|
+
const previousDistance = pinchDistanceRef.current;
|
|
2129
|
+
pinchDistanceRef.current = distance;
|
|
2130
|
+
if (previousDistance && distance > 0) {
|
|
2131
|
+
const view = controlsRef.current?.getTargetView();
|
|
2132
|
+
if (view) {
|
|
2133
|
+
const nextFov = view.fov * (previousDistance / distance);
|
|
2134
|
+
controlsRef.current?.applyViewDelta(
|
|
2135
|
+
{ fov: nextFov - view.fov },
|
|
2136
|
+
{ recordVelocity: true }
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
const rect = element.getBoundingClientRect();
|
|
2143
|
+
const speed = resolveSpeed3(
|
|
2144
|
+
optionsRef.current.rotateSpeed,
|
|
2145
|
+
DEFAULT_ROTATE_SPEED3
|
|
2146
|
+
);
|
|
2147
|
+
const sign = optionsRef.current.invert ? -1 : 1;
|
|
2148
|
+
const deltaYaw = sign * -(next.x - previous.x) * 360 * speed / Math.max(rect.width, 1);
|
|
2149
|
+
const deltaPitch = sign * (next.y - previous.y) * 180 * speed / Math.max(rect.height, 1);
|
|
2150
|
+
controlsRef.current?.applyViewDelta(
|
|
2151
|
+
{ yaw: deltaYaw, pitch: deltaPitch },
|
|
2152
|
+
{ recordVelocity: true }
|
|
2153
|
+
);
|
|
2154
|
+
};
|
|
2155
|
+
const releasePointer = (event) => {
|
|
2156
|
+
if (!pointersRef.current.has(event.pointerId)) {
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
pointersRef.current.delete(event.pointerId);
|
|
2160
|
+
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
2161
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
2162
|
+
}
|
|
2163
|
+
if (pointersRef.current.size < 2) {
|
|
2164
|
+
pinchDistanceRef.current = null;
|
|
2165
|
+
}
|
|
2166
|
+
syncActive();
|
|
2167
|
+
};
|
|
2168
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
2169
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
2170
|
+
element.addEventListener("pointerup", releasePointer);
|
|
2171
|
+
element.addEventListener("pointercancel", releasePointer);
|
|
2172
|
+
return () => {
|
|
2173
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
2174
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
2175
|
+
element.removeEventListener("pointerup", releasePointer);
|
|
2176
|
+
element.removeEventListener("pointercancel", releasePointer);
|
|
2177
|
+
clearPointers();
|
|
2178
|
+
};
|
|
2179
|
+
}, [controlsRef, enabled, gl]);
|
|
2180
|
+
return null;
|
|
673
2181
|
}
|
|
674
2182
|
|
|
675
2183
|
// src/pano-view.tsx
|
|
676
|
-
import { jsx as
|
|
2184
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
677
2185
|
var DEFAULT_VIEW = {
|
|
678
2186
|
yaw: 0,
|
|
679
2187
|
pitch: 0,
|
|
680
2188
|
fov: 75
|
|
681
2189
|
};
|
|
2190
|
+
var DEFAULT_POINTER_ROTATE_SPEED = 0.35;
|
|
2191
|
+
var DEFAULT_MOUSE_ZOOM_SPEED = 0.08;
|
|
682
2192
|
var DEFAULT_CANVAS_STYLE = {
|
|
683
2193
|
display: "block",
|
|
684
2194
|
height: "100%",
|
|
685
2195
|
width: "100%"
|
|
686
2196
|
};
|
|
2197
|
+
function resolveChannel(value, defaults) {
|
|
2198
|
+
if (value === false) {
|
|
2199
|
+
return { mount: false };
|
|
2200
|
+
}
|
|
2201
|
+
if (value === true || value === void 0) {
|
|
2202
|
+
return { mount: true, props: { ...defaults } };
|
|
2203
|
+
}
|
|
2204
|
+
return {
|
|
2205
|
+
mount: true,
|
|
2206
|
+
props: {
|
|
2207
|
+
...defaults,
|
|
2208
|
+
...value,
|
|
2209
|
+
enabled: value.enabled !== false
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
function resolveContextMenuChannel(value) {
|
|
2214
|
+
if (value === false) {
|
|
2215
|
+
return { mount: false };
|
|
2216
|
+
}
|
|
2217
|
+
if (value === true || value === void 0) {
|
|
2218
|
+
return { mount: true, props: {} };
|
|
2219
|
+
}
|
|
2220
|
+
return { mount: true, props: { ...value } };
|
|
2221
|
+
}
|
|
687
2222
|
var PanoView = forwardRef2(
|
|
688
2223
|
function PanoView2({
|
|
689
2224
|
children,
|
|
@@ -691,6 +2226,7 @@ var PanoView = forwardRef2(
|
|
|
691
2226
|
minFov = 30,
|
|
692
2227
|
maxFov = 100,
|
|
693
2228
|
controls = true,
|
|
2229
|
+
contextMenu = true,
|
|
694
2230
|
onViewChange,
|
|
695
2231
|
onPanoramaClick,
|
|
696
2232
|
onPanoramaDoubleClick,
|
|
@@ -700,16 +2236,18 @@ var PanoView = forwardRef2(
|
|
|
700
2236
|
"aria-label": ariaLabel = "Panorama viewer",
|
|
701
2237
|
...divProps
|
|
702
2238
|
}, ref) {
|
|
703
|
-
const rootRef =
|
|
704
|
-
const controlsRef =
|
|
2239
|
+
const rootRef = useRef9(null);
|
|
2240
|
+
const controlsRef = useRef9(null);
|
|
2241
|
+
const eventBus = useMemo4(() => createPanoEventBus(), []);
|
|
705
2242
|
const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
|
|
706
|
-
const
|
|
2243
|
+
const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
|
|
2244
|
+
const fallbackViewRef = useRef9(DEFAULT_VIEW);
|
|
707
2245
|
const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
|
|
708
2246
|
const normalizedMaxFov = Math.min(
|
|
709
2247
|
179,
|
|
710
2248
|
Math.max(maxFov, normalizedMinFov + 1)
|
|
711
2249
|
);
|
|
712
|
-
const normalizedInitialView =
|
|
2250
|
+
const normalizedInitialView = useMemo4(
|
|
713
2251
|
() => ({
|
|
714
2252
|
yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
|
|
715
2253
|
pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
|
|
@@ -728,15 +2266,98 @@ var PanoView = forwardRef2(
|
|
|
728
2266
|
);
|
|
729
2267
|
fallbackViewRef.current = normalizedInitialView;
|
|
730
2268
|
const controlsEnabled = controls !== false;
|
|
731
|
-
const controlOptions =
|
|
2269
|
+
const controlOptions = useMemo4(
|
|
732
2270
|
() => typeof controls === "object" ? controls : {},
|
|
733
2271
|
[controls]
|
|
734
2272
|
);
|
|
2273
|
+
const userControlsEnabled = controlsEnabled && controlOptions.enabled !== false;
|
|
2274
|
+
const allModeInvert = controlOptions.invert === true;
|
|
2275
|
+
const topRotateSpeed = controlOptions.rotateSpeed ?? DEFAULT_POINTER_ROTATE_SPEED;
|
|
2276
|
+
const topZoomSpeed = controlOptions.zoomSpeed ?? DEFAULT_MOUSE_ZOOM_SPEED;
|
|
2277
|
+
const mouseChannel = useMemo4(
|
|
2278
|
+
() => resolveChannel(controlOptions.mouse, {
|
|
2279
|
+
enabled: true,
|
|
2280
|
+
rotateSpeed: topRotateSpeed,
|
|
2281
|
+
zoomSpeed: topZoomSpeed,
|
|
2282
|
+
wheel: true,
|
|
2283
|
+
invert: allModeInvert,
|
|
2284
|
+
buttons: ["left"]
|
|
2285
|
+
}),
|
|
2286
|
+
[
|
|
2287
|
+
allModeInvert,
|
|
2288
|
+
controlOptions.mouse,
|
|
2289
|
+
topRotateSpeed,
|
|
2290
|
+
topZoomSpeed
|
|
2291
|
+
]
|
|
2292
|
+
);
|
|
2293
|
+
const touchChannel = useMemo4(
|
|
2294
|
+
() => resolveChannel(controlOptions.touch, {
|
|
2295
|
+
enabled: true,
|
|
2296
|
+
rotateSpeed: topRotateSpeed,
|
|
2297
|
+
invert: allModeInvert,
|
|
2298
|
+
pinchZoom: true
|
|
2299
|
+
}),
|
|
2300
|
+
[allModeInvert, controlOptions.touch, topRotateSpeed]
|
|
2301
|
+
);
|
|
2302
|
+
const keyboardChannel = useMemo4(
|
|
2303
|
+
() => resolveChannel(controlOptions.keyboard, {
|
|
2304
|
+
enabled: true
|
|
2305
|
+
}),
|
|
2306
|
+
[controlOptions.keyboard]
|
|
2307
|
+
);
|
|
2308
|
+
const toggleFullscreen = useCallback3(async () => {
|
|
2309
|
+
if (typeof document === "undefined") {
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
if (document.fullscreenElement) {
|
|
2313
|
+
await document.exitFullscreen?.();
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
await rootRef.current?.requestFullscreen?.();
|
|
2317
|
+
}, []);
|
|
2318
|
+
const [isViewerFullscreen, setIsViewerFullscreen] = useState3(false);
|
|
2319
|
+
useEffect10(() => {
|
|
2320
|
+
if (typeof document === "undefined") {
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const syncFullscreen = () => {
|
|
2324
|
+
const fullscreenElement = document.fullscreenElement;
|
|
2325
|
+
const root = rootRef.current;
|
|
2326
|
+
if (!fullscreenElement || !root) {
|
|
2327
|
+
setIsViewerFullscreen(false);
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
setIsViewerFullscreen(
|
|
2331
|
+
fullscreenElement === root || fullscreenElement.contains(root) || root.contains(fullscreenElement)
|
|
2332
|
+
);
|
|
2333
|
+
};
|
|
2334
|
+
syncFullscreen();
|
|
2335
|
+
document.addEventListener("fullscreenchange", syncFullscreen);
|
|
2336
|
+
return () => {
|
|
2337
|
+
document.removeEventListener("fullscreenchange", syncFullscreen);
|
|
2338
|
+
};
|
|
2339
|
+
}, []);
|
|
2340
|
+
const contextMenuActions = useMemo4(
|
|
2341
|
+
() => ({
|
|
2342
|
+
reset: () => {
|
|
2343
|
+
controlsRef.current?.reset();
|
|
2344
|
+
},
|
|
2345
|
+
toggleFullscreen: () => {
|
|
2346
|
+
void toggleFullscreen();
|
|
2347
|
+
},
|
|
2348
|
+
isFullscreen: isViewerFullscreen
|
|
2349
|
+
}),
|
|
2350
|
+
[isViewerFullscreen, toggleFullscreen]
|
|
2351
|
+
);
|
|
2352
|
+
const contextMenuChannel = useMemo4(
|
|
2353
|
+
() => resolveContextMenuChannel(contextMenu),
|
|
2354
|
+
[contextMenu]
|
|
2355
|
+
);
|
|
735
2356
|
const legacyAutoRotateOptions = controlOptions;
|
|
736
|
-
const [legacyAutoRotate, setLegacyAutoRotate] =
|
|
2357
|
+
const [legacyAutoRotate, setLegacyAutoRotate] = useState3(
|
|
737
2358
|
legacyAutoRotateOptions.autoRotate ?? false
|
|
738
2359
|
);
|
|
739
|
-
|
|
2360
|
+
useEffect10(() => {
|
|
740
2361
|
setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
|
|
741
2362
|
}, [legacyAutoRotateOptions.autoRotate]);
|
|
742
2363
|
useImperativeHandle2(
|
|
@@ -755,20 +2376,11 @@ var PanoView = forwardRef2(
|
|
|
755
2376
|
stopAutoRotate: () => {
|
|
756
2377
|
setLegacyAutoRotate(false);
|
|
757
2378
|
},
|
|
758
|
-
toggleFullscreen
|
|
759
|
-
if (typeof document === "undefined") {
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
if (document.fullscreenElement) {
|
|
763
|
-
await document.exitFullscreen?.();
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
await rootRef.current?.requestFullscreen?.();
|
|
767
|
-
}
|
|
2379
|
+
toggleFullscreen
|
|
768
2380
|
}),
|
|
769
|
-
[]
|
|
2381
|
+
[toggleFullscreen]
|
|
770
2382
|
);
|
|
771
|
-
const rootStyle =
|
|
2383
|
+
const rootStyle = useMemo4(
|
|
772
2384
|
() => ({
|
|
773
2385
|
overflow: "hidden",
|
|
774
2386
|
position: "relative",
|
|
@@ -776,7 +2388,7 @@ var PanoView = forwardRef2(
|
|
|
776
2388
|
}),
|
|
777
2389
|
[style]
|
|
778
2390
|
);
|
|
779
|
-
return /* @__PURE__ */
|
|
2391
|
+
return /* @__PURE__ */ jsxs3(
|
|
780
2392
|
"div",
|
|
781
2393
|
{
|
|
782
2394
|
...divProps,
|
|
@@ -784,7 +2396,7 @@ var PanoView = forwardRef2(
|
|
|
784
2396
|
"aria-label": ariaLabel,
|
|
785
2397
|
style: rootStyle,
|
|
786
2398
|
children: [
|
|
787
|
-
/* @__PURE__ */
|
|
2399
|
+
/* @__PURE__ */ jsx4(
|
|
788
2400
|
Canvas,
|
|
789
2401
|
{
|
|
790
2402
|
"aria-label": `${ariaLabel} canvas`,
|
|
@@ -804,41 +2416,71 @@ var PanoView = forwardRef2(
|
|
|
804
2416
|
},
|
|
805
2417
|
style: {
|
|
806
2418
|
...DEFAULT_CANVAS_STYLE,
|
|
807
|
-
touchAction:
|
|
2419
|
+
touchAction: userControlsEnabled ? "none" : "auto"
|
|
808
2420
|
},
|
|
809
|
-
tabIndex:
|
|
810
|
-
children: /* @__PURE__ */
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
2421
|
+
tabIndex: userControlsEnabled ? 0 : void 0,
|
|
2422
|
+
children: /* @__PURE__ */ jsx4(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ jsx4(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx4(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ jsx4(
|
|
2423
|
+
PanoContextMenuOverlayContext.Provider,
|
|
2424
|
+
{
|
|
2425
|
+
value: contextMenuOverlayApi,
|
|
2426
|
+
children: /* @__PURE__ */ jsxs3(
|
|
2427
|
+
PanoContextMenuActionsContext.Provider,
|
|
2428
|
+
{
|
|
2429
|
+
value: contextMenuActions,
|
|
2430
|
+
children: [
|
|
2431
|
+
/* @__PURE__ */ jsx4(
|
|
2432
|
+
PanoramaViewRuntime,
|
|
2433
|
+
{
|
|
2434
|
+
ref: controlsRef,
|
|
2435
|
+
eventBus,
|
|
2436
|
+
initialView: normalizedInitialView,
|
|
2437
|
+
maxFov: normalizedMaxFov,
|
|
2438
|
+
minFov: normalizedMinFov,
|
|
2439
|
+
options: controlOptions
|
|
2440
|
+
}
|
|
2441
|
+
),
|
|
2442
|
+
(onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ jsx4(
|
|
2443
|
+
PanoEvents,
|
|
2444
|
+
{
|
|
2445
|
+
onViewChange,
|
|
2446
|
+
onClick: onPanoramaClick,
|
|
2447
|
+
onDoubleClick: onPanoramaDoubleClick,
|
|
2448
|
+
onPointerMove: onPanoramaPointerMove
|
|
2449
|
+
}
|
|
2450
|
+
),
|
|
2451
|
+
/* @__PURE__ */ jsx4(
|
|
2452
|
+
AutoRotate,
|
|
2453
|
+
{
|
|
2454
|
+
enabled: legacyAutoRotate,
|
|
2455
|
+
speed: legacyAutoRotateOptions.autoRotateSpeed
|
|
2456
|
+
}
|
|
2457
|
+
),
|
|
2458
|
+
userControlsEnabled && mouseChannel.mount ? /* @__PURE__ */ jsx4(
|
|
2459
|
+
MouseControls,
|
|
2460
|
+
{
|
|
2461
|
+
...mouseChannel.props,
|
|
2462
|
+
fovSpeed: controlOptions.fovSpeed
|
|
2463
|
+
}
|
|
2464
|
+
) : null,
|
|
2465
|
+
userControlsEnabled && touchChannel.mount ? /* @__PURE__ */ jsx4(TouchControls, { ...touchChannel.props }) : null,
|
|
2466
|
+
userControlsEnabled && keyboardChannel.mount ? /* @__PURE__ */ jsx4(
|
|
2467
|
+
KeyboardControls,
|
|
2468
|
+
{
|
|
2469
|
+
...keyboardChannel.props,
|
|
2470
|
+
fovSpeed: keyboardChannel.props.fovSpeed ?? controlOptions.fovSpeed
|
|
2471
|
+
}
|
|
2472
|
+
) : null,
|
|
2473
|
+
/* @__PURE__ */ jsx4(PanoramaEventSurface, {}),
|
|
2474
|
+
contextMenuChannel.mount ? /* @__PURE__ */ jsx4(PanoContextMenu, { ...contextMenuChannel.props }) : null,
|
|
2475
|
+
children
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
)
|
|
2479
|
+
}
|
|
2480
|
+
) }) }) })
|
|
840
2481
|
}
|
|
841
2482
|
),
|
|
2483
|
+
contextMenuOverlay,
|
|
842
2484
|
hotspotAccessibilityControls
|
|
843
2485
|
]
|
|
844
2486
|
}
|
|
@@ -847,8 +2489,8 @@ var PanoView = forwardRef2(
|
|
|
847
2489
|
);
|
|
848
2490
|
|
|
849
2491
|
// src/sphere.tsx
|
|
850
|
-
import { useThree as
|
|
851
|
-
import { useEffect as
|
|
2492
|
+
import { useThree as useThree7 } from "@react-three/fiber";
|
|
2493
|
+
import { useEffect as useEffect11, useRef as useRef10, useState as useState4 } from "react";
|
|
852
2494
|
import {
|
|
853
2495
|
BackSide as BackSide2,
|
|
854
2496
|
LinearFilter,
|
|
@@ -858,7 +2500,7 @@ import {
|
|
|
858
2500
|
SRGBColorSpace,
|
|
859
2501
|
TextureLoader
|
|
860
2502
|
} from "three";
|
|
861
|
-
import { jsx as
|
|
2503
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
862
2504
|
function Sphere({
|
|
863
2505
|
src,
|
|
864
2506
|
yawOffset = 0,
|
|
@@ -866,13 +2508,13 @@ function Sphere({
|
|
|
866
2508
|
onError,
|
|
867
2509
|
visible = true
|
|
868
2510
|
}) {
|
|
869
|
-
const gl =
|
|
870
|
-
const [texture, setTexture] =
|
|
871
|
-
const onLoadRef =
|
|
872
|
-
const onErrorRef =
|
|
2511
|
+
const gl = useThree7((state) => state.gl);
|
|
2512
|
+
const [texture, setTexture] = useState4(null);
|
|
2513
|
+
const onLoadRef = useRef10(onLoad);
|
|
2514
|
+
const onErrorRef = useRef10(onError);
|
|
873
2515
|
onLoadRef.current = onLoad;
|
|
874
2516
|
onErrorRef.current = onError;
|
|
875
|
-
|
|
2517
|
+
useEffect11(() => {
|
|
876
2518
|
let active = true;
|
|
877
2519
|
const loader = new TextureLoader();
|
|
878
2520
|
setTexture(null);
|
|
@@ -906,7 +2548,7 @@ function Sphere({
|
|
|
906
2548
|
active = false;
|
|
907
2549
|
};
|
|
908
2550
|
}, [gl, src]);
|
|
909
|
-
|
|
2551
|
+
useEffect11(
|
|
910
2552
|
() => () => {
|
|
911
2553
|
texture?.dispose();
|
|
912
2554
|
},
|
|
@@ -915,14 +2557,14 @@ function Sphere({
|
|
|
915
2557
|
if (!texture) {
|
|
916
2558
|
return null;
|
|
917
2559
|
}
|
|
918
|
-
return /* @__PURE__ */
|
|
2560
|
+
return /* @__PURE__ */ jsxs4(
|
|
919
2561
|
"mesh",
|
|
920
2562
|
{
|
|
921
2563
|
rotation: [0, MathUtils3.degToRad(90 + yawOffset), 0],
|
|
922
2564
|
visible,
|
|
923
2565
|
children: [
|
|
924
|
-
/* @__PURE__ */
|
|
925
|
-
/* @__PURE__ */
|
|
2566
|
+
/* @__PURE__ */ jsx5("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
|
|
2567
|
+
/* @__PURE__ */ jsx5(
|
|
926
2568
|
"meshBasicMaterial",
|
|
927
2569
|
{
|
|
928
2570
|
map: texture,
|
|
@@ -936,15 +2578,15 @@ function Sphere({
|
|
|
936
2578
|
}
|
|
937
2579
|
|
|
938
2580
|
// src/panorama-scenes.tsx
|
|
939
|
-
import { useFrame as
|
|
2581
|
+
import { useFrame as useFrame6, useThree as useThree10 } from "@react-three/fiber";
|
|
940
2582
|
import {
|
|
941
2583
|
Fragment,
|
|
942
|
-
useCallback as
|
|
943
|
-
useContext as
|
|
944
|
-
useEffect as
|
|
945
|
-
useMemo as
|
|
946
|
-
useRef as
|
|
947
|
-
useState as
|
|
2584
|
+
useCallback as useCallback4,
|
|
2585
|
+
useContext as useContext10,
|
|
2586
|
+
useEffect as useEffect14,
|
|
2587
|
+
useMemo as useMemo7,
|
|
2588
|
+
useRef as useRef12,
|
|
2589
|
+
useState as useState6
|
|
948
2590
|
} from "react";
|
|
949
2591
|
import {
|
|
950
2592
|
DoubleSide,
|
|
@@ -952,17 +2594,17 @@ import {
|
|
|
952
2594
|
LinearFilter as LinearFilter4,
|
|
953
2595
|
PlaneGeometry as PlaneGeometry2,
|
|
954
2596
|
ShaderMaterial,
|
|
955
|
-
Vector3 as
|
|
2597
|
+
Vector3 as Vector35,
|
|
956
2598
|
WebGLRenderTarget
|
|
957
2599
|
} from "three";
|
|
958
2600
|
|
|
959
2601
|
// src/tile/texture-manager-context.tsx
|
|
960
|
-
import { useThree as
|
|
2602
|
+
import { useThree as useThree8 } from "@react-three/fiber";
|
|
961
2603
|
import {
|
|
962
|
-
createContext as
|
|
963
|
-
useContext as
|
|
964
|
-
useEffect as
|
|
965
|
-
useMemo as
|
|
2604
|
+
createContext as createContext5,
|
|
2605
|
+
useContext as useContext9,
|
|
2606
|
+
useEffect as useEffect12,
|
|
2607
|
+
useMemo as useMemo5
|
|
966
2608
|
} from "react";
|
|
967
2609
|
|
|
968
2610
|
// src/tile/texture-manager.ts
|
|
@@ -1192,8 +2834,8 @@ var TileTextureManager = class {
|
|
|
1192
2834
|
};
|
|
1193
2835
|
|
|
1194
2836
|
// src/tile/texture-manager-context.tsx
|
|
1195
|
-
import { jsx as
|
|
1196
|
-
var TileTextureManagerContext =
|
|
2837
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2838
|
+
var TileTextureManagerContext = createContext5(
|
|
1197
2839
|
null
|
|
1198
2840
|
);
|
|
1199
2841
|
function TileTextureManagerProvider({
|
|
@@ -1201,9 +2843,9 @@ function TileTextureManagerProvider({
|
|
|
1201
2843
|
maxTextureMemoryMb = 128,
|
|
1202
2844
|
maxConcurrentLoads = 4
|
|
1203
2845
|
}) {
|
|
1204
|
-
const gl =
|
|
2846
|
+
const gl = useThree8((state) => state.gl);
|
|
1205
2847
|
const anisotropy = gl.capabilities.getMaxAnisotropy();
|
|
1206
|
-
const manager =
|
|
2848
|
+
const manager = useMemo5(
|
|
1207
2849
|
() => new TileTextureManager({
|
|
1208
2850
|
anisotropy,
|
|
1209
2851
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1212,24 +2854,24 @@ function TileTextureManagerProvider({
|
|
|
1212
2854
|
}),
|
|
1213
2855
|
[anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
|
|
1214
2856
|
);
|
|
1215
|
-
|
|
2857
|
+
useEffect12(() => {
|
|
1216
2858
|
manager.resume();
|
|
1217
2859
|
return () => manager.dispose();
|
|
1218
2860
|
}, [manager]);
|
|
1219
|
-
return /* @__PURE__ */
|
|
2861
|
+
return /* @__PURE__ */ jsx6(TileTextureManagerContext.Provider, { value: manager, children });
|
|
1220
2862
|
}
|
|
1221
2863
|
function useSharedTileTextureManager() {
|
|
1222
|
-
return
|
|
2864
|
+
return useContext9(TileTextureManagerContext);
|
|
1223
2865
|
}
|
|
1224
2866
|
|
|
1225
2867
|
// src/tile/tile.tsx
|
|
1226
|
-
import { useFrame as
|
|
2868
|
+
import { useFrame as useFrame5, useThree as useThree9 } from "@react-three/fiber";
|
|
1227
2869
|
import {
|
|
1228
2870
|
memo,
|
|
1229
|
-
useEffect as
|
|
1230
|
-
useMemo as
|
|
1231
|
-
useRef as
|
|
1232
|
-
useState as
|
|
2871
|
+
useEffect as useEffect13,
|
|
2872
|
+
useMemo as useMemo6,
|
|
2873
|
+
useRef as useRef11,
|
|
2874
|
+
useState as useState5
|
|
1233
2875
|
} from "react";
|
|
1234
2876
|
import {
|
|
1235
2877
|
ClampToEdgeWrapping as ClampToEdgeWrapping2,
|
|
@@ -1240,7 +2882,7 @@ import {
|
|
|
1240
2882
|
PerspectiveCamera as PerspectiveCamera2,
|
|
1241
2883
|
SRGBColorSpace as SRGBColorSpace3,
|
|
1242
2884
|
TextureLoader as TextureLoader3,
|
|
1243
|
-
Vector3 as
|
|
2885
|
+
Vector3 as Vector34
|
|
1244
2886
|
} from "three";
|
|
1245
2887
|
|
|
1246
2888
|
// src/tile/face-layout.ts
|
|
@@ -1248,7 +2890,7 @@ import {
|
|
|
1248
2890
|
Box3,
|
|
1249
2891
|
Euler as Euler2,
|
|
1250
2892
|
Quaternion,
|
|
1251
|
-
Vector3 as
|
|
2893
|
+
Vector3 as Vector33
|
|
1252
2894
|
} from "three";
|
|
1253
2895
|
var CUBE_FACES = ["f", "r", "b", "l", "u", "d"];
|
|
1254
2896
|
var PREVIEW_FACE_ORDER = ["l", "f", "r", "b", "u", "d"];
|
|
@@ -1312,12 +2954,12 @@ function getTileBoundingBox(address, radius = CUBE_RADIUS) {
|
|
|
1312
2954
|
const halfWidth = local.width / 2;
|
|
1313
2955
|
const halfHeight = local.height / 2;
|
|
1314
2956
|
const quaternion = getFaceQuaternion(address.face);
|
|
1315
|
-
const position = new
|
|
2957
|
+
const position = new Vector33(...transform.position);
|
|
1316
2958
|
const corners = [
|
|
1317
|
-
new
|
|
1318
|
-
new
|
|
1319
|
-
new
|
|
1320
|
-
new
|
|
2959
|
+
new Vector33(local.x - halfWidth, local.y - halfHeight, 0),
|
|
2960
|
+
new Vector33(local.x + halfWidth, local.y - halfHeight, 0),
|
|
2961
|
+
new Vector33(local.x - halfWidth, local.y + halfHeight, 0),
|
|
2962
|
+
new Vector33(local.x + halfWidth, local.y + halfHeight, 0)
|
|
1321
2963
|
];
|
|
1322
2964
|
for (const corner of corners) {
|
|
1323
2965
|
corner.applyQuaternion(quaternion).add(position);
|
|
@@ -1426,7 +3068,7 @@ function resolveTemplateUrl(template, address) {
|
|
|
1426
3068
|
}
|
|
1427
3069
|
|
|
1428
3070
|
// src/tile/tile.tsx
|
|
1429
|
-
import { jsx as
|
|
3071
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1430
3072
|
var MAX_LOD_UPDATE_INTERVAL_MS = 100;
|
|
1431
3073
|
var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
|
|
1432
3074
|
var FAST_FOV_THRESHOLD_DEGREES = 0.5;
|
|
@@ -1435,10 +3077,10 @@ var VISIBILITY_FOV_PADDING_DEGREES = 8;
|
|
|
1435
3077
|
var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
|
|
1436
3078
|
var SHARED_TILE_GEOMETRY = new PlaneGeometry(1, 1);
|
|
1437
3079
|
function usePreviewTexture(url, anisotropy, onError) {
|
|
1438
|
-
const [texture, setTexture] =
|
|
1439
|
-
const onErrorRef =
|
|
3080
|
+
const [texture, setTexture] = useState5(null);
|
|
3081
|
+
const onErrorRef = useRef11(onError);
|
|
1440
3082
|
onErrorRef.current = onError;
|
|
1441
|
-
|
|
3083
|
+
useEffect13(() => {
|
|
1442
3084
|
if (!url) {
|
|
1443
3085
|
setTexture(null);
|
|
1444
3086
|
return;
|
|
@@ -1475,7 +3117,7 @@ function usePreviewTexture(url, anisotropy, onError) {
|
|
|
1475
3117
|
active = false;
|
|
1476
3118
|
};
|
|
1477
3119
|
}, [anisotropy, url]);
|
|
1478
|
-
|
|
3120
|
+
useEffect13(
|
|
1479
3121
|
() => () => {
|
|
1480
3122
|
texture?.dispose();
|
|
1481
3123
|
},
|
|
@@ -1488,7 +3130,7 @@ function PreviewFace({
|
|
|
1488
3130
|
texture,
|
|
1489
3131
|
faceOrder
|
|
1490
3132
|
}) {
|
|
1491
|
-
const geometry =
|
|
3133
|
+
const geometry = useMemo6(() => {
|
|
1492
3134
|
const nextGeometry = new PlaneGeometry(
|
|
1493
3135
|
PREVIEW_RADIUS * 2,
|
|
1494
3136
|
PREVIEW_RADIUS * 2
|
|
@@ -1506,21 +3148,21 @@ function PreviewFace({
|
|
|
1506
3148
|
uv.needsUpdate = true;
|
|
1507
3149
|
return nextGeometry;
|
|
1508
3150
|
}, [face, faceOrder]);
|
|
1509
|
-
|
|
3151
|
+
useEffect13(
|
|
1510
3152
|
() => () => {
|
|
1511
3153
|
geometry.dispose();
|
|
1512
3154
|
},
|
|
1513
3155
|
[geometry]
|
|
1514
3156
|
);
|
|
1515
3157
|
const transform = getFaceTransform(face, PREVIEW_RADIUS);
|
|
1516
|
-
return /* @__PURE__ */
|
|
3158
|
+
return /* @__PURE__ */ jsx7(
|
|
1517
3159
|
"mesh",
|
|
1518
3160
|
{
|
|
1519
3161
|
geometry,
|
|
1520
3162
|
position: transform.position,
|
|
1521
3163
|
renderOrder: -100,
|
|
1522
3164
|
rotation: transform.rotation,
|
|
1523
|
-
children: /* @__PURE__ */
|
|
3165
|
+
children: /* @__PURE__ */ jsx7(
|
|
1524
3166
|
"meshBasicMaterial",
|
|
1525
3167
|
{
|
|
1526
3168
|
depthTest: false,
|
|
@@ -1536,7 +3178,7 @@ function PreviewCube({
|
|
|
1536
3178
|
texture,
|
|
1537
3179
|
faceOrder
|
|
1538
3180
|
}) {
|
|
1539
|
-
return CUBE_FACES.map((face) => /* @__PURE__ */
|
|
3181
|
+
return CUBE_FACES.map((face) => /* @__PURE__ */ jsx7(
|
|
1540
3182
|
PreviewFace,
|
|
1541
3183
|
{
|
|
1542
3184
|
face,
|
|
@@ -1547,14 +3189,14 @@ function PreviewCube({
|
|
|
1547
3189
|
));
|
|
1548
3190
|
}
|
|
1549
3191
|
function useManagedTexture(manager, key, url, address) {
|
|
1550
|
-
const [managedState, setManagedState] =
|
|
3192
|
+
const [managedState, setManagedState] = useState5(() => ({
|
|
1551
3193
|
manager,
|
|
1552
3194
|
key,
|
|
1553
3195
|
url,
|
|
1554
3196
|
snapshot: manager.getSnapshot(key)
|
|
1555
3197
|
}));
|
|
1556
3198
|
const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
|
|
1557
|
-
|
|
3199
|
+
useEffect13(
|
|
1558
3200
|
() => manager.acquire(key, url, address, (nextSnapshot) => {
|
|
1559
3201
|
setManagedState({
|
|
1560
3202
|
manager,
|
|
@@ -1581,14 +3223,14 @@ var TileMesh = memo(function TileMesh2({
|
|
|
1581
3223
|
if (!snapshot.texture) {
|
|
1582
3224
|
return null;
|
|
1583
3225
|
}
|
|
1584
|
-
return /* @__PURE__ */
|
|
3226
|
+
return /* @__PURE__ */ jsx7("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx7(
|
|
1585
3227
|
"mesh",
|
|
1586
3228
|
{
|
|
1587
3229
|
geometry: SHARED_TILE_GEOMETRY,
|
|
1588
3230
|
position: [layout.x, layout.y, 0],
|
|
1589
3231
|
renderOrder,
|
|
1590
3232
|
scale: [layout.width, layout.height, 1],
|
|
1591
|
-
children: /* @__PURE__ */
|
|
3233
|
+
children: /* @__PURE__ */ jsx7(
|
|
1592
3234
|
"meshBasicMaterial",
|
|
1593
3235
|
{
|
|
1594
3236
|
depthTest: false,
|
|
@@ -1605,7 +3247,7 @@ var TileLayerMeshes = memo(function TileLayerMeshes2({
|
|
|
1605
3247
|
manager,
|
|
1606
3248
|
resolveUrl
|
|
1607
3249
|
}) {
|
|
1608
|
-
return layer.addresses.map((address) => /* @__PURE__ */
|
|
3250
|
+
return layer.addresses.map((address) => /* @__PURE__ */ jsx7(
|
|
1609
3251
|
TileMesh,
|
|
1610
3252
|
{
|
|
1611
3253
|
address,
|
|
@@ -1705,8 +3347,8 @@ function parentAddresses(child, parentLevel, parentFaceSize, tileSize) {
|
|
|
1705
3347
|
return addresses;
|
|
1706
3348
|
}
|
|
1707
3349
|
function centerFacingAddress(candidates, camera) {
|
|
1708
|
-
const direction = camera.getWorldDirection(new
|
|
1709
|
-
const center = new
|
|
3350
|
+
const direction = camera.getWorldDirection(new Vector34());
|
|
3351
|
+
const center = new Vector34();
|
|
1710
3352
|
let bestCandidate = candidates[0];
|
|
1711
3353
|
let bestAlignment = -Infinity;
|
|
1712
3354
|
for (const candidate of candidates) {
|
|
@@ -1773,11 +3415,11 @@ function Tile({
|
|
|
1773
3415
|
onTileError,
|
|
1774
3416
|
onLevelChange
|
|
1775
3417
|
}) {
|
|
1776
|
-
const { camera, gl, size } =
|
|
1777
|
-
const multiresConfig =
|
|
3418
|
+
const { camera, gl, size } = useThree9();
|
|
3419
|
+
const multiresConfig = useMemo6(() => parseMultires(multires), [multires]);
|
|
1778
3420
|
const { levels, tileSize } = multiresConfig;
|
|
1779
3421
|
const multiresToken = `${tileSize}:${levels.join(",")}`;
|
|
1780
|
-
const candidatesByLevel =
|
|
3422
|
+
const candidatesByLevel = useMemo6(
|
|
1781
3423
|
() => buildTileCandidates(levels, tileSize),
|
|
1782
3424
|
[levels, tileSize]
|
|
1783
3425
|
);
|
|
@@ -1789,13 +3431,13 @@ function Tile({
|
|
|
1789
3431
|
onPreviewError
|
|
1790
3432
|
);
|
|
1791
3433
|
const sharedManager = useSharedTileTextureManager();
|
|
1792
|
-
const progressRef =
|
|
1793
|
-
const errorRef =
|
|
1794
|
-
const levelChangeRef =
|
|
3434
|
+
const progressRef = useRef11(onLoadProgress);
|
|
3435
|
+
const errorRef = useRef11(onTileError);
|
|
3436
|
+
const levelChangeRef = useRef11(onLevelChange);
|
|
1795
3437
|
progressRef.current = onLoadProgress;
|
|
1796
3438
|
errorRef.current = onTileError;
|
|
1797
3439
|
levelChangeRef.current = onLevelChange;
|
|
1798
|
-
const ownedManager =
|
|
3440
|
+
const ownedManager = useMemo6(
|
|
1799
3441
|
() => new TileTextureManager({
|
|
1800
3442
|
anisotropy,
|
|
1801
3443
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1816,7 +3458,7 @@ function Tile({
|
|
|
1816
3458
|
]
|
|
1817
3459
|
);
|
|
1818
3460
|
const manager = sharedManager ?? ownedManager;
|
|
1819
|
-
|
|
3461
|
+
useEffect13(() => {
|
|
1820
3462
|
manager.resume();
|
|
1821
3463
|
return () => {
|
|
1822
3464
|
if (manager === ownedManager) {
|
|
@@ -1826,11 +3468,11 @@ function Tile({
|
|
|
1826
3468
|
queueMicrotask(() => manager.releaseUnused());
|
|
1827
3469
|
};
|
|
1828
3470
|
}, [manager, ownedManager]);
|
|
1829
|
-
const readyRef =
|
|
1830
|
-
|
|
3471
|
+
const readyRef = useRef11(false);
|
|
3472
|
+
useEffect13(() => {
|
|
1831
3473
|
readyRef.current = false;
|
|
1832
3474
|
}, [effectivePreviewUrl]);
|
|
1833
|
-
|
|
3475
|
+
useEffect13(() => {
|
|
1834
3476
|
if (!previewTexture || readyRef.current) {
|
|
1835
3477
|
return;
|
|
1836
3478
|
}
|
|
@@ -1838,31 +3480,31 @@ function Tile({
|
|
|
1838
3480
|
onReady?.();
|
|
1839
3481
|
}, [onReady, previewTexture]);
|
|
1840
3482
|
const template = urlTemplate ?? buildDefaultTileUrlTemplate();
|
|
1841
|
-
const resolveUrl =
|
|
3483
|
+
const resolveUrl = useMemo6(
|
|
1842
3484
|
() => (address) => {
|
|
1843
3485
|
const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
|
|
1844
3486
|
return resolveRelativeTileUrl(baseUrl, relativePath);
|
|
1845
3487
|
},
|
|
1846
3488
|
[baseUrl, resolveTileUrl, template]
|
|
1847
3489
|
);
|
|
1848
|
-
const [layers, setLayers] =
|
|
1849
|
-
const layerTokenRef =
|
|
1850
|
-
const currentLevelRef =
|
|
1851
|
-
const lastUpdateAtRef =
|
|
1852
|
-
const visibilityDirtyRef =
|
|
1853
|
-
const lastVisibilityQuaternionRef =
|
|
1854
|
-
const lastVisibilityFovRef =
|
|
1855
|
-
const lastViewportRef =
|
|
1856
|
-
const frustumRef =
|
|
1857
|
-
const projectionMatrixRef =
|
|
1858
|
-
const visibilityCameraRef =
|
|
1859
|
-
|
|
3490
|
+
const [layers, setLayers] = useState5([]);
|
|
3491
|
+
const layerTokenRef = useRef11("");
|
|
3492
|
+
const currentLevelRef = useRef11(1);
|
|
3493
|
+
const lastUpdateAtRef = useRef11(-Infinity);
|
|
3494
|
+
const visibilityDirtyRef = useRef11(true);
|
|
3495
|
+
const lastVisibilityQuaternionRef = useRef11(camera.quaternion.clone());
|
|
3496
|
+
const lastVisibilityFovRef = useRef11(Number.NaN);
|
|
3497
|
+
const lastViewportRef = useRef11({ width: 0, height: 0, dpr: 0 });
|
|
3498
|
+
const frustumRef = useRef11(new Frustum());
|
|
3499
|
+
const projectionMatrixRef = useRef11(new Matrix4());
|
|
3500
|
+
const visibilityCameraRef = useRef11(new PerspectiveCamera2());
|
|
3501
|
+
useEffect13(() => {
|
|
1860
3502
|
currentLevelRef.current = 1;
|
|
1861
3503
|
layerTokenRef.current = "";
|
|
1862
3504
|
visibilityDirtyRef.current = true;
|
|
1863
3505
|
setLayers([]);
|
|
1864
3506
|
}, [manager, multiresToken]);
|
|
1865
|
-
|
|
3507
|
+
useEffect13(() => {
|
|
1866
3508
|
if (loadMode !== "preview") {
|
|
1867
3509
|
return;
|
|
1868
3510
|
}
|
|
@@ -1876,7 +3518,7 @@ function Tile({
|
|
|
1876
3518
|
setLayers(nextLayers);
|
|
1877
3519
|
}
|
|
1878
3520
|
};
|
|
1879
|
-
|
|
3521
|
+
useFrame5(() => {
|
|
1880
3522
|
if (loadMode === "preview") {
|
|
1881
3523
|
return;
|
|
1882
3524
|
}
|
|
@@ -1942,15 +3584,15 @@ function Tile({
|
|
|
1942
3584
|
};
|
|
1943
3585
|
commitLayers(nextLayers);
|
|
1944
3586
|
});
|
|
1945
|
-
return /* @__PURE__ */
|
|
1946
|
-
previewTexture ? /* @__PURE__ */
|
|
3587
|
+
return /* @__PURE__ */ jsxs5("group", { visible, children: [
|
|
3588
|
+
previewTexture ? /* @__PURE__ */ jsx7(
|
|
1947
3589
|
PreviewCube,
|
|
1948
3590
|
{
|
|
1949
3591
|
texture: previewTexture,
|
|
1950
3592
|
faceOrder: previewFaceOrder
|
|
1951
3593
|
}
|
|
1952
3594
|
) : null,
|
|
1953
|
-
layers.map((layer) => /* @__PURE__ */
|
|
3595
|
+
layers.map((layer) => /* @__PURE__ */ jsx7(
|
|
1954
3596
|
TileLayerMeshes,
|
|
1955
3597
|
{
|
|
1956
3598
|
layer,
|
|
@@ -1963,7 +3605,7 @@ function Tile({
|
|
|
1963
3605
|
}
|
|
1964
3606
|
|
|
1965
3607
|
// src/panorama-scenes.tsx
|
|
1966
|
-
import { jsx as
|
|
3608
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1967
3609
|
var TRANSITION_DEFAULTS = {
|
|
1968
3610
|
none: { duration: 0, krpanoBlend: "NOBLEND" },
|
|
1969
3611
|
crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
|
|
@@ -2013,7 +3655,7 @@ function SceneSource({
|
|
|
2013
3655
|
return null;
|
|
2014
3656
|
}
|
|
2015
3657
|
if (scene.type === "sphere") {
|
|
2016
|
-
return /* @__PURE__ */
|
|
3658
|
+
return /* @__PURE__ */ jsx8(
|
|
2017
3659
|
Sphere,
|
|
2018
3660
|
{
|
|
2019
3661
|
src: scene.src,
|
|
@@ -2025,7 +3667,7 @@ function SceneSource({
|
|
|
2025
3667
|
);
|
|
2026
3668
|
}
|
|
2027
3669
|
const { id: _id, type: _type, ...tileProps } = scene;
|
|
2028
|
-
return /* @__PURE__ */
|
|
3670
|
+
return /* @__PURE__ */ jsx8(
|
|
2029
3671
|
Tile,
|
|
2030
3672
|
{
|
|
2031
3673
|
...tileProps,
|
|
@@ -2041,17 +3683,17 @@ function SnapshotCapture({
|
|
|
2041
3683
|
maxPixels,
|
|
2042
3684
|
onCaptured
|
|
2043
3685
|
}) {
|
|
2044
|
-
const { camera, gl, scene, size } =
|
|
2045
|
-
const capturedRequestRef =
|
|
2046
|
-
const renderTargetRef =
|
|
2047
|
-
|
|
3686
|
+
const { camera, gl, scene, size } = useThree10();
|
|
3687
|
+
const capturedRequestRef = useRef12(0);
|
|
3688
|
+
const renderTargetRef = useRef12(null);
|
|
3689
|
+
useEffect14(
|
|
2048
3690
|
() => () => {
|
|
2049
3691
|
renderTargetRef.current?.dispose();
|
|
2050
3692
|
renderTargetRef.current = null;
|
|
2051
3693
|
},
|
|
2052
3694
|
[]
|
|
2053
3695
|
);
|
|
2054
|
-
|
|
3696
|
+
useFrame6(() => {
|
|
2055
3697
|
if (requestId === 0 || requestId === capturedRequestRef.current) {
|
|
2056
3698
|
return;
|
|
2057
3699
|
}
|
|
@@ -2097,12 +3739,12 @@ function SnapshotOverlay({
|
|
|
2097
3739
|
runId,
|
|
2098
3740
|
onFinish
|
|
2099
3741
|
}) {
|
|
2100
|
-
const { camera, size } =
|
|
2101
|
-
const meshRef =
|
|
2102
|
-
const startRef =
|
|
2103
|
-
const finishedRef =
|
|
2104
|
-
const directionRef =
|
|
2105
|
-
const material =
|
|
3742
|
+
const { camera, size } = useThree10();
|
|
3743
|
+
const meshRef = useRef12(null);
|
|
3744
|
+
const startRef = useRef12(null);
|
|
3745
|
+
const finishedRef = useRef12(false);
|
|
3746
|
+
const directionRef = useRef12(new Vector35());
|
|
3747
|
+
const material = useMemo7(
|
|
2106
3748
|
() => new ShaderMaterial({
|
|
2107
3749
|
transparent: true,
|
|
2108
3750
|
depthTest: false,
|
|
@@ -2181,13 +3823,13 @@ function SnapshotOverlay({
|
|
|
2181
3823
|
}),
|
|
2182
3824
|
[snapshot.texture, transition.preset]
|
|
2183
3825
|
);
|
|
2184
|
-
|
|
2185
|
-
|
|
3826
|
+
useEffect14(() => () => material.dispose(), [material]);
|
|
3827
|
+
useEffect14(() => {
|
|
2186
3828
|
startRef.current = null;
|
|
2187
3829
|
finishedRef.current = false;
|
|
2188
3830
|
material.uniforms.progress.value = 0;
|
|
2189
3831
|
}, [material, runId]);
|
|
2190
|
-
|
|
3832
|
+
useFrame6((state) => {
|
|
2191
3833
|
const mesh = meshRef.current;
|
|
2192
3834
|
if (!mesh || !camera.isPerspectiveCamera) {
|
|
2193
3835
|
return;
|
|
@@ -2212,9 +3854,9 @@ function SnapshotOverlay({
|
|
|
2212
3854
|
onFinish();
|
|
2213
3855
|
}
|
|
2214
3856
|
});
|
|
2215
|
-
return /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
3857
|
+
return /* @__PURE__ */ jsxs6("mesh", { ref: meshRef, renderOrder: 1e4, frustumCulled: false, children: [
|
|
3858
|
+
/* @__PURE__ */ jsx8("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
|
|
3859
|
+
/* @__PURE__ */ jsx8("primitive", { attach: "material", object: material })
|
|
2218
3860
|
] });
|
|
2219
3861
|
}
|
|
2220
3862
|
var FULLSCREEN_PLANE = new PlaneGeometry2(1, 1);
|
|
@@ -2227,47 +3869,47 @@ function PanoramaScenesController({
|
|
|
2227
3869
|
onTransitionError,
|
|
2228
3870
|
snapshotMaxPixels = 3686400
|
|
2229
3871
|
}) {
|
|
2230
|
-
const controlsRef =
|
|
3872
|
+
const controlsRef = useContext10(PanoramaViewContext);
|
|
2231
3873
|
const manager = useSharedTileTextureManager();
|
|
2232
|
-
const transition =
|
|
3874
|
+
const transition = useMemo7(
|
|
2233
3875
|
() => resolveTransition(transitionInput),
|
|
2234
3876
|
[transitionInput]
|
|
2235
3877
|
);
|
|
2236
|
-
const sceneMap =
|
|
3878
|
+
const sceneMap = useMemo7(
|
|
2237
3879
|
() => new Map(scenes.map((scene) => [scene.id, scene])),
|
|
2238
3880
|
[scenes]
|
|
2239
3881
|
);
|
|
2240
3882
|
const initialScene = sceneMap.get(activeSceneId) ?? null;
|
|
2241
|
-
const [slots, setSlots] =
|
|
3883
|
+
const [slots, setSlots] = useState6([
|
|
2242
3884
|
initialScene,
|
|
2243
3885
|
null
|
|
2244
3886
|
]);
|
|
2245
|
-
const [activeSlot, setActiveSlot] =
|
|
2246
|
-
const [liveSlot, setLiveSlot] =
|
|
2247
|
-
const [pendingSlot, setPendingSlot] =
|
|
2248
|
-
const [phase, setPhase] =
|
|
2249
|
-
const [captureRequestId, setCaptureRequestId] =
|
|
2250
|
-
const [transitionRunId, setTransitionRunId] =
|
|
2251
|
-
const [snapshot, setSnapshot] =
|
|
2252
|
-
const [recoveryKey, setRecoveryKey] =
|
|
2253
|
-
const requestedSceneIdRef =
|
|
2254
|
-
const captureReasonRef =
|
|
2255
|
-
const interactionReleaseRef =
|
|
2256
|
-
const slotsRef =
|
|
2257
|
-
const phaseRef =
|
|
2258
|
-
const liveSlotRef =
|
|
2259
|
-
const transitionFromRef =
|
|
3887
|
+
const [activeSlot, setActiveSlot] = useState6(0);
|
|
3888
|
+
const [liveSlot, setLiveSlot] = useState6(0);
|
|
3889
|
+
const [pendingSlot, setPendingSlot] = useState6(null);
|
|
3890
|
+
const [phase, setPhase] = useState6(initialScene ? "idle" : "preloading");
|
|
3891
|
+
const [captureRequestId, setCaptureRequestId] = useState6(0);
|
|
3892
|
+
const [transitionRunId, setTransitionRunId] = useState6(0);
|
|
3893
|
+
const [snapshot, setSnapshot] = useState6(null);
|
|
3894
|
+
const [recoveryKey, setRecoveryKey] = useState6(0);
|
|
3895
|
+
const requestedSceneIdRef = useRef12(activeSceneId);
|
|
3896
|
+
const captureReasonRef = useRef12("start");
|
|
3897
|
+
const interactionReleaseRef = useRef12(null);
|
|
3898
|
+
const slotsRef = useRef12(slots);
|
|
3899
|
+
const phaseRef = useRef12(phase);
|
|
3900
|
+
const liveSlotRef = useRef12(liveSlot);
|
|
3901
|
+
const transitionFromRef = useRef12(initialScene);
|
|
2260
3902
|
slotsRef.current = slots;
|
|
2261
3903
|
phaseRef.current = phase;
|
|
2262
3904
|
liveSlotRef.current = liveSlot;
|
|
2263
|
-
const releaseInteraction =
|
|
3905
|
+
const releaseInteraction = useCallback4(() => {
|
|
2264
3906
|
interactionReleaseRef.current?.();
|
|
2265
3907
|
interactionReleaseRef.current = null;
|
|
2266
3908
|
}, []);
|
|
2267
|
-
const reportError =
|
|
3909
|
+
const reportError = useCallback4((sceneId, error) => {
|
|
2268
3910
|
onTransitionError?.({ sceneId, error });
|
|
2269
3911
|
}, [onTransitionError]);
|
|
2270
|
-
const startPreload =
|
|
3912
|
+
const startPreload = useCallback4((scene) => {
|
|
2271
3913
|
const nextSlot = 1 - liveSlotRef.current;
|
|
2272
3914
|
requestedSceneIdRef.current = scene.id;
|
|
2273
3915
|
setSlots((current) => {
|
|
@@ -2278,7 +3920,7 @@ function PanoramaScenesController({
|
|
|
2278
3920
|
setPendingSlot(nextSlot);
|
|
2279
3921
|
setPhase("preloading");
|
|
2280
3922
|
}, []);
|
|
2281
|
-
|
|
3923
|
+
useEffect14(() => {
|
|
2282
3924
|
const requested = sceneMap.get(activeSceneId);
|
|
2283
3925
|
if (!requested) {
|
|
2284
3926
|
reportError(activeSceneId, new Error(`Unknown panorama scene: ${activeSceneId}`));
|
|
@@ -2302,7 +3944,7 @@ function PanoramaScenesController({
|
|
|
2302
3944
|
}
|
|
2303
3945
|
startPreload(requested);
|
|
2304
3946
|
}, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
|
|
2305
|
-
const handleReady =
|
|
3947
|
+
const handleReady = useCallback4((slot) => {
|
|
2306
3948
|
if (slot !== pendingSlot || phaseRef.current !== "preloading") {
|
|
2307
3949
|
return;
|
|
2308
3950
|
}
|
|
@@ -2333,7 +3975,7 @@ function PanoramaScenesController({
|
|
|
2333
3975
|
setPhase("capturing");
|
|
2334
3976
|
setCaptureRequestId((current) => current + 1);
|
|
2335
3977
|
}, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
|
|
2336
|
-
const handleCaptured =
|
|
3978
|
+
const handleCaptured = useCallback4((nextSnapshot) => {
|
|
2337
3979
|
setSnapshot((current) => {
|
|
2338
3980
|
current?.dispose();
|
|
2339
3981
|
return nextSnapshot;
|
|
@@ -2355,7 +3997,7 @@ function PanoramaScenesController({
|
|
|
2355
3997
|
setPhase("transitioning");
|
|
2356
3998
|
setTransitionRunId((current) => current + 1);
|
|
2357
3999
|
}, [manager, pendingSlot, sceneMap, startPreload]);
|
|
2358
|
-
const finishTransition =
|
|
4000
|
+
const finishTransition = useCallback4(() => {
|
|
2359
4001
|
const nextSlot = pendingSlot;
|
|
2360
4002
|
const previousScene = transitionFromRef.current;
|
|
2361
4003
|
const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
|
|
@@ -2380,12 +4022,12 @@ function PanoramaScenesController({
|
|
|
2380
4022
|
});
|
|
2381
4023
|
}
|
|
2382
4024
|
}, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
|
|
2383
|
-
|
|
4025
|
+
useEffect14(() => () => {
|
|
2384
4026
|
releaseInteraction();
|
|
2385
4027
|
snapshot?.dispose();
|
|
2386
4028
|
}, [releaseInteraction, snapshot]);
|
|
2387
|
-
const { gl } =
|
|
2388
|
-
|
|
4029
|
+
const { gl } = useThree10();
|
|
4030
|
+
useEffect14(() => {
|
|
2389
4031
|
const canvas = gl.domElement;
|
|
2390
4032
|
const onContextLost = (event) => {
|
|
2391
4033
|
event.preventDefault();
|
|
@@ -2409,8 +4051,8 @@ function PanoramaScenesController({
|
|
|
2409
4051
|
}, [gl, manager, releaseInteraction, snapshot]);
|
|
2410
4052
|
const renderedScene = slots[liveSlot];
|
|
2411
4053
|
const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
|
|
2412
|
-
return /* @__PURE__ */
|
|
2413
|
-
/* @__PURE__ */
|
|
4054
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
4055
|
+
/* @__PURE__ */ jsx8(
|
|
2414
4056
|
SnapshotCapture,
|
|
2415
4057
|
{
|
|
2416
4058
|
requestId: captureRequestId,
|
|
@@ -2422,7 +4064,7 @@ function PanoramaScenesController({
|
|
|
2422
4064
|
const source = slots[slot];
|
|
2423
4065
|
const isLive = slot === liveSlot;
|
|
2424
4066
|
const isPending = slot === pendingSlot;
|
|
2425
|
-
return /* @__PURE__ */
|
|
4067
|
+
return /* @__PURE__ */ jsx8(
|
|
2426
4068
|
SceneSource,
|
|
2427
4069
|
{
|
|
2428
4070
|
scene: source,
|
|
@@ -2438,7 +4080,7 @@ function PanoramaScenesController({
|
|
|
2438
4080
|
`${slot}:${source?.id ?? "empty"}:${recoveryKey}`
|
|
2439
4081
|
);
|
|
2440
4082
|
}),
|
|
2441
|
-
snapshotVisible ? /* @__PURE__ */
|
|
4083
|
+
snapshotVisible ? /* @__PURE__ */ jsx8(
|
|
2442
4084
|
SnapshotOverlay,
|
|
2443
4085
|
{
|
|
2444
4086
|
snapshot,
|
|
@@ -2456,19 +4098,19 @@ function PanoramaScenes({
|
|
|
2456
4098
|
maxConcurrentTileLoads,
|
|
2457
4099
|
...props
|
|
2458
4100
|
}) {
|
|
2459
|
-
return /* @__PURE__ */
|
|
4101
|
+
return /* @__PURE__ */ jsx8(
|
|
2460
4102
|
TileTextureManagerProvider,
|
|
2461
4103
|
{
|
|
2462
4104
|
maxTextureMemoryMb,
|
|
2463
4105
|
maxConcurrentLoads: maxConcurrentTileLoads,
|
|
2464
|
-
children: /* @__PURE__ */
|
|
4106
|
+
children: /* @__PURE__ */ jsx8(PanoramaScenesController, { ...props })
|
|
2465
4107
|
}
|
|
2466
4108
|
);
|
|
2467
4109
|
}
|
|
2468
4110
|
|
|
2469
4111
|
// src/hotspot/image-hotspot.tsx
|
|
2470
|
-
import { useThree as
|
|
2471
|
-
import { useEffect as
|
|
4112
|
+
import { useThree as useThree11 } from "@react-three/fiber";
|
|
4113
|
+
import { useEffect as useEffect16, useRef as useRef14, useState as useState7 } from "react";
|
|
2472
4114
|
import {
|
|
2473
4115
|
ClampToEdgeWrapping as ClampToEdgeWrapping3,
|
|
2474
4116
|
DoubleSide as DoubleSide2,
|
|
@@ -2479,12 +4121,12 @@ import {
|
|
|
2479
4121
|
} from "three";
|
|
2480
4122
|
|
|
2481
4123
|
// src/hotspot/hotspot-anchor.tsx
|
|
2482
|
-
import { useFrame as
|
|
4124
|
+
import { useFrame as useFrame7 } from "@react-three/fiber";
|
|
2483
4125
|
import {
|
|
2484
|
-
useContext as
|
|
2485
|
-
useEffect as
|
|
2486
|
-
useMemo as
|
|
2487
|
-
useRef as
|
|
4126
|
+
useContext as useContext11,
|
|
4127
|
+
useEffect as useEffect15,
|
|
4128
|
+
useMemo as useMemo8,
|
|
4129
|
+
useRef as useRef13
|
|
2488
4130
|
} from "react";
|
|
2489
4131
|
import {
|
|
2490
4132
|
EdgesGeometry,
|
|
@@ -2493,9 +4135,9 @@ import {
|
|
|
2493
4135
|
PlaneGeometry as PlaneGeometry3,
|
|
2494
4136
|
PerspectiveCamera as PerspectiveCamera4,
|
|
2495
4137
|
Quaternion as Quaternion2,
|
|
2496
|
-
Vector3 as
|
|
4138
|
+
Vector3 as Vector36
|
|
2497
4139
|
} from "three";
|
|
2498
|
-
import { jsx as
|
|
4140
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2499
4141
|
var DEFAULT_FLOATING_DISTANCE = 10;
|
|
2500
4142
|
var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
|
|
2501
4143
|
var PANORAMA_SURFACE_INSET = 4;
|
|
@@ -2503,7 +4145,7 @@ var DEFAULT_REFERENCE_FOV = 75;
|
|
|
2503
4145
|
var MIN_ANGULAR_SIZE = 0.01;
|
|
2504
4146
|
var MAX_ANGULAR_SIZE = 179;
|
|
2505
4147
|
var DRAG_EPSILON_DEGREES = 1e-3;
|
|
2506
|
-
var LOCAL_FORWARD = new
|
|
4148
|
+
var LOCAL_FORWARD = new Vector36(0, 0, 1);
|
|
2507
4149
|
var HOTSPOT_MODE_RENDERING = {
|
|
2508
4150
|
surface: { orientation: "surface", placement: "surface" },
|
|
2509
4151
|
billboard: { orientation: "billboard", placement: "floating" }
|
|
@@ -2552,7 +4194,7 @@ function createSurfaceQuaternion(position) {
|
|
|
2552
4194
|
const inward = outward.clone().negate();
|
|
2553
4195
|
const pitch = MathUtils4.degToRad(position.pitch);
|
|
2554
4196
|
const yaw = MathUtils4.degToRad(position.yaw);
|
|
2555
|
-
const localUp = new
|
|
4197
|
+
const localUp = new Vector36(
|
|
2556
4198
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
2557
4199
|
Math.cos(pitch),
|
|
2558
4200
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -2601,10 +4243,10 @@ function HotspotAnchor({
|
|
|
2601
4243
|
onPositionChange,
|
|
2602
4244
|
onDragEnd
|
|
2603
4245
|
}) {
|
|
2604
|
-
const controlsRef =
|
|
2605
|
-
const groupRef =
|
|
2606
|
-
const dragStateRef =
|
|
2607
|
-
const suppressNextClickRef =
|
|
4246
|
+
const controlsRef = useContext11(PanoramaViewContext);
|
|
4247
|
+
const groupRef = useRef13(null);
|
|
4248
|
+
const dragStateRef = useRef13(null);
|
|
4249
|
+
const suppressNextClickRef = useRef13(false);
|
|
2608
4250
|
const normalizedPosition = normalizePanoPosition(position);
|
|
2609
4251
|
const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
|
|
2610
4252
|
const { orientation: orientation2, placement } = rendering;
|
|
@@ -2617,17 +4259,17 @@ function HotspotAnchor({
|
|
|
2617
4259
|
scaledWidth,
|
|
2618
4260
|
scaledHeight
|
|
2619
4261
|
);
|
|
2620
|
-
const worldPosition =
|
|
4262
|
+
const worldPosition = useMemo8(
|
|
2621
4263
|
() => panoPositionToVector3(normalizedPosition, resolvedDistance),
|
|
2622
4264
|
[normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
|
|
2623
4265
|
);
|
|
2624
|
-
const surfaceQuaternion3 =
|
|
4266
|
+
const surfaceQuaternion3 = useMemo8(
|
|
2625
4267
|
() => createSurfaceQuaternion(normalizedPosition),
|
|
2626
4268
|
[normalizedPosition.pitch, normalizedPosition.yaw]
|
|
2627
4269
|
);
|
|
2628
4270
|
const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
|
|
2629
4271
|
const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
|
|
2630
|
-
const focusGeometry =
|
|
4272
|
+
const focusGeometry = useMemo8(() => {
|
|
2631
4273
|
const planeGeometry = new PlaneGeometry3(1, 1);
|
|
2632
4274
|
const geometry = new EdgesGeometry(planeGeometry);
|
|
2633
4275
|
planeGeometry.dispose();
|
|
@@ -2645,15 +4287,15 @@ function HotspotAnchor({
|
|
|
2645
4287
|
});
|
|
2646
4288
|
} : void 0
|
|
2647
4289
|
});
|
|
2648
|
-
|
|
4290
|
+
useEffect15(
|
|
2649
4291
|
() => () => {
|
|
2650
4292
|
dragStateRef.current?.releaseInteractionLock();
|
|
2651
4293
|
dragStateRef.current = null;
|
|
2652
4294
|
},
|
|
2653
4295
|
[]
|
|
2654
4296
|
);
|
|
2655
|
-
|
|
2656
|
-
|
|
4297
|
+
useEffect15(() => () => focusGeometry.dispose(), [focusGeometry]);
|
|
4298
|
+
useFrame7(({ camera }) => {
|
|
2657
4299
|
if (!groupRef.current) {
|
|
2658
4300
|
return;
|
|
2659
4301
|
}
|
|
@@ -2664,7 +4306,7 @@ function HotspotAnchor({
|
|
|
2664
4306
|
}
|
|
2665
4307
|
groupRef.current.rotateOnAxis(
|
|
2666
4308
|
LOCAL_FORWARD,
|
|
2667
|
-
MathUtils4.degToRad(rotation)
|
|
4309
|
+
MathUtils4.degToRad(-rotation)
|
|
2668
4310
|
);
|
|
2669
4311
|
const scaleFactor = scaleMode === "fixed" && camera instanceof PerspectiveCamera4 ? fixedScaleFactor(camera.fov, referenceFov) : 1;
|
|
2670
4312
|
groupRef.current.scale.set(
|
|
@@ -2699,7 +4341,7 @@ function HotspotAnchor({
|
|
|
2699
4341
|
dragState.releaseInteractionLock();
|
|
2700
4342
|
dragStateRef.current = null;
|
|
2701
4343
|
};
|
|
2702
|
-
return /* @__PURE__ */
|
|
4344
|
+
return /* @__PURE__ */ jsxs7(
|
|
2703
4345
|
"group",
|
|
2704
4346
|
{
|
|
2705
4347
|
ref: groupRef,
|
|
@@ -2769,14 +4411,14 @@ function HotspotAnchor({
|
|
|
2769
4411
|
} : void 0,
|
|
2770
4412
|
onPointerUp: interactive ? (event) => releasePointer(event, true) : void 0,
|
|
2771
4413
|
children: [
|
|
2772
|
-
focused ? focusContent ?? /* @__PURE__ */
|
|
4414
|
+
focused ? focusContent ?? /* @__PURE__ */ jsx9(
|
|
2773
4415
|
"lineSegments",
|
|
2774
4416
|
{
|
|
2775
4417
|
geometry: focusGeometry,
|
|
2776
4418
|
position: [0, 0, 0.01],
|
|
2777
4419
|
renderOrder: renderOrder + 1,
|
|
2778
4420
|
scale: [1.12, 1.12, 1],
|
|
2779
|
-
children: /* @__PURE__ */
|
|
4421
|
+
children: /* @__PURE__ */ jsx9("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
|
|
2780
4422
|
}
|
|
2781
4423
|
) : null,
|
|
2782
4424
|
children
|
|
@@ -2786,7 +4428,7 @@ function HotspotAnchor({
|
|
|
2786
4428
|
}
|
|
2787
4429
|
|
|
2788
4430
|
// src/hotspot/image-hotspot.tsx
|
|
2789
|
-
import { jsx as
|
|
4431
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2790
4432
|
function clampOpacity(opacity) {
|
|
2791
4433
|
if (!Number.isFinite(opacity)) {
|
|
2792
4434
|
return 1;
|
|
@@ -2794,13 +4436,13 @@ function clampOpacity(opacity) {
|
|
|
2794
4436
|
return Math.max(0, Math.min(opacity, 1));
|
|
2795
4437
|
}
|
|
2796
4438
|
function useHotspotImageTexture(src, onLoad, onError) {
|
|
2797
|
-
const gl =
|
|
2798
|
-
const [texture, setTexture] =
|
|
2799
|
-
const onLoadRef =
|
|
2800
|
-
const onErrorRef =
|
|
4439
|
+
const gl = useThree11((state) => state.gl);
|
|
4440
|
+
const [texture, setTexture] = useState7(null);
|
|
4441
|
+
const onLoadRef = useRef14(onLoad);
|
|
4442
|
+
const onErrorRef = useRef14(onError);
|
|
2801
4443
|
onLoadRef.current = onLoad;
|
|
2802
4444
|
onErrorRef.current = onError;
|
|
2803
|
-
|
|
4445
|
+
useEffect16(() => {
|
|
2804
4446
|
let active = true;
|
|
2805
4447
|
setTexture(null);
|
|
2806
4448
|
if (!src) {
|
|
@@ -2838,7 +4480,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
|
|
|
2838
4480
|
active = false;
|
|
2839
4481
|
};
|
|
2840
4482
|
}, [gl, src]);
|
|
2841
|
-
|
|
4483
|
+
useEffect16(
|
|
2842
4484
|
() => () => {
|
|
2843
4485
|
texture?.dispose();
|
|
2844
4486
|
},
|
|
@@ -2856,9 +4498,9 @@ function ImageHotspot({
|
|
|
2856
4498
|
...anchorProps
|
|
2857
4499
|
}) {
|
|
2858
4500
|
const texture = useHotspotImageTexture(src, onLoad, onError);
|
|
2859
|
-
return /* @__PURE__ */
|
|
2860
|
-
/* @__PURE__ */
|
|
2861
|
-
/* @__PURE__ */
|
|
4501
|
+
return /* @__PURE__ */ jsx10(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsxs8("mesh", { children: [
|
|
4502
|
+
/* @__PURE__ */ jsx10("planeGeometry", { args: [1, 1] }),
|
|
4503
|
+
/* @__PURE__ */ jsx10(
|
|
2862
4504
|
"meshBasicMaterial",
|
|
2863
4505
|
{
|
|
2864
4506
|
map: texture,
|
|
@@ -2872,8 +4514,8 @@ function ImageHotspot({
|
|
|
2872
4514
|
}
|
|
2873
4515
|
|
|
2874
4516
|
// src/hotspot/polygon-hotspot.tsx
|
|
2875
|
-
import { useThree as
|
|
2876
|
-
import { useEffect as
|
|
4517
|
+
import { useThree as useThree12 } from "@react-three/fiber";
|
|
4518
|
+
import { useEffect as useEffect17, useMemo as useMemo9, useRef as useRef15 } from "react";
|
|
2877
4519
|
import {
|
|
2878
4520
|
BufferGeometry,
|
|
2879
4521
|
DoubleSide as DoubleSide3,
|
|
@@ -2881,13 +4523,13 @@ import {
|
|
|
2881
4523
|
Matrix4 as Matrix43,
|
|
2882
4524
|
Quaternion as Quaternion3,
|
|
2883
4525
|
ShapeUtils,
|
|
2884
|
-
Vector2 as
|
|
2885
|
-
Vector3 as
|
|
4526
|
+
Vector2 as Vector23,
|
|
4527
|
+
Vector3 as Vector37
|
|
2886
4528
|
} from "three";
|
|
2887
4529
|
import { Line2 } from "three/addons/lines/Line2.js";
|
|
2888
4530
|
import { LineGeometry } from "three/addons/lines/LineGeometry.js";
|
|
2889
4531
|
import { LineMaterial } from "three/addons/lines/LineMaterial.js";
|
|
2890
|
-
import { jsx as
|
|
4532
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2891
4533
|
var POLYGON_RADIUS = 990;
|
|
2892
4534
|
var MAX_FILL_SEGMENT_DEGREES = 7;
|
|
2893
4535
|
function cloneVertices(vertices) {
|
|
@@ -2922,7 +4564,7 @@ function intersects(a, b, c, d) {
|
|
|
2922
4564
|
}
|
|
2923
4565
|
function hasSelfIntersection(vertices) {
|
|
2924
4566
|
const points = vertices.map(
|
|
2925
|
-
(vertex) => new
|
|
4567
|
+
(vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch)
|
|
2926
4568
|
);
|
|
2927
4569
|
for (let index = 0; index < points.length; index += 1) {
|
|
2928
4570
|
const nextIndex = (index + 1) % points.length;
|
|
@@ -2959,7 +4601,7 @@ function polygonContainsDirection(vertices, direction) {
|
|
|
2959
4601
|
}
|
|
2960
4602
|
function fitsHemisphere(vertices) {
|
|
2961
4603
|
const directions = vertices.map((vertex) => panoPositionToVector3(vertex).normalize());
|
|
2962
|
-
const candidates = [new
|
|
4604
|
+
const candidates = [new Vector37()];
|
|
2963
4605
|
for (const direction of directions) {
|
|
2964
4606
|
candidates[0].add(direction);
|
|
2965
4607
|
candidates.push(direction.clone());
|
|
@@ -2983,7 +4625,7 @@ function validatePolygonVertices(vertices) {
|
|
|
2983
4625
|
if (hasSelfIntersection(unwrapped)) {
|
|
2984
4626
|
issues.push({ code: "self_intersection", message: "Polygon edges cannot cross." });
|
|
2985
4627
|
}
|
|
2986
|
-
if (polygonContainsDirection(normalized, new
|
|
4628
|
+
if (polygonContainsDirection(normalized, new Vector37(0, 1, 0)) || polygonContainsDirection(normalized, new Vector37(0, -1, 0))) {
|
|
2987
4629
|
issues.push({ code: "contains_pole", message: "Polygons cannot contain a panorama pole." });
|
|
2988
4630
|
}
|
|
2989
4631
|
if (!fitsHemisphere(normalized)) {
|
|
@@ -2994,7 +4636,7 @@ function validatePolygonVertices(vertices) {
|
|
|
2994
4636
|
function polygonCenter(vertices) {
|
|
2995
4637
|
const sum = vertices.reduce(
|
|
2996
4638
|
(total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
|
|
2997
|
-
new
|
|
4639
|
+
new Vector37()
|
|
2998
4640
|
);
|
|
2999
4641
|
return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
|
|
3000
4642
|
}
|
|
@@ -3003,7 +4645,7 @@ function surfaceQuaternion(position) {
|
|
|
3003
4645
|
const inward = outward.clone().negate();
|
|
3004
4646
|
const pitch = position.pitch * Math.PI / 180;
|
|
3005
4647
|
const yaw = position.yaw * Math.PI / 180;
|
|
3006
|
-
const localUp = new
|
|
4648
|
+
const localUp = new Vector37(
|
|
3007
4649
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
3008
4650
|
Math.cos(pitch),
|
|
3009
4651
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -3016,7 +4658,7 @@ function makeWorldToLocal(center) {
|
|
|
3016
4658
|
return new Matrix43().compose(
|
|
3017
4659
|
panoPositionToVector3(center, POLYGON_RADIUS),
|
|
3018
4660
|
surfaceQuaternion(center),
|
|
3019
|
-
new
|
|
4661
|
+
new Vector37(1, 1, 1)
|
|
3020
4662
|
).invert();
|
|
3021
4663
|
}
|
|
3022
4664
|
function projectedPoint(a, b, c, weightB, weightC, radius, worldToLocal) {
|
|
@@ -3071,7 +4713,7 @@ function addSubdividedTriangle(positions, indices, a, b, c, worldToLocal, steps)
|
|
|
3071
4713
|
}
|
|
3072
4714
|
function makeFillGeometry(vertices, center) {
|
|
3073
4715
|
const unwrapped = unwrapPolygonVertices(vertices);
|
|
3074
|
-
const contour = unwrapped.map((vertex) => new
|
|
4716
|
+
const contour = unwrapped.map((vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch));
|
|
3075
4717
|
const triangles = ShapeUtils.triangulateShape(contour, []);
|
|
3076
4718
|
const positions = [];
|
|
3077
4719
|
const indices = [];
|
|
@@ -3160,38 +4802,38 @@ function PolygonHotspot({
|
|
|
3160
4802
|
onInvalid,
|
|
3161
4803
|
...anchorProps
|
|
3162
4804
|
}) {
|
|
3163
|
-
const gl =
|
|
3164
|
-
const size =
|
|
3165
|
-
const issues =
|
|
4805
|
+
const gl = useThree12((state) => state.gl);
|
|
4806
|
+
const size = useThree12((state) => state.size);
|
|
4807
|
+
const issues = useMemo9(() => validatePolygonVertices(vertices), [vertices]);
|
|
3166
4808
|
const valid = issues.length === 0;
|
|
3167
|
-
const center =
|
|
3168
|
-
const fillGeometry =
|
|
4809
|
+
const center = useMemo9(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
|
|
4810
|
+
const fillGeometry = useMemo9(
|
|
3169
4811
|
() => valid && center ? makeFillGeometry(vertices, center) : null,
|
|
3170
4812
|
[center, valid, vertices]
|
|
3171
4813
|
);
|
|
3172
|
-
const strokePositions =
|
|
4814
|
+
const strokePositions = useMemo9(
|
|
3173
4815
|
() => valid && center ? makeStrokePositions(vertices, center) : null,
|
|
3174
4816
|
[center, valid, vertices]
|
|
3175
4817
|
);
|
|
3176
4818
|
const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3177
4819
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3178
|
-
const strokeLine =
|
|
4820
|
+
const strokeLine = useMemo9(
|
|
3179
4821
|
() => strokePositions ? makeStrokeLine(strokePositions, stroke, strokeWidth * devicePixelRatio, resolvedStrokeOpacity) : null,
|
|
3180
4822
|
[devicePixelRatio, resolvedStrokeOpacity, stroke, strokePositions, strokeWidth]
|
|
3181
4823
|
);
|
|
3182
|
-
const focusStrokeLine =
|
|
4824
|
+
const focusStrokeLine = useMemo9(
|
|
3183
4825
|
() => strokePositions ? makeStrokeLine(strokePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3184
4826
|
[devicePixelRatio, strokePositions, strokeWidth]
|
|
3185
4827
|
);
|
|
3186
|
-
const startVerticesRef =
|
|
4828
|
+
const startVerticesRef = useRef15(null);
|
|
3187
4829
|
const issuesKey = issues.map((issue) => issue.code).join(",");
|
|
3188
|
-
|
|
4830
|
+
useEffect17(() => {
|
|
3189
4831
|
if (!valid) onInvalid?.(issues);
|
|
3190
4832
|
}, [issues, issuesKey, onInvalid, valid]);
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
4833
|
+
useEffect17(() => () => fillGeometry?.dispose(), [fillGeometry]);
|
|
4834
|
+
useEffect17(() => () => disposeStrokeLine(strokeLine), [strokeLine]);
|
|
4835
|
+
useEffect17(() => () => disposeStrokeLine(focusStrokeLine), [focusStrokeLine]);
|
|
4836
|
+
useEffect17(() => {
|
|
3195
4837
|
const width = gl.domElement.width;
|
|
3196
4838
|
const height = gl.domElement.height;
|
|
3197
4839
|
for (const line of [strokeLine, focusStrokeLine]) {
|
|
@@ -3210,7 +4852,7 @@ function PolygonHotspot({
|
|
|
3210
4852
|
startPosition: event.startPosition
|
|
3211
4853
|
});
|
|
3212
4854
|
};
|
|
3213
|
-
return /* @__PURE__ */
|
|
4855
|
+
return /* @__PURE__ */ jsxs9(
|
|
3214
4856
|
HotspotAnchor,
|
|
3215
4857
|
{
|
|
3216
4858
|
...anchorProps,
|
|
@@ -3225,7 +4867,7 @@ function PolygonHotspot({
|
|
|
3225
4867
|
draggable,
|
|
3226
4868
|
interactive,
|
|
3227
4869
|
useAngularScale: false,
|
|
3228
|
-
focusContent: /* @__PURE__ */
|
|
4870
|
+
focusContent: /* @__PURE__ */ jsx11("primitive", { object: focusStrokeLine, renderOrder: renderOrder + 2 }),
|
|
3229
4871
|
onDragStart: (event) => {
|
|
3230
4872
|
startVerticesRef.current = cloneVertices(vertices);
|
|
3231
4873
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3236,7 +4878,7 @@ function PolygonHotspot({
|
|
|
3236
4878
|
startVerticesRef.current = null;
|
|
3237
4879
|
},
|
|
3238
4880
|
children: [
|
|
3239
|
-
/* @__PURE__ */
|
|
4881
|
+
/* @__PURE__ */ jsx11("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx11(
|
|
3240
4882
|
"meshBasicMaterial",
|
|
3241
4883
|
{
|
|
3242
4884
|
color: fill,
|
|
@@ -3246,25 +4888,25 @@ function PolygonHotspot({
|
|
|
3246
4888
|
transparent: true
|
|
3247
4889
|
}
|
|
3248
4890
|
) }),
|
|
3249
|
-
/* @__PURE__ */
|
|
4891
|
+
/* @__PURE__ */ jsx11("primitive", { object: strokeLine, renderOrder: renderOrder + 1 })
|
|
3250
4892
|
]
|
|
3251
4893
|
}
|
|
3252
4894
|
);
|
|
3253
4895
|
}
|
|
3254
4896
|
|
|
3255
4897
|
// src/hotspot/polyline-hotspot.tsx
|
|
3256
|
-
import { useThree as
|
|
3257
|
-
import { useEffect as
|
|
4898
|
+
import { useThree as useThree13 } from "@react-three/fiber";
|
|
4899
|
+
import { useEffect as useEffect18, useMemo as useMemo10, useRef as useRef16 } from "react";
|
|
3258
4900
|
import {
|
|
3259
4901
|
MathUtils as MathUtils5,
|
|
3260
4902
|
Matrix4 as Matrix44,
|
|
3261
4903
|
Quaternion as Quaternion4,
|
|
3262
|
-
Vector3 as
|
|
4904
|
+
Vector3 as Vector38
|
|
3263
4905
|
} from "three";
|
|
3264
4906
|
import { Line2 as Line22 } from "three/addons/lines/Line2.js";
|
|
3265
4907
|
import { LineGeometry as LineGeometry2 } from "three/addons/lines/LineGeometry.js";
|
|
3266
4908
|
import { LineMaterial as LineMaterial2 } from "three/addons/lines/LineMaterial.js";
|
|
3267
|
-
import { jsx as
|
|
4909
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
3268
4910
|
var POLYLINE_RADIUS = 990;
|
|
3269
4911
|
var MAX_SEGMENT_DEGREES = 7;
|
|
3270
4912
|
function cloneVertices2(vertices) {
|
|
@@ -3289,7 +4931,7 @@ function unwrapVertices(vertices) {
|
|
|
3289
4931
|
function polylineCenter(vertices) {
|
|
3290
4932
|
const sum = vertices.reduce(
|
|
3291
4933
|
(total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
|
|
3292
|
-
new
|
|
4934
|
+
new Vector38()
|
|
3293
4935
|
);
|
|
3294
4936
|
return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
|
|
3295
4937
|
}
|
|
@@ -3298,7 +4940,7 @@ function surfaceQuaternion2(position) {
|
|
|
3298
4940
|
const inward = outward.clone().negate();
|
|
3299
4941
|
const pitch = MathUtils5.degToRad(position.pitch);
|
|
3300
4942
|
const yaw = MathUtils5.degToRad(position.yaw);
|
|
3301
|
-
const localUp = new
|
|
4943
|
+
const localUp = new Vector38(
|
|
3302
4944
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
3303
4945
|
Math.cos(pitch),
|
|
3304
4946
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -3311,7 +4953,7 @@ function makeWorldToLocal2(center) {
|
|
|
3311
4953
|
return new Matrix44().compose(
|
|
3312
4954
|
panoPositionToVector3(center, POLYLINE_RADIUS),
|
|
3313
4955
|
surfaceQuaternion2(center),
|
|
3314
|
-
new
|
|
4956
|
+
new Vector38(1, 1, 1)
|
|
3315
4957
|
).invert();
|
|
3316
4958
|
}
|
|
3317
4959
|
function subdivisionSteps(vertices) {
|
|
@@ -3401,32 +5043,32 @@ function PolylineHotspot({
|
|
|
3401
5043
|
onInvalid,
|
|
3402
5044
|
...anchorProps
|
|
3403
5045
|
}) {
|
|
3404
|
-
const gl =
|
|
3405
|
-
const size =
|
|
3406
|
-
const issues =
|
|
5046
|
+
const gl = useThree13((state) => state.gl);
|
|
5047
|
+
const size = useThree13((state) => state.size);
|
|
5048
|
+
const issues = useMemo10(() => validatePolylineVertices(vertices), [vertices]);
|
|
3407
5049
|
const valid = issues.length === 0;
|
|
3408
|
-
const center =
|
|
3409
|
-
const linePositions =
|
|
5050
|
+
const center = useMemo10(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
|
|
5051
|
+
const linePositions = useMemo10(
|
|
3410
5052
|
() => valid && center ? makeLinePositions(vertices, center) : null,
|
|
3411
5053
|
[center, valid, vertices]
|
|
3412
5054
|
);
|
|
3413
5055
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3414
5056
|
const opacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3415
|
-
const line =
|
|
5057
|
+
const line = useMemo10(
|
|
3416
5058
|
() => linePositions ? makeLine(linePositions, stroke, strokeWidth * devicePixelRatio, opacity) : null,
|
|
3417
5059
|
[devicePixelRatio, linePositions, opacity, stroke, strokeWidth]
|
|
3418
5060
|
);
|
|
3419
|
-
const focusLine =
|
|
5061
|
+
const focusLine = useMemo10(
|
|
3420
5062
|
() => linePositions ? makeLine(linePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3421
5063
|
[devicePixelRatio, linePositions, strokeWidth]
|
|
3422
5064
|
);
|
|
3423
|
-
const startVerticesRef =
|
|
3424
|
-
|
|
5065
|
+
const startVerticesRef = useRef16(null);
|
|
5066
|
+
useEffect18(() => {
|
|
3425
5067
|
if (!valid) onInvalid?.(issues);
|
|
3426
5068
|
}, [issues, onInvalid, valid]);
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
5069
|
+
useEffect18(() => () => disposeLine(line), [line]);
|
|
5070
|
+
useEffect18(() => () => disposeLine(focusLine), [focusLine]);
|
|
5071
|
+
useEffect18(() => {
|
|
3430
5072
|
const width = gl.domElement.width;
|
|
3431
5073
|
const height = gl.domElement.height;
|
|
3432
5074
|
for (const nextLine of [line, focusLine]) {
|
|
@@ -3444,7 +5086,7 @@ function PolylineHotspot({
|
|
|
3444
5086
|
startPosition: event.startPosition
|
|
3445
5087
|
});
|
|
3446
5088
|
};
|
|
3447
|
-
return /* @__PURE__ */
|
|
5089
|
+
return /* @__PURE__ */ jsx12(
|
|
3448
5090
|
HotspotAnchor,
|
|
3449
5091
|
{
|
|
3450
5092
|
...anchorProps,
|
|
@@ -3458,7 +5100,7 @@ function PolylineHotspot({
|
|
|
3458
5100
|
renderOrder,
|
|
3459
5101
|
draggable,
|
|
3460
5102
|
useAngularScale: false,
|
|
3461
|
-
focusContent: /* @__PURE__ */
|
|
5103
|
+
focusContent: /* @__PURE__ */ jsx12("primitive", { object: focusLine, renderOrder: renderOrder + 2 }),
|
|
3462
5104
|
onDragStart: (event) => {
|
|
3463
5105
|
startVerticesRef.current = cloneVertices2(vertices);
|
|
3464
5106
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3468,13 +5110,13 @@ function PolylineHotspot({
|
|
|
3468
5110
|
emitVerticesChange(event, onDragEnd);
|
|
3469
5111
|
startVerticesRef.current = null;
|
|
3470
5112
|
},
|
|
3471
|
-
children: /* @__PURE__ */
|
|
5113
|
+
children: /* @__PURE__ */ jsx12("primitive", { object: line, renderOrder: renderOrder + 1 })
|
|
3472
5114
|
}
|
|
3473
5115
|
);
|
|
3474
5116
|
}
|
|
3475
5117
|
|
|
3476
5118
|
// src/hotspot/graphic-hotspot.tsx
|
|
3477
|
-
import { useEffect as
|
|
5119
|
+
import { useEffect as useEffect19, useRef as useRef17, useState as useState8 } from "react";
|
|
3478
5120
|
import {
|
|
3479
5121
|
CanvasTexture,
|
|
3480
5122
|
ClampToEdgeWrapping as ClampToEdgeWrapping4,
|
|
@@ -3483,7 +5125,7 @@ import {
|
|
|
3483
5125
|
LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
|
|
3484
5126
|
SRGBColorSpace as SRGBColorSpace5
|
|
3485
5127
|
} from "three";
|
|
3486
|
-
import { jsx as
|
|
5128
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3487
5129
|
var GRAPHIC_TEXTURE_SIZE = 512;
|
|
3488
5130
|
function clampOpacity2(opacity) {
|
|
3489
5131
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3494,14 +5136,34 @@ function clampOpacity2(opacity) {
|
|
|
3494
5136
|
function resolveStrokeWidth(value) {
|
|
3495
5137
|
return Number.isFinite(value) ? Math.max(value, 0) : 8;
|
|
3496
5138
|
}
|
|
3497
|
-
function
|
|
3498
|
-
|
|
5139
|
+
function resolveCornerRadius(value) {
|
|
5140
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(value, 0.5)) : 0;
|
|
5141
|
+
}
|
|
5142
|
+
function roundedRectanglePath(context, x, y, width, height, radiusRatio, aspectWidth, aspectHeight) {
|
|
5143
|
+
const safeAspectWidth = Number.isFinite(aspectWidth) && aspectWidth > 0 ? aspectWidth : 1;
|
|
5144
|
+
const safeAspectHeight = Number.isFinite(aspectHeight) && aspectHeight > 0 ? aspectHeight : 1;
|
|
5145
|
+
const shortSide = Math.min(safeAspectWidth, safeAspectHeight);
|
|
5146
|
+
const safeRadiusRatio = resolveCornerRadius(radiusRatio);
|
|
5147
|
+
const radiusX = Math.min(width / 2, width * safeRadiusRatio * shortSide / safeAspectWidth);
|
|
5148
|
+
const radiusY = Math.min(height / 2, height * safeRadiusRatio * shortSide / safeAspectHeight);
|
|
5149
|
+
context.beginPath();
|
|
5150
|
+
context.moveTo(x + radiusX, y);
|
|
5151
|
+
context.lineTo(x + width - radiusX, y);
|
|
5152
|
+
context.ellipse(x + width - radiusX, y + radiusY, radiusX, radiusY, 0, -Math.PI / 2, 0);
|
|
5153
|
+
context.lineTo(x + width, y + height - radiusY);
|
|
5154
|
+
context.ellipse(x + width - radiusX, y + height - radiusY, radiusX, radiusY, 0, 0, Math.PI / 2);
|
|
5155
|
+
context.lineTo(x + radiusX, y + height);
|
|
5156
|
+
context.ellipse(x + radiusX, y + height - radiusY, radiusX, radiusY, 0, Math.PI / 2, Math.PI);
|
|
5157
|
+
context.lineTo(x, y + radiusY);
|
|
5158
|
+
context.ellipse(x + radiusX, y + radiusY, radiusX, radiusY, 0, Math.PI, Math.PI * 1.5);
|
|
5159
|
+
context.closePath();
|
|
5160
|
+
}
|
|
5161
|
+
function polygonPath(context, points) {
|
|
3499
5162
|
context.beginPath();
|
|
3500
|
-
context.moveTo(
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
context.arcTo(x, y, x + width, y, safeRadius);
|
|
5163
|
+
context.moveTo(points[0][0], points[0][1]);
|
|
5164
|
+
for (const [x, y] of points.slice(1)) {
|
|
5165
|
+
context.lineTo(x, y);
|
|
5166
|
+
}
|
|
3505
5167
|
context.closePath();
|
|
3506
5168
|
}
|
|
3507
5169
|
function paintPath(context, graphic, path) {
|
|
@@ -3515,7 +5177,7 @@ function paintPath(context, graphic, path) {
|
|
|
3515
5177
|
path ? context.stroke(path) : context.stroke();
|
|
3516
5178
|
}
|
|
3517
5179
|
}
|
|
3518
|
-
function drawGraphic(context, graphic) {
|
|
5180
|
+
function drawGraphic(context, graphic, aspectWidth, aspectHeight) {
|
|
3519
5181
|
const padding = Math.max(resolveStrokeWidth(graphic.strokeWidth) / 2 + 2, 4);
|
|
3520
5182
|
const drawableSize = GRAPHIC_TEXTURE_SIZE - padding * 2;
|
|
3521
5183
|
context.clearRect(0, 0, GRAPHIC_TEXTURE_SIZE, GRAPHIC_TEXTURE_SIZE);
|
|
@@ -3533,6 +5195,63 @@ function drawGraphic(context, graphic) {
|
|
|
3533
5195
|
paintPath(context, graphic);
|
|
3534
5196
|
return;
|
|
3535
5197
|
}
|
|
5198
|
+
if (graphic.kind === "triangle") {
|
|
5199
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5200
|
+
polygonPath(context, [
|
|
5201
|
+
[center, padding],
|
|
5202
|
+
[padding + drawableSize, padding + drawableSize],
|
|
5203
|
+
[padding, padding + drawableSize]
|
|
5204
|
+
]);
|
|
5205
|
+
paintPath(context, graphic);
|
|
5206
|
+
return;
|
|
5207
|
+
}
|
|
5208
|
+
if (graphic.kind === "diamond") {
|
|
5209
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5210
|
+
polygonPath(context, [
|
|
5211
|
+
[center, padding],
|
|
5212
|
+
[padding + drawableSize, center],
|
|
5213
|
+
[center, padding + drawableSize],
|
|
5214
|
+
[padding, center]
|
|
5215
|
+
]);
|
|
5216
|
+
paintPath(context, graphic);
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
5219
|
+
if (graphic.kind === "star") {
|
|
5220
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5221
|
+
const outerRadius = drawableSize / 2;
|
|
5222
|
+
const innerRadius = outerRadius * 0.45;
|
|
5223
|
+
const points = [];
|
|
5224
|
+
for (let index = 0; index < 10; index += 1) {
|
|
5225
|
+
const angle = -Math.PI / 2 + index * Math.PI / 5;
|
|
5226
|
+
const radius = index % 2 === 0 ? outerRadius : innerRadius;
|
|
5227
|
+
points.push([
|
|
5228
|
+
center + Math.cos(angle) * radius,
|
|
5229
|
+
center + Math.sin(angle) * radius
|
|
5230
|
+
]);
|
|
5231
|
+
}
|
|
5232
|
+
polygonPath(context, points);
|
|
5233
|
+
paintPath(context, graphic);
|
|
5234
|
+
return;
|
|
5235
|
+
}
|
|
5236
|
+
if (graphic.kind === "arrow") {
|
|
5237
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5238
|
+
const top = padding;
|
|
5239
|
+
const bottom = padding + drawableSize;
|
|
5240
|
+
const headBottom = top + drawableSize * 0.43;
|
|
5241
|
+
const headHalfWidth = drawableSize * 0.38;
|
|
5242
|
+
const stemHalfWidth = drawableSize * 0.14;
|
|
5243
|
+
polygonPath(context, [
|
|
5244
|
+
[center, top],
|
|
5245
|
+
[center + headHalfWidth, headBottom],
|
|
5246
|
+
[center + stemHalfWidth, headBottom],
|
|
5247
|
+
[center + stemHalfWidth, bottom],
|
|
5248
|
+
[center - stemHalfWidth, bottom],
|
|
5249
|
+
[center - stemHalfWidth, headBottom],
|
|
5250
|
+
[center - headHalfWidth, headBottom]
|
|
5251
|
+
]);
|
|
5252
|
+
paintPath(context, graphic);
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
3536
5255
|
if (graphic.kind === "rectangle") {
|
|
3537
5256
|
roundedRectanglePath(
|
|
3538
5257
|
context,
|
|
@@ -3540,7 +5259,9 @@ function drawGraphic(context, graphic) {
|
|
|
3540
5259
|
padding,
|
|
3541
5260
|
drawableSize,
|
|
3542
5261
|
drawableSize,
|
|
3543
|
-
graphic.cornerRadius ?? 0
|
|
5262
|
+
graphic.cornerRadius ?? 0,
|
|
5263
|
+
aspectWidth,
|
|
5264
|
+
aspectHeight
|
|
3544
5265
|
);
|
|
3545
5266
|
paintPath(context, graphic);
|
|
3546
5267
|
return;
|
|
@@ -3579,7 +5300,7 @@ function drawGraphic(context, graphic) {
|
|
|
3579
5300
|
paintPath(context, graphic, path);
|
|
3580
5301
|
context.restore();
|
|
3581
5302
|
}
|
|
3582
|
-
function createGraphicTexture(graphic) {
|
|
5303
|
+
function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
|
|
3583
5304
|
const canvas = document.createElement("canvas");
|
|
3584
5305
|
canvas.width = GRAPHIC_TEXTURE_SIZE;
|
|
3585
5306
|
canvas.height = GRAPHIC_TEXTURE_SIZE;
|
|
@@ -3587,7 +5308,7 @@ function createGraphicTexture(graphic) {
|
|
|
3587
5308
|
if (!context) {
|
|
3588
5309
|
throw new Error("Canvas 2D is unavailable for this graphic hotspot.");
|
|
3589
5310
|
}
|
|
3590
|
-
drawGraphic(context, graphic);
|
|
5311
|
+
drawGraphic(context, graphic, aspectWidth, aspectHeight);
|
|
3591
5312
|
const texture = new CanvasTexture(canvas);
|
|
3592
5313
|
texture.colorSpace = SRGBColorSpace5;
|
|
3593
5314
|
texture.wrapS = ClampToEdgeWrapping4;
|
|
@@ -3598,16 +5319,16 @@ function createGraphicTexture(graphic) {
|
|
|
3598
5319
|
texture.needsUpdate = true;
|
|
3599
5320
|
return texture;
|
|
3600
5321
|
}
|
|
3601
|
-
function useGraphicTexture(graphic, onLoad, onError) {
|
|
3602
|
-
const [texture, setTexture] =
|
|
3603
|
-
const onLoadRef =
|
|
3604
|
-
const onErrorRef =
|
|
5322
|
+
function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
|
|
5323
|
+
const [texture, setTexture] = useState8(null);
|
|
5324
|
+
const onLoadRef = useRef17(onLoad);
|
|
5325
|
+
const onErrorRef = useRef17(onError);
|
|
3605
5326
|
onLoadRef.current = onLoad;
|
|
3606
5327
|
onErrorRef.current = onError;
|
|
3607
|
-
|
|
5328
|
+
useEffect19(() => {
|
|
3608
5329
|
let nextTexture = null;
|
|
3609
5330
|
try {
|
|
3610
|
-
nextTexture = createGraphicTexture(graphic);
|
|
5331
|
+
nextTexture = createGraphicTexture(graphic, aspectWidth, aspectHeight);
|
|
3611
5332
|
setTexture(nextTexture);
|
|
3612
5333
|
onLoadRef.current?.(nextTexture);
|
|
3613
5334
|
} catch (error) {
|
|
@@ -3617,7 +5338,7 @@ function useGraphicTexture(graphic, onLoad, onError) {
|
|
|
3617
5338
|
return () => {
|
|
3618
5339
|
nextTexture?.dispose();
|
|
3619
5340
|
};
|
|
3620
|
-
}, [graphic]);
|
|
5341
|
+
}, [aspectHeight, aspectWidth, graphic]);
|
|
3621
5342
|
return texture;
|
|
3622
5343
|
}
|
|
3623
5344
|
function CanvasGraphicHotspot({
|
|
@@ -3629,10 +5350,10 @@ function CanvasGraphicHotspot({
|
|
|
3629
5350
|
onError,
|
|
3630
5351
|
...anchorProps
|
|
3631
5352
|
}) {
|
|
3632
|
-
const texture = useGraphicTexture(graphic, onLoad, onError);
|
|
3633
|
-
return /* @__PURE__ */
|
|
3634
|
-
/* @__PURE__ */
|
|
3635
|
-
/* @__PURE__ */
|
|
5353
|
+
const texture = useGraphicTexture(graphic, width, height, onLoad, onError);
|
|
5354
|
+
return /* @__PURE__ */ jsx13(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsxs10("mesh", { children: [
|
|
5355
|
+
/* @__PURE__ */ jsx13("planeGeometry", { args: [1, 1] }),
|
|
5356
|
+
/* @__PURE__ */ jsx13(
|
|
3636
5357
|
"meshBasicMaterial",
|
|
3637
5358
|
{
|
|
3638
5359
|
map: texture,
|
|
@@ -3648,14 +5369,14 @@ function GraphicHotspot(props) {
|
|
|
3648
5369
|
const { graphic, ...restProps } = props;
|
|
3649
5370
|
if (graphic.kind === "svg") {
|
|
3650
5371
|
const imageProps = restProps;
|
|
3651
|
-
return /* @__PURE__ */
|
|
5372
|
+
return /* @__PURE__ */ jsx13(ImageHotspot, { ...imageProps, src: graphic.src });
|
|
3652
5373
|
}
|
|
3653
|
-
return /* @__PURE__ */
|
|
5374
|
+
return /* @__PURE__ */ jsx13(CanvasGraphicHotspot, { ...restProps, graphic });
|
|
3654
5375
|
}
|
|
3655
5376
|
|
|
3656
5377
|
// src/hotspot/sequence-hotspot.tsx
|
|
3657
|
-
import { useFrame as
|
|
3658
|
-
import { useEffect as
|
|
5378
|
+
import { useFrame as useFrame8, useThree as useThree14 } from "@react-three/fiber";
|
|
5379
|
+
import { useEffect as useEffect20, useRef as useRef18, useState as useState9 } from "react";
|
|
3659
5380
|
import {
|
|
3660
5381
|
ClampToEdgeWrapping as ClampToEdgeWrapping5,
|
|
3661
5382
|
DoubleSide as DoubleSide5,
|
|
@@ -3664,7 +5385,7 @@ import {
|
|
|
3664
5385
|
SRGBColorSpace as SRGBColorSpace6,
|
|
3665
5386
|
TextureLoader as TextureLoader5
|
|
3666
5387
|
} from "three";
|
|
3667
|
-
import { jsx as
|
|
5388
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3668
5389
|
var DEFAULT_SEQUENCE_FPS = 12;
|
|
3669
5390
|
function clampOpacity3(opacity) {
|
|
3670
5391
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3684,13 +5405,13 @@ function useSpriteTexture({
|
|
|
3684
5405
|
onError,
|
|
3685
5406
|
onLoadProgress
|
|
3686
5407
|
}) {
|
|
3687
|
-
const gl =
|
|
3688
|
-
const [texture, setTexture] =
|
|
3689
|
-
const onErrorRef =
|
|
3690
|
-
const onLoadProgressRef =
|
|
5408
|
+
const gl = useThree14((state) => state.gl);
|
|
5409
|
+
const [texture, setTexture] = useState9(null);
|
|
5410
|
+
const onErrorRef = useRef18(onError);
|
|
5411
|
+
const onLoadProgressRef = useRef18(onLoadProgress);
|
|
3691
5412
|
onErrorRef.current = onError;
|
|
3692
5413
|
onLoadProgressRef.current = onLoadProgress;
|
|
3693
|
-
|
|
5414
|
+
useEffect20(() => {
|
|
3694
5415
|
let active = true;
|
|
3695
5416
|
setTexture(null);
|
|
3696
5417
|
if (!src) {
|
|
@@ -3731,7 +5452,7 @@ function useSpriteTexture({
|
|
|
3731
5452
|
active = false;
|
|
3732
5453
|
};
|
|
3733
5454
|
}, [gl, id, src]);
|
|
3734
|
-
|
|
5455
|
+
useEffect20(
|
|
3735
5456
|
() => () => {
|
|
3736
5457
|
texture?.dispose();
|
|
3737
5458
|
},
|
|
@@ -3760,24 +5481,24 @@ function SequenceHotspot({
|
|
|
3760
5481
|
const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
|
|
3761
5482
|
const frameCount = resolveFrameCount(requestedFrameCount);
|
|
3762
5483
|
const safeFps = resolveFps(fps);
|
|
3763
|
-
const [frameIndex, setFrameIndex] =
|
|
3764
|
-
const elapsedRef =
|
|
3765
|
-
const frameIndexRef =
|
|
3766
|
-
const endedRef =
|
|
3767
|
-
const lastPlaybackStateRef =
|
|
3768
|
-
const onFrameChangeRef =
|
|
3769
|
-
const onPlaybackStateChangeRef =
|
|
3770
|
-
const onEndedRef =
|
|
5484
|
+
const [frameIndex, setFrameIndex] = useState9(0);
|
|
5485
|
+
const elapsedRef = useRef18(0);
|
|
5486
|
+
const frameIndexRef = useRef18(0);
|
|
5487
|
+
const endedRef = useRef18(false);
|
|
5488
|
+
const lastPlaybackStateRef = useRef18(null);
|
|
5489
|
+
const onFrameChangeRef = useRef18(onFrameChange);
|
|
5490
|
+
const onPlaybackStateChangeRef = useRef18(onPlaybackStateChange);
|
|
5491
|
+
const onEndedRef = useRef18(onEnded);
|
|
3771
5492
|
onFrameChangeRef.current = onFrameChange;
|
|
3772
5493
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3773
5494
|
onEndedRef.current = onEnded;
|
|
3774
|
-
|
|
5495
|
+
useEffect20(() => {
|
|
3775
5496
|
elapsedRef.current = 0;
|
|
3776
5497
|
frameIndexRef.current = 0;
|
|
3777
5498
|
endedRef.current = false;
|
|
3778
5499
|
setFrameIndex(0);
|
|
3779
5500
|
}, [frameCount, frameDirection, src]);
|
|
3780
|
-
|
|
5501
|
+
useEffect20(() => {
|
|
3781
5502
|
if (!texture) {
|
|
3782
5503
|
return;
|
|
3783
5504
|
}
|
|
@@ -3791,13 +5512,13 @@ function SequenceHotspot({
|
|
|
3791
5512
|
);
|
|
3792
5513
|
texture.needsUpdate = true;
|
|
3793
5514
|
}, [frameCount, frameDirection, frameIndex, texture]);
|
|
3794
|
-
|
|
5515
|
+
useEffect20(() => {
|
|
3795
5516
|
if (!texture) {
|
|
3796
5517
|
return;
|
|
3797
5518
|
}
|
|
3798
5519
|
onFrameChangeRef.current?.({ id, index: 0, src });
|
|
3799
5520
|
}, [id, src, texture]);
|
|
3800
|
-
|
|
5521
|
+
useEffect20(() => {
|
|
3801
5522
|
if (!texture) {
|
|
3802
5523
|
return;
|
|
3803
5524
|
}
|
|
@@ -3816,7 +5537,7 @@ function SequenceHotspot({
|
|
|
3816
5537
|
onPlaybackStateChangeRef.current?.(nextState);
|
|
3817
5538
|
}
|
|
3818
5539
|
}, [playing, texture]);
|
|
3819
|
-
|
|
5540
|
+
useFrame8((_, deltaSeconds) => {
|
|
3820
5541
|
if (!texture || !playing || endedRef.current) {
|
|
3821
5542
|
return;
|
|
3822
5543
|
}
|
|
@@ -3846,9 +5567,9 @@ function SequenceHotspot({
|
|
|
3846
5567
|
onFrameChangeRef.current?.({ id, index: nextIndex, src });
|
|
3847
5568
|
}
|
|
3848
5569
|
});
|
|
3849
|
-
return /* @__PURE__ */
|
|
3850
|
-
/* @__PURE__ */
|
|
3851
|
-
/* @__PURE__ */
|
|
5570
|
+
return /* @__PURE__ */ jsx14(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsxs11("mesh", { children: [
|
|
5571
|
+
/* @__PURE__ */ jsx14("planeGeometry", { args: [1, 1] }),
|
|
5572
|
+
/* @__PURE__ */ jsx14(
|
|
3852
5573
|
"meshBasicMaterial",
|
|
3853
5574
|
{
|
|
3854
5575
|
map: texture,
|
|
@@ -3862,8 +5583,8 @@ function SequenceHotspot({
|
|
|
3862
5583
|
}
|
|
3863
5584
|
|
|
3864
5585
|
// src/hotspot/video-hotspot.tsx
|
|
3865
|
-
import { useThree as
|
|
3866
|
-
import { useEffect as
|
|
5586
|
+
import { useThree as useThree15 } from "@react-three/fiber";
|
|
5587
|
+
import { useEffect as useEffect21, useRef as useRef19, useState as useState10 } from "react";
|
|
3867
5588
|
import {
|
|
3868
5589
|
ClampToEdgeWrapping as ClampToEdgeWrapping6,
|
|
3869
5590
|
DoubleSide as DoubleSide6,
|
|
@@ -3873,7 +5594,7 @@ import {
|
|
|
3873
5594
|
TextureLoader as TextureLoader6,
|
|
3874
5595
|
VideoTexture
|
|
3875
5596
|
} from "three";
|
|
3876
|
-
import { jsx as
|
|
5597
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3877
5598
|
function clampOpacity4(opacity) {
|
|
3878
5599
|
if (!Number.isFinite(opacity)) {
|
|
3879
5600
|
return 1;
|
|
@@ -3887,11 +5608,11 @@ function clampVolume(volume) {
|
|
|
3887
5608
|
return Math.max(0, Math.min(volume, 1));
|
|
3888
5609
|
}
|
|
3889
5610
|
function usePosterTexture(id, poster, onError) {
|
|
3890
|
-
const gl =
|
|
3891
|
-
const [texture, setTexture] =
|
|
3892
|
-
const onErrorRef =
|
|
5611
|
+
const gl = useThree15((state) => state.gl);
|
|
5612
|
+
const [texture, setTexture] = useState10(null);
|
|
5613
|
+
const onErrorRef = useRef19(onError);
|
|
3893
5614
|
onErrorRef.current = onError;
|
|
3894
|
-
|
|
5615
|
+
useEffect21(() => {
|
|
3895
5616
|
let active = true;
|
|
3896
5617
|
setTexture(null);
|
|
3897
5618
|
if (!poster) {
|
|
@@ -3928,7 +5649,7 @@ function usePosterTexture(id, poster, onError) {
|
|
|
3928
5649
|
active = false;
|
|
3929
5650
|
};
|
|
3930
5651
|
}, [gl, id, poster]);
|
|
3931
|
-
|
|
5652
|
+
useEffect21(
|
|
3932
5653
|
() => () => {
|
|
3933
5654
|
texture?.dispose();
|
|
3934
5655
|
},
|
|
@@ -3946,14 +5667,14 @@ function useVideoResource({
|
|
|
3946
5667
|
poster,
|
|
3947
5668
|
src
|
|
3948
5669
|
}) {
|
|
3949
|
-
const [resource, setResource] =
|
|
3950
|
-
const onEndedRef =
|
|
3951
|
-
const onErrorRef =
|
|
3952
|
-
const onPlaybackStateChangeRef =
|
|
5670
|
+
const [resource, setResource] = useState10(null);
|
|
5671
|
+
const onEndedRef = useRef19(onEnded);
|
|
5672
|
+
const onErrorRef = useRef19(onError);
|
|
5673
|
+
const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
|
|
3953
5674
|
onEndedRef.current = onEnded;
|
|
3954
5675
|
onErrorRef.current = onError;
|
|
3955
5676
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3956
|
-
|
|
5677
|
+
useEffect21(() => {
|
|
3957
5678
|
if (!src) {
|
|
3958
5679
|
setResource(null);
|
|
3959
5680
|
return;
|
|
@@ -4054,11 +5775,11 @@ function VideoHotspot({
|
|
|
4054
5775
|
src
|
|
4055
5776
|
});
|
|
4056
5777
|
const posterTexture = usePosterTexture(id, poster, onError);
|
|
4057
|
-
const onPlaybackErrorRef =
|
|
4058
|
-
const onPlaybackStateChangeRef =
|
|
5778
|
+
const onPlaybackErrorRef = useRef19(onPlaybackError);
|
|
5779
|
+
const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
|
|
4059
5780
|
onPlaybackErrorRef.current = onPlaybackError;
|
|
4060
5781
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
4061
|
-
|
|
5782
|
+
useEffect21(() => {
|
|
4062
5783
|
if (!resource) {
|
|
4063
5784
|
return;
|
|
4064
5785
|
}
|
|
@@ -4068,7 +5789,7 @@ function VideoHotspot({
|
|
|
4068
5789
|
resource.video.playsInline = playsInline;
|
|
4069
5790
|
resource.video.preload = preload;
|
|
4070
5791
|
}, [loop, muted, playsInline, preload, resource, volume]);
|
|
4071
|
-
|
|
5792
|
+
useEffect21(() => {
|
|
4072
5793
|
if (!resource) {
|
|
4073
5794
|
return;
|
|
4074
5795
|
}
|
|
@@ -4091,9 +5812,9 @@ function VideoHotspot({
|
|
|
4091
5812
|
};
|
|
4092
5813
|
}, [playing, resource]);
|
|
4093
5814
|
const activeTexture = resource?.ready ? resource.texture : posterTexture;
|
|
4094
|
-
return /* @__PURE__ */
|
|
4095
|
-
/* @__PURE__ */
|
|
4096
|
-
/* @__PURE__ */
|
|
5815
|
+
return /* @__PURE__ */ jsx15(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsxs12("mesh", { children: [
|
|
5816
|
+
/* @__PURE__ */ jsx15("planeGeometry", { args: [1, 1] }),
|
|
5817
|
+
/* @__PURE__ */ jsx15(
|
|
4097
5818
|
"meshBasicMaterial",
|
|
4098
5819
|
{
|
|
4099
5820
|
map: activeTexture,
|
|
@@ -4109,7 +5830,12 @@ export {
|
|
|
4109
5830
|
AutoRotate,
|
|
4110
5831
|
GraphicHotspot,
|
|
4111
5832
|
ImageHotspot,
|
|
5833
|
+
KeyboardControls,
|
|
4112
5834
|
MAX_HOTSPOT_PITCH,
|
|
5835
|
+
MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY,
|
|
5836
|
+
MouseControls,
|
|
5837
|
+
PanoContextMenu,
|
|
5838
|
+
PanoEvents,
|
|
4113
5839
|
PanoView,
|
|
4114
5840
|
PanoramaScenes,
|
|
4115
5841
|
PolygonHotspot,
|
|
@@ -4117,12 +5843,19 @@ export {
|
|
|
4117
5843
|
SequenceHotspot,
|
|
4118
5844
|
Sphere,
|
|
4119
5845
|
Tile,
|
|
5846
|
+
TouchControls,
|
|
4120
5847
|
VideoHotspot,
|
|
4121
5848
|
clampPanoPitch,
|
|
5849
|
+
composePanoContextMenuItems,
|
|
5850
|
+
createDefaultPanoContextMenuItems,
|
|
5851
|
+
createPanoContextMenuPresets,
|
|
5852
|
+
cycleSceneId,
|
|
4122
5853
|
normalizePanoPosition,
|
|
4123
5854
|
normalizePanoYaw,
|
|
4124
5855
|
panoPositionToVector3,
|
|
5856
|
+
resolvePanoContextMenuEntries,
|
|
4125
5857
|
unwrapPolygonVertices,
|
|
5858
|
+
usePanoEvents,
|
|
4126
5859
|
validatePolygonVertices,
|
|
4127
5860
|
validatePolylineVertices,
|
|
4128
5861
|
vector3ToPanoPosition
|