@ericchen1990/pano-view 0.1.5 → 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 +276 -7
- package/dist/index.cjs +2410 -653
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +395 -12
- package/dist/index.d.ts +395 -12
- package/dist/index.mjs +2423 -671
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,30 +4,70 @@
|
|
|
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,
|
|
20
58
|
useRef
|
|
21
59
|
} from "react";
|
|
22
60
|
import { Euler, MathUtils, PerspectiveCamera } from "three";
|
|
23
|
-
var MAX_PITCH =
|
|
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,131 +576,1649 @@ 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
|
-
|
|
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 [];
|
|
568
607
|
}
|
|
569
|
-
return
|
|
608
|
+
return (typeof value === "string" ? [value] : [...value]).filter(
|
|
609
|
+
(entry) => entry.length > 0
|
|
610
|
+
);
|
|
570
611
|
}
|
|
571
|
-
function
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
619
|
}
|
|
575
|
-
return
|
|
620
|
+
return map;
|
|
576
621
|
}
|
|
577
|
-
function
|
|
578
|
-
return
|
|
579
|
-
yaw: normalizePanoYaw(position.yaw),
|
|
580
|
-
pitch: clampPanoPitch(position.pitch)
|
|
581
|
-
};
|
|
622
|
+
function matchAction(event, keyMap) {
|
|
623
|
+
return keyMap.get(event.key.toLowerCase()) ?? keyMap.get(event.code.toLowerCase()) ?? null;
|
|
582
624
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 1;
|
|
586
|
-
const yaw = MathUtils2.degToRad(normalized.yaw);
|
|
587
|
-
const pitch = MathUtils2.degToRad(normalized.pitch);
|
|
588
|
-
const cosPitch = Math.cos(pitch);
|
|
589
|
-
return new Vector3(
|
|
590
|
-
cosPitch * Math.sin(yaw) * safeRadius,
|
|
591
|
-
Math.sin(pitch) * safeRadius,
|
|
592
|
-
-cosPitch * Math.cos(yaw) * safeRadius
|
|
593
|
-
);
|
|
625
|
+
function resolveSpeed(value, fallback) {
|
|
626
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
594
627
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return { yaw: 0, pitch: 0 };
|
|
628
|
+
function cycleSceneId(scenes, activeSceneId, step) {
|
|
629
|
+
if (scenes.length === 0) {
|
|
630
|
+
return null;
|
|
599
631
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
Math.asin(MathUtils2.clamp(vector.y / length, -1, 1))
|
|
604
|
-
)
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// src/panorama-radius.ts
|
|
609
|
-
var DEFAULT_PANORAMA_RADIUS = 1e3;
|
|
610
|
-
var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
|
|
611
|
-
var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
|
|
612
|
-
|
|
613
|
-
// src/panorama-event-surface.tsx
|
|
614
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
615
|
-
var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
|
|
616
|
-
function makePanoramaPointerEvent(event) {
|
|
617
|
-
const nativeEvent = event.nativeEvent;
|
|
618
|
-
return {
|
|
619
|
-
position: vector3ToPanoPosition(event.ray.direction),
|
|
620
|
-
nativeEvent,
|
|
621
|
-
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
622
|
-
button: nativeEvent.button,
|
|
623
|
-
buttons: nativeEvent.buttons,
|
|
624
|
-
clientX: nativeEvent.clientX,
|
|
625
|
-
clientY: nativeEvent.clientY
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
function emitPanoramaEvent(event, callback) {
|
|
629
|
-
if (!callback) {
|
|
630
|
-
return;
|
|
632
|
+
const index = scenes.findIndex((scene) => scene.id === activeSceneId);
|
|
633
|
+
if (index < 0) {
|
|
634
|
+
return null;
|
|
631
635
|
}
|
|
632
|
-
|
|
633
|
-
|
|
636
|
+
const nextIndex = (index + step + scenes.length) % scenes.length;
|
|
637
|
+
return scenes[nextIndex]?.id ?? null;
|
|
634
638
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
|
639
649
|
}) {
|
|
640
|
-
|
|
641
|
-
|
|
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>.");
|
|
642
661
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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,
|
|
2027
|
+
opacity: 0,
|
|
2028
|
+
side: BackSide,
|
|
2029
|
+
transparent: true
|
|
2030
|
+
}
|
|
2031
|
+
)
|
|
2032
|
+
]
|
|
2033
|
+
}
|
|
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;
|
|
664
2077
|
}
|
|
665
|
-
|
|
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;
|
|
666
2181
|
}
|
|
667
2182
|
|
|
668
2183
|
// src/pano-view.tsx
|
|
669
|
-
import { jsx as
|
|
2184
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
670
2185
|
var DEFAULT_VIEW = {
|
|
671
2186
|
yaw: 0,
|
|
672
2187
|
pitch: 0,
|
|
673
2188
|
fov: 75
|
|
674
2189
|
};
|
|
2190
|
+
var DEFAULT_POINTER_ROTATE_SPEED = 0.35;
|
|
2191
|
+
var DEFAULT_MOUSE_ZOOM_SPEED = 0.08;
|
|
675
2192
|
var DEFAULT_CANVAS_STYLE = {
|
|
676
2193
|
display: "block",
|
|
677
2194
|
height: "100%",
|
|
678
2195
|
width: "100%"
|
|
679
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
|
+
}
|
|
680
2222
|
var PanoView = forwardRef2(
|
|
681
2223
|
function PanoView2({
|
|
682
2224
|
children,
|
|
@@ -684,6 +2226,7 @@ var PanoView = forwardRef2(
|
|
|
684
2226
|
minFov = 30,
|
|
685
2227
|
maxFov = 100,
|
|
686
2228
|
controls = true,
|
|
2229
|
+
contextMenu = true,
|
|
687
2230
|
onViewChange,
|
|
688
2231
|
onPanoramaClick,
|
|
689
2232
|
onPanoramaDoubleClick,
|
|
@@ -693,19 +2236,21 @@ var PanoView = forwardRef2(
|
|
|
693
2236
|
"aria-label": ariaLabel = "Panorama viewer",
|
|
694
2237
|
...divProps
|
|
695
2238
|
}, ref) {
|
|
696
|
-
const rootRef =
|
|
697
|
-
const controlsRef =
|
|
2239
|
+
const rootRef = useRef9(null);
|
|
2240
|
+
const controlsRef = useRef9(null);
|
|
2241
|
+
const eventBus = useMemo4(() => createPanoEventBus(), []);
|
|
698
2242
|
const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
|
|
699
|
-
const
|
|
2243
|
+
const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
|
|
2244
|
+
const fallbackViewRef = useRef9(DEFAULT_VIEW);
|
|
700
2245
|
const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
|
|
701
2246
|
const normalizedMaxFov = Math.min(
|
|
702
2247
|
179,
|
|
703
2248
|
Math.max(maxFov, normalizedMinFov + 1)
|
|
704
2249
|
);
|
|
705
|
-
const normalizedInitialView =
|
|
2250
|
+
const normalizedInitialView = useMemo4(
|
|
706
2251
|
() => ({
|
|
707
2252
|
yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
|
|
708
|
-
pitch: initialView?.pitch ?? DEFAULT_VIEW.pitch,
|
|
2253
|
+
pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
|
|
709
2254
|
fov: Math.max(
|
|
710
2255
|
normalizedMinFov,
|
|
711
2256
|
Math.min(initialView?.fov ?? DEFAULT_VIEW.fov, normalizedMaxFov)
|
|
@@ -721,15 +2266,98 @@ var PanoView = forwardRef2(
|
|
|
721
2266
|
);
|
|
722
2267
|
fallbackViewRef.current = normalizedInitialView;
|
|
723
2268
|
const controlsEnabled = controls !== false;
|
|
724
|
-
const controlOptions =
|
|
2269
|
+
const controlOptions = useMemo4(
|
|
725
2270
|
() => typeof controls === "object" ? controls : {},
|
|
726
2271
|
[controls]
|
|
727
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
|
+
);
|
|
728
2356
|
const legacyAutoRotateOptions = controlOptions;
|
|
729
|
-
const [legacyAutoRotate, setLegacyAutoRotate] =
|
|
2357
|
+
const [legacyAutoRotate, setLegacyAutoRotate] = useState3(
|
|
730
2358
|
legacyAutoRotateOptions.autoRotate ?? false
|
|
731
2359
|
);
|
|
732
|
-
|
|
2360
|
+
useEffect10(() => {
|
|
733
2361
|
setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
|
|
734
2362
|
}, [legacyAutoRotateOptions.autoRotate]);
|
|
735
2363
|
useImperativeHandle2(
|
|
@@ -748,20 +2376,11 @@ var PanoView = forwardRef2(
|
|
|
748
2376
|
stopAutoRotate: () => {
|
|
749
2377
|
setLegacyAutoRotate(false);
|
|
750
2378
|
},
|
|
751
|
-
toggleFullscreen
|
|
752
|
-
if (typeof document === "undefined") {
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
if (document.fullscreenElement) {
|
|
756
|
-
await document.exitFullscreen?.();
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
await rootRef.current?.requestFullscreen?.();
|
|
760
|
-
}
|
|
2379
|
+
toggleFullscreen
|
|
761
2380
|
}),
|
|
762
|
-
[]
|
|
2381
|
+
[toggleFullscreen]
|
|
763
2382
|
);
|
|
764
|
-
const rootStyle =
|
|
2383
|
+
const rootStyle = useMemo4(
|
|
765
2384
|
() => ({
|
|
766
2385
|
overflow: "hidden",
|
|
767
2386
|
position: "relative",
|
|
@@ -769,7 +2388,7 @@ var PanoView = forwardRef2(
|
|
|
769
2388
|
}),
|
|
770
2389
|
[style]
|
|
771
2390
|
);
|
|
772
|
-
return /* @__PURE__ */
|
|
2391
|
+
return /* @__PURE__ */ jsxs3(
|
|
773
2392
|
"div",
|
|
774
2393
|
{
|
|
775
2394
|
...divProps,
|
|
@@ -777,7 +2396,7 @@ var PanoView = forwardRef2(
|
|
|
777
2396
|
"aria-label": ariaLabel,
|
|
778
2397
|
style: rootStyle,
|
|
779
2398
|
children: [
|
|
780
|
-
/* @__PURE__ */
|
|
2399
|
+
/* @__PURE__ */ jsx4(
|
|
781
2400
|
Canvas,
|
|
782
2401
|
{
|
|
783
2402
|
"aria-label": `${ariaLabel} canvas`,
|
|
@@ -797,41 +2416,71 @@ var PanoView = forwardRef2(
|
|
|
797
2416
|
},
|
|
798
2417
|
style: {
|
|
799
2418
|
...DEFAULT_CANVAS_STYLE,
|
|
800
|
-
touchAction:
|
|
2419
|
+
touchAction: userControlsEnabled ? "none" : "auto"
|
|
801
2420
|
},
|
|
802
|
-
tabIndex:
|
|
803
|
-
children: /* @__PURE__ */
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
+
) }) }) })
|
|
833
2481
|
}
|
|
834
2482
|
),
|
|
2483
|
+
contextMenuOverlay,
|
|
835
2484
|
hotspotAccessibilityControls
|
|
836
2485
|
]
|
|
837
2486
|
}
|
|
@@ -840,8 +2489,8 @@ var PanoView = forwardRef2(
|
|
|
840
2489
|
);
|
|
841
2490
|
|
|
842
2491
|
// src/sphere.tsx
|
|
843
|
-
import { useThree as
|
|
844
|
-
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";
|
|
845
2494
|
import {
|
|
846
2495
|
BackSide as BackSide2,
|
|
847
2496
|
LinearFilter,
|
|
@@ -851,7 +2500,7 @@ import {
|
|
|
851
2500
|
SRGBColorSpace,
|
|
852
2501
|
TextureLoader
|
|
853
2502
|
} from "three";
|
|
854
|
-
import { jsx as
|
|
2503
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
855
2504
|
function Sphere({
|
|
856
2505
|
src,
|
|
857
2506
|
yawOffset = 0,
|
|
@@ -859,13 +2508,13 @@ function Sphere({
|
|
|
859
2508
|
onError,
|
|
860
2509
|
visible = true
|
|
861
2510
|
}) {
|
|
862
|
-
const gl =
|
|
863
|
-
const [texture, setTexture] =
|
|
864
|
-
const onLoadRef =
|
|
865
|
-
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);
|
|
866
2515
|
onLoadRef.current = onLoad;
|
|
867
2516
|
onErrorRef.current = onError;
|
|
868
|
-
|
|
2517
|
+
useEffect11(() => {
|
|
869
2518
|
let active = true;
|
|
870
2519
|
const loader = new TextureLoader();
|
|
871
2520
|
setTexture(null);
|
|
@@ -899,7 +2548,7 @@ function Sphere({
|
|
|
899
2548
|
active = false;
|
|
900
2549
|
};
|
|
901
2550
|
}, [gl, src]);
|
|
902
|
-
|
|
2551
|
+
useEffect11(
|
|
903
2552
|
() => () => {
|
|
904
2553
|
texture?.dispose();
|
|
905
2554
|
},
|
|
@@ -908,14 +2557,14 @@ function Sphere({
|
|
|
908
2557
|
if (!texture) {
|
|
909
2558
|
return null;
|
|
910
2559
|
}
|
|
911
|
-
return /* @__PURE__ */
|
|
2560
|
+
return /* @__PURE__ */ jsxs4(
|
|
912
2561
|
"mesh",
|
|
913
2562
|
{
|
|
914
2563
|
rotation: [0, MathUtils3.degToRad(90 + yawOffset), 0],
|
|
915
2564
|
visible,
|
|
916
2565
|
children: [
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */
|
|
2566
|
+
/* @__PURE__ */ jsx5("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
|
|
2567
|
+
/* @__PURE__ */ jsx5(
|
|
919
2568
|
"meshBasicMaterial",
|
|
920
2569
|
{
|
|
921
2570
|
map: texture,
|
|
@@ -929,15 +2578,15 @@ function Sphere({
|
|
|
929
2578
|
}
|
|
930
2579
|
|
|
931
2580
|
// src/panorama-scenes.tsx
|
|
932
|
-
import { useFrame as
|
|
2581
|
+
import { useFrame as useFrame6, useThree as useThree10 } from "@react-three/fiber";
|
|
933
2582
|
import {
|
|
934
2583
|
Fragment,
|
|
935
|
-
useCallback as
|
|
936
|
-
useContext as
|
|
937
|
-
useEffect as
|
|
938
|
-
useMemo as
|
|
939
|
-
useRef as
|
|
940
|
-
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
|
|
941
2590
|
} from "react";
|
|
942
2591
|
import {
|
|
943
2592
|
DoubleSide,
|
|
@@ -945,17 +2594,17 @@ import {
|
|
|
945
2594
|
LinearFilter as LinearFilter4,
|
|
946
2595
|
PlaneGeometry as PlaneGeometry2,
|
|
947
2596
|
ShaderMaterial,
|
|
948
|
-
Vector3 as
|
|
2597
|
+
Vector3 as Vector35,
|
|
949
2598
|
WebGLRenderTarget
|
|
950
2599
|
} from "three";
|
|
951
2600
|
|
|
952
2601
|
// src/tile/texture-manager-context.tsx
|
|
953
|
-
import { useThree as
|
|
2602
|
+
import { useThree as useThree8 } from "@react-three/fiber";
|
|
954
2603
|
import {
|
|
955
|
-
createContext as
|
|
956
|
-
useContext as
|
|
957
|
-
useEffect as
|
|
958
|
-
useMemo as
|
|
2604
|
+
createContext as createContext5,
|
|
2605
|
+
useContext as useContext9,
|
|
2606
|
+
useEffect as useEffect12,
|
|
2607
|
+
useMemo as useMemo5
|
|
959
2608
|
} from "react";
|
|
960
2609
|
|
|
961
2610
|
// src/tile/texture-manager.ts
|
|
@@ -1185,8 +2834,8 @@ var TileTextureManager = class {
|
|
|
1185
2834
|
};
|
|
1186
2835
|
|
|
1187
2836
|
// src/tile/texture-manager-context.tsx
|
|
1188
|
-
import { jsx as
|
|
1189
|
-
var TileTextureManagerContext =
|
|
2837
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2838
|
+
var TileTextureManagerContext = createContext5(
|
|
1190
2839
|
null
|
|
1191
2840
|
);
|
|
1192
2841
|
function TileTextureManagerProvider({
|
|
@@ -1194,9 +2843,9 @@ function TileTextureManagerProvider({
|
|
|
1194
2843
|
maxTextureMemoryMb = 128,
|
|
1195
2844
|
maxConcurrentLoads = 4
|
|
1196
2845
|
}) {
|
|
1197
|
-
const gl =
|
|
2846
|
+
const gl = useThree8((state) => state.gl);
|
|
1198
2847
|
const anisotropy = gl.capabilities.getMaxAnisotropy();
|
|
1199
|
-
const manager =
|
|
2848
|
+
const manager = useMemo5(
|
|
1200
2849
|
() => new TileTextureManager({
|
|
1201
2850
|
anisotropy,
|
|
1202
2851
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1205,24 +2854,24 @@ function TileTextureManagerProvider({
|
|
|
1205
2854
|
}),
|
|
1206
2855
|
[anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
|
|
1207
2856
|
);
|
|
1208
|
-
|
|
2857
|
+
useEffect12(() => {
|
|
1209
2858
|
manager.resume();
|
|
1210
2859
|
return () => manager.dispose();
|
|
1211
2860
|
}, [manager]);
|
|
1212
|
-
return /* @__PURE__ */
|
|
2861
|
+
return /* @__PURE__ */ jsx6(TileTextureManagerContext.Provider, { value: manager, children });
|
|
1213
2862
|
}
|
|
1214
2863
|
function useSharedTileTextureManager() {
|
|
1215
|
-
return
|
|
2864
|
+
return useContext9(TileTextureManagerContext);
|
|
1216
2865
|
}
|
|
1217
2866
|
|
|
1218
2867
|
// src/tile/tile.tsx
|
|
1219
|
-
import { useFrame as
|
|
2868
|
+
import { useFrame as useFrame5, useThree as useThree9 } from "@react-three/fiber";
|
|
1220
2869
|
import {
|
|
1221
2870
|
memo,
|
|
1222
|
-
useEffect as
|
|
1223
|
-
useMemo as
|
|
1224
|
-
useRef as
|
|
1225
|
-
useState as
|
|
2871
|
+
useEffect as useEffect13,
|
|
2872
|
+
useMemo as useMemo6,
|
|
2873
|
+
useRef as useRef11,
|
|
2874
|
+
useState as useState5
|
|
1226
2875
|
} from "react";
|
|
1227
2876
|
import {
|
|
1228
2877
|
ClampToEdgeWrapping as ClampToEdgeWrapping2,
|
|
@@ -1233,7 +2882,7 @@ import {
|
|
|
1233
2882
|
PerspectiveCamera as PerspectiveCamera2,
|
|
1234
2883
|
SRGBColorSpace as SRGBColorSpace3,
|
|
1235
2884
|
TextureLoader as TextureLoader3,
|
|
1236
|
-
Vector3 as
|
|
2885
|
+
Vector3 as Vector34
|
|
1237
2886
|
} from "three";
|
|
1238
2887
|
|
|
1239
2888
|
// src/tile/face-layout.ts
|
|
@@ -1241,7 +2890,7 @@ import {
|
|
|
1241
2890
|
Box3,
|
|
1242
2891
|
Euler as Euler2,
|
|
1243
2892
|
Quaternion,
|
|
1244
|
-
Vector3 as
|
|
2893
|
+
Vector3 as Vector33
|
|
1245
2894
|
} from "three";
|
|
1246
2895
|
var CUBE_FACES = ["f", "r", "b", "l", "u", "d"];
|
|
1247
2896
|
var PREVIEW_FACE_ORDER = ["l", "f", "r", "b", "u", "d"];
|
|
@@ -1305,12 +2954,12 @@ function getTileBoundingBox(address, radius = CUBE_RADIUS) {
|
|
|
1305
2954
|
const halfWidth = local.width / 2;
|
|
1306
2955
|
const halfHeight = local.height / 2;
|
|
1307
2956
|
const quaternion = getFaceQuaternion(address.face);
|
|
1308
|
-
const position = new
|
|
2957
|
+
const position = new Vector33(...transform.position);
|
|
1309
2958
|
const corners = [
|
|
1310
|
-
new
|
|
1311
|
-
new
|
|
1312
|
-
new
|
|
1313
|
-
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)
|
|
1314
2963
|
];
|
|
1315
2964
|
for (const corner of corners) {
|
|
1316
2965
|
corner.applyQuaternion(quaternion).add(position);
|
|
@@ -1419,7 +3068,7 @@ function resolveTemplateUrl(template, address) {
|
|
|
1419
3068
|
}
|
|
1420
3069
|
|
|
1421
3070
|
// src/tile/tile.tsx
|
|
1422
|
-
import { jsx as
|
|
3071
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1423
3072
|
var MAX_LOD_UPDATE_INTERVAL_MS = 100;
|
|
1424
3073
|
var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
|
|
1425
3074
|
var FAST_FOV_THRESHOLD_DEGREES = 0.5;
|
|
@@ -1428,10 +3077,10 @@ var VISIBILITY_FOV_PADDING_DEGREES = 8;
|
|
|
1428
3077
|
var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
|
|
1429
3078
|
var SHARED_TILE_GEOMETRY = new PlaneGeometry(1, 1);
|
|
1430
3079
|
function usePreviewTexture(url, anisotropy, onError) {
|
|
1431
|
-
const [texture, setTexture] =
|
|
1432
|
-
const onErrorRef =
|
|
3080
|
+
const [texture, setTexture] = useState5(null);
|
|
3081
|
+
const onErrorRef = useRef11(onError);
|
|
1433
3082
|
onErrorRef.current = onError;
|
|
1434
|
-
|
|
3083
|
+
useEffect13(() => {
|
|
1435
3084
|
if (!url) {
|
|
1436
3085
|
setTexture(null);
|
|
1437
3086
|
return;
|
|
@@ -1468,7 +3117,7 @@ function usePreviewTexture(url, anisotropy, onError) {
|
|
|
1468
3117
|
active = false;
|
|
1469
3118
|
};
|
|
1470
3119
|
}, [anisotropy, url]);
|
|
1471
|
-
|
|
3120
|
+
useEffect13(
|
|
1472
3121
|
() => () => {
|
|
1473
3122
|
texture?.dispose();
|
|
1474
3123
|
},
|
|
@@ -1481,7 +3130,7 @@ function PreviewFace({
|
|
|
1481
3130
|
texture,
|
|
1482
3131
|
faceOrder
|
|
1483
3132
|
}) {
|
|
1484
|
-
const geometry =
|
|
3133
|
+
const geometry = useMemo6(() => {
|
|
1485
3134
|
const nextGeometry = new PlaneGeometry(
|
|
1486
3135
|
PREVIEW_RADIUS * 2,
|
|
1487
3136
|
PREVIEW_RADIUS * 2
|
|
@@ -1499,21 +3148,21 @@ function PreviewFace({
|
|
|
1499
3148
|
uv.needsUpdate = true;
|
|
1500
3149
|
return nextGeometry;
|
|
1501
3150
|
}, [face, faceOrder]);
|
|
1502
|
-
|
|
3151
|
+
useEffect13(
|
|
1503
3152
|
() => () => {
|
|
1504
3153
|
geometry.dispose();
|
|
1505
3154
|
},
|
|
1506
3155
|
[geometry]
|
|
1507
3156
|
);
|
|
1508
3157
|
const transform = getFaceTransform(face, PREVIEW_RADIUS);
|
|
1509
|
-
return /* @__PURE__ */
|
|
3158
|
+
return /* @__PURE__ */ jsx7(
|
|
1510
3159
|
"mesh",
|
|
1511
3160
|
{
|
|
1512
3161
|
geometry,
|
|
1513
3162
|
position: transform.position,
|
|
1514
3163
|
renderOrder: -100,
|
|
1515
3164
|
rotation: transform.rotation,
|
|
1516
|
-
children: /* @__PURE__ */
|
|
3165
|
+
children: /* @__PURE__ */ jsx7(
|
|
1517
3166
|
"meshBasicMaterial",
|
|
1518
3167
|
{
|
|
1519
3168
|
depthTest: false,
|
|
@@ -1529,7 +3178,7 @@ function PreviewCube({
|
|
|
1529
3178
|
texture,
|
|
1530
3179
|
faceOrder
|
|
1531
3180
|
}) {
|
|
1532
|
-
return CUBE_FACES.map((face) => /* @__PURE__ */
|
|
3181
|
+
return CUBE_FACES.map((face) => /* @__PURE__ */ jsx7(
|
|
1533
3182
|
PreviewFace,
|
|
1534
3183
|
{
|
|
1535
3184
|
face,
|
|
@@ -1540,14 +3189,14 @@ function PreviewCube({
|
|
|
1540
3189
|
));
|
|
1541
3190
|
}
|
|
1542
3191
|
function useManagedTexture(manager, key, url, address) {
|
|
1543
|
-
const [managedState, setManagedState] =
|
|
3192
|
+
const [managedState, setManagedState] = useState5(() => ({
|
|
1544
3193
|
manager,
|
|
1545
3194
|
key,
|
|
1546
3195
|
url,
|
|
1547
3196
|
snapshot: manager.getSnapshot(key)
|
|
1548
3197
|
}));
|
|
1549
3198
|
const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
|
|
1550
|
-
|
|
3199
|
+
useEffect13(
|
|
1551
3200
|
() => manager.acquire(key, url, address, (nextSnapshot) => {
|
|
1552
3201
|
setManagedState({
|
|
1553
3202
|
manager,
|
|
@@ -1574,14 +3223,14 @@ var TileMesh = memo(function TileMesh2({
|
|
|
1574
3223
|
if (!snapshot.texture) {
|
|
1575
3224
|
return null;
|
|
1576
3225
|
}
|
|
1577
|
-
return /* @__PURE__ */
|
|
3226
|
+
return /* @__PURE__ */ jsx7("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx7(
|
|
1578
3227
|
"mesh",
|
|
1579
3228
|
{
|
|
1580
3229
|
geometry: SHARED_TILE_GEOMETRY,
|
|
1581
3230
|
position: [layout.x, layout.y, 0],
|
|
1582
3231
|
renderOrder,
|
|
1583
3232
|
scale: [layout.width, layout.height, 1],
|
|
1584
|
-
children: /* @__PURE__ */
|
|
3233
|
+
children: /* @__PURE__ */ jsx7(
|
|
1585
3234
|
"meshBasicMaterial",
|
|
1586
3235
|
{
|
|
1587
3236
|
depthTest: false,
|
|
@@ -1598,7 +3247,7 @@ var TileLayerMeshes = memo(function TileLayerMeshes2({
|
|
|
1598
3247
|
manager,
|
|
1599
3248
|
resolveUrl
|
|
1600
3249
|
}) {
|
|
1601
|
-
return layer.addresses.map((address) => /* @__PURE__ */
|
|
3250
|
+
return layer.addresses.map((address) => /* @__PURE__ */ jsx7(
|
|
1602
3251
|
TileMesh,
|
|
1603
3252
|
{
|
|
1604
3253
|
address,
|
|
@@ -1698,8 +3347,8 @@ function parentAddresses(child, parentLevel, parentFaceSize, tileSize) {
|
|
|
1698
3347
|
return addresses;
|
|
1699
3348
|
}
|
|
1700
3349
|
function centerFacingAddress(candidates, camera) {
|
|
1701
|
-
const direction = camera.getWorldDirection(new
|
|
1702
|
-
const center = new
|
|
3350
|
+
const direction = camera.getWorldDirection(new Vector34());
|
|
3351
|
+
const center = new Vector34();
|
|
1703
3352
|
let bestCandidate = candidates[0];
|
|
1704
3353
|
let bestAlignment = -Infinity;
|
|
1705
3354
|
for (const candidate of candidates) {
|
|
@@ -1766,11 +3415,11 @@ function Tile({
|
|
|
1766
3415
|
onTileError,
|
|
1767
3416
|
onLevelChange
|
|
1768
3417
|
}) {
|
|
1769
|
-
const { camera, gl, size } =
|
|
1770
|
-
const multiresConfig =
|
|
3418
|
+
const { camera, gl, size } = useThree9();
|
|
3419
|
+
const multiresConfig = useMemo6(() => parseMultires(multires), [multires]);
|
|
1771
3420
|
const { levels, tileSize } = multiresConfig;
|
|
1772
3421
|
const multiresToken = `${tileSize}:${levels.join(",")}`;
|
|
1773
|
-
const candidatesByLevel =
|
|
3422
|
+
const candidatesByLevel = useMemo6(
|
|
1774
3423
|
() => buildTileCandidates(levels, tileSize),
|
|
1775
3424
|
[levels, tileSize]
|
|
1776
3425
|
);
|
|
@@ -1782,13 +3431,13 @@ function Tile({
|
|
|
1782
3431
|
onPreviewError
|
|
1783
3432
|
);
|
|
1784
3433
|
const sharedManager = useSharedTileTextureManager();
|
|
1785
|
-
const progressRef =
|
|
1786
|
-
const errorRef =
|
|
1787
|
-
const levelChangeRef =
|
|
3434
|
+
const progressRef = useRef11(onLoadProgress);
|
|
3435
|
+
const errorRef = useRef11(onTileError);
|
|
3436
|
+
const levelChangeRef = useRef11(onLevelChange);
|
|
1788
3437
|
progressRef.current = onLoadProgress;
|
|
1789
3438
|
errorRef.current = onTileError;
|
|
1790
3439
|
levelChangeRef.current = onLevelChange;
|
|
1791
|
-
const ownedManager =
|
|
3440
|
+
const ownedManager = useMemo6(
|
|
1792
3441
|
() => new TileTextureManager({
|
|
1793
3442
|
anisotropy,
|
|
1794
3443
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1809,7 +3458,7 @@ function Tile({
|
|
|
1809
3458
|
]
|
|
1810
3459
|
);
|
|
1811
3460
|
const manager = sharedManager ?? ownedManager;
|
|
1812
|
-
|
|
3461
|
+
useEffect13(() => {
|
|
1813
3462
|
manager.resume();
|
|
1814
3463
|
return () => {
|
|
1815
3464
|
if (manager === ownedManager) {
|
|
@@ -1819,11 +3468,11 @@ function Tile({
|
|
|
1819
3468
|
queueMicrotask(() => manager.releaseUnused());
|
|
1820
3469
|
};
|
|
1821
3470
|
}, [manager, ownedManager]);
|
|
1822
|
-
const readyRef =
|
|
1823
|
-
|
|
3471
|
+
const readyRef = useRef11(false);
|
|
3472
|
+
useEffect13(() => {
|
|
1824
3473
|
readyRef.current = false;
|
|
1825
3474
|
}, [effectivePreviewUrl]);
|
|
1826
|
-
|
|
3475
|
+
useEffect13(() => {
|
|
1827
3476
|
if (!previewTexture || readyRef.current) {
|
|
1828
3477
|
return;
|
|
1829
3478
|
}
|
|
@@ -1831,31 +3480,31 @@ function Tile({
|
|
|
1831
3480
|
onReady?.();
|
|
1832
3481
|
}, [onReady, previewTexture]);
|
|
1833
3482
|
const template = urlTemplate ?? buildDefaultTileUrlTemplate();
|
|
1834
|
-
const resolveUrl =
|
|
3483
|
+
const resolveUrl = useMemo6(
|
|
1835
3484
|
() => (address) => {
|
|
1836
3485
|
const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
|
|
1837
3486
|
return resolveRelativeTileUrl(baseUrl, relativePath);
|
|
1838
3487
|
},
|
|
1839
3488
|
[baseUrl, resolveTileUrl, template]
|
|
1840
3489
|
);
|
|
1841
|
-
const [layers, setLayers] =
|
|
1842
|
-
const layerTokenRef =
|
|
1843
|
-
const currentLevelRef =
|
|
1844
|
-
const lastUpdateAtRef =
|
|
1845
|
-
const visibilityDirtyRef =
|
|
1846
|
-
const lastVisibilityQuaternionRef =
|
|
1847
|
-
const lastVisibilityFovRef =
|
|
1848
|
-
const lastViewportRef =
|
|
1849
|
-
const frustumRef =
|
|
1850
|
-
const projectionMatrixRef =
|
|
1851
|
-
const visibilityCameraRef =
|
|
1852
|
-
|
|
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(() => {
|
|
1853
3502
|
currentLevelRef.current = 1;
|
|
1854
3503
|
layerTokenRef.current = "";
|
|
1855
3504
|
visibilityDirtyRef.current = true;
|
|
1856
3505
|
setLayers([]);
|
|
1857
3506
|
}, [manager, multiresToken]);
|
|
1858
|
-
|
|
3507
|
+
useEffect13(() => {
|
|
1859
3508
|
if (loadMode !== "preview") {
|
|
1860
3509
|
return;
|
|
1861
3510
|
}
|
|
@@ -1869,7 +3518,7 @@ function Tile({
|
|
|
1869
3518
|
setLayers(nextLayers);
|
|
1870
3519
|
}
|
|
1871
3520
|
};
|
|
1872
|
-
|
|
3521
|
+
useFrame5(() => {
|
|
1873
3522
|
if (loadMode === "preview") {
|
|
1874
3523
|
return;
|
|
1875
3524
|
}
|
|
@@ -1935,15 +3584,15 @@ function Tile({
|
|
|
1935
3584
|
};
|
|
1936
3585
|
commitLayers(nextLayers);
|
|
1937
3586
|
});
|
|
1938
|
-
return /* @__PURE__ */
|
|
1939
|
-
previewTexture ? /* @__PURE__ */
|
|
3587
|
+
return /* @__PURE__ */ jsxs5("group", { visible, children: [
|
|
3588
|
+
previewTexture ? /* @__PURE__ */ jsx7(
|
|
1940
3589
|
PreviewCube,
|
|
1941
3590
|
{
|
|
1942
3591
|
texture: previewTexture,
|
|
1943
3592
|
faceOrder: previewFaceOrder
|
|
1944
3593
|
}
|
|
1945
3594
|
) : null,
|
|
1946
|
-
layers.map((layer) => /* @__PURE__ */
|
|
3595
|
+
layers.map((layer) => /* @__PURE__ */ jsx7(
|
|
1947
3596
|
TileLayerMeshes,
|
|
1948
3597
|
{
|
|
1949
3598
|
layer,
|
|
@@ -1956,7 +3605,7 @@ function Tile({
|
|
|
1956
3605
|
}
|
|
1957
3606
|
|
|
1958
3607
|
// src/panorama-scenes.tsx
|
|
1959
|
-
import { jsx as
|
|
3608
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1960
3609
|
var TRANSITION_DEFAULTS = {
|
|
1961
3610
|
none: { duration: 0, krpanoBlend: "NOBLEND" },
|
|
1962
3611
|
crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
|
|
@@ -2006,7 +3655,7 @@ function SceneSource({
|
|
|
2006
3655
|
return null;
|
|
2007
3656
|
}
|
|
2008
3657
|
if (scene.type === "sphere") {
|
|
2009
|
-
return /* @__PURE__ */
|
|
3658
|
+
return /* @__PURE__ */ jsx8(
|
|
2010
3659
|
Sphere,
|
|
2011
3660
|
{
|
|
2012
3661
|
src: scene.src,
|
|
@@ -2018,7 +3667,7 @@ function SceneSource({
|
|
|
2018
3667
|
);
|
|
2019
3668
|
}
|
|
2020
3669
|
const { id: _id, type: _type, ...tileProps } = scene;
|
|
2021
|
-
return /* @__PURE__ */
|
|
3670
|
+
return /* @__PURE__ */ jsx8(
|
|
2022
3671
|
Tile,
|
|
2023
3672
|
{
|
|
2024
3673
|
...tileProps,
|
|
@@ -2034,17 +3683,17 @@ function SnapshotCapture({
|
|
|
2034
3683
|
maxPixels,
|
|
2035
3684
|
onCaptured
|
|
2036
3685
|
}) {
|
|
2037
|
-
const { camera, gl, scene, size } =
|
|
2038
|
-
const capturedRequestRef =
|
|
2039
|
-
const renderTargetRef =
|
|
2040
|
-
|
|
3686
|
+
const { camera, gl, scene, size } = useThree10();
|
|
3687
|
+
const capturedRequestRef = useRef12(0);
|
|
3688
|
+
const renderTargetRef = useRef12(null);
|
|
3689
|
+
useEffect14(
|
|
2041
3690
|
() => () => {
|
|
2042
3691
|
renderTargetRef.current?.dispose();
|
|
2043
3692
|
renderTargetRef.current = null;
|
|
2044
3693
|
},
|
|
2045
3694
|
[]
|
|
2046
3695
|
);
|
|
2047
|
-
|
|
3696
|
+
useFrame6(() => {
|
|
2048
3697
|
if (requestId === 0 || requestId === capturedRequestRef.current) {
|
|
2049
3698
|
return;
|
|
2050
3699
|
}
|
|
@@ -2090,12 +3739,12 @@ function SnapshotOverlay({
|
|
|
2090
3739
|
runId,
|
|
2091
3740
|
onFinish
|
|
2092
3741
|
}) {
|
|
2093
|
-
const { camera, size } =
|
|
2094
|
-
const meshRef =
|
|
2095
|
-
const startRef =
|
|
2096
|
-
const finishedRef =
|
|
2097
|
-
const directionRef =
|
|
2098
|
-
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(
|
|
2099
3748
|
() => new ShaderMaterial({
|
|
2100
3749
|
transparent: true,
|
|
2101
3750
|
depthTest: false,
|
|
@@ -2174,13 +3823,13 @@ function SnapshotOverlay({
|
|
|
2174
3823
|
}),
|
|
2175
3824
|
[snapshot.texture, transition.preset]
|
|
2176
3825
|
);
|
|
2177
|
-
|
|
2178
|
-
|
|
3826
|
+
useEffect14(() => () => material.dispose(), [material]);
|
|
3827
|
+
useEffect14(() => {
|
|
2179
3828
|
startRef.current = null;
|
|
2180
3829
|
finishedRef.current = false;
|
|
2181
3830
|
material.uniforms.progress.value = 0;
|
|
2182
3831
|
}, [material, runId]);
|
|
2183
|
-
|
|
3832
|
+
useFrame6((state) => {
|
|
2184
3833
|
const mesh = meshRef.current;
|
|
2185
3834
|
if (!mesh || !camera.isPerspectiveCamera) {
|
|
2186
3835
|
return;
|
|
@@ -2205,9 +3854,9 @@ function SnapshotOverlay({
|
|
|
2205
3854
|
onFinish();
|
|
2206
3855
|
}
|
|
2207
3856
|
});
|
|
2208
|
-
return /* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2210
|
-
/* @__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 })
|
|
2211
3860
|
] });
|
|
2212
3861
|
}
|
|
2213
3862
|
var FULLSCREEN_PLANE = new PlaneGeometry2(1, 1);
|
|
@@ -2220,47 +3869,47 @@ function PanoramaScenesController({
|
|
|
2220
3869
|
onTransitionError,
|
|
2221
3870
|
snapshotMaxPixels = 3686400
|
|
2222
3871
|
}) {
|
|
2223
|
-
const controlsRef =
|
|
3872
|
+
const controlsRef = useContext10(PanoramaViewContext);
|
|
2224
3873
|
const manager = useSharedTileTextureManager();
|
|
2225
|
-
const transition =
|
|
3874
|
+
const transition = useMemo7(
|
|
2226
3875
|
() => resolveTransition(transitionInput),
|
|
2227
3876
|
[transitionInput]
|
|
2228
3877
|
);
|
|
2229
|
-
const sceneMap =
|
|
3878
|
+
const sceneMap = useMemo7(
|
|
2230
3879
|
() => new Map(scenes.map((scene) => [scene.id, scene])),
|
|
2231
3880
|
[scenes]
|
|
2232
3881
|
);
|
|
2233
3882
|
const initialScene = sceneMap.get(activeSceneId) ?? null;
|
|
2234
|
-
const [slots, setSlots] =
|
|
3883
|
+
const [slots, setSlots] = useState6([
|
|
2235
3884
|
initialScene,
|
|
2236
3885
|
null
|
|
2237
3886
|
]);
|
|
2238
|
-
const [activeSlot, setActiveSlot] =
|
|
2239
|
-
const [liveSlot, setLiveSlot] =
|
|
2240
|
-
const [pendingSlot, setPendingSlot] =
|
|
2241
|
-
const [phase, setPhase] =
|
|
2242
|
-
const [captureRequestId, setCaptureRequestId] =
|
|
2243
|
-
const [transitionRunId, setTransitionRunId] =
|
|
2244
|
-
const [snapshot, setSnapshot] =
|
|
2245
|
-
const [recoveryKey, setRecoveryKey] =
|
|
2246
|
-
const requestedSceneIdRef =
|
|
2247
|
-
const captureReasonRef =
|
|
2248
|
-
const interactionReleaseRef =
|
|
2249
|
-
const slotsRef =
|
|
2250
|
-
const phaseRef =
|
|
2251
|
-
const liveSlotRef =
|
|
2252
|
-
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);
|
|
2253
3902
|
slotsRef.current = slots;
|
|
2254
3903
|
phaseRef.current = phase;
|
|
2255
3904
|
liveSlotRef.current = liveSlot;
|
|
2256
|
-
const releaseInteraction =
|
|
3905
|
+
const releaseInteraction = useCallback4(() => {
|
|
2257
3906
|
interactionReleaseRef.current?.();
|
|
2258
3907
|
interactionReleaseRef.current = null;
|
|
2259
3908
|
}, []);
|
|
2260
|
-
const reportError =
|
|
3909
|
+
const reportError = useCallback4((sceneId, error) => {
|
|
2261
3910
|
onTransitionError?.({ sceneId, error });
|
|
2262
3911
|
}, [onTransitionError]);
|
|
2263
|
-
const startPreload =
|
|
3912
|
+
const startPreload = useCallback4((scene) => {
|
|
2264
3913
|
const nextSlot = 1 - liveSlotRef.current;
|
|
2265
3914
|
requestedSceneIdRef.current = scene.id;
|
|
2266
3915
|
setSlots((current) => {
|
|
@@ -2271,7 +3920,7 @@ function PanoramaScenesController({
|
|
|
2271
3920
|
setPendingSlot(nextSlot);
|
|
2272
3921
|
setPhase("preloading");
|
|
2273
3922
|
}, []);
|
|
2274
|
-
|
|
3923
|
+
useEffect14(() => {
|
|
2275
3924
|
const requested = sceneMap.get(activeSceneId);
|
|
2276
3925
|
if (!requested) {
|
|
2277
3926
|
reportError(activeSceneId, new Error(`Unknown panorama scene: ${activeSceneId}`));
|
|
@@ -2295,7 +3944,7 @@ function PanoramaScenesController({
|
|
|
2295
3944
|
}
|
|
2296
3945
|
startPreload(requested);
|
|
2297
3946
|
}, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
|
|
2298
|
-
const handleReady =
|
|
3947
|
+
const handleReady = useCallback4((slot) => {
|
|
2299
3948
|
if (slot !== pendingSlot || phaseRef.current !== "preloading") {
|
|
2300
3949
|
return;
|
|
2301
3950
|
}
|
|
@@ -2326,7 +3975,7 @@ function PanoramaScenesController({
|
|
|
2326
3975
|
setPhase("capturing");
|
|
2327
3976
|
setCaptureRequestId((current) => current + 1);
|
|
2328
3977
|
}, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
|
|
2329
|
-
const handleCaptured =
|
|
3978
|
+
const handleCaptured = useCallback4((nextSnapshot) => {
|
|
2330
3979
|
setSnapshot((current) => {
|
|
2331
3980
|
current?.dispose();
|
|
2332
3981
|
return nextSnapshot;
|
|
@@ -2348,7 +3997,7 @@ function PanoramaScenesController({
|
|
|
2348
3997
|
setPhase("transitioning");
|
|
2349
3998
|
setTransitionRunId((current) => current + 1);
|
|
2350
3999
|
}, [manager, pendingSlot, sceneMap, startPreload]);
|
|
2351
|
-
const finishTransition =
|
|
4000
|
+
const finishTransition = useCallback4(() => {
|
|
2352
4001
|
const nextSlot = pendingSlot;
|
|
2353
4002
|
const previousScene = transitionFromRef.current;
|
|
2354
4003
|
const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
|
|
@@ -2373,12 +4022,12 @@ function PanoramaScenesController({
|
|
|
2373
4022
|
});
|
|
2374
4023
|
}
|
|
2375
4024
|
}, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
|
|
2376
|
-
|
|
4025
|
+
useEffect14(() => () => {
|
|
2377
4026
|
releaseInteraction();
|
|
2378
4027
|
snapshot?.dispose();
|
|
2379
4028
|
}, [releaseInteraction, snapshot]);
|
|
2380
|
-
const { gl } =
|
|
2381
|
-
|
|
4029
|
+
const { gl } = useThree10();
|
|
4030
|
+
useEffect14(() => {
|
|
2382
4031
|
const canvas = gl.domElement;
|
|
2383
4032
|
const onContextLost = (event) => {
|
|
2384
4033
|
event.preventDefault();
|
|
@@ -2402,8 +4051,8 @@ function PanoramaScenesController({
|
|
|
2402
4051
|
}, [gl, manager, releaseInteraction, snapshot]);
|
|
2403
4052
|
const renderedScene = slots[liveSlot];
|
|
2404
4053
|
const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
|
|
2405
|
-
return /* @__PURE__ */
|
|
2406
|
-
/* @__PURE__ */
|
|
4054
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
4055
|
+
/* @__PURE__ */ jsx8(
|
|
2407
4056
|
SnapshotCapture,
|
|
2408
4057
|
{
|
|
2409
4058
|
requestId: captureRequestId,
|
|
@@ -2415,7 +4064,7 @@ function PanoramaScenesController({
|
|
|
2415
4064
|
const source = slots[slot];
|
|
2416
4065
|
const isLive = slot === liveSlot;
|
|
2417
4066
|
const isPending = slot === pendingSlot;
|
|
2418
|
-
return /* @__PURE__ */
|
|
4067
|
+
return /* @__PURE__ */ jsx8(
|
|
2419
4068
|
SceneSource,
|
|
2420
4069
|
{
|
|
2421
4070
|
scene: source,
|
|
@@ -2431,7 +4080,7 @@ function PanoramaScenesController({
|
|
|
2431
4080
|
`${slot}:${source?.id ?? "empty"}:${recoveryKey}`
|
|
2432
4081
|
);
|
|
2433
4082
|
}),
|
|
2434
|
-
snapshotVisible ? /* @__PURE__ */
|
|
4083
|
+
snapshotVisible ? /* @__PURE__ */ jsx8(
|
|
2435
4084
|
SnapshotOverlay,
|
|
2436
4085
|
{
|
|
2437
4086
|
snapshot,
|
|
@@ -2449,19 +4098,19 @@ function PanoramaScenes({
|
|
|
2449
4098
|
maxConcurrentTileLoads,
|
|
2450
4099
|
...props
|
|
2451
4100
|
}) {
|
|
2452
|
-
return /* @__PURE__ */
|
|
4101
|
+
return /* @__PURE__ */ jsx8(
|
|
2453
4102
|
TileTextureManagerProvider,
|
|
2454
4103
|
{
|
|
2455
4104
|
maxTextureMemoryMb,
|
|
2456
4105
|
maxConcurrentLoads: maxConcurrentTileLoads,
|
|
2457
|
-
children: /* @__PURE__ */
|
|
4106
|
+
children: /* @__PURE__ */ jsx8(PanoramaScenesController, { ...props })
|
|
2458
4107
|
}
|
|
2459
4108
|
);
|
|
2460
4109
|
}
|
|
2461
4110
|
|
|
2462
4111
|
// src/hotspot/image-hotspot.tsx
|
|
2463
|
-
import { useThree as
|
|
2464
|
-
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";
|
|
2465
4114
|
import {
|
|
2466
4115
|
ClampToEdgeWrapping as ClampToEdgeWrapping3,
|
|
2467
4116
|
DoubleSide as DoubleSide2,
|
|
@@ -2472,12 +4121,12 @@ import {
|
|
|
2472
4121
|
} from "three";
|
|
2473
4122
|
|
|
2474
4123
|
// src/hotspot/hotspot-anchor.tsx
|
|
2475
|
-
import { useFrame as
|
|
4124
|
+
import { useFrame as useFrame7 } from "@react-three/fiber";
|
|
2476
4125
|
import {
|
|
2477
|
-
useContext as
|
|
2478
|
-
useEffect as
|
|
2479
|
-
useMemo as
|
|
2480
|
-
useRef as
|
|
4126
|
+
useContext as useContext11,
|
|
4127
|
+
useEffect as useEffect15,
|
|
4128
|
+
useMemo as useMemo8,
|
|
4129
|
+
useRef as useRef13
|
|
2481
4130
|
} from "react";
|
|
2482
4131
|
import {
|
|
2483
4132
|
EdgesGeometry,
|
|
@@ -2486,9 +4135,9 @@ import {
|
|
|
2486
4135
|
PlaneGeometry as PlaneGeometry3,
|
|
2487
4136
|
PerspectiveCamera as PerspectiveCamera4,
|
|
2488
4137
|
Quaternion as Quaternion2,
|
|
2489
|
-
Vector3 as
|
|
4138
|
+
Vector3 as Vector36
|
|
2490
4139
|
} from "three";
|
|
2491
|
-
import { jsx as
|
|
4140
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2492
4141
|
var DEFAULT_FLOATING_DISTANCE = 10;
|
|
2493
4142
|
var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
|
|
2494
4143
|
var PANORAMA_SURFACE_INSET = 4;
|
|
@@ -2496,7 +4145,7 @@ var DEFAULT_REFERENCE_FOV = 75;
|
|
|
2496
4145
|
var MIN_ANGULAR_SIZE = 0.01;
|
|
2497
4146
|
var MAX_ANGULAR_SIZE = 179;
|
|
2498
4147
|
var DRAG_EPSILON_DEGREES = 1e-3;
|
|
2499
|
-
var LOCAL_FORWARD = new
|
|
4148
|
+
var LOCAL_FORWARD = new Vector36(0, 0, 1);
|
|
2500
4149
|
var HOTSPOT_MODE_RENDERING = {
|
|
2501
4150
|
surface: { orientation: "surface", placement: "surface" },
|
|
2502
4151
|
billboard: { orientation: "billboard", placement: "floating" }
|
|
@@ -2505,6 +4154,9 @@ function angularSizeToWorldSize(size, radius) {
|
|
|
2505
4154
|
const safeSize = Number.isFinite(size) ? MathUtils4.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
|
|
2506
4155
|
return 2 * radius * Math.tan(MathUtils4.degToRad(safeSize) / 2);
|
|
2507
4156
|
}
|
|
4157
|
+
function resolveScale(scale) {
|
|
4158
|
+
return Number.isFinite(scale) && scale > 0 ? scale : 1;
|
|
4159
|
+
}
|
|
2508
4160
|
function resolveDistance(placement, distance, width, height) {
|
|
2509
4161
|
const maximumDistance = maximumHotspotDistance(width, height);
|
|
2510
4162
|
if (placement === "surface") {
|
|
@@ -2542,7 +4194,7 @@ function createSurfaceQuaternion(position) {
|
|
|
2542
4194
|
const inward = outward.clone().negate();
|
|
2543
4195
|
const pitch = MathUtils4.degToRad(position.pitch);
|
|
2544
4196
|
const yaw = MathUtils4.degToRad(position.yaw);
|
|
2545
|
-
const localUp = new
|
|
4197
|
+
const localUp = new Vector36(
|
|
2546
4198
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
2547
4199
|
Math.cos(pitch),
|
|
2548
4200
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -2569,6 +4221,7 @@ function HotspotAnchor({
|
|
|
2569
4221
|
position,
|
|
2570
4222
|
width,
|
|
2571
4223
|
height,
|
|
4224
|
+
scale = 1,
|
|
2572
4225
|
useAngularScale = true,
|
|
2573
4226
|
focusContent,
|
|
2574
4227
|
mode = "billboard",
|
|
@@ -2590,25 +4243,33 @@ function HotspotAnchor({
|
|
|
2590
4243
|
onPositionChange,
|
|
2591
4244
|
onDragEnd
|
|
2592
4245
|
}) {
|
|
2593
|
-
const controlsRef =
|
|
2594
|
-
const groupRef =
|
|
2595
|
-
const dragStateRef =
|
|
2596
|
-
const suppressNextClickRef =
|
|
4246
|
+
const controlsRef = useContext11(PanoramaViewContext);
|
|
4247
|
+
const groupRef = useRef13(null);
|
|
4248
|
+
const dragStateRef = useRef13(null);
|
|
4249
|
+
const suppressNextClickRef = useRef13(false);
|
|
2597
4250
|
const normalizedPosition = normalizePanoPosition(position);
|
|
2598
4251
|
const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
|
|
2599
4252
|
const { orientation: orientation2, placement } = rendering;
|
|
2600
|
-
const
|
|
2601
|
-
const
|
|
4253
|
+
const resolvedScale = resolveScale(scale);
|
|
4254
|
+
const scaledWidth = width * resolvedScale;
|
|
4255
|
+
const scaledHeight = height * resolvedScale;
|
|
4256
|
+
const resolvedDistance = resolveDistance(
|
|
4257
|
+
placement,
|
|
4258
|
+
distance,
|
|
4259
|
+
scaledWidth,
|
|
4260
|
+
scaledHeight
|
|
4261
|
+
);
|
|
4262
|
+
const worldPosition = useMemo8(
|
|
2602
4263
|
() => panoPositionToVector3(normalizedPosition, resolvedDistance),
|
|
2603
4264
|
[normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
|
|
2604
4265
|
);
|
|
2605
|
-
const surfaceQuaternion3 =
|
|
4266
|
+
const surfaceQuaternion3 = useMemo8(
|
|
2606
4267
|
() => createSurfaceQuaternion(normalizedPosition),
|
|
2607
4268
|
[normalizedPosition.pitch, normalizedPosition.yaw]
|
|
2608
4269
|
);
|
|
2609
|
-
const worldWidth = angularSizeToWorldSize(
|
|
2610
|
-
const worldHeight = angularSizeToWorldSize(
|
|
2611
|
-
const focusGeometry =
|
|
4270
|
+
const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
|
|
4271
|
+
const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
|
|
4272
|
+
const focusGeometry = useMemo8(() => {
|
|
2612
4273
|
const planeGeometry = new PlaneGeometry3(1, 1);
|
|
2613
4274
|
const geometry = new EdgesGeometry(planeGeometry);
|
|
2614
4275
|
planeGeometry.dispose();
|
|
@@ -2626,15 +4287,15 @@ function HotspotAnchor({
|
|
|
2626
4287
|
});
|
|
2627
4288
|
} : void 0
|
|
2628
4289
|
});
|
|
2629
|
-
|
|
4290
|
+
useEffect15(
|
|
2630
4291
|
() => () => {
|
|
2631
4292
|
dragStateRef.current?.releaseInteractionLock();
|
|
2632
4293
|
dragStateRef.current = null;
|
|
2633
4294
|
},
|
|
2634
4295
|
[]
|
|
2635
4296
|
);
|
|
2636
|
-
|
|
2637
|
-
|
|
4297
|
+
useEffect15(() => () => focusGeometry.dispose(), [focusGeometry]);
|
|
4298
|
+
useFrame7(({ camera }) => {
|
|
2638
4299
|
if (!groupRef.current) {
|
|
2639
4300
|
return;
|
|
2640
4301
|
}
|
|
@@ -2645,7 +4306,7 @@ function HotspotAnchor({
|
|
|
2645
4306
|
}
|
|
2646
4307
|
groupRef.current.rotateOnAxis(
|
|
2647
4308
|
LOCAL_FORWARD,
|
|
2648
|
-
MathUtils4.degToRad(rotation)
|
|
4309
|
+
MathUtils4.degToRad(-rotation)
|
|
2649
4310
|
);
|
|
2650
4311
|
const scaleFactor = scaleMode === "fixed" && camera instanceof PerspectiveCamera4 ? fixedScaleFactor(camera.fov, referenceFov) : 1;
|
|
2651
4312
|
groupRef.current.scale.set(
|
|
@@ -2680,7 +4341,7 @@ function HotspotAnchor({
|
|
|
2680
4341
|
dragState.releaseInteractionLock();
|
|
2681
4342
|
dragStateRef.current = null;
|
|
2682
4343
|
};
|
|
2683
|
-
return /* @__PURE__ */
|
|
4344
|
+
return /* @__PURE__ */ jsxs7(
|
|
2684
4345
|
"group",
|
|
2685
4346
|
{
|
|
2686
4347
|
ref: groupRef,
|
|
@@ -2750,14 +4411,14 @@ function HotspotAnchor({
|
|
|
2750
4411
|
} : void 0,
|
|
2751
4412
|
onPointerUp: interactive ? (event) => releasePointer(event, true) : void 0,
|
|
2752
4413
|
children: [
|
|
2753
|
-
focused ? focusContent ?? /* @__PURE__ */
|
|
4414
|
+
focused ? focusContent ?? /* @__PURE__ */ jsx9(
|
|
2754
4415
|
"lineSegments",
|
|
2755
4416
|
{
|
|
2756
4417
|
geometry: focusGeometry,
|
|
2757
4418
|
position: [0, 0, 0.01],
|
|
2758
4419
|
renderOrder: renderOrder + 1,
|
|
2759
4420
|
scale: [1.12, 1.12, 1],
|
|
2760
|
-
children: /* @__PURE__ */
|
|
4421
|
+
children: /* @__PURE__ */ jsx9("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
|
|
2761
4422
|
}
|
|
2762
4423
|
) : null,
|
|
2763
4424
|
children
|
|
@@ -2767,7 +4428,7 @@ function HotspotAnchor({
|
|
|
2767
4428
|
}
|
|
2768
4429
|
|
|
2769
4430
|
// src/hotspot/image-hotspot.tsx
|
|
2770
|
-
import { jsx as
|
|
4431
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2771
4432
|
function clampOpacity(opacity) {
|
|
2772
4433
|
if (!Number.isFinite(opacity)) {
|
|
2773
4434
|
return 1;
|
|
@@ -2775,13 +4436,13 @@ function clampOpacity(opacity) {
|
|
|
2775
4436
|
return Math.max(0, Math.min(opacity, 1));
|
|
2776
4437
|
}
|
|
2777
4438
|
function useHotspotImageTexture(src, onLoad, onError) {
|
|
2778
|
-
const gl =
|
|
2779
|
-
const [texture, setTexture] =
|
|
2780
|
-
const onLoadRef =
|
|
2781
|
-
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);
|
|
2782
4443
|
onLoadRef.current = onLoad;
|
|
2783
4444
|
onErrorRef.current = onError;
|
|
2784
|
-
|
|
4445
|
+
useEffect16(() => {
|
|
2785
4446
|
let active = true;
|
|
2786
4447
|
setTexture(null);
|
|
2787
4448
|
if (!src) {
|
|
@@ -2819,7 +4480,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
|
|
|
2819
4480
|
active = false;
|
|
2820
4481
|
};
|
|
2821
4482
|
}, [gl, src]);
|
|
2822
|
-
|
|
4483
|
+
useEffect16(
|
|
2823
4484
|
() => () => {
|
|
2824
4485
|
texture?.dispose();
|
|
2825
4486
|
},
|
|
@@ -2837,9 +4498,9 @@ function ImageHotspot({
|
|
|
2837
4498
|
...anchorProps
|
|
2838
4499
|
}) {
|
|
2839
4500
|
const texture = useHotspotImageTexture(src, onLoad, onError);
|
|
2840
|
-
return /* @__PURE__ */
|
|
2841
|
-
/* @__PURE__ */
|
|
2842
|
-
/* @__PURE__ */
|
|
4501
|
+
return /* @__PURE__ */ jsx10(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsxs8("mesh", { children: [
|
|
4502
|
+
/* @__PURE__ */ jsx10("planeGeometry", { args: [1, 1] }),
|
|
4503
|
+
/* @__PURE__ */ jsx10(
|
|
2843
4504
|
"meshBasicMaterial",
|
|
2844
4505
|
{
|
|
2845
4506
|
map: texture,
|
|
@@ -2853,8 +4514,8 @@ function ImageHotspot({
|
|
|
2853
4514
|
}
|
|
2854
4515
|
|
|
2855
4516
|
// src/hotspot/polygon-hotspot.tsx
|
|
2856
|
-
import { useThree as
|
|
2857
|
-
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";
|
|
2858
4519
|
import {
|
|
2859
4520
|
BufferGeometry,
|
|
2860
4521
|
DoubleSide as DoubleSide3,
|
|
@@ -2862,13 +4523,13 @@ import {
|
|
|
2862
4523
|
Matrix4 as Matrix43,
|
|
2863
4524
|
Quaternion as Quaternion3,
|
|
2864
4525
|
ShapeUtils,
|
|
2865
|
-
Vector2 as
|
|
2866
|
-
Vector3 as
|
|
4526
|
+
Vector2 as Vector23,
|
|
4527
|
+
Vector3 as Vector37
|
|
2867
4528
|
} from "three";
|
|
2868
4529
|
import { Line2 } from "three/addons/lines/Line2.js";
|
|
2869
4530
|
import { LineGeometry } from "three/addons/lines/LineGeometry.js";
|
|
2870
4531
|
import { LineMaterial } from "three/addons/lines/LineMaterial.js";
|
|
2871
|
-
import { jsx as
|
|
4532
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2872
4533
|
var POLYGON_RADIUS = 990;
|
|
2873
4534
|
var MAX_FILL_SEGMENT_DEGREES = 7;
|
|
2874
4535
|
function cloneVertices(vertices) {
|
|
@@ -2903,7 +4564,7 @@ function intersects(a, b, c, d) {
|
|
|
2903
4564
|
}
|
|
2904
4565
|
function hasSelfIntersection(vertices) {
|
|
2905
4566
|
const points = vertices.map(
|
|
2906
|
-
(vertex) => new
|
|
4567
|
+
(vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch)
|
|
2907
4568
|
);
|
|
2908
4569
|
for (let index = 0; index < points.length; index += 1) {
|
|
2909
4570
|
const nextIndex = (index + 1) % points.length;
|
|
@@ -2940,7 +4601,7 @@ function polygonContainsDirection(vertices, direction) {
|
|
|
2940
4601
|
}
|
|
2941
4602
|
function fitsHemisphere(vertices) {
|
|
2942
4603
|
const directions = vertices.map((vertex) => panoPositionToVector3(vertex).normalize());
|
|
2943
|
-
const candidates = [new
|
|
4604
|
+
const candidates = [new Vector37()];
|
|
2944
4605
|
for (const direction of directions) {
|
|
2945
4606
|
candidates[0].add(direction);
|
|
2946
4607
|
candidates.push(direction.clone());
|
|
@@ -2964,7 +4625,7 @@ function validatePolygonVertices(vertices) {
|
|
|
2964
4625
|
if (hasSelfIntersection(unwrapped)) {
|
|
2965
4626
|
issues.push({ code: "self_intersection", message: "Polygon edges cannot cross." });
|
|
2966
4627
|
}
|
|
2967
|
-
if (polygonContainsDirection(normalized, new
|
|
4628
|
+
if (polygonContainsDirection(normalized, new Vector37(0, 1, 0)) || polygonContainsDirection(normalized, new Vector37(0, -1, 0))) {
|
|
2968
4629
|
issues.push({ code: "contains_pole", message: "Polygons cannot contain a panorama pole." });
|
|
2969
4630
|
}
|
|
2970
4631
|
if (!fitsHemisphere(normalized)) {
|
|
@@ -2975,7 +4636,7 @@ function validatePolygonVertices(vertices) {
|
|
|
2975
4636
|
function polygonCenter(vertices) {
|
|
2976
4637
|
const sum = vertices.reduce(
|
|
2977
4638
|
(total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
|
|
2978
|
-
new
|
|
4639
|
+
new Vector37()
|
|
2979
4640
|
);
|
|
2980
4641
|
return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
|
|
2981
4642
|
}
|
|
@@ -2984,7 +4645,7 @@ function surfaceQuaternion(position) {
|
|
|
2984
4645
|
const inward = outward.clone().negate();
|
|
2985
4646
|
const pitch = position.pitch * Math.PI / 180;
|
|
2986
4647
|
const yaw = position.yaw * Math.PI / 180;
|
|
2987
|
-
const localUp = new
|
|
4648
|
+
const localUp = new Vector37(
|
|
2988
4649
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
2989
4650
|
Math.cos(pitch),
|
|
2990
4651
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -2997,7 +4658,7 @@ function makeWorldToLocal(center) {
|
|
|
2997
4658
|
return new Matrix43().compose(
|
|
2998
4659
|
panoPositionToVector3(center, POLYGON_RADIUS),
|
|
2999
4660
|
surfaceQuaternion(center),
|
|
3000
|
-
new
|
|
4661
|
+
new Vector37(1, 1, 1)
|
|
3001
4662
|
).invert();
|
|
3002
4663
|
}
|
|
3003
4664
|
function projectedPoint(a, b, c, weightB, weightC, radius, worldToLocal) {
|
|
@@ -3052,7 +4713,7 @@ function addSubdividedTriangle(positions, indices, a, b, c, worldToLocal, steps)
|
|
|
3052
4713
|
}
|
|
3053
4714
|
function makeFillGeometry(vertices, center) {
|
|
3054
4715
|
const unwrapped = unwrapPolygonVertices(vertices);
|
|
3055
|
-
const contour = unwrapped.map((vertex) => new
|
|
4716
|
+
const contour = unwrapped.map((vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch));
|
|
3056
4717
|
const triangles = ShapeUtils.triangulateShape(contour, []);
|
|
3057
4718
|
const positions = [];
|
|
3058
4719
|
const indices = [];
|
|
@@ -3141,38 +4802,38 @@ function PolygonHotspot({
|
|
|
3141
4802
|
onInvalid,
|
|
3142
4803
|
...anchorProps
|
|
3143
4804
|
}) {
|
|
3144
|
-
const gl =
|
|
3145
|
-
const size =
|
|
3146
|
-
const issues =
|
|
4805
|
+
const gl = useThree12((state) => state.gl);
|
|
4806
|
+
const size = useThree12((state) => state.size);
|
|
4807
|
+
const issues = useMemo9(() => validatePolygonVertices(vertices), [vertices]);
|
|
3147
4808
|
const valid = issues.length === 0;
|
|
3148
|
-
const center =
|
|
3149
|
-
const fillGeometry =
|
|
4809
|
+
const center = useMemo9(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
|
|
4810
|
+
const fillGeometry = useMemo9(
|
|
3150
4811
|
() => valid && center ? makeFillGeometry(vertices, center) : null,
|
|
3151
4812
|
[center, valid, vertices]
|
|
3152
4813
|
);
|
|
3153
|
-
const strokePositions =
|
|
4814
|
+
const strokePositions = useMemo9(
|
|
3154
4815
|
() => valid && center ? makeStrokePositions(vertices, center) : null,
|
|
3155
4816
|
[center, valid, vertices]
|
|
3156
4817
|
);
|
|
3157
4818
|
const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3158
4819
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3159
|
-
const strokeLine =
|
|
4820
|
+
const strokeLine = useMemo9(
|
|
3160
4821
|
() => strokePositions ? makeStrokeLine(strokePositions, stroke, strokeWidth * devicePixelRatio, resolvedStrokeOpacity) : null,
|
|
3161
4822
|
[devicePixelRatio, resolvedStrokeOpacity, stroke, strokePositions, strokeWidth]
|
|
3162
4823
|
);
|
|
3163
|
-
const focusStrokeLine =
|
|
4824
|
+
const focusStrokeLine = useMemo9(
|
|
3164
4825
|
() => strokePositions ? makeStrokeLine(strokePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3165
4826
|
[devicePixelRatio, strokePositions, strokeWidth]
|
|
3166
4827
|
);
|
|
3167
|
-
const startVerticesRef =
|
|
4828
|
+
const startVerticesRef = useRef15(null);
|
|
3168
4829
|
const issuesKey = issues.map((issue) => issue.code).join(",");
|
|
3169
|
-
|
|
4830
|
+
useEffect17(() => {
|
|
3170
4831
|
if (!valid) onInvalid?.(issues);
|
|
3171
4832
|
}, [issues, issuesKey, onInvalid, valid]);
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
4833
|
+
useEffect17(() => () => fillGeometry?.dispose(), [fillGeometry]);
|
|
4834
|
+
useEffect17(() => () => disposeStrokeLine(strokeLine), [strokeLine]);
|
|
4835
|
+
useEffect17(() => () => disposeStrokeLine(focusStrokeLine), [focusStrokeLine]);
|
|
4836
|
+
useEffect17(() => {
|
|
3176
4837
|
const width = gl.domElement.width;
|
|
3177
4838
|
const height = gl.domElement.height;
|
|
3178
4839
|
for (const line of [strokeLine, focusStrokeLine]) {
|
|
@@ -3191,7 +4852,7 @@ function PolygonHotspot({
|
|
|
3191
4852
|
startPosition: event.startPosition
|
|
3192
4853
|
});
|
|
3193
4854
|
};
|
|
3194
|
-
return /* @__PURE__ */
|
|
4855
|
+
return /* @__PURE__ */ jsxs9(
|
|
3195
4856
|
HotspotAnchor,
|
|
3196
4857
|
{
|
|
3197
4858
|
...anchorProps,
|
|
@@ -3206,7 +4867,7 @@ function PolygonHotspot({
|
|
|
3206
4867
|
draggable,
|
|
3207
4868
|
interactive,
|
|
3208
4869
|
useAngularScale: false,
|
|
3209
|
-
focusContent: /* @__PURE__ */
|
|
4870
|
+
focusContent: /* @__PURE__ */ jsx11("primitive", { object: focusStrokeLine, renderOrder: renderOrder + 2 }),
|
|
3210
4871
|
onDragStart: (event) => {
|
|
3211
4872
|
startVerticesRef.current = cloneVertices(vertices);
|
|
3212
4873
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3217,7 +4878,7 @@ function PolygonHotspot({
|
|
|
3217
4878
|
startVerticesRef.current = null;
|
|
3218
4879
|
},
|
|
3219
4880
|
children: [
|
|
3220
|
-
/* @__PURE__ */
|
|
4881
|
+
/* @__PURE__ */ jsx11("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx11(
|
|
3221
4882
|
"meshBasicMaterial",
|
|
3222
4883
|
{
|
|
3223
4884
|
color: fill,
|
|
@@ -3227,25 +4888,25 @@ function PolygonHotspot({
|
|
|
3227
4888
|
transparent: true
|
|
3228
4889
|
}
|
|
3229
4890
|
) }),
|
|
3230
|
-
/* @__PURE__ */
|
|
4891
|
+
/* @__PURE__ */ jsx11("primitive", { object: strokeLine, renderOrder: renderOrder + 1 })
|
|
3231
4892
|
]
|
|
3232
4893
|
}
|
|
3233
4894
|
);
|
|
3234
4895
|
}
|
|
3235
4896
|
|
|
3236
4897
|
// src/hotspot/polyline-hotspot.tsx
|
|
3237
|
-
import { useThree as
|
|
3238
|
-
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";
|
|
3239
4900
|
import {
|
|
3240
4901
|
MathUtils as MathUtils5,
|
|
3241
4902
|
Matrix4 as Matrix44,
|
|
3242
4903
|
Quaternion as Quaternion4,
|
|
3243
|
-
Vector3 as
|
|
4904
|
+
Vector3 as Vector38
|
|
3244
4905
|
} from "three";
|
|
3245
4906
|
import { Line2 as Line22 } from "three/addons/lines/Line2.js";
|
|
3246
4907
|
import { LineGeometry as LineGeometry2 } from "three/addons/lines/LineGeometry.js";
|
|
3247
4908
|
import { LineMaterial as LineMaterial2 } from "three/addons/lines/LineMaterial.js";
|
|
3248
|
-
import { jsx as
|
|
4909
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
3249
4910
|
var POLYLINE_RADIUS = 990;
|
|
3250
4911
|
var MAX_SEGMENT_DEGREES = 7;
|
|
3251
4912
|
function cloneVertices2(vertices) {
|
|
@@ -3270,7 +4931,7 @@ function unwrapVertices(vertices) {
|
|
|
3270
4931
|
function polylineCenter(vertices) {
|
|
3271
4932
|
const sum = vertices.reduce(
|
|
3272
4933
|
(total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
|
|
3273
|
-
new
|
|
4934
|
+
new Vector38()
|
|
3274
4935
|
);
|
|
3275
4936
|
return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
|
|
3276
4937
|
}
|
|
@@ -3279,7 +4940,7 @@ function surfaceQuaternion2(position) {
|
|
|
3279
4940
|
const inward = outward.clone().negate();
|
|
3280
4941
|
const pitch = MathUtils5.degToRad(position.pitch);
|
|
3281
4942
|
const yaw = MathUtils5.degToRad(position.yaw);
|
|
3282
|
-
const localUp = new
|
|
4943
|
+
const localUp = new Vector38(
|
|
3283
4944
|
-Math.sin(pitch) * Math.sin(yaw),
|
|
3284
4945
|
Math.cos(pitch),
|
|
3285
4946
|
Math.sin(pitch) * Math.cos(yaw)
|
|
@@ -3292,7 +4953,7 @@ function makeWorldToLocal2(center) {
|
|
|
3292
4953
|
return new Matrix44().compose(
|
|
3293
4954
|
panoPositionToVector3(center, POLYLINE_RADIUS),
|
|
3294
4955
|
surfaceQuaternion2(center),
|
|
3295
|
-
new
|
|
4956
|
+
new Vector38(1, 1, 1)
|
|
3296
4957
|
).invert();
|
|
3297
4958
|
}
|
|
3298
4959
|
function subdivisionSteps(vertices) {
|
|
@@ -3382,32 +5043,32 @@ function PolylineHotspot({
|
|
|
3382
5043
|
onInvalid,
|
|
3383
5044
|
...anchorProps
|
|
3384
5045
|
}) {
|
|
3385
|
-
const gl =
|
|
3386
|
-
const size =
|
|
3387
|
-
const issues =
|
|
5046
|
+
const gl = useThree13((state) => state.gl);
|
|
5047
|
+
const size = useThree13((state) => state.size);
|
|
5048
|
+
const issues = useMemo10(() => validatePolylineVertices(vertices), [vertices]);
|
|
3388
5049
|
const valid = issues.length === 0;
|
|
3389
|
-
const center =
|
|
3390
|
-
const linePositions =
|
|
5050
|
+
const center = useMemo10(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
|
|
5051
|
+
const linePositions = useMemo10(
|
|
3391
5052
|
() => valid && center ? makeLinePositions(vertices, center) : null,
|
|
3392
5053
|
[center, valid, vertices]
|
|
3393
5054
|
);
|
|
3394
5055
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3395
5056
|
const opacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3396
|
-
const line =
|
|
5057
|
+
const line = useMemo10(
|
|
3397
5058
|
() => linePositions ? makeLine(linePositions, stroke, strokeWidth * devicePixelRatio, opacity) : null,
|
|
3398
5059
|
[devicePixelRatio, linePositions, opacity, stroke, strokeWidth]
|
|
3399
5060
|
);
|
|
3400
|
-
const focusLine =
|
|
5061
|
+
const focusLine = useMemo10(
|
|
3401
5062
|
() => linePositions ? makeLine(linePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3402
5063
|
[devicePixelRatio, linePositions, strokeWidth]
|
|
3403
5064
|
);
|
|
3404
|
-
const startVerticesRef =
|
|
3405
|
-
|
|
5065
|
+
const startVerticesRef = useRef16(null);
|
|
5066
|
+
useEffect18(() => {
|
|
3406
5067
|
if (!valid) onInvalid?.(issues);
|
|
3407
5068
|
}, [issues, onInvalid, valid]);
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
5069
|
+
useEffect18(() => () => disposeLine(line), [line]);
|
|
5070
|
+
useEffect18(() => () => disposeLine(focusLine), [focusLine]);
|
|
5071
|
+
useEffect18(() => {
|
|
3411
5072
|
const width = gl.domElement.width;
|
|
3412
5073
|
const height = gl.domElement.height;
|
|
3413
5074
|
for (const nextLine of [line, focusLine]) {
|
|
@@ -3425,7 +5086,7 @@ function PolylineHotspot({
|
|
|
3425
5086
|
startPosition: event.startPosition
|
|
3426
5087
|
});
|
|
3427
5088
|
};
|
|
3428
|
-
return /* @__PURE__ */
|
|
5089
|
+
return /* @__PURE__ */ jsx12(
|
|
3429
5090
|
HotspotAnchor,
|
|
3430
5091
|
{
|
|
3431
5092
|
...anchorProps,
|
|
@@ -3439,7 +5100,7 @@ function PolylineHotspot({
|
|
|
3439
5100
|
renderOrder,
|
|
3440
5101
|
draggable,
|
|
3441
5102
|
useAngularScale: false,
|
|
3442
|
-
focusContent: /* @__PURE__ */
|
|
5103
|
+
focusContent: /* @__PURE__ */ jsx12("primitive", { object: focusLine, renderOrder: renderOrder + 2 }),
|
|
3443
5104
|
onDragStart: (event) => {
|
|
3444
5105
|
startVerticesRef.current = cloneVertices2(vertices);
|
|
3445
5106
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3449,13 +5110,13 @@ function PolylineHotspot({
|
|
|
3449
5110
|
emitVerticesChange(event, onDragEnd);
|
|
3450
5111
|
startVerticesRef.current = null;
|
|
3451
5112
|
},
|
|
3452
|
-
children: /* @__PURE__ */
|
|
5113
|
+
children: /* @__PURE__ */ jsx12("primitive", { object: line, renderOrder: renderOrder + 1 })
|
|
3453
5114
|
}
|
|
3454
5115
|
);
|
|
3455
5116
|
}
|
|
3456
5117
|
|
|
3457
5118
|
// src/hotspot/graphic-hotspot.tsx
|
|
3458
|
-
import { useEffect as
|
|
5119
|
+
import { useEffect as useEffect19, useRef as useRef17, useState as useState8 } from "react";
|
|
3459
5120
|
import {
|
|
3460
5121
|
CanvasTexture,
|
|
3461
5122
|
ClampToEdgeWrapping as ClampToEdgeWrapping4,
|
|
@@ -3464,7 +5125,7 @@ import {
|
|
|
3464
5125
|
LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
|
|
3465
5126
|
SRGBColorSpace as SRGBColorSpace5
|
|
3466
5127
|
} from "three";
|
|
3467
|
-
import { jsx as
|
|
5128
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3468
5129
|
var GRAPHIC_TEXTURE_SIZE = 512;
|
|
3469
5130
|
function clampOpacity2(opacity) {
|
|
3470
5131
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3475,14 +5136,34 @@ function clampOpacity2(opacity) {
|
|
|
3475
5136
|
function resolveStrokeWidth(value) {
|
|
3476
5137
|
return Number.isFinite(value) ? Math.max(value, 0) : 8;
|
|
3477
5138
|
}
|
|
3478
|
-
function
|
|
3479
|
-
|
|
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) {
|
|
3480
5162
|
context.beginPath();
|
|
3481
|
-
context.moveTo(
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
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
|
+
}
|
|
3486
5167
|
context.closePath();
|
|
3487
5168
|
}
|
|
3488
5169
|
function paintPath(context, graphic, path) {
|
|
@@ -3496,7 +5177,7 @@ function paintPath(context, graphic, path) {
|
|
|
3496
5177
|
path ? context.stroke(path) : context.stroke();
|
|
3497
5178
|
}
|
|
3498
5179
|
}
|
|
3499
|
-
function drawGraphic(context, graphic) {
|
|
5180
|
+
function drawGraphic(context, graphic, aspectWidth, aspectHeight) {
|
|
3500
5181
|
const padding = Math.max(resolveStrokeWidth(graphic.strokeWidth) / 2 + 2, 4);
|
|
3501
5182
|
const drawableSize = GRAPHIC_TEXTURE_SIZE - padding * 2;
|
|
3502
5183
|
context.clearRect(0, 0, GRAPHIC_TEXTURE_SIZE, GRAPHIC_TEXTURE_SIZE);
|
|
@@ -3514,6 +5195,63 @@ function drawGraphic(context, graphic) {
|
|
|
3514
5195
|
paintPath(context, graphic);
|
|
3515
5196
|
return;
|
|
3516
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
|
+
}
|
|
3517
5255
|
if (graphic.kind === "rectangle") {
|
|
3518
5256
|
roundedRectanglePath(
|
|
3519
5257
|
context,
|
|
@@ -3521,7 +5259,9 @@ function drawGraphic(context, graphic) {
|
|
|
3521
5259
|
padding,
|
|
3522
5260
|
drawableSize,
|
|
3523
5261
|
drawableSize,
|
|
3524
|
-
graphic.cornerRadius ?? 0
|
|
5262
|
+
graphic.cornerRadius ?? 0,
|
|
5263
|
+
aspectWidth,
|
|
5264
|
+
aspectHeight
|
|
3525
5265
|
);
|
|
3526
5266
|
paintPath(context, graphic);
|
|
3527
5267
|
return;
|
|
@@ -3560,7 +5300,7 @@ function drawGraphic(context, graphic) {
|
|
|
3560
5300
|
paintPath(context, graphic, path);
|
|
3561
5301
|
context.restore();
|
|
3562
5302
|
}
|
|
3563
|
-
function createGraphicTexture(graphic) {
|
|
5303
|
+
function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
|
|
3564
5304
|
const canvas = document.createElement("canvas");
|
|
3565
5305
|
canvas.width = GRAPHIC_TEXTURE_SIZE;
|
|
3566
5306
|
canvas.height = GRAPHIC_TEXTURE_SIZE;
|
|
@@ -3568,7 +5308,7 @@ function createGraphicTexture(graphic) {
|
|
|
3568
5308
|
if (!context) {
|
|
3569
5309
|
throw new Error("Canvas 2D is unavailable for this graphic hotspot.");
|
|
3570
5310
|
}
|
|
3571
|
-
drawGraphic(context, graphic);
|
|
5311
|
+
drawGraphic(context, graphic, aspectWidth, aspectHeight);
|
|
3572
5312
|
const texture = new CanvasTexture(canvas);
|
|
3573
5313
|
texture.colorSpace = SRGBColorSpace5;
|
|
3574
5314
|
texture.wrapS = ClampToEdgeWrapping4;
|
|
@@ -3579,16 +5319,16 @@ function createGraphicTexture(graphic) {
|
|
|
3579
5319
|
texture.needsUpdate = true;
|
|
3580
5320
|
return texture;
|
|
3581
5321
|
}
|
|
3582
|
-
function useGraphicTexture(graphic, onLoad, onError) {
|
|
3583
|
-
const [texture, setTexture] =
|
|
3584
|
-
const onLoadRef =
|
|
3585
|
-
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);
|
|
3586
5326
|
onLoadRef.current = onLoad;
|
|
3587
5327
|
onErrorRef.current = onError;
|
|
3588
|
-
|
|
5328
|
+
useEffect19(() => {
|
|
3589
5329
|
let nextTexture = null;
|
|
3590
5330
|
try {
|
|
3591
|
-
nextTexture = createGraphicTexture(graphic);
|
|
5331
|
+
nextTexture = createGraphicTexture(graphic, aspectWidth, aspectHeight);
|
|
3592
5332
|
setTexture(nextTexture);
|
|
3593
5333
|
onLoadRef.current?.(nextTexture);
|
|
3594
5334
|
} catch (error) {
|
|
@@ -3598,7 +5338,7 @@ function useGraphicTexture(graphic, onLoad, onError) {
|
|
|
3598
5338
|
return () => {
|
|
3599
5339
|
nextTexture?.dispose();
|
|
3600
5340
|
};
|
|
3601
|
-
}, [graphic]);
|
|
5341
|
+
}, [aspectHeight, aspectWidth, graphic]);
|
|
3602
5342
|
return texture;
|
|
3603
5343
|
}
|
|
3604
5344
|
function CanvasGraphicHotspot({
|
|
@@ -3610,10 +5350,10 @@ function CanvasGraphicHotspot({
|
|
|
3610
5350
|
onError,
|
|
3611
5351
|
...anchorProps
|
|
3612
5352
|
}) {
|
|
3613
|
-
const texture = useGraphicTexture(graphic, onLoad, onError);
|
|
3614
|
-
return /* @__PURE__ */
|
|
3615
|
-
/* @__PURE__ */
|
|
3616
|
-
/* @__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(
|
|
3617
5357
|
"meshBasicMaterial",
|
|
3618
5358
|
{
|
|
3619
5359
|
map: texture,
|
|
@@ -3629,14 +5369,14 @@ function GraphicHotspot(props) {
|
|
|
3629
5369
|
const { graphic, ...restProps } = props;
|
|
3630
5370
|
if (graphic.kind === "svg") {
|
|
3631
5371
|
const imageProps = restProps;
|
|
3632
|
-
return /* @__PURE__ */
|
|
5372
|
+
return /* @__PURE__ */ jsx13(ImageHotspot, { ...imageProps, src: graphic.src });
|
|
3633
5373
|
}
|
|
3634
|
-
return /* @__PURE__ */
|
|
5374
|
+
return /* @__PURE__ */ jsx13(CanvasGraphicHotspot, { ...restProps, graphic });
|
|
3635
5375
|
}
|
|
3636
5376
|
|
|
3637
5377
|
// src/hotspot/sequence-hotspot.tsx
|
|
3638
|
-
import { useFrame as
|
|
3639
|
-
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";
|
|
3640
5380
|
import {
|
|
3641
5381
|
ClampToEdgeWrapping as ClampToEdgeWrapping5,
|
|
3642
5382
|
DoubleSide as DoubleSide5,
|
|
@@ -3645,7 +5385,7 @@ import {
|
|
|
3645
5385
|
SRGBColorSpace as SRGBColorSpace6,
|
|
3646
5386
|
TextureLoader as TextureLoader5
|
|
3647
5387
|
} from "three";
|
|
3648
|
-
import { jsx as
|
|
5388
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3649
5389
|
var DEFAULT_SEQUENCE_FPS = 12;
|
|
3650
5390
|
function clampOpacity3(opacity) {
|
|
3651
5391
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3665,13 +5405,13 @@ function useSpriteTexture({
|
|
|
3665
5405
|
onError,
|
|
3666
5406
|
onLoadProgress
|
|
3667
5407
|
}) {
|
|
3668
|
-
const gl =
|
|
3669
|
-
const [texture, setTexture] =
|
|
3670
|
-
const onErrorRef =
|
|
3671
|
-
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);
|
|
3672
5412
|
onErrorRef.current = onError;
|
|
3673
5413
|
onLoadProgressRef.current = onLoadProgress;
|
|
3674
|
-
|
|
5414
|
+
useEffect20(() => {
|
|
3675
5415
|
let active = true;
|
|
3676
5416
|
setTexture(null);
|
|
3677
5417
|
if (!src) {
|
|
@@ -3712,7 +5452,7 @@ function useSpriteTexture({
|
|
|
3712
5452
|
active = false;
|
|
3713
5453
|
};
|
|
3714
5454
|
}, [gl, id, src]);
|
|
3715
|
-
|
|
5455
|
+
useEffect20(
|
|
3716
5456
|
() => () => {
|
|
3717
5457
|
texture?.dispose();
|
|
3718
5458
|
},
|
|
@@ -3741,24 +5481,24 @@ function SequenceHotspot({
|
|
|
3741
5481
|
const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
|
|
3742
5482
|
const frameCount = resolveFrameCount(requestedFrameCount);
|
|
3743
5483
|
const safeFps = resolveFps(fps);
|
|
3744
|
-
const [frameIndex, setFrameIndex] =
|
|
3745
|
-
const elapsedRef =
|
|
3746
|
-
const frameIndexRef =
|
|
3747
|
-
const endedRef =
|
|
3748
|
-
const lastPlaybackStateRef =
|
|
3749
|
-
const onFrameChangeRef =
|
|
3750
|
-
const onPlaybackStateChangeRef =
|
|
3751
|
-
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);
|
|
3752
5492
|
onFrameChangeRef.current = onFrameChange;
|
|
3753
5493
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3754
5494
|
onEndedRef.current = onEnded;
|
|
3755
|
-
|
|
5495
|
+
useEffect20(() => {
|
|
3756
5496
|
elapsedRef.current = 0;
|
|
3757
5497
|
frameIndexRef.current = 0;
|
|
3758
5498
|
endedRef.current = false;
|
|
3759
5499
|
setFrameIndex(0);
|
|
3760
5500
|
}, [frameCount, frameDirection, src]);
|
|
3761
|
-
|
|
5501
|
+
useEffect20(() => {
|
|
3762
5502
|
if (!texture) {
|
|
3763
5503
|
return;
|
|
3764
5504
|
}
|
|
@@ -3772,13 +5512,13 @@ function SequenceHotspot({
|
|
|
3772
5512
|
);
|
|
3773
5513
|
texture.needsUpdate = true;
|
|
3774
5514
|
}, [frameCount, frameDirection, frameIndex, texture]);
|
|
3775
|
-
|
|
5515
|
+
useEffect20(() => {
|
|
3776
5516
|
if (!texture) {
|
|
3777
5517
|
return;
|
|
3778
5518
|
}
|
|
3779
5519
|
onFrameChangeRef.current?.({ id, index: 0, src });
|
|
3780
5520
|
}, [id, src, texture]);
|
|
3781
|
-
|
|
5521
|
+
useEffect20(() => {
|
|
3782
5522
|
if (!texture) {
|
|
3783
5523
|
return;
|
|
3784
5524
|
}
|
|
@@ -3797,7 +5537,7 @@ function SequenceHotspot({
|
|
|
3797
5537
|
onPlaybackStateChangeRef.current?.(nextState);
|
|
3798
5538
|
}
|
|
3799
5539
|
}, [playing, texture]);
|
|
3800
|
-
|
|
5540
|
+
useFrame8((_, deltaSeconds) => {
|
|
3801
5541
|
if (!texture || !playing || endedRef.current) {
|
|
3802
5542
|
return;
|
|
3803
5543
|
}
|
|
@@ -3827,9 +5567,9 @@ function SequenceHotspot({
|
|
|
3827
5567
|
onFrameChangeRef.current?.({ id, index: nextIndex, src });
|
|
3828
5568
|
}
|
|
3829
5569
|
});
|
|
3830
|
-
return /* @__PURE__ */
|
|
3831
|
-
/* @__PURE__ */
|
|
3832
|
-
/* @__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(
|
|
3833
5573
|
"meshBasicMaterial",
|
|
3834
5574
|
{
|
|
3835
5575
|
map: texture,
|
|
@@ -3843,8 +5583,8 @@ function SequenceHotspot({
|
|
|
3843
5583
|
}
|
|
3844
5584
|
|
|
3845
5585
|
// src/hotspot/video-hotspot.tsx
|
|
3846
|
-
import { useThree as
|
|
3847
|
-
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";
|
|
3848
5588
|
import {
|
|
3849
5589
|
ClampToEdgeWrapping as ClampToEdgeWrapping6,
|
|
3850
5590
|
DoubleSide as DoubleSide6,
|
|
@@ -3854,7 +5594,7 @@ import {
|
|
|
3854
5594
|
TextureLoader as TextureLoader6,
|
|
3855
5595
|
VideoTexture
|
|
3856
5596
|
} from "three";
|
|
3857
|
-
import { jsx as
|
|
5597
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3858
5598
|
function clampOpacity4(opacity) {
|
|
3859
5599
|
if (!Number.isFinite(opacity)) {
|
|
3860
5600
|
return 1;
|
|
@@ -3868,11 +5608,11 @@ function clampVolume(volume) {
|
|
|
3868
5608
|
return Math.max(0, Math.min(volume, 1));
|
|
3869
5609
|
}
|
|
3870
5610
|
function usePosterTexture(id, poster, onError) {
|
|
3871
|
-
const gl =
|
|
3872
|
-
const [texture, setTexture] =
|
|
3873
|
-
const onErrorRef =
|
|
5611
|
+
const gl = useThree15((state) => state.gl);
|
|
5612
|
+
const [texture, setTexture] = useState10(null);
|
|
5613
|
+
const onErrorRef = useRef19(onError);
|
|
3874
5614
|
onErrorRef.current = onError;
|
|
3875
|
-
|
|
5615
|
+
useEffect21(() => {
|
|
3876
5616
|
let active = true;
|
|
3877
5617
|
setTexture(null);
|
|
3878
5618
|
if (!poster) {
|
|
@@ -3909,7 +5649,7 @@ function usePosterTexture(id, poster, onError) {
|
|
|
3909
5649
|
active = false;
|
|
3910
5650
|
};
|
|
3911
5651
|
}, [gl, id, poster]);
|
|
3912
|
-
|
|
5652
|
+
useEffect21(
|
|
3913
5653
|
() => () => {
|
|
3914
5654
|
texture?.dispose();
|
|
3915
5655
|
},
|
|
@@ -3927,14 +5667,14 @@ function useVideoResource({
|
|
|
3927
5667
|
poster,
|
|
3928
5668
|
src
|
|
3929
5669
|
}) {
|
|
3930
|
-
const [resource, setResource] =
|
|
3931
|
-
const onEndedRef =
|
|
3932
|
-
const onErrorRef =
|
|
3933
|
-
const onPlaybackStateChangeRef =
|
|
5670
|
+
const [resource, setResource] = useState10(null);
|
|
5671
|
+
const onEndedRef = useRef19(onEnded);
|
|
5672
|
+
const onErrorRef = useRef19(onError);
|
|
5673
|
+
const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
|
|
3934
5674
|
onEndedRef.current = onEnded;
|
|
3935
5675
|
onErrorRef.current = onError;
|
|
3936
5676
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3937
|
-
|
|
5677
|
+
useEffect21(() => {
|
|
3938
5678
|
if (!src) {
|
|
3939
5679
|
setResource(null);
|
|
3940
5680
|
return;
|
|
@@ -4035,11 +5775,11 @@ function VideoHotspot({
|
|
|
4035
5775
|
src
|
|
4036
5776
|
});
|
|
4037
5777
|
const posterTexture = usePosterTexture(id, poster, onError);
|
|
4038
|
-
const onPlaybackErrorRef =
|
|
4039
|
-
const onPlaybackStateChangeRef =
|
|
5778
|
+
const onPlaybackErrorRef = useRef19(onPlaybackError);
|
|
5779
|
+
const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
|
|
4040
5780
|
onPlaybackErrorRef.current = onPlaybackError;
|
|
4041
5781
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
4042
|
-
|
|
5782
|
+
useEffect21(() => {
|
|
4043
5783
|
if (!resource) {
|
|
4044
5784
|
return;
|
|
4045
5785
|
}
|
|
@@ -4049,7 +5789,7 @@ function VideoHotspot({
|
|
|
4049
5789
|
resource.video.playsInline = playsInline;
|
|
4050
5790
|
resource.video.preload = preload;
|
|
4051
5791
|
}, [loop, muted, playsInline, preload, resource, volume]);
|
|
4052
|
-
|
|
5792
|
+
useEffect21(() => {
|
|
4053
5793
|
if (!resource) {
|
|
4054
5794
|
return;
|
|
4055
5795
|
}
|
|
@@ -4072,9 +5812,9 @@ function VideoHotspot({
|
|
|
4072
5812
|
};
|
|
4073
5813
|
}, [playing, resource]);
|
|
4074
5814
|
const activeTexture = resource?.ready ? resource.texture : posterTexture;
|
|
4075
|
-
return /* @__PURE__ */
|
|
4076
|
-
/* @__PURE__ */
|
|
4077
|
-
/* @__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(
|
|
4078
5818
|
"meshBasicMaterial",
|
|
4079
5819
|
{
|
|
4080
5820
|
map: activeTexture,
|
|
@@ -4090,7 +5830,12 @@ export {
|
|
|
4090
5830
|
AutoRotate,
|
|
4091
5831
|
GraphicHotspot,
|
|
4092
5832
|
ImageHotspot,
|
|
5833
|
+
KeyboardControls,
|
|
4093
5834
|
MAX_HOTSPOT_PITCH,
|
|
5835
|
+
MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY,
|
|
5836
|
+
MouseControls,
|
|
5837
|
+
PanoContextMenu,
|
|
5838
|
+
PanoEvents,
|
|
4094
5839
|
PanoView,
|
|
4095
5840
|
PanoramaScenes,
|
|
4096
5841
|
PolygonHotspot,
|
|
@@ -4098,12 +5843,19 @@ export {
|
|
|
4098
5843
|
SequenceHotspot,
|
|
4099
5844
|
Sphere,
|
|
4100
5845
|
Tile,
|
|
5846
|
+
TouchControls,
|
|
4101
5847
|
VideoHotspot,
|
|
4102
5848
|
clampPanoPitch,
|
|
5849
|
+
composePanoContextMenuItems,
|
|
5850
|
+
createDefaultPanoContextMenuItems,
|
|
5851
|
+
createPanoContextMenuPresets,
|
|
5852
|
+
cycleSceneId,
|
|
4103
5853
|
normalizePanoPosition,
|
|
4104
5854
|
normalizePanoYaw,
|
|
4105
5855
|
panoPositionToVector3,
|
|
5856
|
+
resolvePanoContextMenuEntries,
|
|
4106
5857
|
unwrapPolygonVertices,
|
|
5858
|
+
usePanoEvents,
|
|
4107
5859
|
validatePolygonVertices,
|
|
4108
5860
|
validatePolylineVertices,
|
|
4109
5861
|
vector3ToPanoPosition
|