@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.cjs
CHANGED
|
@@ -24,7 +24,12 @@ __export(index_exports, {
|
|
|
24
24
|
AutoRotate: () => AutoRotate,
|
|
25
25
|
GraphicHotspot: () => GraphicHotspot,
|
|
26
26
|
ImageHotspot: () => ImageHotspot,
|
|
27
|
+
KeyboardControls: () => KeyboardControls,
|
|
27
28
|
MAX_HOTSPOT_PITCH: () => MAX_HOTSPOT_PITCH,
|
|
29
|
+
MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY: () => MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY,
|
|
30
|
+
MouseControls: () => MouseControls,
|
|
31
|
+
PanoContextMenu: () => PanoContextMenu,
|
|
32
|
+
PanoEvents: () => PanoEvents,
|
|
28
33
|
PanoView: () => PanoView,
|
|
29
34
|
PanoramaScenes: () => PanoramaScenes,
|
|
30
35
|
PolygonHotspot: () => PolygonHotspot,
|
|
@@ -32,12 +37,19 @@ __export(index_exports, {
|
|
|
32
37
|
SequenceHotspot: () => SequenceHotspot,
|
|
33
38
|
Sphere: () => Sphere,
|
|
34
39
|
Tile: () => Tile,
|
|
40
|
+
TouchControls: () => TouchControls,
|
|
35
41
|
VideoHotspot: () => VideoHotspot,
|
|
36
42
|
clampPanoPitch: () => clampPanoPitch,
|
|
43
|
+
composePanoContextMenuItems: () => composePanoContextMenuItems,
|
|
44
|
+
createDefaultPanoContextMenuItems: () => createDefaultPanoContextMenuItems,
|
|
45
|
+
createPanoContextMenuPresets: () => createPanoContextMenuPresets,
|
|
46
|
+
cycleSceneId: () => cycleSceneId,
|
|
37
47
|
normalizePanoPosition: () => normalizePanoPosition,
|
|
38
48
|
normalizePanoYaw: () => normalizePanoYaw,
|
|
39
49
|
panoPositionToVector3: () => panoPositionToVector3,
|
|
50
|
+
resolvePanoContextMenuEntries: () => resolvePanoContextMenuEntries,
|
|
40
51
|
unwrapPolygonVertices: () => unwrapPolygonVertices,
|
|
52
|
+
usePanoEvents: () => usePanoEvents,
|
|
41
53
|
validatePolygonVertices: () => validatePolygonVertices,
|
|
42
54
|
validatePolylineVertices: () => validatePolylineVertices,
|
|
43
55
|
vector3ToPanoPosition: () => vector3ToPanoPosition
|
|
@@ -45,21 +57,59 @@ __export(index_exports, {
|
|
|
45
57
|
module.exports = __toCommonJS(index_exports);
|
|
46
58
|
|
|
47
59
|
// src/pano-view.tsx
|
|
48
|
-
var
|
|
49
|
-
var
|
|
60
|
+
var import_fiber8 = require("@react-three/fiber");
|
|
61
|
+
var import_react11 = require("react");
|
|
50
62
|
|
|
51
|
-
// src/
|
|
52
|
-
var
|
|
63
|
+
// src/auto-rotate.tsx
|
|
64
|
+
var import_fiber2 = require("@react-three/fiber");
|
|
65
|
+
var import_react3 = require("react");
|
|
66
|
+
|
|
67
|
+
// src/pano-event-bus.ts
|
|
53
68
|
var import_react = require("react");
|
|
69
|
+
function createPanoEventBus() {
|
|
70
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
71
|
+
return {
|
|
72
|
+
subscribe(type, listener) {
|
|
73
|
+
let set = listeners.get(type);
|
|
74
|
+
if (!set) {
|
|
75
|
+
set = /* @__PURE__ */ new Set();
|
|
76
|
+
listeners.set(type, set);
|
|
77
|
+
}
|
|
78
|
+
set.add(listener);
|
|
79
|
+
return () => {
|
|
80
|
+
set?.delete(listener);
|
|
81
|
+
if (set && set.size === 0) {
|
|
82
|
+
listeners.delete(type);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
emit(type, payload) {
|
|
87
|
+
const set = listeners.get(type);
|
|
88
|
+
if (!set || set.size === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const listener of [...set]) {
|
|
92
|
+
listener(payload);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var PanoEventBusContext = (0, import_react.createContext)(null);
|
|
98
|
+
|
|
99
|
+
// src/panorama-view-runtime.tsx
|
|
100
|
+
var import_fiber = require("@react-three/fiber");
|
|
101
|
+
var import_react2 = require("react");
|
|
54
102
|
var import_three = require("three");
|
|
55
|
-
var MAX_PITCH =
|
|
103
|
+
var MAX_PITCH = 90;
|
|
104
|
+
var BOUNCE_OVERSHOOT_PITCH = 12;
|
|
105
|
+
var BOUNCE_OVERSHOOT_FOV_RATIO = 0.15;
|
|
56
106
|
var INERTIA_ROTATION_DAMPING = 8;
|
|
57
107
|
var INERTIA_ZOOM_DAMPING = 12;
|
|
58
108
|
var DEFAULT_ROTATE_DAMPING = 14;
|
|
59
109
|
var DEFAULT_ZOOM_DAMPING = 16;
|
|
60
|
-
var
|
|
61
|
-
var MIN_ZOOM_VELOCITY = 0.01;
|
|
110
|
+
var DEFAULT_FRICTION_STOP = 0.01;
|
|
62
111
|
var VIEW_SETTLE_EPSILON = 1e-3;
|
|
112
|
+
var PanoramaViewContext = (0, import_react2.createContext)(null);
|
|
63
113
|
function clamp(value, min, max) {
|
|
64
114
|
return Math.max(min, Math.min(max, value));
|
|
65
115
|
}
|
|
@@ -75,54 +125,104 @@ function dampingFactor(damping, deltaSeconds) {
|
|
|
75
125
|
function resolveDamping(value, fallback) {
|
|
76
126
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
77
127
|
}
|
|
78
|
-
function
|
|
79
|
-
return
|
|
128
|
+
function resolveFrictionStop(value) {
|
|
129
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_FRICTION_STOP;
|
|
80
130
|
}
|
|
81
131
|
function viewsEqual(a, b) {
|
|
82
132
|
return a.yaw === b.yaw && a.pitch === b.pitch && a.fov === b.fov;
|
|
83
133
|
}
|
|
84
|
-
var
|
|
85
|
-
const { camera
|
|
86
|
-
const viewRef = (0,
|
|
87
|
-
const targetViewRef = (0,
|
|
88
|
-
const initialViewRef = (0,
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
134
|
+
var PanoramaViewRuntime = (0, import_react2.forwardRef)(function PanoramaViewRuntime2({ initialView, minFov, maxFov, options, eventBus }, ref) {
|
|
135
|
+
const { camera } = (0, import_fiber.useThree)();
|
|
136
|
+
const viewRef = (0, import_react2.useRef)({ ...initialView });
|
|
137
|
+
const targetViewRef = (0, import_react2.useRef)({ ...initialView });
|
|
138
|
+
const initialViewRef = (0, import_react2.useRef)({ ...initialView });
|
|
139
|
+
const yawVelocityRef = (0, import_react2.useRef)(0);
|
|
140
|
+
const pitchVelocityRef = (0, import_react2.useRef)(0);
|
|
141
|
+
const zoomVelocityRef = (0, import_react2.useRef)(0);
|
|
142
|
+
const dirtyRef = (0, import_react2.useRef)(true);
|
|
143
|
+
const settledRef = (0, import_react2.useRef)(false);
|
|
144
|
+
const interactingRef = (0, import_react2.useRef)(false);
|
|
145
|
+
const mouseActiveRef = (0, import_react2.useRef)(false);
|
|
146
|
+
const touchActiveRef = (0, import_react2.useRef)(false);
|
|
147
|
+
const interactionLockCountRef = (0, import_react2.useRef)(0);
|
|
148
|
+
const keyboardActiveRef = (0, import_react2.useRef)(false);
|
|
149
|
+
const lastVelocitySampleAtRef = (0, import_react2.useRef)(null);
|
|
150
|
+
const optionsRef = (0, import_react2.useRef)(options);
|
|
151
|
+
const eventBusRef = (0, import_react2.useRef)(eventBus);
|
|
152
|
+
const lastEmittedViewRef = (0, import_react2.useRef)(null);
|
|
153
|
+
const eulerRef = (0, import_react2.useRef)(new import_three.Euler(0, 0, 0, "YXZ"));
|
|
154
|
+
const minFovRef = (0, import_react2.useRef)(minFov);
|
|
155
|
+
const maxFovRef = (0, import_react2.useRef)(maxFov);
|
|
101
156
|
optionsRef.current = options;
|
|
102
|
-
|
|
103
|
-
|
|
157
|
+
eventBusRef.current = eventBus;
|
|
158
|
+
minFovRef.current = minFov;
|
|
159
|
+
maxFovRef.current = maxFov;
|
|
160
|
+
const hasInertia = () => yawVelocityRef.current !== 0 || pitchVelocityRef.current !== 0 || zoomVelocityRef.current !== 0;
|
|
161
|
+
const isUserInteracting = () => mouseActiveRef.current || touchActiveRef.current || keyboardActiveRef.current || interactionLockCountRef.current > 0 || hasInertia();
|
|
162
|
+
const syncInteracting = () => {
|
|
163
|
+
interactingRef.current = mouseActiveRef.current || touchActiveRef.current;
|
|
164
|
+
};
|
|
165
|
+
const hardConstrainView = (view, current = targetViewRef.current) => {
|
|
104
166
|
return {
|
|
105
167
|
yaw: normalizeYaw(view.yaw ?? current.yaw),
|
|
106
168
|
pitch: clamp(view.pitch ?? current.pitch, -MAX_PITCH, MAX_PITCH),
|
|
107
|
-
fov: clamp(
|
|
169
|
+
fov: clamp(
|
|
170
|
+
view.fov ?? current.fov,
|
|
171
|
+
minFovRef.current,
|
|
172
|
+
maxFovRef.current
|
|
173
|
+
)
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const softConstrainView = (view, current = targetViewRef.current) => {
|
|
177
|
+
const bouncing = optionsRef.current.bouncingLimits === true;
|
|
178
|
+
if (!bouncing || !interactingRef.current) {
|
|
179
|
+
return hardConstrainView(view, current);
|
|
180
|
+
}
|
|
181
|
+
const fovRange = Math.max(maxFovRef.current - minFovRef.current, 1);
|
|
182
|
+
const fovOvershoot = fovRange * BOUNCE_OVERSHOOT_FOV_RATIO;
|
|
183
|
+
return {
|
|
184
|
+
yaw: normalizeYaw(view.yaw ?? current.yaw),
|
|
185
|
+
pitch: clamp(
|
|
186
|
+
view.pitch ?? current.pitch,
|
|
187
|
+
-MAX_PITCH - BOUNCE_OVERSHOOT_PITCH,
|
|
188
|
+
MAX_PITCH + BOUNCE_OVERSHOOT_PITCH
|
|
189
|
+
),
|
|
190
|
+
fov: clamp(
|
|
191
|
+
view.fov ?? current.fov,
|
|
192
|
+
minFovRef.current - fovOvershoot,
|
|
193
|
+
maxFovRef.current + fovOvershoot
|
|
194
|
+
)
|
|
108
195
|
};
|
|
109
196
|
};
|
|
110
197
|
const stopVelocity = () => {
|
|
111
198
|
yawVelocityRef.current = 0;
|
|
112
199
|
pitchVelocityRef.current = 0;
|
|
113
200
|
zoomVelocityRef.current = 0;
|
|
201
|
+
lastVelocitySampleAtRef.current = null;
|
|
202
|
+
};
|
|
203
|
+
const markDirty = () => {
|
|
204
|
+
dirtyRef.current = true;
|
|
205
|
+
settledRef.current = false;
|
|
206
|
+
};
|
|
207
|
+
const snapTargetToHardLimits = () => {
|
|
208
|
+
const next = hardConstrainView(targetViewRef.current);
|
|
209
|
+
if (next.yaw !== targetViewRef.current.yaw || next.pitch !== targetViewRef.current.pitch || next.fov !== targetViewRef.current.fov) {
|
|
210
|
+
targetViewRef.current = next;
|
|
211
|
+
markDirty();
|
|
212
|
+
}
|
|
114
213
|
};
|
|
115
214
|
const acquireInteractionLock = () => {
|
|
116
215
|
interactionLockCountRef.current += 1;
|
|
117
216
|
stopVelocity();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
217
|
+
if (mouseActiveRef.current) {
|
|
218
|
+
mouseActiveRef.current = false;
|
|
219
|
+
eventBusRef.current.emit("viewinteractionend", { source: "mouse" });
|
|
220
|
+
}
|
|
221
|
+
if (touchActiveRef.current) {
|
|
222
|
+
touchActiveRef.current = false;
|
|
223
|
+
eventBusRef.current.emit("viewinteractionend", { source: "touch" });
|
|
124
224
|
}
|
|
125
|
-
|
|
225
|
+
syncInteracting();
|
|
126
226
|
let released = false;
|
|
127
227
|
return () => {
|
|
128
228
|
if (released) {
|
|
@@ -136,233 +236,145 @@ var PanoramaControls = (0, import_react.forwardRef)(function PanoramaControls2({
|
|
|
136
236
|
};
|
|
137
237
|
};
|
|
138
238
|
const setView = (view, setOptions) => {
|
|
139
|
-
const nextView =
|
|
239
|
+
const nextView = hardConstrainView(view);
|
|
140
240
|
targetViewRef.current = nextView;
|
|
141
241
|
viewRef.current = nextView;
|
|
142
242
|
if (setOptions?.immediate !== false) {
|
|
143
243
|
stopVelocity();
|
|
144
244
|
}
|
|
145
|
-
|
|
245
|
+
markDirty();
|
|
246
|
+
};
|
|
247
|
+
const applyViewDelta = (delta, applyOptions) => {
|
|
248
|
+
if (interactionLockCountRef.current > 0) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const current = targetViewRef.current;
|
|
252
|
+
const nextYaw = delta.yaw !== void 0 && Number.isFinite(delta.yaw) ? current.yaw + delta.yaw : current.yaw;
|
|
253
|
+
const nextPitch = delta.pitch !== void 0 && Number.isFinite(delta.pitch) ? current.pitch + delta.pitch : current.pitch;
|
|
254
|
+
const nextFov = delta.fov !== void 0 && Number.isFinite(delta.fov) ? current.fov + delta.fov : current.fov;
|
|
255
|
+
targetViewRef.current = softConstrainView({
|
|
256
|
+
yaw: nextYaw,
|
|
257
|
+
pitch: nextPitch,
|
|
258
|
+
fov: nextFov
|
|
259
|
+
});
|
|
260
|
+
markDirty();
|
|
261
|
+
if (applyOptions?.recordVelocity) {
|
|
262
|
+
const now = performance.now();
|
|
263
|
+
const previousAt = lastVelocitySampleAtRef.current;
|
|
264
|
+
const elapsed = previousAt === null ? 1 / 60 : Math.max((now - previousAt) / 1e3, 1 / 120);
|
|
265
|
+
lastVelocitySampleAtRef.current = now;
|
|
266
|
+
if (delta.yaw !== void 0 && Number.isFinite(delta.yaw)) {
|
|
267
|
+
yawVelocityRef.current = delta.yaw / elapsed;
|
|
268
|
+
}
|
|
269
|
+
if (delta.pitch !== void 0 && Number.isFinite(delta.pitch)) {
|
|
270
|
+
pitchVelocityRef.current = delta.pitch / elapsed;
|
|
271
|
+
}
|
|
272
|
+
if (delta.fov !== void 0 && Number.isFinite(delta.fov)) {
|
|
273
|
+
zoomVelocityRef.current = clamp(delta.fov / elapsed, -180, 180);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
146
277
|
};
|
|
147
|
-
(0,
|
|
278
|
+
(0, import_react2.useImperativeHandle)(
|
|
148
279
|
ref,
|
|
149
280
|
() => ({
|
|
150
281
|
getView: () => ({ ...viewRef.current }),
|
|
282
|
+
getTargetView: () => ({ ...targetViewRef.current }),
|
|
151
283
|
setView,
|
|
152
284
|
reset: () => setView(initialViewRef.current),
|
|
285
|
+
applyViewDelta,
|
|
286
|
+
setKeyboardActive: (active) => {
|
|
287
|
+
const wasActive = keyboardActiveRef.current;
|
|
288
|
+
keyboardActiveRef.current = active;
|
|
289
|
+
if (active && !wasActive) {
|
|
290
|
+
eventBusRef.current.emit("viewinteractionstart", {
|
|
291
|
+
source: "keyboard"
|
|
292
|
+
});
|
|
293
|
+
} else if (!active && wasActive) {
|
|
294
|
+
eventBusRef.current.emit("viewinteractionend", {
|
|
295
|
+
source: "keyboard"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
setPointerActive: (source, active) => {
|
|
300
|
+
const wasActive = source === "mouse" ? mouseActiveRef.current : touchActiveRef.current;
|
|
301
|
+
if (source === "mouse") {
|
|
302
|
+
mouseActiveRef.current = active;
|
|
303
|
+
} else {
|
|
304
|
+
touchActiveRef.current = active;
|
|
305
|
+
}
|
|
306
|
+
syncInteracting();
|
|
307
|
+
if (active && !wasActive) {
|
|
308
|
+
eventBusRef.current.emit("viewinteractionstart", { source });
|
|
309
|
+
} else if (!active && wasActive) {
|
|
310
|
+
eventBusRef.current.emit("viewinteractionend", { source });
|
|
311
|
+
}
|
|
312
|
+
if (active) {
|
|
313
|
+
stopVelocity();
|
|
314
|
+
} else if (!mouseActiveRef.current && !touchActiveRef.current) {
|
|
315
|
+
lastVelocitySampleAtRef.current = null;
|
|
316
|
+
snapTargetToHardLimits();
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
isInteractionLocked: () => interactionLockCountRef.current > 0,
|
|
320
|
+
isUserInteracting,
|
|
153
321
|
acquireInteractionLock,
|
|
154
322
|
applyAutoRotation: (yawDelta) => {
|
|
155
323
|
const inertiaFinished = yawVelocityRef.current === 0 && pitchVelocityRef.current === 0 && zoomVelocityRef.current === 0;
|
|
156
|
-
if (!Number.isFinite(yawDelta) || interactionLockCountRef.current > 0 || interactingRef.current || !inertiaFinished) {
|
|
324
|
+
if (!Number.isFinite(yawDelta) || interactionLockCountRef.current > 0 || interactingRef.current || keyboardActiveRef.current || !inertiaFinished) {
|
|
157
325
|
return false;
|
|
158
326
|
}
|
|
159
327
|
targetViewRef.current.yaw += yawDelta;
|
|
160
|
-
|
|
328
|
+
markDirty();
|
|
161
329
|
return true;
|
|
162
330
|
}
|
|
163
331
|
}),
|
|
164
|
-
[
|
|
332
|
+
[]
|
|
165
333
|
);
|
|
166
|
-
(0,
|
|
334
|
+
(0, import_react2.useEffect)(() => {
|
|
167
335
|
initialViewRef.current = { ...initialView };
|
|
168
336
|
setView(initialView);
|
|
169
337
|
}, [initialView.fov, initialView.pitch, initialView.yaw]);
|
|
170
|
-
(0, import_react.useEffect)(() => {
|
|
171
|
-
const element = gl.domElement;
|
|
172
|
-
if (!enabled) {
|
|
173
|
-
pointersRef.current.clear();
|
|
174
|
-
interactingRef.current = false;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const updatePointer = (event) => {
|
|
178
|
-
const previous = pointersRef.current.get(event.pointerId);
|
|
179
|
-
const now = performance.now();
|
|
180
|
-
const next = { x: event.clientX, y: event.clientY, at: now };
|
|
181
|
-
pointersRef.current.set(event.pointerId, next);
|
|
182
|
-
return { previous, next };
|
|
183
|
-
};
|
|
184
|
-
const onPointerDown = (event) => {
|
|
185
|
-
if (interactionLockCountRef.current > 0) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
event.preventDefault();
|
|
189
|
-
interactingRef.current = true;
|
|
190
|
-
stopVelocity();
|
|
191
|
-
pointersRef.current.set(event.pointerId, {
|
|
192
|
-
x: event.clientX,
|
|
193
|
-
y: event.clientY,
|
|
194
|
-
at: performance.now()
|
|
195
|
-
});
|
|
196
|
-
element.setPointerCapture?.(event.pointerId);
|
|
197
|
-
if (pointersRef.current.size === 2) {
|
|
198
|
-
const [first, second] = Array.from(pointersRef.current.values());
|
|
199
|
-
pinchDistanceRef.current = pointerDistance(first, second);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
const onPointerMove = (event) => {
|
|
203
|
-
if (interactionLockCountRef.current > 0) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (!pointersRef.current.has(event.pointerId)) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
event.preventDefault();
|
|
210
|
-
const { previous, next } = updatePointer(event);
|
|
211
|
-
if (!previous) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const pointers = Array.from(pointersRef.current.values());
|
|
215
|
-
if (pointers.length >= 2) {
|
|
216
|
-
const distance = pointerDistance(pointers[0], pointers[1]);
|
|
217
|
-
const previousDistance = pinchDistanceRef.current;
|
|
218
|
-
pinchDistanceRef.current = distance;
|
|
219
|
-
if (previousDistance && distance > 0) {
|
|
220
|
-
const currentFov = targetViewRef.current.fov;
|
|
221
|
-
const nextFov = clamp(
|
|
222
|
-
currentFov * (previousDistance / distance),
|
|
223
|
-
minFov,
|
|
224
|
-
maxFov
|
|
225
|
-
);
|
|
226
|
-
const elapsed2 = Math.max((next.at - previous.at) / 1e3, 1 / 120);
|
|
227
|
-
zoomVelocityRef.current = (nextFov - currentFov) / elapsed2;
|
|
228
|
-
targetViewRef.current = {
|
|
229
|
-
...targetViewRef.current,
|
|
230
|
-
fov: nextFov
|
|
231
|
-
};
|
|
232
|
-
dirtyRef.current = true;
|
|
233
|
-
}
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const rect = element.getBoundingClientRect();
|
|
237
|
-
const rotateSpeed = optionsRef.current.rotateSpeed ?? 0.35;
|
|
238
|
-
const deltaYaw = -(next.x - previous.x) * 360 * rotateSpeed / Math.max(rect.width, 1);
|
|
239
|
-
const deltaPitch = (next.y - previous.y) * 180 * rotateSpeed / Math.max(rect.height, 1);
|
|
240
|
-
const elapsed = Math.max((next.at - previous.at) / 1e3, 1 / 120);
|
|
241
|
-
targetViewRef.current = constrainView({
|
|
242
|
-
yaw: targetViewRef.current.yaw + deltaYaw,
|
|
243
|
-
pitch: targetViewRef.current.pitch + deltaPitch
|
|
244
|
-
});
|
|
245
|
-
yawVelocityRef.current = deltaYaw / elapsed;
|
|
246
|
-
pitchVelocityRef.current = deltaPitch / elapsed;
|
|
247
|
-
dirtyRef.current = true;
|
|
248
|
-
};
|
|
249
|
-
const releasePointer = (event) => {
|
|
250
|
-
pointersRef.current.delete(event.pointerId);
|
|
251
|
-
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
252
|
-
element.releasePointerCapture?.(event.pointerId);
|
|
253
|
-
}
|
|
254
|
-
if (pointersRef.current.size < 2) {
|
|
255
|
-
pinchDistanceRef.current = null;
|
|
256
|
-
}
|
|
257
|
-
interactingRef.current = pointersRef.current.size > 0;
|
|
258
|
-
};
|
|
259
|
-
const onWheel = (event) => {
|
|
260
|
-
if (interactionLockCountRef.current > 0) {
|
|
261
|
-
event.preventDefault();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
event.preventDefault();
|
|
265
|
-
const zoomSpeed = optionsRef.current.zoomSpeed ?? 0.08;
|
|
266
|
-
const delta = event.deltaY * zoomSpeed;
|
|
267
|
-
const nextFov = clamp(
|
|
268
|
-
targetViewRef.current.fov + delta,
|
|
269
|
-
minFov,
|
|
270
|
-
maxFov
|
|
271
|
-
);
|
|
272
|
-
zoomVelocityRef.current = clamp(
|
|
273
|
-
zoomVelocityRef.current + delta * 12,
|
|
274
|
-
-180,
|
|
275
|
-
180
|
|
276
|
-
);
|
|
277
|
-
targetViewRef.current = {
|
|
278
|
-
...targetViewRef.current,
|
|
279
|
-
fov: nextFov
|
|
280
|
-
};
|
|
281
|
-
dirtyRef.current = true;
|
|
282
|
-
};
|
|
283
|
-
const onKeyDown = (event) => {
|
|
284
|
-
if (interactionLockCountRef.current > 0) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (optionsRef.current.keyboard === false) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const step = event.shiftKey ? 10 : 3;
|
|
291
|
-
let next = null;
|
|
292
|
-
if (event.key === "ArrowLeft") {
|
|
293
|
-
next = { yaw: targetViewRef.current.yaw - step };
|
|
294
|
-
} else if (event.key === "ArrowRight") {
|
|
295
|
-
next = { yaw: targetViewRef.current.yaw + step };
|
|
296
|
-
} else if (event.key === "ArrowUp") {
|
|
297
|
-
next = { pitch: targetViewRef.current.pitch + step };
|
|
298
|
-
} else if (event.key === "ArrowDown") {
|
|
299
|
-
next = { pitch: targetViewRef.current.pitch - step };
|
|
300
|
-
} else if (event.key === "+" || event.key === "=") {
|
|
301
|
-
next = { fov: targetViewRef.current.fov - step };
|
|
302
|
-
} else if (event.key === "-" || event.key === "_") {
|
|
303
|
-
next = { fov: targetViewRef.current.fov + step };
|
|
304
|
-
} else if (event.key === "0") {
|
|
305
|
-
setView(initialViewRef.current);
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (next) {
|
|
310
|
-
event.preventDefault();
|
|
311
|
-
targetViewRef.current = constrainView(next);
|
|
312
|
-
dirtyRef.current = true;
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
element.addEventListener("pointerdown", onPointerDown);
|
|
316
|
-
element.addEventListener("pointermove", onPointerMove);
|
|
317
|
-
element.addEventListener("pointerup", releasePointer);
|
|
318
|
-
element.addEventListener("pointercancel", releasePointer);
|
|
319
|
-
element.addEventListener("wheel", onWheel, { passive: false });
|
|
320
|
-
element.addEventListener("keydown", onKeyDown);
|
|
321
|
-
return () => {
|
|
322
|
-
element.removeEventListener("pointerdown", onPointerDown);
|
|
323
|
-
element.removeEventListener("pointermove", onPointerMove);
|
|
324
|
-
element.removeEventListener("pointerup", releasePointer);
|
|
325
|
-
element.removeEventListener("pointercancel", releasePointer);
|
|
326
|
-
element.removeEventListener("wheel", onWheel);
|
|
327
|
-
element.removeEventListener("keydown", onKeyDown);
|
|
328
|
-
pointersRef.current.clear();
|
|
329
|
-
};
|
|
330
|
-
}, [enabled, gl, maxFov, minFov]);
|
|
331
338
|
(0, import_fiber.useFrame)((_, deltaSeconds) => {
|
|
332
339
|
if (!(camera instanceof import_three.PerspectiveCamera)) {
|
|
333
340
|
return;
|
|
334
341
|
}
|
|
342
|
+
const frictionStop = resolveFrictionStop(optionsRef.current.frictionStop);
|
|
335
343
|
const inertiaEnabled = optionsRef.current.inertia !== false;
|
|
336
344
|
if (!interactingRef.current && inertiaEnabled) {
|
|
337
345
|
const rotationDamping = Math.exp(
|
|
338
346
|
-INERTIA_ROTATION_DAMPING * deltaSeconds
|
|
339
347
|
);
|
|
340
348
|
const zoomDamping2 = Math.exp(-INERTIA_ZOOM_DAMPING * deltaSeconds);
|
|
341
|
-
if (Math.abs(yawVelocityRef.current) >=
|
|
349
|
+
if (Math.abs(yawVelocityRef.current) >= frictionStop) {
|
|
342
350
|
targetViewRef.current.yaw += yawVelocityRef.current * deltaSeconds;
|
|
343
351
|
yawVelocityRef.current *= rotationDamping;
|
|
344
|
-
|
|
352
|
+
markDirty();
|
|
345
353
|
} else {
|
|
346
354
|
yawVelocityRef.current = 0;
|
|
347
355
|
}
|
|
348
|
-
if (Math.abs(pitchVelocityRef.current) >=
|
|
356
|
+
if (Math.abs(pitchVelocityRef.current) >= frictionStop) {
|
|
349
357
|
targetViewRef.current.pitch += pitchVelocityRef.current * deltaSeconds;
|
|
350
358
|
pitchVelocityRef.current *= rotationDamping;
|
|
351
|
-
|
|
359
|
+
markDirty();
|
|
352
360
|
} else {
|
|
353
361
|
pitchVelocityRef.current = 0;
|
|
354
362
|
}
|
|
355
|
-
if (Math.abs(zoomVelocityRef.current) >=
|
|
363
|
+
if (Math.abs(zoomVelocityRef.current) >= frictionStop) {
|
|
356
364
|
targetViewRef.current.fov += zoomVelocityRef.current * deltaSeconds;
|
|
357
365
|
zoomVelocityRef.current *= zoomDamping2;
|
|
358
|
-
|
|
366
|
+
markDirty();
|
|
359
367
|
} else {
|
|
360
368
|
zoomVelocityRef.current = 0;
|
|
361
369
|
}
|
|
362
370
|
} else if (!inertiaEnabled) {
|
|
363
371
|
stopVelocity();
|
|
364
372
|
}
|
|
365
|
-
|
|
373
|
+
if (!interactingRef.current) {
|
|
374
|
+
targetViewRef.current = hardConstrainView(targetViewRef.current);
|
|
375
|
+
} else {
|
|
376
|
+
targetViewRef.current = softConstrainView(targetViewRef.current);
|
|
377
|
+
}
|
|
366
378
|
if (!dirtyRef.current) {
|
|
367
379
|
return;
|
|
368
380
|
}
|
|
@@ -399,7 +411,12 @@ var PanoramaControls = (0, import_react.forwardRef)(function PanoramaControls2({
|
|
|
399
411
|
const lastEmitted = lastEmittedViewRef.current;
|
|
400
412
|
if (!lastEmitted || !viewsEqual(lastEmitted, nextView)) {
|
|
401
413
|
lastEmittedViewRef.current = nextView;
|
|
402
|
-
|
|
414
|
+
eventBusRef.current.emit("viewchange", nextView);
|
|
415
|
+
}
|
|
416
|
+
const wasSettled = settledRef.current;
|
|
417
|
+
settledRef.current = hasSettled;
|
|
418
|
+
if (hasSettled && !wasSettled) {
|
|
419
|
+
eventBusRef.current.emit("viewsettled", nextView);
|
|
403
420
|
}
|
|
404
421
|
dirtyRef.current = !hasSettled;
|
|
405
422
|
});
|
|
@@ -407,12 +424,9 @@ var PanoramaControls = (0, import_react.forwardRef)(function PanoramaControls2({
|
|
|
407
424
|
});
|
|
408
425
|
|
|
409
426
|
// src/auto-rotate.tsx
|
|
410
|
-
var import_fiber2 = require("@react-three/fiber");
|
|
411
|
-
var import_react2 = require("react");
|
|
412
427
|
var DEFAULT_AUTO_ROTATE_SPEED = 18;
|
|
413
428
|
var DEFAULT_AUTO_ROTATE_ACCELERATION = 18;
|
|
414
429
|
var DEFAULT_AUTO_ROTATE_START_DELAY = 2;
|
|
415
|
-
var PanoramaControlsContext = (0, import_react2.createContext)(null);
|
|
416
430
|
function resolveStartDelay(value) {
|
|
417
431
|
return Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
418
432
|
}
|
|
@@ -425,19 +439,31 @@ function AutoRotate({
|
|
|
425
439
|
acceleration = DEFAULT_AUTO_ROTATE_ACCELERATION,
|
|
426
440
|
startDelay = DEFAULT_AUTO_ROTATE_START_DELAY
|
|
427
441
|
}) {
|
|
428
|
-
const controlsRef = (0,
|
|
429
|
-
const
|
|
430
|
-
const
|
|
442
|
+
const controlsRef = (0, import_react3.useContext)(PanoramaViewContext);
|
|
443
|
+
const eventBus = (0, import_react3.useContext)(PanoEventBusContext);
|
|
444
|
+
const elapsedRef = (0, import_react3.useRef)(0);
|
|
445
|
+
const rotationSpeedRef = (0, import_react3.useRef)(0);
|
|
446
|
+
const rotatingRef = (0, import_react3.useRef)(false);
|
|
447
|
+
const yawAccumulatedRef = (0, import_react3.useRef)(0);
|
|
431
448
|
if (!controlsRef) {
|
|
432
449
|
throw new Error("<AutoRotate> must be rendered inside <PanoView>.");
|
|
433
450
|
}
|
|
434
|
-
(0,
|
|
451
|
+
(0, import_react3.useEffect)(() => {
|
|
435
452
|
elapsedRef.current = 0;
|
|
436
453
|
rotationSpeedRef.current = 0;
|
|
437
|
-
|
|
454
|
+
yawAccumulatedRef.current = 0;
|
|
455
|
+
if (rotatingRef.current) {
|
|
456
|
+
rotatingRef.current = false;
|
|
457
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
458
|
+
}
|
|
459
|
+
}, [enabled, eventBus, startDelay]);
|
|
438
460
|
(0, import_fiber2.useFrame)((_, deltaSeconds) => {
|
|
439
461
|
if (!enabled || !Number.isFinite(speed) || speed === 0) {
|
|
440
462
|
rotationSpeedRef.current = 0;
|
|
463
|
+
if (rotatingRef.current) {
|
|
464
|
+
rotatingRef.current = false;
|
|
465
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
466
|
+
}
|
|
441
467
|
return;
|
|
442
468
|
}
|
|
443
469
|
const delaySeconds = resolveStartDelay(startDelay) / 1e3;
|
|
@@ -456,17 +482,30 @@ function AutoRotate({
|
|
|
456
482
|
const yawDelta = Math.sign(speed) * averageSpeed * deltaSeconds;
|
|
457
483
|
if (!controlsRef.current?.applyAutoRotation(yawDelta)) {
|
|
458
484
|
rotationSpeedRef.current = 0;
|
|
485
|
+
if (rotatingRef.current) {
|
|
486
|
+
rotatingRef.current = false;
|
|
487
|
+
eventBus?.emit("autorotatestop", void 0);
|
|
488
|
+
}
|
|
459
489
|
return;
|
|
460
490
|
}
|
|
491
|
+
if (!rotatingRef.current) {
|
|
492
|
+
rotatingRef.current = true;
|
|
493
|
+
eventBus?.emit("autorotatestart", void 0);
|
|
494
|
+
}
|
|
495
|
+
yawAccumulatedRef.current += Math.abs(yawDelta);
|
|
496
|
+
while (yawAccumulatedRef.current >= 360) {
|
|
497
|
+
yawAccumulatedRef.current -= 360;
|
|
498
|
+
eventBus?.emit("autorotateoneround", void 0);
|
|
499
|
+
}
|
|
461
500
|
rotationSpeedRef.current = nextSpeed;
|
|
462
501
|
});
|
|
463
502
|
return null;
|
|
464
503
|
}
|
|
465
504
|
|
|
466
505
|
// src/hotspot/accessibility.tsx
|
|
467
|
-
var
|
|
506
|
+
var import_react4 = require("react");
|
|
468
507
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
469
|
-
var HotspotAccessibilityContext = (0,
|
|
508
|
+
var HotspotAccessibilityContext = (0, import_react4.createContext)(null);
|
|
470
509
|
var VISUALLY_HIDDEN_STYLE = {
|
|
471
510
|
border: 0,
|
|
472
511
|
clip: "rect(0 0 0 0)",
|
|
@@ -492,12 +531,12 @@ function useHotspotAccessibility({
|
|
|
492
531
|
ariaLabel,
|
|
493
532
|
onActivate
|
|
494
533
|
}) {
|
|
495
|
-
const registry = (0,
|
|
496
|
-
const [focused, setFocused] = (0,
|
|
497
|
-
const activateRef = (0,
|
|
534
|
+
const registry = (0, import_react4.useContext)(HotspotAccessibilityContext);
|
|
535
|
+
const [focused, setFocused] = (0, import_react4.useState)(false);
|
|
536
|
+
const activateRef = (0, import_react4.useRef)(onActivate);
|
|
498
537
|
const enabled = Boolean(onActivate);
|
|
499
538
|
activateRef.current = onActivate;
|
|
500
|
-
(0,
|
|
539
|
+
(0, import_react4.useEffect)(() => {
|
|
501
540
|
if (!enabled) {
|
|
502
541
|
setFocused(false);
|
|
503
542
|
return;
|
|
@@ -519,8 +558,8 @@ function useHotspotAccessibility({
|
|
|
519
558
|
return focused;
|
|
520
559
|
}
|
|
521
560
|
function useHotspotAccessibilityLayer() {
|
|
522
|
-
const [entries, setEntries] = (0,
|
|
523
|
-
const register = (0,
|
|
561
|
+
const [entries, setEntries] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
|
|
562
|
+
const register = (0, import_react4.useCallback)(
|
|
524
563
|
(entry) => {
|
|
525
564
|
const token = Symbol(entry.id);
|
|
526
565
|
const nextEntry = { ...entry, token };
|
|
@@ -542,11 +581,11 @@ function useHotspotAccessibilityLayer() {
|
|
|
542
581
|
},
|
|
543
582
|
[]
|
|
544
583
|
);
|
|
545
|
-
const registry = (0,
|
|
584
|
+
const registry = (0, import_react4.useMemo)(
|
|
546
585
|
() => ({ register }),
|
|
547
586
|
[register]
|
|
548
587
|
);
|
|
549
|
-
const controls = (0,
|
|
588
|
+
const controls = (0, import_react4.useMemo)(
|
|
550
589
|
() => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-label": "Panorama hotspots", style: { pointerEvents: "none" }, children: Array.from(entries.values()).map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
551
590
|
"button",
|
|
552
591
|
{
|
|
@@ -571,138 +610,1647 @@ function useHotspotAccessibilityLayer() {
|
|
|
571
610
|
return { controls, registry };
|
|
572
611
|
}
|
|
573
612
|
|
|
574
|
-
// src/
|
|
575
|
-
var
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
var
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
613
|
+
// src/keyboard-controls.tsx
|
|
614
|
+
var import_fiber3 = require("@react-three/fiber");
|
|
615
|
+
var import_react5 = require("react");
|
|
616
|
+
var DEFAULT_ROTATE_SPEED = 60;
|
|
617
|
+
var DEFAULT_ZOOM_SPEED = 30;
|
|
618
|
+
var DEFAULT_SHIFT_MULTIPLIER = 3;
|
|
619
|
+
var DEFAULT_KEYS = {
|
|
620
|
+
left: ["ArrowLeft"],
|
|
621
|
+
right: ["ArrowRight"],
|
|
622
|
+
up: ["ArrowUp"],
|
|
623
|
+
down: ["ArrowDown"],
|
|
624
|
+
zoomIn: ["+", "="],
|
|
625
|
+
zoomOut: ["-", "_"],
|
|
626
|
+
previousScene: ["[", "PageUp"],
|
|
627
|
+
nextScene: ["]", "PageDown"],
|
|
628
|
+
reset: ["0"]
|
|
629
|
+
};
|
|
630
|
+
var CONTINUOUS_ACTIONS = /* @__PURE__ */ new Set([
|
|
631
|
+
"left",
|
|
632
|
+
"right",
|
|
633
|
+
"up",
|
|
634
|
+
"down",
|
|
635
|
+
"zoomIn",
|
|
636
|
+
"zoomOut"
|
|
637
|
+
]);
|
|
638
|
+
function normalizeBinding(value) {
|
|
639
|
+
if (value === void 0) {
|
|
640
|
+
return [];
|
|
587
641
|
}
|
|
588
|
-
return
|
|
642
|
+
return (typeof value === "string" ? [value] : [...value]).filter(
|
|
643
|
+
(entry) => entry.length > 0
|
|
644
|
+
);
|
|
589
645
|
}
|
|
590
|
-
function
|
|
591
|
-
|
|
592
|
-
|
|
646
|
+
function resolveKeyMap(overrides) {
|
|
647
|
+
const map = /* @__PURE__ */ new Map();
|
|
648
|
+
for (const action of Object.keys(DEFAULT_KEYS)) {
|
|
649
|
+
const bindings = overrides && action in overrides ? normalizeBinding(overrides[action]) : [...DEFAULT_KEYS[action]];
|
|
650
|
+
for (const binding of bindings) {
|
|
651
|
+
map.set(binding.toLowerCase(), action);
|
|
652
|
+
}
|
|
593
653
|
}
|
|
594
|
-
return
|
|
654
|
+
return map;
|
|
595
655
|
}
|
|
596
|
-
function
|
|
597
|
-
return
|
|
598
|
-
yaw: normalizePanoYaw(position.yaw),
|
|
599
|
-
pitch: clampPanoPitch(position.pitch)
|
|
600
|
-
};
|
|
656
|
+
function matchAction(event, keyMap) {
|
|
657
|
+
return keyMap.get(event.key.toLowerCase()) ?? keyMap.get(event.code.toLowerCase()) ?? null;
|
|
601
658
|
}
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 1;
|
|
605
|
-
const yaw = import_three2.MathUtils.degToRad(normalized.yaw);
|
|
606
|
-
const pitch = import_three2.MathUtils.degToRad(normalized.pitch);
|
|
607
|
-
const cosPitch = Math.cos(pitch);
|
|
608
|
-
return new import_three2.Vector3(
|
|
609
|
-
cosPitch * Math.sin(yaw) * safeRadius,
|
|
610
|
-
Math.sin(pitch) * safeRadius,
|
|
611
|
-
-cosPitch * Math.cos(yaw) * safeRadius
|
|
612
|
-
);
|
|
659
|
+
function resolveSpeed(value, fallback) {
|
|
660
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
613
661
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return { yaw: 0, pitch: 0 };
|
|
662
|
+
function cycleSceneId(scenes, activeSceneId, step) {
|
|
663
|
+
if (scenes.length === 0) {
|
|
664
|
+
return null;
|
|
618
665
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
Math.asin(import_three2.MathUtils.clamp(vector.y / length, -1, 1))
|
|
623
|
-
)
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// src/panorama-radius.ts
|
|
628
|
-
var DEFAULT_PANORAMA_RADIUS = 1e3;
|
|
629
|
-
var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
|
|
630
|
-
var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
|
|
631
|
-
|
|
632
|
-
// src/panorama-event-surface.tsx
|
|
633
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
634
|
-
var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
|
|
635
|
-
function makePanoramaPointerEvent(event) {
|
|
636
|
-
const nativeEvent = event.nativeEvent;
|
|
637
|
-
return {
|
|
638
|
-
position: vector3ToPanoPosition(event.ray.direction),
|
|
639
|
-
nativeEvent,
|
|
640
|
-
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
641
|
-
button: nativeEvent.button,
|
|
642
|
-
buttons: nativeEvent.buttons,
|
|
643
|
-
clientX: nativeEvent.clientX,
|
|
644
|
-
clientY: nativeEvent.clientY
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
function emitPanoramaEvent(event, callback) {
|
|
648
|
-
if (!callback) {
|
|
649
|
-
return;
|
|
666
|
+
const index = scenes.findIndex((scene) => scene.id === activeSceneId);
|
|
667
|
+
if (index < 0) {
|
|
668
|
+
return null;
|
|
650
669
|
}
|
|
651
|
-
|
|
652
|
-
|
|
670
|
+
const nextIndex = (index + step + scenes.length) % scenes.length;
|
|
671
|
+
return scenes[nextIndex]?.id ?? null;
|
|
653
672
|
}
|
|
654
|
-
function
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
673
|
+
function KeyboardControls({
|
|
674
|
+
enabled = true,
|
|
675
|
+
keys,
|
|
676
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED,
|
|
677
|
+
zoomSpeed = DEFAULT_ZOOM_SPEED,
|
|
678
|
+
shiftMultiplier = DEFAULT_SHIFT_MULTIPLIER,
|
|
679
|
+
invert = false,
|
|
680
|
+
fovSpeed,
|
|
681
|
+
onPreviousScene,
|
|
682
|
+
onNextScene
|
|
658
683
|
}) {
|
|
659
|
-
|
|
660
|
-
|
|
684
|
+
const controlsRef = (0, import_react5.useContext)(PanoramaViewContext);
|
|
685
|
+
const { gl } = (0, import_fiber3.useThree)();
|
|
686
|
+
const pressedRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
|
|
687
|
+
const shiftHeldRef = (0, import_react5.useRef)(false);
|
|
688
|
+
const invertRef = (0, import_react5.useRef)(invert);
|
|
689
|
+
const fovSpeedRef = (0, import_react5.useRef)(fovSpeed);
|
|
690
|
+
const onPreviousSceneRef = (0, import_react5.useRef)(onPreviousScene);
|
|
691
|
+
const onNextSceneRef = (0, import_react5.useRef)(onNextScene);
|
|
692
|
+
const keyMap = (0, import_react5.useMemo)(() => resolveKeyMap(keys), [keys]);
|
|
693
|
+
if (!controlsRef) {
|
|
694
|
+
throw new Error("<KeyboardControls> must be rendered inside <PanoView>.");
|
|
661
695
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
696
|
+
invertRef.current = invert;
|
|
697
|
+
fovSpeedRef.current = fovSpeed;
|
|
698
|
+
onPreviousSceneRef.current = onPreviousScene;
|
|
699
|
+
onNextSceneRef.current = onNextScene;
|
|
700
|
+
(0, import_react5.useEffect)(() => {
|
|
701
|
+
const element = gl.domElement;
|
|
702
|
+
if (!enabled) {
|
|
703
|
+
pressedRef.current.clear();
|
|
704
|
+
shiftHeldRef.current = false;
|
|
705
|
+
controlsRef.current?.setKeyboardActive(false);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const syncKeyboardActive = () => {
|
|
709
|
+
let continuous = false;
|
|
710
|
+
for (const action of pressedRef.current) {
|
|
711
|
+
if (CONTINUOUS_ACTIONS.has(action)) {
|
|
712
|
+
continuous = true;
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
controlsRef.current?.setKeyboardActive(continuous);
|
|
717
|
+
};
|
|
718
|
+
const clearPressed = () => {
|
|
719
|
+
pressedRef.current.clear();
|
|
720
|
+
shiftHeldRef.current = false;
|
|
721
|
+
controlsRef.current?.setKeyboardActive(false);
|
|
722
|
+
};
|
|
723
|
+
const onKeyDown = (event) => {
|
|
724
|
+
if (event.key === "Shift") {
|
|
725
|
+
shiftHeldRef.current = true;
|
|
726
|
+
}
|
|
727
|
+
const action = matchAction(event, keyMap);
|
|
728
|
+
if (!action) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
event.preventDefault();
|
|
735
|
+
if (action === "reset") {
|
|
736
|
+
if (!event.repeat) {
|
|
737
|
+
controlsRef.current?.reset();
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (action === "previousScene") {
|
|
742
|
+
if (!event.repeat) {
|
|
743
|
+
onPreviousSceneRef.current?.();
|
|
744
|
+
}
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (action === "nextScene") {
|
|
748
|
+
if (!event.repeat) {
|
|
749
|
+
onNextSceneRef.current?.();
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
pressedRef.current.add(action);
|
|
754
|
+
syncKeyboardActive();
|
|
755
|
+
};
|
|
756
|
+
const onKeyUp = (event) => {
|
|
757
|
+
if (event.key === "Shift") {
|
|
758
|
+
shiftHeldRef.current = false;
|
|
759
|
+
}
|
|
760
|
+
const action = matchAction(event, keyMap);
|
|
761
|
+
if (!action || !CONTINUOUS_ACTIONS.has(action)) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
pressedRef.current.delete(action);
|
|
765
|
+
syncKeyboardActive();
|
|
766
|
+
};
|
|
767
|
+
const onBlur = () => {
|
|
768
|
+
clearPressed();
|
|
769
|
+
};
|
|
770
|
+
element.addEventListener("keydown", onKeyDown);
|
|
771
|
+
element.addEventListener("keyup", onKeyUp);
|
|
772
|
+
element.addEventListener("blur", onBlur);
|
|
773
|
+
window.addEventListener("blur", onBlur);
|
|
774
|
+
return () => {
|
|
775
|
+
element.removeEventListener("keydown", onKeyDown);
|
|
776
|
+
element.removeEventListener("keyup", onKeyUp);
|
|
777
|
+
element.removeEventListener("blur", onBlur);
|
|
778
|
+
window.removeEventListener("blur", onBlur);
|
|
779
|
+
clearPressed();
|
|
780
|
+
};
|
|
781
|
+
}, [controlsRef, enabled, gl, keyMap]);
|
|
782
|
+
(0, import_fiber3.useFrame)((_, deltaSeconds) => {
|
|
783
|
+
if (!enabled || pressedRef.current.size === 0) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const controls = controlsRef.current;
|
|
787
|
+
if (!controls) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const multiplier = shiftHeldRef.current ? resolveSpeed(shiftMultiplier, DEFAULT_SHIFT_MULTIPLIER) : 1;
|
|
791
|
+
const rotation = resolveSpeed(rotateSpeed, DEFAULT_ROTATE_SPEED) * multiplier * deltaSeconds;
|
|
792
|
+
let zoom = resolveSpeed(zoomSpeed, DEFAULT_ZOOM_SPEED) * multiplier * deltaSeconds;
|
|
793
|
+
const fovCap = fovSpeedRef.current;
|
|
794
|
+
if (Number.isFinite(fovCap) && fovCap >= 0) {
|
|
795
|
+
zoom = Math.min(zoom, fovCap * deltaSeconds);
|
|
796
|
+
}
|
|
797
|
+
const pitchSign = invertRef.current ? -1 : 1;
|
|
798
|
+
let yaw = 0;
|
|
799
|
+
let pitch = 0;
|
|
800
|
+
let fov = 0;
|
|
801
|
+
for (const action of pressedRef.current) {
|
|
802
|
+
switch (action) {
|
|
803
|
+
case "left":
|
|
804
|
+
yaw -= rotation;
|
|
805
|
+
break;
|
|
806
|
+
case "right":
|
|
807
|
+
yaw += rotation;
|
|
808
|
+
break;
|
|
809
|
+
case "up":
|
|
810
|
+
pitch += pitchSign * rotation;
|
|
811
|
+
break;
|
|
812
|
+
case "down":
|
|
813
|
+
pitch -= pitchSign * rotation;
|
|
814
|
+
break;
|
|
815
|
+
case "zoomIn":
|
|
816
|
+
fov -= zoom;
|
|
817
|
+
break;
|
|
818
|
+
case "zoomOut":
|
|
819
|
+
fov += zoom;
|
|
820
|
+
break;
|
|
821
|
+
case "previousScene":
|
|
822
|
+
case "nextScene":
|
|
823
|
+
case "reset":
|
|
824
|
+
break;
|
|
825
|
+
default: {
|
|
826
|
+
const _exhaustive = action;
|
|
827
|
+
void _exhaustive;
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (yaw !== 0 || pitch !== 0 || fov !== 0) {
|
|
833
|
+
controls.applyViewDelta({ yaw, pitch, fov });
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/mouse-controls.tsx
|
|
840
|
+
var import_fiber4 = require("@react-three/fiber");
|
|
841
|
+
var import_react6 = require("react");
|
|
842
|
+
var DEFAULT_ROTATE_SPEED2 = 0.35;
|
|
843
|
+
var DEFAULT_ZOOM_SPEED2 = 0.08;
|
|
844
|
+
var BUTTON_FLAG = {
|
|
845
|
+
left: 0,
|
|
846
|
+
middle: 1,
|
|
847
|
+
right: 2
|
|
848
|
+
};
|
|
849
|
+
function resolveSpeed2(value, fallback) {
|
|
850
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
851
|
+
}
|
|
852
|
+
function resolveButtons(buttons) {
|
|
853
|
+
const list = buttons && buttons.length > 0 ? buttons : ["left"];
|
|
854
|
+
return new Set(list.map((button) => BUTTON_FLAG[button]));
|
|
855
|
+
}
|
|
856
|
+
function MouseControls({
|
|
857
|
+
enabled = true,
|
|
858
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED2,
|
|
859
|
+
zoomSpeed = DEFAULT_ZOOM_SPEED2,
|
|
860
|
+
wheel = true,
|
|
861
|
+
invert = false,
|
|
862
|
+
buttons,
|
|
863
|
+
fovSpeed
|
|
864
|
+
}) {
|
|
865
|
+
const controlsRef = (0, import_react6.useContext)(PanoramaViewContext);
|
|
866
|
+
const { gl } = (0, import_fiber4.useThree)();
|
|
867
|
+
const pointersRef = (0, import_react6.useRef)(
|
|
868
|
+
/* @__PURE__ */ new Map()
|
|
869
|
+
);
|
|
870
|
+
const optionsRef = (0, import_react6.useRef)({
|
|
871
|
+
rotateSpeed,
|
|
872
|
+
zoomSpeed,
|
|
873
|
+
wheel,
|
|
874
|
+
invert,
|
|
875
|
+
fovSpeed
|
|
876
|
+
});
|
|
877
|
+
if (!controlsRef) {
|
|
878
|
+
throw new Error("<MouseControls> must be rendered inside <PanoView>.");
|
|
879
|
+
}
|
|
880
|
+
optionsRef.current = {
|
|
881
|
+
rotateSpeed,
|
|
882
|
+
zoomSpeed,
|
|
883
|
+
wheel,
|
|
884
|
+
invert,
|
|
885
|
+
fovSpeed
|
|
886
|
+
};
|
|
887
|
+
(0, import_react6.useEffect)(() => {
|
|
888
|
+
const element = gl.domElement;
|
|
889
|
+
const allowedButtons = resolveButtons(buttons);
|
|
890
|
+
const clearPointers = () => {
|
|
891
|
+
for (const pointerId of pointersRef.current.keys()) {
|
|
892
|
+
if (element.hasPointerCapture?.(pointerId)) {
|
|
893
|
+
element.releasePointerCapture?.(pointerId);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
pointersRef.current.clear();
|
|
897
|
+
controlsRef.current?.setPointerActive("mouse", false);
|
|
898
|
+
};
|
|
899
|
+
if (!enabled) {
|
|
900
|
+
clearPointers();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const isMouseLike = (event) => event.pointerType === "mouse" || event.pointerType === "pen";
|
|
904
|
+
const syncActive = () => {
|
|
905
|
+
controlsRef.current?.setPointerActive(
|
|
906
|
+
"mouse",
|
|
907
|
+
pointersRef.current.size > 0
|
|
908
|
+
);
|
|
909
|
+
};
|
|
910
|
+
const onPointerDown = (event) => {
|
|
911
|
+
if (!isMouseLike(event)) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (!allowedButtons.has(event.button)) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
event.preventDefault();
|
|
921
|
+
pointersRef.current.set(event.pointerId, {
|
|
922
|
+
x: event.clientX,
|
|
923
|
+
y: event.clientY,
|
|
924
|
+
at: performance.now()
|
|
925
|
+
});
|
|
926
|
+
element.setPointerCapture?.(event.pointerId);
|
|
927
|
+
syncActive();
|
|
928
|
+
};
|
|
929
|
+
const onPointerMove = (event) => {
|
|
930
|
+
if (!isMouseLike(event)) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const previous = pointersRef.current.get(event.pointerId);
|
|
937
|
+
if (!previous) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
event.preventDefault();
|
|
941
|
+
const next = {
|
|
942
|
+
x: event.clientX,
|
|
943
|
+
y: event.clientY,
|
|
944
|
+
at: performance.now()
|
|
945
|
+
};
|
|
946
|
+
pointersRef.current.set(event.pointerId, next);
|
|
947
|
+
const rect = element.getBoundingClientRect();
|
|
948
|
+
const speed = resolveSpeed2(
|
|
949
|
+
optionsRef.current.rotateSpeed,
|
|
950
|
+
DEFAULT_ROTATE_SPEED2
|
|
951
|
+
);
|
|
952
|
+
const sign = optionsRef.current.invert ? -1 : 1;
|
|
953
|
+
const deltaYaw = sign * -(next.x - previous.x) * 360 * speed / Math.max(rect.width, 1);
|
|
954
|
+
const deltaPitch = sign * (next.y - previous.y) * 180 * speed / Math.max(rect.height, 1);
|
|
955
|
+
controlsRef.current?.applyViewDelta(
|
|
956
|
+
{ yaw: deltaYaw, pitch: deltaPitch },
|
|
957
|
+
{ recordVelocity: true }
|
|
958
|
+
);
|
|
959
|
+
};
|
|
960
|
+
const releasePointer = (event) => {
|
|
961
|
+
if (!pointersRef.current.has(event.pointerId)) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
pointersRef.current.delete(event.pointerId);
|
|
965
|
+
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
966
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
967
|
+
}
|
|
968
|
+
syncActive();
|
|
969
|
+
};
|
|
970
|
+
const onWheel = (event) => {
|
|
971
|
+
if (!optionsRef.current.wheel) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
975
|
+
event.preventDefault();
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
event.preventDefault();
|
|
979
|
+
const speed = resolveSpeed2(
|
|
980
|
+
optionsRef.current.zoomSpeed,
|
|
981
|
+
DEFAULT_ZOOM_SPEED2
|
|
982
|
+
);
|
|
983
|
+
let delta = event.deltaY * speed;
|
|
984
|
+
const cap = optionsRef.current.fovSpeed;
|
|
985
|
+
if (Number.isFinite(cap) && cap > 0) {
|
|
986
|
+
delta = Math.max(-cap, Math.min(cap, delta));
|
|
987
|
+
}
|
|
988
|
+
controlsRef.current?.applyViewDelta(
|
|
989
|
+
{ fov: delta },
|
|
990
|
+
{ recordVelocity: true }
|
|
991
|
+
);
|
|
992
|
+
};
|
|
993
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
994
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
995
|
+
element.addEventListener("pointerup", releasePointer);
|
|
996
|
+
element.addEventListener("pointercancel", releasePointer);
|
|
997
|
+
element.addEventListener("wheel", onWheel, { passive: false });
|
|
998
|
+
return () => {
|
|
999
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
1000
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
1001
|
+
element.removeEventListener("pointerup", releasePointer);
|
|
1002
|
+
element.removeEventListener("pointercancel", releasePointer);
|
|
1003
|
+
element.removeEventListener("wheel", onWheel);
|
|
1004
|
+
clearPointers();
|
|
1005
|
+
};
|
|
1006
|
+
}, [buttons, controlsRef, enabled, gl]);
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/pano-context-menu.tsx
|
|
1011
|
+
var import_react7 = require("react");
|
|
1012
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1013
|
+
var DEFAULT_ICON_SIZE = 16;
|
|
1014
|
+
var MENU_EDGE_PADDING = 8;
|
|
1015
|
+
var MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY = 0.4;
|
|
1016
|
+
var PanoContextMenuOverlayContext = (0, import_react7.createContext)(null);
|
|
1017
|
+
var DEFAULT_APPEARANCE = {
|
|
1018
|
+
background: "#161616",
|
|
1019
|
+
color: "#f4f4f4",
|
|
1020
|
+
border: "1px solid #2e2e2e",
|
|
1021
|
+
borderRadius: 8,
|
|
1022
|
+
shadow: "0 8px 24px rgba(0, 0, 0, 0.45)",
|
|
1023
|
+
opacity: 1,
|
|
1024
|
+
fontSize: 13,
|
|
1025
|
+
minWidth: 180,
|
|
1026
|
+
padding: 4,
|
|
1027
|
+
iconSize: DEFAULT_ICON_SIZE,
|
|
1028
|
+
itemPadding: "6px 10px",
|
|
1029
|
+
itemHoverBackground: "#2a2a2a",
|
|
1030
|
+
itemDisabledColor: "#777777",
|
|
1031
|
+
separatorColor: "#333333",
|
|
1032
|
+
separatorMargin: "4px 6px"
|
|
1033
|
+
};
|
|
1034
|
+
function clampBackgroundOpacity(value) {
|
|
1035
|
+
if (!Number.isFinite(value)) {
|
|
1036
|
+
return DEFAULT_APPEARANCE.opacity;
|
|
1037
|
+
}
|
|
1038
|
+
return Math.min(
|
|
1039
|
+
1,
|
|
1040
|
+
Math.max(MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY, value)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
function isActionItem(item) {
|
|
1044
|
+
return item.type !== "separator";
|
|
1045
|
+
}
|
|
1046
|
+
function DefaultContextMenuPanel({
|
|
1047
|
+
items,
|
|
1048
|
+
appearance,
|
|
1049
|
+
className,
|
|
1050
|
+
style,
|
|
1051
|
+
itemClassName,
|
|
1052
|
+
separatorClassName,
|
|
1053
|
+
selectContext,
|
|
1054
|
+
onRequestClose
|
|
1055
|
+
}) {
|
|
1056
|
+
const menuRef = (0, import_react7.useRef)(null);
|
|
1057
|
+
const [activeIndex, setActiveIndex] = (0, import_react7.useState)(0);
|
|
1058
|
+
const resolved = { ...DEFAULT_APPEARANCE, ...appearance };
|
|
1059
|
+
const iconSize = Number.isFinite(resolved.iconSize) ? Math.max(0, resolved.iconSize) : DEFAULT_ICON_SIZE;
|
|
1060
|
+
const visibleItems = (0, import_react7.useMemo)(
|
|
1061
|
+
() => items.filter((item) => {
|
|
1062
|
+
if (!isActionItem(item)) {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
return !item.hidden;
|
|
1066
|
+
}),
|
|
1067
|
+
[items]
|
|
1068
|
+
);
|
|
1069
|
+
const focusableIndices = (0, import_react7.useMemo)(() => {
|
|
1070
|
+
const indices = [];
|
|
1071
|
+
visibleItems.forEach((item, index) => {
|
|
1072
|
+
if (isActionItem(item) && !item.disabled) {
|
|
1073
|
+
indices.push(index);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
return indices;
|
|
1077
|
+
}, [visibleItems]);
|
|
1078
|
+
(0, import_react7.useEffect)(() => {
|
|
1079
|
+
const first = focusableIndices[0];
|
|
1080
|
+
setActiveIndex(first ?? 0);
|
|
1081
|
+
const frame = requestAnimationFrame(() => {
|
|
1082
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1083
|
+
'[role="menuitem"]:not([aria-disabled="true"])'
|
|
1084
|
+
);
|
|
1085
|
+
buttons?.[0]?.focus();
|
|
1086
|
+
});
|
|
1087
|
+
return () => cancelAnimationFrame(frame);
|
|
1088
|
+
}, [focusableIndices]);
|
|
1089
|
+
const activateItem = (0, import_react7.useCallback)(
|
|
1090
|
+
(item) => {
|
|
1091
|
+
if (item.disabled) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
item.onSelect(selectContext);
|
|
1095
|
+
onRequestClose();
|
|
1096
|
+
},
|
|
1097
|
+
[onRequestClose, selectContext]
|
|
1098
|
+
);
|
|
1099
|
+
const moveFocus = (direction) => {
|
|
1100
|
+
if (focusableIndices.length === 0) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const currentSlot = focusableIndices.indexOf(activeIndex);
|
|
1104
|
+
const nextSlot = currentSlot === -1 ? 0 : (currentSlot + direction + focusableIndices.length) % focusableIndices.length;
|
|
1105
|
+
const nextIndex = focusableIndices[nextSlot];
|
|
1106
|
+
setActiveIndex(nextIndex);
|
|
1107
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1108
|
+
'[role="menuitem"]'
|
|
1109
|
+
);
|
|
1110
|
+
buttons?.[nextIndex]?.focus();
|
|
1111
|
+
};
|
|
1112
|
+
const handleKeyDown = (event) => {
|
|
1113
|
+
switch (event.key) {
|
|
1114
|
+
case "ArrowDown":
|
|
1115
|
+
event.preventDefault();
|
|
1116
|
+
moveFocus(1);
|
|
1117
|
+
break;
|
|
1118
|
+
case "ArrowUp":
|
|
1119
|
+
event.preventDefault();
|
|
1120
|
+
moveFocus(-1);
|
|
1121
|
+
break;
|
|
1122
|
+
case "Home": {
|
|
1123
|
+
event.preventDefault();
|
|
1124
|
+
const first = focusableIndices[0];
|
|
1125
|
+
if (first !== void 0) {
|
|
1126
|
+
setActiveIndex(first);
|
|
1127
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1128
|
+
'[role="menuitem"]'
|
|
1129
|
+
);
|
|
1130
|
+
buttons?.[first]?.focus();
|
|
1131
|
+
}
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
case "End": {
|
|
1135
|
+
event.preventDefault();
|
|
1136
|
+
const last = focusableIndices[focusableIndices.length - 1];
|
|
1137
|
+
if (last !== void 0) {
|
|
1138
|
+
setActiveIndex(last);
|
|
1139
|
+
const buttons = menuRef.current?.querySelectorAll(
|
|
1140
|
+
'[role="menuitem"]'
|
|
1141
|
+
);
|
|
1142
|
+
buttons?.[last]?.focus();
|
|
1143
|
+
}
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
case "Escape":
|
|
1147
|
+
event.preventDefault();
|
|
1148
|
+
onRequestClose();
|
|
1149
|
+
break;
|
|
1150
|
+
case "Enter":
|
|
1151
|
+
case " ": {
|
|
1152
|
+
event.preventDefault();
|
|
1153
|
+
const item = visibleItems[activeIndex];
|
|
1154
|
+
if (item && isActionItem(item)) {
|
|
1155
|
+
activateItem(item);
|
|
1156
|
+
}
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
default: {
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const backgroundOpacity = clampBackgroundOpacity(resolved.opacity);
|
|
1165
|
+
const panelStyle = {
|
|
1166
|
+
color: resolved.color,
|
|
1167
|
+
border: resolved.border,
|
|
1168
|
+
borderRadius: resolved.borderRadius,
|
|
1169
|
+
boxShadow: resolved.shadow,
|
|
1170
|
+
fontSize: resolved.fontSize,
|
|
1171
|
+
minWidth: resolved.minWidth,
|
|
1172
|
+
padding: resolved.padding,
|
|
1173
|
+
pointerEvents: "auto",
|
|
1174
|
+
position: "relative",
|
|
1175
|
+
userSelect: "none",
|
|
1176
|
+
...style
|
|
1177
|
+
};
|
|
1178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1179
|
+
"div",
|
|
1180
|
+
{
|
|
1181
|
+
ref: menuRef,
|
|
1182
|
+
"aria-label": "Panorama context menu",
|
|
1183
|
+
className,
|
|
1184
|
+
onKeyDown: handleKeyDown,
|
|
1185
|
+
role: "menu",
|
|
1186
|
+
style: panelStyle,
|
|
1187
|
+
tabIndex: -1,
|
|
1188
|
+
children: [
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1190
|
+
"div",
|
|
1191
|
+
{
|
|
1192
|
+
"aria-hidden": true,
|
|
1193
|
+
style: {
|
|
1194
|
+
background: resolved.background,
|
|
1195
|
+
borderRadius: resolved.borderRadius,
|
|
1196
|
+
inset: 0,
|
|
1197
|
+
opacity: backgroundOpacity,
|
|
1198
|
+
pointerEvents: "none",
|
|
1199
|
+
position: "absolute",
|
|
1200
|
+
zIndex: 0
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
),
|
|
1204
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "relative", zIndex: 1 }, children: visibleItems.map((item, index) => {
|
|
1205
|
+
if (!isActionItem(item)) {
|
|
1206
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1207
|
+
"div",
|
|
1208
|
+
{
|
|
1209
|
+
"aria-hidden": true,
|
|
1210
|
+
className: [separatorClassName, item.className].filter(Boolean).join(" ") || void 0,
|
|
1211
|
+
role: "separator",
|
|
1212
|
+
style: {
|
|
1213
|
+
background: resolved.separatorColor,
|
|
1214
|
+
height: 1,
|
|
1215
|
+
margin: resolved.separatorMargin,
|
|
1216
|
+
...item.style
|
|
1217
|
+
}
|
|
1218
|
+
},
|
|
1219
|
+
item.id ?? `separator-${index}`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
const disabled = item.disabled === true;
|
|
1223
|
+
const iconNode = item.icon ?? (item.image ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1224
|
+
"img",
|
|
1225
|
+
{
|
|
1226
|
+
alt: item.imageAlt ?? "",
|
|
1227
|
+
draggable: false,
|
|
1228
|
+
src: item.image,
|
|
1229
|
+
style: {
|
|
1230
|
+
display: "block",
|
|
1231
|
+
height: iconSize,
|
|
1232
|
+
objectFit: "contain",
|
|
1233
|
+
width: iconSize
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
) : null);
|
|
1237
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1238
|
+
"button",
|
|
1239
|
+
{
|
|
1240
|
+
"aria-disabled": disabled || void 0,
|
|
1241
|
+
className: [itemClassName, item.className].filter(Boolean).join(" ") || void 0,
|
|
1242
|
+
disabled,
|
|
1243
|
+
onClick: (event) => {
|
|
1244
|
+
event.preventDefault();
|
|
1245
|
+
event.stopPropagation();
|
|
1246
|
+
activateItem(item);
|
|
1247
|
+
},
|
|
1248
|
+
onMouseEnter: (event) => {
|
|
1249
|
+
if (!disabled) {
|
|
1250
|
+
setActiveIndex(index);
|
|
1251
|
+
event.currentTarget.focus();
|
|
1252
|
+
}
|
|
1253
|
+
},
|
|
1254
|
+
role: "menuitem",
|
|
1255
|
+
style: {
|
|
1256
|
+
alignItems: "center",
|
|
1257
|
+
background: !disabled && activeIndex === index ? resolved.itemHoverBackground : "transparent",
|
|
1258
|
+
border: "none",
|
|
1259
|
+
borderRadius: 4,
|
|
1260
|
+
color: disabled ? resolved.itemDisabledColor : "inherit",
|
|
1261
|
+
cursor: disabled ? "default" : "pointer",
|
|
1262
|
+
display: "flex",
|
|
1263
|
+
font: "inherit",
|
|
1264
|
+
gap: 8,
|
|
1265
|
+
padding: resolved.itemPadding,
|
|
1266
|
+
textAlign: "left",
|
|
1267
|
+
width: "100%",
|
|
1268
|
+
...item.style
|
|
1269
|
+
},
|
|
1270
|
+
tabIndex: disabled ? -1 : activeIndex === index ? 0 : -1,
|
|
1271
|
+
type: "button",
|
|
1272
|
+
children: [
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1274
|
+
"span",
|
|
1275
|
+
{
|
|
1276
|
+
"aria-hidden": true,
|
|
1277
|
+
style: {
|
|
1278
|
+
alignItems: "center",
|
|
1279
|
+
display: "inline-flex",
|
|
1280
|
+
flexShrink: 0,
|
|
1281
|
+
height: iconSize,
|
|
1282
|
+
justifyContent: "center",
|
|
1283
|
+
width: iconSize
|
|
1284
|
+
},
|
|
1285
|
+
children: iconNode
|
|
1286
|
+
}
|
|
1287
|
+
),
|
|
1288
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1 }, children: item.label })
|
|
1289
|
+
]
|
|
1290
|
+
},
|
|
1291
|
+
item.id
|
|
1292
|
+
);
|
|
1293
|
+
}) })
|
|
1294
|
+
]
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
function PositionedMenuShell({
|
|
1299
|
+
clientX,
|
|
1300
|
+
clientY,
|
|
1301
|
+
overlayElement,
|
|
1302
|
+
children
|
|
1303
|
+
}) {
|
|
1304
|
+
const shellRef = (0, import_react7.useRef)(null);
|
|
1305
|
+
const [offset, setOffset] = (0, import_react7.useState)({ left: 0, top: 0 });
|
|
1306
|
+
(0, import_react7.useLayoutEffect)(() => {
|
|
1307
|
+
const shell = shellRef.current;
|
|
1308
|
+
if (!overlayElement || !shell) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const overlayRect = overlayElement.getBoundingClientRect();
|
|
1312
|
+
const menuRect = shell.getBoundingClientRect();
|
|
1313
|
+
const rawLeft = clientX - overlayRect.left;
|
|
1314
|
+
const rawTop = clientY - overlayRect.top;
|
|
1315
|
+
const maxLeft = Math.max(
|
|
1316
|
+
MENU_EDGE_PADDING,
|
|
1317
|
+
overlayRect.width - menuRect.width - MENU_EDGE_PADDING
|
|
1318
|
+
);
|
|
1319
|
+
const maxTop = Math.max(
|
|
1320
|
+
MENU_EDGE_PADDING,
|
|
1321
|
+
overlayRect.height - menuRect.height - MENU_EDGE_PADDING
|
|
1322
|
+
);
|
|
1323
|
+
setOffset({
|
|
1324
|
+
left: Math.min(Math.max(rawLeft, MENU_EDGE_PADDING), maxLeft),
|
|
1325
|
+
top: Math.min(Math.max(rawTop, MENU_EDGE_PADDING), maxTop)
|
|
1326
|
+
});
|
|
1327
|
+
}, [clientX, clientY, overlayElement, children]);
|
|
1328
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1329
|
+
"div",
|
|
1330
|
+
{
|
|
1331
|
+
ref: shellRef,
|
|
1332
|
+
style: {
|
|
1333
|
+
left: offset.left,
|
|
1334
|
+
position: "absolute",
|
|
1335
|
+
top: offset.top,
|
|
1336
|
+
zIndex: 20
|
|
1337
|
+
},
|
|
1338
|
+
children
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function usePanoContextMenuOverlay() {
|
|
1343
|
+
const [session, setSession] = (0, import_react7.useState)(null);
|
|
1344
|
+
const overlayRef = (0, import_react7.useRef)(null);
|
|
1345
|
+
const [overlayElement, setOverlayElement] = (0, import_react7.useState)(
|
|
1346
|
+
null
|
|
1347
|
+
);
|
|
1348
|
+
const api = (0, import_react7.useMemo)(
|
|
1349
|
+
() => ({ setSession, overlayRef }),
|
|
1350
|
+
[]
|
|
1351
|
+
);
|
|
1352
|
+
const setOverlayNode = (0, import_react7.useCallback)((node) => {
|
|
1353
|
+
overlayRef.current = node;
|
|
1354
|
+
setOverlayElement(node);
|
|
1355
|
+
}, []);
|
|
1356
|
+
(0, import_react7.useEffect)(() => {
|
|
1357
|
+
if (!session) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const handlePointerDown = (event) => {
|
|
1361
|
+
const overlay2 = overlayRef.current;
|
|
1362
|
+
if (!overlay2) {
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const target = event.target;
|
|
1366
|
+
if (target instanceof Node && overlay2.contains(target)) {
|
|
1367
|
+
const menuRoot = overlay2.querySelector(
|
|
1368
|
+
'[role="menu"], [data-pano-context-menu]'
|
|
1369
|
+
);
|
|
1370
|
+
if (menuRoot && menuRoot.contains(target)) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
setSession(null);
|
|
1375
|
+
};
|
|
1376
|
+
const handleKeyDown = (event) => {
|
|
1377
|
+
if (event.key === "Escape") {
|
|
1378
|
+
setSession(null);
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
1382
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1383
|
+
return () => {
|
|
1384
|
+
document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
1385
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
1386
|
+
};
|
|
1387
|
+
}, [session]);
|
|
1388
|
+
const overlay = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1389
|
+
"div",
|
|
1390
|
+
{
|
|
1391
|
+
ref: setOverlayNode,
|
|
1392
|
+
"aria-hidden": session ? void 0 : true,
|
|
1393
|
+
style: {
|
|
1394
|
+
inset: 0,
|
|
1395
|
+
pointerEvents: "none",
|
|
1396
|
+
position: "absolute",
|
|
1397
|
+
zIndex: 20
|
|
1398
|
+
},
|
|
1399
|
+
children: session ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1400
|
+
PositionedMenuShell,
|
|
1401
|
+
{
|
|
1402
|
+
clientX: session.event.clientX,
|
|
1403
|
+
clientY: session.event.clientY,
|
|
1404
|
+
overlayElement,
|
|
1405
|
+
children: session.render()
|
|
1406
|
+
},
|
|
1407
|
+
session.key
|
|
1408
|
+
) : null
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
return { api, overlay };
|
|
1412
|
+
}
|
|
1413
|
+
function PanoContextMenu({
|
|
1414
|
+
items,
|
|
1415
|
+
prepend,
|
|
1416
|
+
append,
|
|
1417
|
+
appearance,
|
|
1418
|
+
className,
|
|
1419
|
+
style,
|
|
1420
|
+
itemClassName,
|
|
1421
|
+
separatorClassName,
|
|
1422
|
+
children
|
|
1423
|
+
}) {
|
|
1424
|
+
const eventBus = (0, import_react7.useContext)(PanoEventBusContext);
|
|
1425
|
+
const controlsRef = (0, import_react7.useContext)(PanoramaViewContext);
|
|
1426
|
+
const overlayApi = (0, import_react7.useContext)(PanoContextMenuOverlayContext);
|
|
1427
|
+
const actions = (0, import_react7.useContext)(PanoContextMenuActionsContext);
|
|
1428
|
+
const instanceId = (0, import_react7.useId)();
|
|
1429
|
+
const sessionKeyRef = (0, import_react7.useRef)(0);
|
|
1430
|
+
const propsRef = (0, import_react7.useRef)({
|
|
1431
|
+
items,
|
|
1432
|
+
prepend,
|
|
1433
|
+
append,
|
|
1434
|
+
appearance,
|
|
1435
|
+
className,
|
|
1436
|
+
style,
|
|
1437
|
+
itemClassName,
|
|
1438
|
+
separatorClassName,
|
|
1439
|
+
children
|
|
1440
|
+
});
|
|
1441
|
+
propsRef.current = {
|
|
1442
|
+
items,
|
|
1443
|
+
prepend,
|
|
1444
|
+
append,
|
|
1445
|
+
appearance,
|
|
1446
|
+
className,
|
|
1447
|
+
style,
|
|
1448
|
+
itemClassName,
|
|
1449
|
+
separatorClassName,
|
|
1450
|
+
children
|
|
1451
|
+
};
|
|
1452
|
+
if (!eventBus || !controlsRef) {
|
|
1453
|
+
throw new Error("<PanoContextMenu> must be rendered inside <PanoView>.");
|
|
1454
|
+
}
|
|
1455
|
+
if (!overlayApi) {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
"<PanoContextMenu> requires the PanoView context-menu overlay host."
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
const close = (0, import_react7.useCallback)(() => {
|
|
1461
|
+
overlayApi.setSession(null);
|
|
1462
|
+
}, [overlayApi]);
|
|
1463
|
+
const openMenu = (0, import_react7.useCallback)(
|
|
1464
|
+
(event) => {
|
|
1465
|
+
event.nativeEvent.preventDefault();
|
|
1466
|
+
const view = controlsRef.current?.getView() ?? {
|
|
1467
|
+
yaw: 0,
|
|
1468
|
+
pitch: 0,
|
|
1469
|
+
fov: 75
|
|
1470
|
+
};
|
|
1471
|
+
const selectContext = {
|
|
1472
|
+
position: event.position,
|
|
1473
|
+
view,
|
|
1474
|
+
nativeEvent: event.nativeEvent,
|
|
1475
|
+
close
|
|
1476
|
+
};
|
|
1477
|
+
const current = propsRef.current;
|
|
1478
|
+
sessionKeyRef.current += 1;
|
|
1479
|
+
const key = `${instanceId}-${sessionKeyRef.current}`;
|
|
1480
|
+
const hasCustomChildren = typeof current.children === "function";
|
|
1481
|
+
const presets = actions ? createPanoContextMenuPresets(actions) : null;
|
|
1482
|
+
const resolvedItems = presets ? composePanoContextMenuItems(current, presets) : current.items?.filter(isActionEntry) ?? [];
|
|
1483
|
+
if (!hasCustomChildren && resolvedItems.length === 0) {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
overlayApi.setSession({
|
|
1487
|
+
key,
|
|
1488
|
+
event,
|
|
1489
|
+
view,
|
|
1490
|
+
render: () => {
|
|
1491
|
+
if (hasCustomChildren && current.children) {
|
|
1492
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-pano-context-menu": true, style: { pointerEvents: "auto" }, children: current.children({ event, view, close }) });
|
|
1493
|
+
}
|
|
1494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1495
|
+
DefaultContextMenuPanel,
|
|
1496
|
+
{
|
|
1497
|
+
appearance: current.appearance,
|
|
1498
|
+
className: current.className,
|
|
1499
|
+
itemClassName: current.itemClassName,
|
|
1500
|
+
items: resolvedItems,
|
|
1501
|
+
onRequestClose: close,
|
|
1502
|
+
selectContext,
|
|
1503
|
+
separatorClassName: current.separatorClassName,
|
|
1504
|
+
style: current.style
|
|
1505
|
+
}
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
},
|
|
1510
|
+
[actions, close, controlsRef, instanceId, overlayApi]
|
|
1511
|
+
);
|
|
1512
|
+
(0, import_react7.useEffect)(() => {
|
|
1513
|
+
const unsubscribeContextMenu = eventBus.subscribe(
|
|
1514
|
+
"contextmenu",
|
|
1515
|
+
openMenu
|
|
1516
|
+
);
|
|
1517
|
+
const unsubscribeInteraction = eventBus.subscribe(
|
|
1518
|
+
"viewinteractionstart",
|
|
1519
|
+
() => {
|
|
1520
|
+
close();
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
return () => {
|
|
1524
|
+
unsubscribeContextMenu();
|
|
1525
|
+
unsubscribeInteraction();
|
|
1526
|
+
close();
|
|
1527
|
+
};
|
|
1528
|
+
}, [close, eventBus, openMenu]);
|
|
1529
|
+
return null;
|
|
1530
|
+
}
|
|
1531
|
+
function ResetViewIcon() {
|
|
1532
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1533
|
+
"svg",
|
|
1534
|
+
{
|
|
1535
|
+
"aria-hidden": true,
|
|
1536
|
+
fill: "none",
|
|
1537
|
+
height: "100%",
|
|
1538
|
+
stroke: "currentColor",
|
|
1539
|
+
strokeLinecap: "round",
|
|
1540
|
+
strokeLinejoin: "round",
|
|
1541
|
+
strokeWidth: "1.75",
|
|
1542
|
+
viewBox: "0 0 16 16",
|
|
1543
|
+
width: "100%",
|
|
1544
|
+
children: [
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3.5 6.5A4.5 4.5 0 1 1 4 10" }),
|
|
1546
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3.5 3.5v3h3" })
|
|
1547
|
+
]
|
|
1548
|
+
}
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
function FullscreenIcon() {
|
|
1552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1553
|
+
"svg",
|
|
1554
|
+
{
|
|
1555
|
+
"aria-hidden": true,
|
|
1556
|
+
fill: "none",
|
|
1557
|
+
height: "100%",
|
|
1558
|
+
stroke: "currentColor",
|
|
1559
|
+
strokeLinecap: "round",
|
|
1560
|
+
strokeLinejoin: "round",
|
|
1561
|
+
strokeWidth: "1.75",
|
|
1562
|
+
viewBox: "0 0 16 16",
|
|
1563
|
+
width: "100%",
|
|
1564
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 6V3h3M10 3h3v3M13 10v3h-3M6 13H3v-3" })
|
|
1565
|
+
}
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
function ExitFullscreenIcon() {
|
|
1569
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1570
|
+
"svg",
|
|
1571
|
+
{
|
|
1572
|
+
"aria-hidden": true,
|
|
1573
|
+
fill: "none",
|
|
1574
|
+
height: "100%",
|
|
1575
|
+
stroke: "currentColor",
|
|
1576
|
+
strokeLinecap: "round",
|
|
1577
|
+
strokeLinejoin: "round",
|
|
1578
|
+
strokeWidth: "1.75",
|
|
1579
|
+
viewBox: "0 0 16 16",
|
|
1580
|
+
width: "100%",
|
|
1581
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M6 3v3H3M10 3v3h3M3 10h3v3M10 10h3v3" })
|
|
1582
|
+
}
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
function createPanoContextMenuPresets(actions) {
|
|
1586
|
+
const isFullscreen = actions.isFullscreen === true;
|
|
1587
|
+
const resetView = {
|
|
1588
|
+
id: "pano-reset-view",
|
|
1589
|
+
label: "Reset view",
|
|
1590
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ResetViewIcon, {}),
|
|
1591
|
+
onSelect: () => {
|
|
1592
|
+
actions.reset();
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
const fullscreen = {
|
|
1596
|
+
id: "pano-fullscreen",
|
|
1597
|
+
label: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
1598
|
+
icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ExitFullscreenIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FullscreenIcon, {}),
|
|
1599
|
+
onSelect: () => {
|
|
1600
|
+
actions.toggleFullscreen();
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
const separator = (id = "pano-separator") => ({
|
|
1604
|
+
type: "separator",
|
|
1605
|
+
id
|
|
1606
|
+
});
|
|
1607
|
+
return {
|
|
1608
|
+
resetView,
|
|
1609
|
+
fullscreen,
|
|
1610
|
+
separator,
|
|
1611
|
+
defaults: [resetView, separator("pano-default-separator"), fullscreen]
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function createDefaultPanoContextMenuItems(actions) {
|
|
1615
|
+
return createPanoContextMenuPresets(actions).defaults;
|
|
1616
|
+
}
|
|
1617
|
+
function isPresetId(value) {
|
|
1618
|
+
return value === "resetView" || value === "fullscreen" || value === "separator";
|
|
1619
|
+
}
|
|
1620
|
+
function isPresetRef(value) {
|
|
1621
|
+
return typeof value === "object" && value !== null && "preset" in value && isPresetId(value.preset);
|
|
1622
|
+
}
|
|
1623
|
+
function isActionEntry(value) {
|
|
1624
|
+
if (typeof value === "string") {
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
if (isPresetRef(value)) {
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
function applyPresetOverrides(item, overrides) {
|
|
1633
|
+
if (item.type === "separator") {
|
|
1634
|
+
return {
|
|
1635
|
+
...item,
|
|
1636
|
+
id: overrides.id ?? item.id,
|
|
1637
|
+
className: overrides.className ?? item.className,
|
|
1638
|
+
style: overrides.style ?? item.style
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
...item,
|
|
1643
|
+
id: overrides.id ?? item.id,
|
|
1644
|
+
label: overrides.label ?? item.label,
|
|
1645
|
+
className: overrides.className ?? item.className,
|
|
1646
|
+
style: overrides.style ?? item.style,
|
|
1647
|
+
disabled: overrides.disabled ?? item.disabled,
|
|
1648
|
+
hidden: overrides.hidden ?? item.hidden
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function resolvePresetEntry(entry, presets) {
|
|
1652
|
+
const presetId = typeof entry === "string" ? entry : entry.preset;
|
|
1653
|
+
const overrides = typeof entry === "string" ? {} : entry;
|
|
1654
|
+
switch (presetId) {
|
|
1655
|
+
case "resetView":
|
|
1656
|
+
return applyPresetOverrides(presets.resetView, overrides);
|
|
1657
|
+
case "fullscreen":
|
|
1658
|
+
return applyPresetOverrides(presets.fullscreen, overrides);
|
|
1659
|
+
case "separator":
|
|
1660
|
+
return applyPresetOverrides(
|
|
1661
|
+
presets.separator(overrides.id ?? "pano-separator"),
|
|
1662
|
+
overrides
|
|
1663
|
+
);
|
|
1664
|
+
default: {
|
|
1665
|
+
const _exhaustive = presetId;
|
|
1666
|
+
return _exhaustive;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function resolvePanoContextMenuEntries(entries, presets) {
|
|
1671
|
+
const resolved = [];
|
|
1672
|
+
for (const entry of entries) {
|
|
1673
|
+
if (isActionEntry(entry)) {
|
|
1674
|
+
resolved.push(entry);
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
resolved.push(resolvePresetEntry(entry, presets));
|
|
1678
|
+
}
|
|
1679
|
+
return resolved;
|
|
1680
|
+
}
|
|
1681
|
+
function composePanoContextMenuItems(props, presets) {
|
|
1682
|
+
if (props.items) {
|
|
1683
|
+
return resolvePanoContextMenuEntries(props.items, presets);
|
|
1684
|
+
}
|
|
1685
|
+
return [
|
|
1686
|
+
...resolvePanoContextMenuEntries(props.prepend ?? [], presets),
|
|
1687
|
+
...presets.defaults,
|
|
1688
|
+
...resolvePanoContextMenuEntries(props.append ?? [], presets)
|
|
1689
|
+
];
|
|
1690
|
+
}
|
|
1691
|
+
var PanoContextMenuActionsContext = (0, import_react7.createContext)(null);
|
|
1692
|
+
|
|
1693
|
+
// src/pano-events.tsx
|
|
1694
|
+
var import_fiber5 = require("@react-three/fiber");
|
|
1695
|
+
var import_react8 = require("react");
|
|
1696
|
+
var DEFAULT_IDLE_TIME_MS = 2e3;
|
|
1697
|
+
function resolveIdleTime(value) {
|
|
1698
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIME_MS;
|
|
1699
|
+
}
|
|
1700
|
+
function usePanoEvents({
|
|
1701
|
+
idleTime = DEFAULT_IDLE_TIME_MS,
|
|
1702
|
+
onViewChange,
|
|
1703
|
+
onViewSettled,
|
|
1704
|
+
onViewInteractionStart,
|
|
1705
|
+
onViewInteractionEnd,
|
|
1706
|
+
onClick,
|
|
1707
|
+
onDoubleClick,
|
|
1708
|
+
onPointerDown,
|
|
1709
|
+
onPointerUp,
|
|
1710
|
+
onPointerMove,
|
|
1711
|
+
onContextMenu,
|
|
1712
|
+
onWheel,
|
|
1713
|
+
onIdle,
|
|
1714
|
+
onIdleEnd,
|
|
1715
|
+
onEnterFullscreen,
|
|
1716
|
+
onExitFullscreen,
|
|
1717
|
+
onResize,
|
|
1718
|
+
onAutoRotateStart,
|
|
1719
|
+
onAutoRotateStop,
|
|
1720
|
+
onAutoRotateOneRound
|
|
1721
|
+
}) {
|
|
1722
|
+
const eventBus = (0, import_react8.useContext)(PanoEventBusContext);
|
|
1723
|
+
const controlsRef = (0, import_react8.useContext)(PanoramaViewContext);
|
|
1724
|
+
const { gl } = (0, import_fiber5.useThree)();
|
|
1725
|
+
if (!eventBus || !controlsRef) {
|
|
1726
|
+
throw new Error("usePanoEvents must be used inside <PanoView>.");
|
|
1727
|
+
}
|
|
1728
|
+
const callbacksRef = (0, import_react8.useRef)({
|
|
1729
|
+
onViewChange,
|
|
1730
|
+
onViewSettled,
|
|
1731
|
+
onViewInteractionStart,
|
|
1732
|
+
onViewInteractionEnd,
|
|
1733
|
+
onClick,
|
|
1734
|
+
onDoubleClick,
|
|
1735
|
+
onPointerDown,
|
|
1736
|
+
onPointerUp,
|
|
1737
|
+
onPointerMove,
|
|
1738
|
+
onContextMenu,
|
|
1739
|
+
onWheel,
|
|
1740
|
+
onIdle,
|
|
1741
|
+
onIdleEnd,
|
|
1742
|
+
onEnterFullscreen,
|
|
1743
|
+
onExitFullscreen,
|
|
1744
|
+
onResize,
|
|
1745
|
+
onAutoRotateStart,
|
|
1746
|
+
onAutoRotateStop,
|
|
1747
|
+
onAutoRotateOneRound
|
|
1748
|
+
});
|
|
1749
|
+
callbacksRef.current = {
|
|
1750
|
+
onViewChange,
|
|
1751
|
+
onViewSettled,
|
|
1752
|
+
onViewInteractionStart,
|
|
1753
|
+
onViewInteractionEnd,
|
|
1754
|
+
onClick,
|
|
1755
|
+
onDoubleClick,
|
|
1756
|
+
onPointerDown,
|
|
1757
|
+
onPointerUp,
|
|
1758
|
+
onPointerMove,
|
|
1759
|
+
onContextMenu,
|
|
1760
|
+
onWheel,
|
|
1761
|
+
onIdle,
|
|
1762
|
+
onIdleEnd,
|
|
1763
|
+
onEnterFullscreen,
|
|
1764
|
+
onExitFullscreen,
|
|
1765
|
+
onResize,
|
|
1766
|
+
onAutoRotateStart,
|
|
1767
|
+
onAutoRotateStop,
|
|
1768
|
+
onAutoRotateOneRound
|
|
1769
|
+
};
|
|
1770
|
+
const idleElapsedRef = (0, import_react8.useRef)(0);
|
|
1771
|
+
const isIdleRef = (0, import_react8.useRef)(false);
|
|
1772
|
+
const idleTimeRef = (0, import_react8.useRef)(resolveIdleTime(idleTime));
|
|
1773
|
+
idleTimeRef.current = resolveIdleTime(idleTime);
|
|
1774
|
+
(0, import_react8.useEffect)(() => {
|
|
1775
|
+
idleElapsedRef.current = 0;
|
|
1776
|
+
isIdleRef.current = false;
|
|
1777
|
+
}, [idleTime]);
|
|
1778
|
+
(0, import_react8.useEffect)(() => {
|
|
1779
|
+
const unsubscribers = [
|
|
1780
|
+
eventBus.subscribe("viewchange", (view) => {
|
|
1781
|
+
callbacksRef.current.onViewChange?.(view);
|
|
1782
|
+
}),
|
|
1783
|
+
eventBus.subscribe("viewsettled", (view) => {
|
|
1784
|
+
callbacksRef.current.onViewSettled?.(view);
|
|
1785
|
+
}),
|
|
1786
|
+
eventBus.subscribe("viewinteractionstart", (event) => {
|
|
1787
|
+
callbacksRef.current.onViewInteractionStart?.(event);
|
|
1788
|
+
}),
|
|
1789
|
+
eventBus.subscribe("viewinteractionend", (event) => {
|
|
1790
|
+
callbacksRef.current.onViewInteractionEnd?.(event);
|
|
1791
|
+
}),
|
|
1792
|
+
eventBus.subscribe("click", (event) => {
|
|
1793
|
+
callbacksRef.current.onClick?.(event);
|
|
1794
|
+
}),
|
|
1795
|
+
eventBus.subscribe("doubleclick", (event) => {
|
|
1796
|
+
callbacksRef.current.onDoubleClick?.(event);
|
|
1797
|
+
}),
|
|
1798
|
+
eventBus.subscribe("pointerdown", (event) => {
|
|
1799
|
+
callbacksRef.current.onPointerDown?.(event);
|
|
1800
|
+
}),
|
|
1801
|
+
eventBus.subscribe("pointerup", (event) => {
|
|
1802
|
+
callbacksRef.current.onPointerUp?.(event);
|
|
1803
|
+
}),
|
|
1804
|
+
eventBus.subscribe("pointermove", (event) => {
|
|
1805
|
+
callbacksRef.current.onPointerMove?.(event);
|
|
1806
|
+
}),
|
|
1807
|
+
eventBus.subscribe("contextmenu", (event) => {
|
|
1808
|
+
callbacksRef.current.onContextMenu?.(event);
|
|
1809
|
+
}),
|
|
1810
|
+
eventBus.subscribe("autorotatestart", () => {
|
|
1811
|
+
callbacksRef.current.onAutoRotateStart?.();
|
|
1812
|
+
}),
|
|
1813
|
+
eventBus.subscribe("autorotatestop", () => {
|
|
1814
|
+
callbacksRef.current.onAutoRotateStop?.();
|
|
1815
|
+
}),
|
|
1816
|
+
eventBus.subscribe("autorotateoneround", () => {
|
|
1817
|
+
callbacksRef.current.onAutoRotateOneRound?.();
|
|
1818
|
+
})
|
|
1819
|
+
];
|
|
1820
|
+
return () => {
|
|
1821
|
+
for (const unsubscribe of unsubscribers) {
|
|
1822
|
+
unsubscribe();
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
}, [eventBus]);
|
|
1826
|
+
(0, import_react8.useEffect)(() => {
|
|
1827
|
+
const element = gl.domElement;
|
|
1828
|
+
const handleWheel = (nativeEvent) => {
|
|
1829
|
+
callbacksRef.current.onWheel?.({
|
|
1830
|
+
delta: nativeEvent.deltaY,
|
|
1831
|
+
nativeEvent
|
|
1832
|
+
});
|
|
1833
|
+
};
|
|
1834
|
+
element.addEventListener("wheel", handleWheel, { passive: true });
|
|
1835
|
+
return () => {
|
|
1836
|
+
element.removeEventListener("wheel", handleWheel);
|
|
1837
|
+
};
|
|
1838
|
+
}, [gl]);
|
|
1839
|
+
(0, import_react8.useEffect)(() => {
|
|
1840
|
+
if (typeof document === "undefined") {
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
const wasFullscreenRef = { current: false };
|
|
1844
|
+
const isViewerFullscreen = () => {
|
|
1845
|
+
const fullscreenElement = document.fullscreenElement;
|
|
1846
|
+
if (!fullscreenElement) {
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
const canvas = gl.domElement;
|
|
1850
|
+
return fullscreenElement === canvas || fullscreenElement.contains(canvas) || canvas.contains(fullscreenElement);
|
|
1851
|
+
};
|
|
1852
|
+
const handleFullscreenChange = () => {
|
|
1853
|
+
const isOurs = isViewerFullscreen();
|
|
1854
|
+
if (isOurs && !wasFullscreenRef.current) {
|
|
1855
|
+
wasFullscreenRef.current = true;
|
|
1856
|
+
callbacksRef.current.onEnterFullscreen?.();
|
|
1857
|
+
} else if (!isOurs && wasFullscreenRef.current) {
|
|
1858
|
+
wasFullscreenRef.current = false;
|
|
1859
|
+
callbacksRef.current.onExitFullscreen?.();
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
wasFullscreenRef.current = isViewerFullscreen();
|
|
1863
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
1864
|
+
return () => {
|
|
1865
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
1866
|
+
};
|
|
1867
|
+
}, [gl]);
|
|
1868
|
+
(0, import_react8.useEffect)(() => {
|
|
1869
|
+
const element = gl.domElement;
|
|
1870
|
+
if (typeof ResizeObserver === "undefined") {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const observer = new ResizeObserver((entries) => {
|
|
1874
|
+
const entry = entries[0];
|
|
1875
|
+
if (!entry) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
const { width, height } = entry.contentRect;
|
|
1879
|
+
callbacksRef.current.onResize?.({ width, height });
|
|
1880
|
+
});
|
|
1881
|
+
observer.observe(element);
|
|
1882
|
+
return () => {
|
|
1883
|
+
observer.disconnect();
|
|
1884
|
+
};
|
|
1885
|
+
}, [gl]);
|
|
1886
|
+
(0, import_fiber5.useFrame)((_, deltaSeconds) => {
|
|
1887
|
+
const interacting = controlsRef.current?.isUserInteracting() ?? false;
|
|
1888
|
+
if (interacting) {
|
|
1889
|
+
idleElapsedRef.current = 0;
|
|
1890
|
+
if (isIdleRef.current) {
|
|
1891
|
+
isIdleRef.current = false;
|
|
1892
|
+
callbacksRef.current.onIdleEnd?.();
|
|
1893
|
+
}
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
idleElapsedRef.current += deltaSeconds * 1e3;
|
|
1897
|
+
if (!isIdleRef.current && idleElapsedRef.current >= idleTimeRef.current) {
|
|
1898
|
+
isIdleRef.current = true;
|
|
1899
|
+
callbacksRef.current.onIdle?.();
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
function PanoEvents(props) {
|
|
1904
|
+
usePanoEvents(props);
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/panorama-event-surface.tsx
|
|
1909
|
+
var import_fiber6 = require("@react-three/fiber");
|
|
1910
|
+
var import_react9 = require("react");
|
|
1911
|
+
var import_three3 = require("three");
|
|
1912
|
+
|
|
1913
|
+
// src/hotspot/coordinates.ts
|
|
1914
|
+
var import_three2 = require("three");
|
|
1915
|
+
|
|
1916
|
+
// src/hotspot/types.ts
|
|
1917
|
+
var MAX_HOTSPOT_PITCH = 90;
|
|
1918
|
+
|
|
1919
|
+
// src/hotspot/coordinates.ts
|
|
1920
|
+
var POLE_EPSILON_DEGREES = 1e-6;
|
|
1921
|
+
function normalizePanoYaw(yaw) {
|
|
1922
|
+
if (!Number.isFinite(yaw)) {
|
|
1923
|
+
return 0;
|
|
1924
|
+
}
|
|
1925
|
+
return import_three2.MathUtils.euclideanModulo(yaw + 180, 360) - 180;
|
|
1926
|
+
}
|
|
1927
|
+
function clampPanoPitch(pitch) {
|
|
1928
|
+
if (!Number.isFinite(pitch)) {
|
|
1929
|
+
return 0;
|
|
1930
|
+
}
|
|
1931
|
+
const clamped = import_three2.MathUtils.clamp(
|
|
1932
|
+
pitch,
|
|
1933
|
+
-MAX_HOTSPOT_PITCH,
|
|
1934
|
+
MAX_HOTSPOT_PITCH
|
|
1935
|
+
);
|
|
1936
|
+
return Math.abs(clamped) >= MAX_HOTSPOT_PITCH - POLE_EPSILON_DEGREES ? Math.sign(clamped) * MAX_HOTSPOT_PITCH : clamped;
|
|
1937
|
+
}
|
|
1938
|
+
function normalizePanoPosition(position) {
|
|
1939
|
+
const pitch = clampPanoPitch(position.pitch);
|
|
1940
|
+
return {
|
|
1941
|
+
yaw: Math.abs(pitch) === MAX_HOTSPOT_PITCH ? 0 : normalizePanoYaw(position.yaw),
|
|
1942
|
+
pitch
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
function panoPositionToVector3(position, radius = 1) {
|
|
1946
|
+
const normalized = normalizePanoPosition(position);
|
|
1947
|
+
const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 1;
|
|
1948
|
+
const yaw = import_three2.MathUtils.degToRad(normalized.yaw);
|
|
1949
|
+
const pitch = import_three2.MathUtils.degToRad(normalized.pitch);
|
|
1950
|
+
const cosPitch = Math.cos(pitch);
|
|
1951
|
+
return new import_three2.Vector3(
|
|
1952
|
+
cosPitch * Math.sin(yaw) * safeRadius,
|
|
1953
|
+
Math.sin(pitch) * safeRadius,
|
|
1954
|
+
-cosPitch * Math.cos(yaw) * safeRadius
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
function vector3ToPanoPosition(vector) {
|
|
1958
|
+
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
1959
|
+
if (!Number.isFinite(length) || length === 0) {
|
|
1960
|
+
return { yaw: 0, pitch: 0 };
|
|
1961
|
+
}
|
|
1962
|
+
return normalizePanoPosition({
|
|
1963
|
+
yaw: import_three2.MathUtils.radToDeg(Math.atan2(vector.x, -vector.z)),
|
|
1964
|
+
pitch: import_three2.MathUtils.radToDeg(
|
|
1965
|
+
Math.asin(import_three2.MathUtils.clamp(vector.y / length, -1, 1))
|
|
1966
|
+
)
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// src/panorama-radius.ts
|
|
1971
|
+
var DEFAULT_PANORAMA_RADIUS = 1e3;
|
|
1972
|
+
var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
|
|
1973
|
+
var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
|
|
1974
|
+
|
|
1975
|
+
// src/panorama-event-surface.tsx
|
|
1976
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1977
|
+
var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
|
|
1978
|
+
function makePanoramaPointerEvent(event) {
|
|
1979
|
+
const nativeEvent = event.nativeEvent;
|
|
1980
|
+
return {
|
|
1981
|
+
position: vector3ToPanoPosition(event.ray.direction),
|
|
1982
|
+
nativeEvent,
|
|
1983
|
+
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
1984
|
+
button: nativeEvent.button,
|
|
1985
|
+
buttons: nativeEvent.buttons,
|
|
1986
|
+
clientX: nativeEvent.clientX,
|
|
1987
|
+
clientY: nativeEvent.clientY
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
function PanoramaEventSurface() {
|
|
1991
|
+
const eventBus = (0, import_react9.useContext)(PanoEventBusContext);
|
|
1992
|
+
const { camera, gl } = (0, import_fiber6.useThree)();
|
|
1993
|
+
(0, import_react9.useEffect)(() => {
|
|
1994
|
+
if (!eventBus) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const element = gl.domElement;
|
|
1998
|
+
const ndc = new import_three3.Vector2();
|
|
1999
|
+
const raycaster = new import_three3.Raycaster();
|
|
2000
|
+
const direction = new import_three3.Vector3();
|
|
2001
|
+
const handleContextMenu = (nativeEvent) => {
|
|
2002
|
+
const rect = element.getBoundingClientRect();
|
|
2003
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
ndc.set(
|
|
2007
|
+
(nativeEvent.clientX - rect.left) / rect.width * 2 - 1,
|
|
2008
|
+
-((nativeEvent.clientY - rect.top) / rect.height) * 2 + 1
|
|
2009
|
+
);
|
|
2010
|
+
raycaster.setFromCamera(ndc, camera);
|
|
2011
|
+
direction.copy(raycaster.ray.direction);
|
|
2012
|
+
eventBus.emit("contextmenu", {
|
|
2013
|
+
position: vector3ToPanoPosition(direction),
|
|
2014
|
+
nativeEvent,
|
|
2015
|
+
pointerId: "pointerId" in nativeEvent ? nativeEvent.pointerId : void 0,
|
|
2016
|
+
button: nativeEvent.button,
|
|
2017
|
+
buttons: nativeEvent.buttons,
|
|
2018
|
+
clientX: nativeEvent.clientX,
|
|
2019
|
+
clientY: nativeEvent.clientY
|
|
2020
|
+
});
|
|
2021
|
+
};
|
|
2022
|
+
element.addEventListener("contextmenu", handleContextMenu);
|
|
2023
|
+
return () => {
|
|
2024
|
+
element.removeEventListener("contextmenu", handleContextMenu);
|
|
2025
|
+
};
|
|
2026
|
+
}, [camera, eventBus, gl]);
|
|
2027
|
+
if (!eventBus) {
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
const emit = (type, event) => {
|
|
2031
|
+
event.stopPropagation();
|
|
2032
|
+
eventBus.emit(type, makePanoramaPointerEvent(event));
|
|
2033
|
+
};
|
|
2034
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2035
|
+
"mesh",
|
|
2036
|
+
{
|
|
2037
|
+
renderOrder: -200,
|
|
2038
|
+
onClick: (event) => emit("click", event),
|
|
2039
|
+
onDoubleClick: (event) => emit("doubleclick", event),
|
|
2040
|
+
onPointerDown: (event) => emit("pointerdown", event),
|
|
2041
|
+
onPointerUp: (event) => emit("pointerup", event),
|
|
2042
|
+
onPointerMove: (event) => emit("pointermove", event),
|
|
2043
|
+
children: [
|
|
2044
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
|
|
2045
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2046
|
+
"meshBasicMaterial",
|
|
2047
|
+
{
|
|
2048
|
+
colorWrite: false,
|
|
2049
|
+
depthTest: false,
|
|
2050
|
+
depthWrite: false,
|
|
2051
|
+
opacity: 0,
|
|
678
2052
|
side: import_three3.BackSide,
|
|
679
2053
|
transparent: true
|
|
680
2054
|
}
|
|
681
|
-
)
|
|
682
|
-
]
|
|
683
|
-
}
|
|
684
|
-
);
|
|
2055
|
+
)
|
|
2056
|
+
]
|
|
2057
|
+
}
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// src/touch-controls.tsx
|
|
2062
|
+
var import_fiber7 = require("@react-three/fiber");
|
|
2063
|
+
var import_react10 = require("react");
|
|
2064
|
+
var DEFAULT_ROTATE_SPEED3 = 0.35;
|
|
2065
|
+
function resolveSpeed3(value, fallback) {
|
|
2066
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
2067
|
+
}
|
|
2068
|
+
function pointerDistance(a, b) {
|
|
2069
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
2070
|
+
}
|
|
2071
|
+
function TouchControls({
|
|
2072
|
+
enabled = true,
|
|
2073
|
+
rotateSpeed = DEFAULT_ROTATE_SPEED3,
|
|
2074
|
+
invert = false,
|
|
2075
|
+
pinchZoom = true
|
|
2076
|
+
}) {
|
|
2077
|
+
const controlsRef = (0, import_react10.useContext)(PanoramaViewContext);
|
|
2078
|
+
const { gl } = (0, import_fiber7.useThree)();
|
|
2079
|
+
const pointersRef = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
|
|
2080
|
+
const pinchDistanceRef = (0, import_react10.useRef)(null);
|
|
2081
|
+
const optionsRef = (0, import_react10.useRef)({ rotateSpeed, invert, pinchZoom });
|
|
2082
|
+
if (!controlsRef) {
|
|
2083
|
+
throw new Error("<TouchControls> must be rendered inside <PanoView>.");
|
|
2084
|
+
}
|
|
2085
|
+
optionsRef.current = { rotateSpeed, invert, pinchZoom };
|
|
2086
|
+
(0, import_react10.useEffect)(() => {
|
|
2087
|
+
const element = gl.domElement;
|
|
2088
|
+
const clearPointers = () => {
|
|
2089
|
+
for (const pointerId of pointersRef.current.keys()) {
|
|
2090
|
+
if (element.hasPointerCapture?.(pointerId)) {
|
|
2091
|
+
element.releasePointerCapture?.(pointerId);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
pointersRef.current.clear();
|
|
2095
|
+
pinchDistanceRef.current = null;
|
|
2096
|
+
controlsRef.current?.setPointerActive("touch", false);
|
|
2097
|
+
};
|
|
2098
|
+
if (!enabled) {
|
|
2099
|
+
clearPointers();
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
const syncActive = () => {
|
|
2103
|
+
controlsRef.current?.setPointerActive(
|
|
2104
|
+
"touch",
|
|
2105
|
+
pointersRef.current.size > 0
|
|
2106
|
+
);
|
|
2107
|
+
};
|
|
2108
|
+
const onPointerDown = (event) => {
|
|
2109
|
+
if (event.pointerType !== "touch") {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
event.preventDefault();
|
|
2116
|
+
pointersRef.current.set(event.pointerId, {
|
|
2117
|
+
x: event.clientX,
|
|
2118
|
+
y: event.clientY,
|
|
2119
|
+
at: performance.now()
|
|
2120
|
+
});
|
|
2121
|
+
element.setPointerCapture?.(event.pointerId);
|
|
2122
|
+
syncActive();
|
|
2123
|
+
if (pointersRef.current.size === 2) {
|
|
2124
|
+
const [first, second] = Array.from(pointersRef.current.values());
|
|
2125
|
+
pinchDistanceRef.current = pointerDistance(first, second);
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
const onPointerMove = (event) => {
|
|
2129
|
+
if (event.pointerType !== "touch") {
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
if (controlsRef.current?.isInteractionLocked()) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
const previous = pointersRef.current.get(event.pointerId);
|
|
2136
|
+
if (!previous) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
event.preventDefault();
|
|
2140
|
+
const next = {
|
|
2141
|
+
x: event.clientX,
|
|
2142
|
+
y: event.clientY,
|
|
2143
|
+
at: performance.now()
|
|
2144
|
+
};
|
|
2145
|
+
pointersRef.current.set(event.pointerId, next);
|
|
2146
|
+
const pointers = Array.from(pointersRef.current.values());
|
|
2147
|
+
if (pointers.length >= 2) {
|
|
2148
|
+
if (!optionsRef.current.pinchZoom) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const distance = pointerDistance(pointers[0], pointers[1]);
|
|
2152
|
+
const previousDistance = pinchDistanceRef.current;
|
|
2153
|
+
pinchDistanceRef.current = distance;
|
|
2154
|
+
if (previousDistance && distance > 0) {
|
|
2155
|
+
const view = controlsRef.current?.getTargetView();
|
|
2156
|
+
if (view) {
|
|
2157
|
+
const nextFov = view.fov * (previousDistance / distance);
|
|
2158
|
+
controlsRef.current?.applyViewDelta(
|
|
2159
|
+
{ fov: nextFov - view.fov },
|
|
2160
|
+
{ recordVelocity: true }
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
const rect = element.getBoundingClientRect();
|
|
2167
|
+
const speed = resolveSpeed3(
|
|
2168
|
+
optionsRef.current.rotateSpeed,
|
|
2169
|
+
DEFAULT_ROTATE_SPEED3
|
|
2170
|
+
);
|
|
2171
|
+
const sign = optionsRef.current.invert ? -1 : 1;
|
|
2172
|
+
const deltaYaw = sign * -(next.x - previous.x) * 360 * speed / Math.max(rect.width, 1);
|
|
2173
|
+
const deltaPitch = sign * (next.y - previous.y) * 180 * speed / Math.max(rect.height, 1);
|
|
2174
|
+
controlsRef.current?.applyViewDelta(
|
|
2175
|
+
{ yaw: deltaYaw, pitch: deltaPitch },
|
|
2176
|
+
{ recordVelocity: true }
|
|
2177
|
+
);
|
|
2178
|
+
};
|
|
2179
|
+
const releasePointer = (event) => {
|
|
2180
|
+
if (!pointersRef.current.has(event.pointerId)) {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
pointersRef.current.delete(event.pointerId);
|
|
2184
|
+
if (element.hasPointerCapture?.(event.pointerId)) {
|
|
2185
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
2186
|
+
}
|
|
2187
|
+
if (pointersRef.current.size < 2) {
|
|
2188
|
+
pinchDistanceRef.current = null;
|
|
2189
|
+
}
|
|
2190
|
+
syncActive();
|
|
2191
|
+
};
|
|
2192
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
2193
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
2194
|
+
element.addEventListener("pointerup", releasePointer);
|
|
2195
|
+
element.addEventListener("pointercancel", releasePointer);
|
|
2196
|
+
return () => {
|
|
2197
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
2198
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
2199
|
+
element.removeEventListener("pointerup", releasePointer);
|
|
2200
|
+
element.removeEventListener("pointercancel", releasePointer);
|
|
2201
|
+
clearPointers();
|
|
2202
|
+
};
|
|
2203
|
+
}, [controlsRef, enabled, gl]);
|
|
2204
|
+
return null;
|
|
685
2205
|
}
|
|
686
2206
|
|
|
687
2207
|
// src/pano-view.tsx
|
|
688
|
-
var
|
|
2208
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
689
2209
|
var DEFAULT_VIEW = {
|
|
690
2210
|
yaw: 0,
|
|
691
2211
|
pitch: 0,
|
|
692
2212
|
fov: 75
|
|
693
2213
|
};
|
|
2214
|
+
var DEFAULT_POINTER_ROTATE_SPEED = 0.35;
|
|
2215
|
+
var DEFAULT_MOUSE_ZOOM_SPEED = 0.08;
|
|
694
2216
|
var DEFAULT_CANVAS_STYLE = {
|
|
695
2217
|
display: "block",
|
|
696
2218
|
height: "100%",
|
|
697
2219
|
width: "100%"
|
|
698
2220
|
};
|
|
699
|
-
|
|
2221
|
+
function resolveChannel(value, defaults) {
|
|
2222
|
+
if (value === false) {
|
|
2223
|
+
return { mount: false };
|
|
2224
|
+
}
|
|
2225
|
+
if (value === true || value === void 0) {
|
|
2226
|
+
return { mount: true, props: { ...defaults } };
|
|
2227
|
+
}
|
|
2228
|
+
return {
|
|
2229
|
+
mount: true,
|
|
2230
|
+
props: {
|
|
2231
|
+
...defaults,
|
|
2232
|
+
...value,
|
|
2233
|
+
enabled: value.enabled !== false
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
function resolveContextMenuChannel(value) {
|
|
2238
|
+
if (value === false) {
|
|
2239
|
+
return { mount: false };
|
|
2240
|
+
}
|
|
2241
|
+
if (value === true || value === void 0) {
|
|
2242
|
+
return { mount: true, props: {} };
|
|
2243
|
+
}
|
|
2244
|
+
return { mount: true, props: { ...value } };
|
|
2245
|
+
}
|
|
2246
|
+
var PanoView = (0, import_react11.forwardRef)(
|
|
700
2247
|
function PanoView2({
|
|
701
2248
|
children,
|
|
702
2249
|
initialView,
|
|
703
2250
|
minFov = 30,
|
|
704
2251
|
maxFov = 100,
|
|
705
2252
|
controls = true,
|
|
2253
|
+
contextMenu = true,
|
|
706
2254
|
onViewChange,
|
|
707
2255
|
onPanoramaClick,
|
|
708
2256
|
onPanoramaDoubleClick,
|
|
@@ -712,19 +2260,21 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
712
2260
|
"aria-label": ariaLabel = "Panorama viewer",
|
|
713
2261
|
...divProps
|
|
714
2262
|
}, ref) {
|
|
715
|
-
const rootRef = (0,
|
|
716
|
-
const controlsRef = (0,
|
|
2263
|
+
const rootRef = (0, import_react11.useRef)(null);
|
|
2264
|
+
const controlsRef = (0, import_react11.useRef)(null);
|
|
2265
|
+
const eventBus = (0, import_react11.useMemo)(() => createPanoEventBus(), []);
|
|
717
2266
|
const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
|
|
718
|
-
const
|
|
2267
|
+
const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
|
|
2268
|
+
const fallbackViewRef = (0, import_react11.useRef)(DEFAULT_VIEW);
|
|
719
2269
|
const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
|
|
720
2270
|
const normalizedMaxFov = Math.min(
|
|
721
2271
|
179,
|
|
722
2272
|
Math.max(maxFov, normalizedMinFov + 1)
|
|
723
2273
|
);
|
|
724
|
-
const normalizedInitialView = (0,
|
|
2274
|
+
const normalizedInitialView = (0, import_react11.useMemo)(
|
|
725
2275
|
() => ({
|
|
726
2276
|
yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
|
|
727
|
-
pitch: initialView?.pitch ?? DEFAULT_VIEW.pitch,
|
|
2277
|
+
pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
|
|
728
2278
|
fov: Math.max(
|
|
729
2279
|
normalizedMinFov,
|
|
730
2280
|
Math.min(initialView?.fov ?? DEFAULT_VIEW.fov, normalizedMaxFov)
|
|
@@ -740,18 +2290,101 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
740
2290
|
);
|
|
741
2291
|
fallbackViewRef.current = normalizedInitialView;
|
|
742
2292
|
const controlsEnabled = controls !== false;
|
|
743
|
-
const controlOptions = (0,
|
|
2293
|
+
const controlOptions = (0, import_react11.useMemo)(
|
|
744
2294
|
() => typeof controls === "object" ? controls : {},
|
|
745
2295
|
[controls]
|
|
746
2296
|
);
|
|
2297
|
+
const userControlsEnabled = controlsEnabled && controlOptions.enabled !== false;
|
|
2298
|
+
const allModeInvert = controlOptions.invert === true;
|
|
2299
|
+
const topRotateSpeed = controlOptions.rotateSpeed ?? DEFAULT_POINTER_ROTATE_SPEED;
|
|
2300
|
+
const topZoomSpeed = controlOptions.zoomSpeed ?? DEFAULT_MOUSE_ZOOM_SPEED;
|
|
2301
|
+
const mouseChannel = (0, import_react11.useMemo)(
|
|
2302
|
+
() => resolveChannel(controlOptions.mouse, {
|
|
2303
|
+
enabled: true,
|
|
2304
|
+
rotateSpeed: topRotateSpeed,
|
|
2305
|
+
zoomSpeed: topZoomSpeed,
|
|
2306
|
+
wheel: true,
|
|
2307
|
+
invert: allModeInvert,
|
|
2308
|
+
buttons: ["left"]
|
|
2309
|
+
}),
|
|
2310
|
+
[
|
|
2311
|
+
allModeInvert,
|
|
2312
|
+
controlOptions.mouse,
|
|
2313
|
+
topRotateSpeed,
|
|
2314
|
+
topZoomSpeed
|
|
2315
|
+
]
|
|
2316
|
+
);
|
|
2317
|
+
const touchChannel = (0, import_react11.useMemo)(
|
|
2318
|
+
() => resolveChannel(controlOptions.touch, {
|
|
2319
|
+
enabled: true,
|
|
2320
|
+
rotateSpeed: topRotateSpeed,
|
|
2321
|
+
invert: allModeInvert,
|
|
2322
|
+
pinchZoom: true
|
|
2323
|
+
}),
|
|
2324
|
+
[allModeInvert, controlOptions.touch, topRotateSpeed]
|
|
2325
|
+
);
|
|
2326
|
+
const keyboardChannel = (0, import_react11.useMemo)(
|
|
2327
|
+
() => resolveChannel(controlOptions.keyboard, {
|
|
2328
|
+
enabled: true
|
|
2329
|
+
}),
|
|
2330
|
+
[controlOptions.keyboard]
|
|
2331
|
+
);
|
|
2332
|
+
const toggleFullscreen = (0, import_react11.useCallback)(async () => {
|
|
2333
|
+
if (typeof document === "undefined") {
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
if (document.fullscreenElement) {
|
|
2337
|
+
await document.exitFullscreen?.();
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
await rootRef.current?.requestFullscreen?.();
|
|
2341
|
+
}, []);
|
|
2342
|
+
const [isViewerFullscreen, setIsViewerFullscreen] = (0, import_react11.useState)(false);
|
|
2343
|
+
(0, import_react11.useEffect)(() => {
|
|
2344
|
+
if (typeof document === "undefined") {
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
const syncFullscreen = () => {
|
|
2348
|
+
const fullscreenElement = document.fullscreenElement;
|
|
2349
|
+
const root = rootRef.current;
|
|
2350
|
+
if (!fullscreenElement || !root) {
|
|
2351
|
+
setIsViewerFullscreen(false);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
setIsViewerFullscreen(
|
|
2355
|
+
fullscreenElement === root || fullscreenElement.contains(root) || root.contains(fullscreenElement)
|
|
2356
|
+
);
|
|
2357
|
+
};
|
|
2358
|
+
syncFullscreen();
|
|
2359
|
+
document.addEventListener("fullscreenchange", syncFullscreen);
|
|
2360
|
+
return () => {
|
|
2361
|
+
document.removeEventListener("fullscreenchange", syncFullscreen);
|
|
2362
|
+
};
|
|
2363
|
+
}, []);
|
|
2364
|
+
const contextMenuActions = (0, import_react11.useMemo)(
|
|
2365
|
+
() => ({
|
|
2366
|
+
reset: () => {
|
|
2367
|
+
controlsRef.current?.reset();
|
|
2368
|
+
},
|
|
2369
|
+
toggleFullscreen: () => {
|
|
2370
|
+
void toggleFullscreen();
|
|
2371
|
+
},
|
|
2372
|
+
isFullscreen: isViewerFullscreen
|
|
2373
|
+
}),
|
|
2374
|
+
[isViewerFullscreen, toggleFullscreen]
|
|
2375
|
+
);
|
|
2376
|
+
const contextMenuChannel = (0, import_react11.useMemo)(
|
|
2377
|
+
() => resolveContextMenuChannel(contextMenu),
|
|
2378
|
+
[contextMenu]
|
|
2379
|
+
);
|
|
747
2380
|
const legacyAutoRotateOptions = controlOptions;
|
|
748
|
-
const [legacyAutoRotate, setLegacyAutoRotate] = (0,
|
|
2381
|
+
const [legacyAutoRotate, setLegacyAutoRotate] = (0, import_react11.useState)(
|
|
749
2382
|
legacyAutoRotateOptions.autoRotate ?? false
|
|
750
2383
|
);
|
|
751
|
-
(0,
|
|
2384
|
+
(0, import_react11.useEffect)(() => {
|
|
752
2385
|
setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
|
|
753
2386
|
}, [legacyAutoRotateOptions.autoRotate]);
|
|
754
|
-
(0,
|
|
2387
|
+
(0, import_react11.useImperativeHandle)(
|
|
755
2388
|
ref,
|
|
756
2389
|
() => ({
|
|
757
2390
|
getView: () => controlsRef.current?.getView() ?? { ...fallbackViewRef.current },
|
|
@@ -767,20 +2400,11 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
767
2400
|
stopAutoRotate: () => {
|
|
768
2401
|
setLegacyAutoRotate(false);
|
|
769
2402
|
},
|
|
770
|
-
toggleFullscreen
|
|
771
|
-
if (typeof document === "undefined") {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
if (document.fullscreenElement) {
|
|
775
|
-
await document.exitFullscreen?.();
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
await rootRef.current?.requestFullscreen?.();
|
|
779
|
-
}
|
|
2403
|
+
toggleFullscreen
|
|
780
2404
|
}),
|
|
781
|
-
[]
|
|
2405
|
+
[toggleFullscreen]
|
|
782
2406
|
);
|
|
783
|
-
const rootStyle = (0,
|
|
2407
|
+
const rootStyle = (0, import_react11.useMemo)(
|
|
784
2408
|
() => ({
|
|
785
2409
|
overflow: "hidden",
|
|
786
2410
|
position: "relative",
|
|
@@ -788,7 +2412,7 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
788
2412
|
}),
|
|
789
2413
|
[style]
|
|
790
2414
|
);
|
|
791
|
-
return /* @__PURE__ */ (0,
|
|
2415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
792
2416
|
"div",
|
|
793
2417
|
{
|
|
794
2418
|
...divProps,
|
|
@@ -796,8 +2420,8 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
796
2420
|
"aria-label": ariaLabel,
|
|
797
2421
|
style: rootStyle,
|
|
798
2422
|
children: [
|
|
799
|
-
/* @__PURE__ */ (0,
|
|
800
|
-
|
|
2423
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2424
|
+
import_fiber8.Canvas,
|
|
801
2425
|
{
|
|
802
2426
|
"aria-label": `${ariaLabel} canvas`,
|
|
803
2427
|
camera: {
|
|
@@ -816,41 +2440,71 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
816
2440
|
},
|
|
817
2441
|
style: {
|
|
818
2442
|
...DEFAULT_CANVAS_STYLE,
|
|
819
|
-
touchAction:
|
|
2443
|
+
touchAction: userControlsEnabled ? "none" : "auto"
|
|
820
2444
|
},
|
|
821
|
-
tabIndex:
|
|
822
|
-
children: /* @__PURE__ */ (0,
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
2445
|
+
tabIndex: userControlsEnabled ? 0 : void 0,
|
|
2446
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2447
|
+
PanoContextMenuOverlayContext.Provider,
|
|
2448
|
+
{
|
|
2449
|
+
value: contextMenuOverlayApi,
|
|
2450
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2451
|
+
PanoContextMenuActionsContext.Provider,
|
|
2452
|
+
{
|
|
2453
|
+
value: contextMenuActions,
|
|
2454
|
+
children: [
|
|
2455
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2456
|
+
PanoramaViewRuntime,
|
|
2457
|
+
{
|
|
2458
|
+
ref: controlsRef,
|
|
2459
|
+
eventBus,
|
|
2460
|
+
initialView: normalizedInitialView,
|
|
2461
|
+
maxFov: normalizedMaxFov,
|
|
2462
|
+
minFov: normalizedMinFov,
|
|
2463
|
+
options: controlOptions
|
|
2464
|
+
}
|
|
2465
|
+
),
|
|
2466
|
+
(onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2467
|
+
PanoEvents,
|
|
2468
|
+
{
|
|
2469
|
+
onViewChange,
|
|
2470
|
+
onClick: onPanoramaClick,
|
|
2471
|
+
onDoubleClick: onPanoramaDoubleClick,
|
|
2472
|
+
onPointerMove: onPanoramaPointerMove
|
|
2473
|
+
}
|
|
2474
|
+
),
|
|
2475
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2476
|
+
AutoRotate,
|
|
2477
|
+
{
|
|
2478
|
+
enabled: legacyAutoRotate,
|
|
2479
|
+
speed: legacyAutoRotateOptions.autoRotateSpeed
|
|
2480
|
+
}
|
|
2481
|
+
),
|
|
2482
|
+
userControlsEnabled && mouseChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2483
|
+
MouseControls,
|
|
2484
|
+
{
|
|
2485
|
+
...mouseChannel.props,
|
|
2486
|
+
fovSpeed: controlOptions.fovSpeed
|
|
2487
|
+
}
|
|
2488
|
+
) : null,
|
|
2489
|
+
userControlsEnabled && touchChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TouchControls, { ...touchChannel.props }) : null,
|
|
2490
|
+
userControlsEnabled && keyboardChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2491
|
+
KeyboardControls,
|
|
2492
|
+
{
|
|
2493
|
+
...keyboardChannel.props,
|
|
2494
|
+
fovSpeed: keyboardChannel.props.fovSpeed ?? controlOptions.fovSpeed
|
|
2495
|
+
}
|
|
2496
|
+
) : null,
|
|
2497
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoramaEventSurface, {}),
|
|
2498
|
+
contextMenuChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoContextMenu, { ...contextMenuChannel.props }) : null,
|
|
2499
|
+
children
|
|
2500
|
+
]
|
|
2501
|
+
}
|
|
2502
|
+
)
|
|
2503
|
+
}
|
|
2504
|
+
) }) }) })
|
|
852
2505
|
}
|
|
853
2506
|
),
|
|
2507
|
+
contextMenuOverlay,
|
|
854
2508
|
hotspotAccessibilityControls
|
|
855
2509
|
]
|
|
856
2510
|
}
|
|
@@ -859,10 +2513,10 @@ var PanoView = (0, import_react4.forwardRef)(
|
|
|
859
2513
|
);
|
|
860
2514
|
|
|
861
2515
|
// src/sphere.tsx
|
|
862
|
-
var
|
|
863
|
-
var
|
|
2516
|
+
var import_fiber9 = require("@react-three/fiber");
|
|
2517
|
+
var import_react12 = require("react");
|
|
864
2518
|
var import_three4 = require("three");
|
|
865
|
-
var
|
|
2519
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
866
2520
|
function Sphere({
|
|
867
2521
|
src,
|
|
868
2522
|
yawOffset = 0,
|
|
@@ -870,13 +2524,13 @@ function Sphere({
|
|
|
870
2524
|
onError,
|
|
871
2525
|
visible = true
|
|
872
2526
|
}) {
|
|
873
|
-
const gl = (0,
|
|
874
|
-
const [texture, setTexture] = (0,
|
|
875
|
-
const onLoadRef = (0,
|
|
876
|
-
const onErrorRef = (0,
|
|
2527
|
+
const gl = (0, import_fiber9.useThree)((state) => state.gl);
|
|
2528
|
+
const [texture, setTexture] = (0, import_react12.useState)(null);
|
|
2529
|
+
const onLoadRef = (0, import_react12.useRef)(onLoad);
|
|
2530
|
+
const onErrorRef = (0, import_react12.useRef)(onError);
|
|
877
2531
|
onLoadRef.current = onLoad;
|
|
878
2532
|
onErrorRef.current = onError;
|
|
879
|
-
(0,
|
|
2533
|
+
(0, import_react12.useEffect)(() => {
|
|
880
2534
|
let active = true;
|
|
881
2535
|
const loader = new import_three4.TextureLoader();
|
|
882
2536
|
setTexture(null);
|
|
@@ -910,7 +2564,7 @@ function Sphere({
|
|
|
910
2564
|
active = false;
|
|
911
2565
|
};
|
|
912
2566
|
}, [gl, src]);
|
|
913
|
-
(0,
|
|
2567
|
+
(0, import_react12.useEffect)(
|
|
914
2568
|
() => () => {
|
|
915
2569
|
texture?.dispose();
|
|
916
2570
|
},
|
|
@@ -919,14 +2573,14 @@ function Sphere({
|
|
|
919
2573
|
if (!texture) {
|
|
920
2574
|
return null;
|
|
921
2575
|
}
|
|
922
|
-
return /* @__PURE__ */ (0,
|
|
2576
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
923
2577
|
"mesh",
|
|
924
2578
|
{
|
|
925
2579
|
rotation: [0, import_three4.MathUtils.degToRad(90 + yawOffset), 0],
|
|
926
2580
|
visible,
|
|
927
2581
|
children: [
|
|
928
|
-
/* @__PURE__ */ (0,
|
|
929
|
-
/* @__PURE__ */ (0,
|
|
2582
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
|
|
2583
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
930
2584
|
"meshBasicMaterial",
|
|
931
2585
|
{
|
|
932
2586
|
map: texture,
|
|
@@ -940,13 +2594,13 @@ function Sphere({
|
|
|
940
2594
|
}
|
|
941
2595
|
|
|
942
2596
|
// src/panorama-scenes.tsx
|
|
943
|
-
var
|
|
944
|
-
var
|
|
2597
|
+
var import_fiber12 = require("@react-three/fiber");
|
|
2598
|
+
var import_react15 = require("react");
|
|
945
2599
|
var import_three8 = require("three");
|
|
946
2600
|
|
|
947
2601
|
// src/tile/texture-manager-context.tsx
|
|
948
|
-
var
|
|
949
|
-
var
|
|
2602
|
+
var import_fiber10 = require("@react-three/fiber");
|
|
2603
|
+
var import_react13 = require("react");
|
|
950
2604
|
|
|
951
2605
|
// src/tile/texture-manager.ts
|
|
952
2606
|
var import_three5 = require("three");
|
|
@@ -1170,8 +2824,8 @@ var TileTextureManager = class {
|
|
|
1170
2824
|
};
|
|
1171
2825
|
|
|
1172
2826
|
// src/tile/texture-manager-context.tsx
|
|
1173
|
-
var
|
|
1174
|
-
var TileTextureManagerContext = (0,
|
|
2827
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2828
|
+
var TileTextureManagerContext = (0, import_react13.createContext)(
|
|
1175
2829
|
null
|
|
1176
2830
|
);
|
|
1177
2831
|
function TileTextureManagerProvider({
|
|
@@ -1179,9 +2833,9 @@ function TileTextureManagerProvider({
|
|
|
1179
2833
|
maxTextureMemoryMb = 128,
|
|
1180
2834
|
maxConcurrentLoads = 4
|
|
1181
2835
|
}) {
|
|
1182
|
-
const gl = (0,
|
|
2836
|
+
const gl = (0, import_fiber10.useThree)((state) => state.gl);
|
|
1183
2837
|
const anisotropy = gl.capabilities.getMaxAnisotropy();
|
|
1184
|
-
const manager = (0,
|
|
2838
|
+
const manager = (0, import_react13.useMemo)(
|
|
1185
2839
|
() => new TileTextureManager({
|
|
1186
2840
|
anisotropy,
|
|
1187
2841
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1190,19 +2844,19 @@ function TileTextureManagerProvider({
|
|
|
1190
2844
|
}),
|
|
1191
2845
|
[anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
|
|
1192
2846
|
);
|
|
1193
|
-
(0,
|
|
2847
|
+
(0, import_react13.useEffect)(() => {
|
|
1194
2848
|
manager.resume();
|
|
1195
2849
|
return () => manager.dispose();
|
|
1196
2850
|
}, [manager]);
|
|
1197
|
-
return /* @__PURE__ */ (0,
|
|
2851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TileTextureManagerContext.Provider, { value: manager, children });
|
|
1198
2852
|
}
|
|
1199
2853
|
function useSharedTileTextureManager() {
|
|
1200
|
-
return (0,
|
|
2854
|
+
return (0, import_react13.useContext)(TileTextureManagerContext);
|
|
1201
2855
|
}
|
|
1202
2856
|
|
|
1203
2857
|
// src/tile/tile.tsx
|
|
1204
|
-
var
|
|
1205
|
-
var
|
|
2858
|
+
var import_fiber11 = require("@react-three/fiber");
|
|
2859
|
+
var import_react14 = require("react");
|
|
1206
2860
|
var import_three7 = require("three");
|
|
1207
2861
|
|
|
1208
2862
|
// src/tile/face-layout.ts
|
|
@@ -1383,7 +3037,7 @@ function resolveTemplateUrl(template, address) {
|
|
|
1383
3037
|
}
|
|
1384
3038
|
|
|
1385
3039
|
// src/tile/tile.tsx
|
|
1386
|
-
var
|
|
3040
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1387
3041
|
var MAX_LOD_UPDATE_INTERVAL_MS = 100;
|
|
1388
3042
|
var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
|
|
1389
3043
|
var FAST_FOV_THRESHOLD_DEGREES = 0.5;
|
|
@@ -1392,10 +3046,10 @@ var VISIBILITY_FOV_PADDING_DEGREES = 8;
|
|
|
1392
3046
|
var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
|
|
1393
3047
|
var SHARED_TILE_GEOMETRY = new import_three7.PlaneGeometry(1, 1);
|
|
1394
3048
|
function usePreviewTexture(url, anisotropy, onError) {
|
|
1395
|
-
const [texture, setTexture] = (0,
|
|
1396
|
-
const onErrorRef = (0,
|
|
3049
|
+
const [texture, setTexture] = (0, import_react14.useState)(null);
|
|
3050
|
+
const onErrorRef = (0, import_react14.useRef)(onError);
|
|
1397
3051
|
onErrorRef.current = onError;
|
|
1398
|
-
(0,
|
|
3052
|
+
(0, import_react14.useEffect)(() => {
|
|
1399
3053
|
if (!url) {
|
|
1400
3054
|
setTexture(null);
|
|
1401
3055
|
return;
|
|
@@ -1432,7 +3086,7 @@ function usePreviewTexture(url, anisotropy, onError) {
|
|
|
1432
3086
|
active = false;
|
|
1433
3087
|
};
|
|
1434
3088
|
}, [anisotropy, url]);
|
|
1435
|
-
(0,
|
|
3089
|
+
(0, import_react14.useEffect)(
|
|
1436
3090
|
() => () => {
|
|
1437
3091
|
texture?.dispose();
|
|
1438
3092
|
},
|
|
@@ -1445,7 +3099,7 @@ function PreviewFace({
|
|
|
1445
3099
|
texture,
|
|
1446
3100
|
faceOrder
|
|
1447
3101
|
}) {
|
|
1448
|
-
const geometry = (0,
|
|
3102
|
+
const geometry = (0, import_react14.useMemo)(() => {
|
|
1449
3103
|
const nextGeometry = new import_three7.PlaneGeometry(
|
|
1450
3104
|
PREVIEW_RADIUS * 2,
|
|
1451
3105
|
PREVIEW_RADIUS * 2
|
|
@@ -1463,21 +3117,21 @@ function PreviewFace({
|
|
|
1463
3117
|
uv.needsUpdate = true;
|
|
1464
3118
|
return nextGeometry;
|
|
1465
3119
|
}, [face, faceOrder]);
|
|
1466
|
-
(0,
|
|
3120
|
+
(0, import_react14.useEffect)(
|
|
1467
3121
|
() => () => {
|
|
1468
3122
|
geometry.dispose();
|
|
1469
3123
|
},
|
|
1470
3124
|
[geometry]
|
|
1471
3125
|
);
|
|
1472
3126
|
const transform = getFaceTransform(face, PREVIEW_RADIUS);
|
|
1473
|
-
return /* @__PURE__ */ (0,
|
|
3127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1474
3128
|
"mesh",
|
|
1475
3129
|
{
|
|
1476
3130
|
geometry,
|
|
1477
3131
|
position: transform.position,
|
|
1478
3132
|
renderOrder: -100,
|
|
1479
3133
|
rotation: transform.rotation,
|
|
1480
|
-
children: /* @__PURE__ */ (0,
|
|
3134
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1481
3135
|
"meshBasicMaterial",
|
|
1482
3136
|
{
|
|
1483
3137
|
depthTest: false,
|
|
@@ -1493,7 +3147,7 @@ function PreviewCube({
|
|
|
1493
3147
|
texture,
|
|
1494
3148
|
faceOrder
|
|
1495
3149
|
}) {
|
|
1496
|
-
return CUBE_FACES.map((face) => /* @__PURE__ */ (0,
|
|
3150
|
+
return CUBE_FACES.map((face) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1497
3151
|
PreviewFace,
|
|
1498
3152
|
{
|
|
1499
3153
|
face,
|
|
@@ -1504,14 +3158,14 @@ function PreviewCube({
|
|
|
1504
3158
|
));
|
|
1505
3159
|
}
|
|
1506
3160
|
function useManagedTexture(manager, key, url, address) {
|
|
1507
|
-
const [managedState, setManagedState] = (0,
|
|
3161
|
+
const [managedState, setManagedState] = (0, import_react14.useState)(() => ({
|
|
1508
3162
|
manager,
|
|
1509
3163
|
key,
|
|
1510
3164
|
url,
|
|
1511
3165
|
snapshot: manager.getSnapshot(key)
|
|
1512
3166
|
}));
|
|
1513
3167
|
const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
|
|
1514
|
-
(0,
|
|
3168
|
+
(0, import_react14.useEffect)(
|
|
1515
3169
|
() => manager.acquire(key, url, address, (nextSnapshot) => {
|
|
1516
3170
|
setManagedState({
|
|
1517
3171
|
manager,
|
|
@@ -1524,7 +3178,7 @@ function useManagedTexture(manager, key, url, address) {
|
|
|
1524
3178
|
);
|
|
1525
3179
|
return snapshot;
|
|
1526
3180
|
}
|
|
1527
|
-
var TileMesh = (0,
|
|
3181
|
+
var TileMesh = (0, import_react14.memo)(function TileMesh2({
|
|
1528
3182
|
address,
|
|
1529
3183
|
manager,
|
|
1530
3184
|
renderOrder,
|
|
@@ -1538,14 +3192,14 @@ var TileMesh = (0, import_react7.memo)(function TileMesh2({
|
|
|
1538
3192
|
if (!snapshot.texture) {
|
|
1539
3193
|
return null;
|
|
1540
3194
|
}
|
|
1541
|
-
return /* @__PURE__ */ (0,
|
|
3195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1542
3196
|
"mesh",
|
|
1543
3197
|
{
|
|
1544
3198
|
geometry: SHARED_TILE_GEOMETRY,
|
|
1545
3199
|
position: [layout.x, layout.y, 0],
|
|
1546
3200
|
renderOrder,
|
|
1547
3201
|
scale: [layout.width, layout.height, 1],
|
|
1548
|
-
children: /* @__PURE__ */ (0,
|
|
3202
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1549
3203
|
"meshBasicMaterial",
|
|
1550
3204
|
{
|
|
1551
3205
|
depthTest: false,
|
|
@@ -1557,12 +3211,12 @@ var TileMesh = (0, import_react7.memo)(function TileMesh2({
|
|
|
1557
3211
|
}
|
|
1558
3212
|
) });
|
|
1559
3213
|
});
|
|
1560
|
-
var TileLayerMeshes = (0,
|
|
3214
|
+
var TileLayerMeshes = (0, import_react14.memo)(function TileLayerMeshes2({
|
|
1561
3215
|
layer,
|
|
1562
3216
|
manager,
|
|
1563
3217
|
resolveUrl
|
|
1564
3218
|
}) {
|
|
1565
|
-
return layer.addresses.map((address) => /* @__PURE__ */ (0,
|
|
3219
|
+
return layer.addresses.map((address) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1566
3220
|
TileMesh,
|
|
1567
3221
|
{
|
|
1568
3222
|
address,
|
|
@@ -1730,11 +3384,11 @@ function Tile({
|
|
|
1730
3384
|
onTileError,
|
|
1731
3385
|
onLevelChange
|
|
1732
3386
|
}) {
|
|
1733
|
-
const { camera, gl, size } = (0,
|
|
1734
|
-
const multiresConfig = (0,
|
|
3387
|
+
const { camera, gl, size } = (0, import_fiber11.useThree)();
|
|
3388
|
+
const multiresConfig = (0, import_react14.useMemo)(() => parseMultires(multires), [multires]);
|
|
1735
3389
|
const { levels, tileSize } = multiresConfig;
|
|
1736
3390
|
const multiresToken = `${tileSize}:${levels.join(",")}`;
|
|
1737
|
-
const candidatesByLevel = (0,
|
|
3391
|
+
const candidatesByLevel = (0, import_react14.useMemo)(
|
|
1738
3392
|
() => buildTileCandidates(levels, tileSize),
|
|
1739
3393
|
[levels, tileSize]
|
|
1740
3394
|
);
|
|
@@ -1746,13 +3400,13 @@ function Tile({
|
|
|
1746
3400
|
onPreviewError
|
|
1747
3401
|
);
|
|
1748
3402
|
const sharedManager = useSharedTileTextureManager();
|
|
1749
|
-
const progressRef = (0,
|
|
1750
|
-
const errorRef = (0,
|
|
1751
|
-
const levelChangeRef = (0,
|
|
3403
|
+
const progressRef = (0, import_react14.useRef)(onLoadProgress);
|
|
3404
|
+
const errorRef = (0, import_react14.useRef)(onTileError);
|
|
3405
|
+
const levelChangeRef = (0, import_react14.useRef)(onLevelChange);
|
|
1752
3406
|
progressRef.current = onLoadProgress;
|
|
1753
3407
|
errorRef.current = onTileError;
|
|
1754
3408
|
levelChangeRef.current = onLevelChange;
|
|
1755
|
-
const ownedManager = (0,
|
|
3409
|
+
const ownedManager = (0, import_react14.useMemo)(
|
|
1756
3410
|
() => new TileTextureManager({
|
|
1757
3411
|
anisotropy,
|
|
1758
3412
|
maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
|
|
@@ -1773,7 +3427,7 @@ function Tile({
|
|
|
1773
3427
|
]
|
|
1774
3428
|
);
|
|
1775
3429
|
const manager = sharedManager ?? ownedManager;
|
|
1776
|
-
(0,
|
|
3430
|
+
(0, import_react14.useEffect)(() => {
|
|
1777
3431
|
manager.resume();
|
|
1778
3432
|
return () => {
|
|
1779
3433
|
if (manager === ownedManager) {
|
|
@@ -1783,11 +3437,11 @@ function Tile({
|
|
|
1783
3437
|
queueMicrotask(() => manager.releaseUnused());
|
|
1784
3438
|
};
|
|
1785
3439
|
}, [manager, ownedManager]);
|
|
1786
|
-
const readyRef = (0,
|
|
1787
|
-
(0,
|
|
3440
|
+
const readyRef = (0, import_react14.useRef)(false);
|
|
3441
|
+
(0, import_react14.useEffect)(() => {
|
|
1788
3442
|
readyRef.current = false;
|
|
1789
3443
|
}, [effectivePreviewUrl]);
|
|
1790
|
-
(0,
|
|
3444
|
+
(0, import_react14.useEffect)(() => {
|
|
1791
3445
|
if (!previewTexture || readyRef.current) {
|
|
1792
3446
|
return;
|
|
1793
3447
|
}
|
|
@@ -1795,31 +3449,31 @@ function Tile({
|
|
|
1795
3449
|
onReady?.();
|
|
1796
3450
|
}, [onReady, previewTexture]);
|
|
1797
3451
|
const template = urlTemplate ?? buildDefaultTileUrlTemplate();
|
|
1798
|
-
const resolveUrl = (0,
|
|
3452
|
+
const resolveUrl = (0, import_react14.useMemo)(
|
|
1799
3453
|
() => (address) => {
|
|
1800
3454
|
const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
|
|
1801
3455
|
return resolveRelativeTileUrl(baseUrl, relativePath);
|
|
1802
3456
|
},
|
|
1803
3457
|
[baseUrl, resolveTileUrl, template]
|
|
1804
3458
|
);
|
|
1805
|
-
const [layers, setLayers] = (0,
|
|
1806
|
-
const layerTokenRef = (0,
|
|
1807
|
-
const currentLevelRef = (0,
|
|
1808
|
-
const lastUpdateAtRef = (0,
|
|
1809
|
-
const visibilityDirtyRef = (0,
|
|
1810
|
-
const lastVisibilityQuaternionRef = (0,
|
|
1811
|
-
const lastVisibilityFovRef = (0,
|
|
1812
|
-
const lastViewportRef = (0,
|
|
1813
|
-
const frustumRef = (0,
|
|
1814
|
-
const projectionMatrixRef = (0,
|
|
1815
|
-
const visibilityCameraRef = (0,
|
|
1816
|
-
(0,
|
|
3459
|
+
const [layers, setLayers] = (0, import_react14.useState)([]);
|
|
3460
|
+
const layerTokenRef = (0, import_react14.useRef)("");
|
|
3461
|
+
const currentLevelRef = (0, import_react14.useRef)(1);
|
|
3462
|
+
const lastUpdateAtRef = (0, import_react14.useRef)(-Infinity);
|
|
3463
|
+
const visibilityDirtyRef = (0, import_react14.useRef)(true);
|
|
3464
|
+
const lastVisibilityQuaternionRef = (0, import_react14.useRef)(camera.quaternion.clone());
|
|
3465
|
+
const lastVisibilityFovRef = (0, import_react14.useRef)(Number.NaN);
|
|
3466
|
+
const lastViewportRef = (0, import_react14.useRef)({ width: 0, height: 0, dpr: 0 });
|
|
3467
|
+
const frustumRef = (0, import_react14.useRef)(new import_three7.Frustum());
|
|
3468
|
+
const projectionMatrixRef = (0, import_react14.useRef)(new import_three7.Matrix4());
|
|
3469
|
+
const visibilityCameraRef = (0, import_react14.useRef)(new import_three7.PerspectiveCamera());
|
|
3470
|
+
(0, import_react14.useEffect)(() => {
|
|
1817
3471
|
currentLevelRef.current = 1;
|
|
1818
3472
|
layerTokenRef.current = "";
|
|
1819
3473
|
visibilityDirtyRef.current = true;
|
|
1820
3474
|
setLayers([]);
|
|
1821
3475
|
}, [manager, multiresToken]);
|
|
1822
|
-
(0,
|
|
3476
|
+
(0, import_react14.useEffect)(() => {
|
|
1823
3477
|
if (loadMode !== "preview") {
|
|
1824
3478
|
return;
|
|
1825
3479
|
}
|
|
@@ -1833,7 +3487,7 @@ function Tile({
|
|
|
1833
3487
|
setLayers(nextLayers);
|
|
1834
3488
|
}
|
|
1835
3489
|
};
|
|
1836
|
-
(0,
|
|
3490
|
+
(0, import_fiber11.useFrame)(() => {
|
|
1837
3491
|
if (loadMode === "preview") {
|
|
1838
3492
|
return;
|
|
1839
3493
|
}
|
|
@@ -1899,15 +3553,15 @@ function Tile({
|
|
|
1899
3553
|
};
|
|
1900
3554
|
commitLayers(nextLayers);
|
|
1901
3555
|
});
|
|
1902
|
-
return /* @__PURE__ */ (0,
|
|
1903
|
-
previewTexture ? /* @__PURE__ */ (0,
|
|
3556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("group", { visible, children: [
|
|
3557
|
+
previewTexture ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1904
3558
|
PreviewCube,
|
|
1905
3559
|
{
|
|
1906
3560
|
texture: previewTexture,
|
|
1907
3561
|
faceOrder: previewFaceOrder
|
|
1908
3562
|
}
|
|
1909
3563
|
) : null,
|
|
1910
|
-
layers.map((layer) => /* @__PURE__ */ (0,
|
|
3564
|
+
layers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1911
3565
|
TileLayerMeshes,
|
|
1912
3566
|
{
|
|
1913
3567
|
layer,
|
|
@@ -1920,7 +3574,7 @@ function Tile({
|
|
|
1920
3574
|
}
|
|
1921
3575
|
|
|
1922
3576
|
// src/panorama-scenes.tsx
|
|
1923
|
-
var
|
|
3577
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1924
3578
|
var TRANSITION_DEFAULTS = {
|
|
1925
3579
|
none: { duration: 0, krpanoBlend: "NOBLEND" },
|
|
1926
3580
|
crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
|
|
@@ -1970,7 +3624,7 @@ function SceneSource({
|
|
|
1970
3624
|
return null;
|
|
1971
3625
|
}
|
|
1972
3626
|
if (scene.type === "sphere") {
|
|
1973
|
-
return /* @__PURE__ */ (0,
|
|
3627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1974
3628
|
Sphere,
|
|
1975
3629
|
{
|
|
1976
3630
|
src: scene.src,
|
|
@@ -1982,7 +3636,7 @@ function SceneSource({
|
|
|
1982
3636
|
);
|
|
1983
3637
|
}
|
|
1984
3638
|
const { id: _id, type: _type, ...tileProps } = scene;
|
|
1985
|
-
return /* @__PURE__ */ (0,
|
|
3639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1986
3640
|
Tile,
|
|
1987
3641
|
{
|
|
1988
3642
|
...tileProps,
|
|
@@ -1998,17 +3652,17 @@ function SnapshotCapture({
|
|
|
1998
3652
|
maxPixels,
|
|
1999
3653
|
onCaptured
|
|
2000
3654
|
}) {
|
|
2001
|
-
const { camera, gl, scene, size } = (0,
|
|
2002
|
-
const capturedRequestRef = (0,
|
|
2003
|
-
const renderTargetRef = (0,
|
|
2004
|
-
(0,
|
|
3655
|
+
const { camera, gl, scene, size } = (0, import_fiber12.useThree)();
|
|
3656
|
+
const capturedRequestRef = (0, import_react15.useRef)(0);
|
|
3657
|
+
const renderTargetRef = (0, import_react15.useRef)(null);
|
|
3658
|
+
(0, import_react15.useEffect)(
|
|
2005
3659
|
() => () => {
|
|
2006
3660
|
renderTargetRef.current?.dispose();
|
|
2007
3661
|
renderTargetRef.current = null;
|
|
2008
3662
|
},
|
|
2009
3663
|
[]
|
|
2010
3664
|
);
|
|
2011
|
-
(0,
|
|
3665
|
+
(0, import_fiber12.useFrame)(() => {
|
|
2012
3666
|
if (requestId === 0 || requestId === capturedRequestRef.current) {
|
|
2013
3667
|
return;
|
|
2014
3668
|
}
|
|
@@ -2054,12 +3708,12 @@ function SnapshotOverlay({
|
|
|
2054
3708
|
runId,
|
|
2055
3709
|
onFinish
|
|
2056
3710
|
}) {
|
|
2057
|
-
const { camera, size } = (0,
|
|
2058
|
-
const meshRef = (0,
|
|
2059
|
-
const startRef = (0,
|
|
2060
|
-
const finishedRef = (0,
|
|
2061
|
-
const directionRef = (0,
|
|
2062
|
-
const material = (0,
|
|
3711
|
+
const { camera, size } = (0, import_fiber12.useThree)();
|
|
3712
|
+
const meshRef = (0, import_react15.useRef)(null);
|
|
3713
|
+
const startRef = (0, import_react15.useRef)(null);
|
|
3714
|
+
const finishedRef = (0, import_react15.useRef)(false);
|
|
3715
|
+
const directionRef = (0, import_react15.useRef)(new import_three8.Vector3());
|
|
3716
|
+
const material = (0, import_react15.useMemo)(
|
|
2063
3717
|
() => new import_three8.ShaderMaterial({
|
|
2064
3718
|
transparent: true,
|
|
2065
3719
|
depthTest: false,
|
|
@@ -2138,13 +3792,13 @@ function SnapshotOverlay({
|
|
|
2138
3792
|
}),
|
|
2139
3793
|
[snapshot.texture, transition.preset]
|
|
2140
3794
|
);
|
|
2141
|
-
(0,
|
|
2142
|
-
(0,
|
|
3795
|
+
(0, import_react15.useEffect)(() => () => material.dispose(), [material]);
|
|
3796
|
+
(0, import_react15.useEffect)(() => {
|
|
2143
3797
|
startRef.current = null;
|
|
2144
3798
|
finishedRef.current = false;
|
|
2145
3799
|
material.uniforms.progress.value = 0;
|
|
2146
3800
|
}, [material, runId]);
|
|
2147
|
-
(0,
|
|
3801
|
+
(0, import_fiber12.useFrame)((state) => {
|
|
2148
3802
|
const mesh = meshRef.current;
|
|
2149
3803
|
if (!mesh || !camera.isPerspectiveCamera) {
|
|
2150
3804
|
return;
|
|
@@ -2169,9 +3823,9 @@ function SnapshotOverlay({
|
|
|
2169
3823
|
onFinish();
|
|
2170
3824
|
}
|
|
2171
3825
|
});
|
|
2172
|
-
return /* @__PURE__ */ (0,
|
|
2173
|
-
/* @__PURE__ */ (0,
|
|
2174
|
-
/* @__PURE__ */ (0,
|
|
3826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("mesh", { ref: meshRef, renderOrder: 1e4, frustumCulled: false, children: [
|
|
3827
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
|
|
3828
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("primitive", { attach: "material", object: material })
|
|
2175
3829
|
] });
|
|
2176
3830
|
}
|
|
2177
3831
|
var FULLSCREEN_PLANE = new import_three8.PlaneGeometry(1, 1);
|
|
@@ -2184,47 +3838,47 @@ function PanoramaScenesController({
|
|
|
2184
3838
|
onTransitionError,
|
|
2185
3839
|
snapshotMaxPixels = 3686400
|
|
2186
3840
|
}) {
|
|
2187
|
-
const controlsRef = (0,
|
|
3841
|
+
const controlsRef = (0, import_react15.useContext)(PanoramaViewContext);
|
|
2188
3842
|
const manager = useSharedTileTextureManager();
|
|
2189
|
-
const transition = (0,
|
|
3843
|
+
const transition = (0, import_react15.useMemo)(
|
|
2190
3844
|
() => resolveTransition(transitionInput),
|
|
2191
3845
|
[transitionInput]
|
|
2192
3846
|
);
|
|
2193
|
-
const sceneMap = (0,
|
|
3847
|
+
const sceneMap = (0, import_react15.useMemo)(
|
|
2194
3848
|
() => new Map(scenes.map((scene) => [scene.id, scene])),
|
|
2195
3849
|
[scenes]
|
|
2196
3850
|
);
|
|
2197
3851
|
const initialScene = sceneMap.get(activeSceneId) ?? null;
|
|
2198
|
-
const [slots, setSlots] = (0,
|
|
3852
|
+
const [slots, setSlots] = (0, import_react15.useState)([
|
|
2199
3853
|
initialScene,
|
|
2200
3854
|
null
|
|
2201
3855
|
]);
|
|
2202
|
-
const [activeSlot, setActiveSlot] = (0,
|
|
2203
|
-
const [liveSlot, setLiveSlot] = (0,
|
|
2204
|
-
const [pendingSlot, setPendingSlot] = (0,
|
|
2205
|
-
const [phase, setPhase] = (0,
|
|
2206
|
-
const [captureRequestId, setCaptureRequestId] = (0,
|
|
2207
|
-
const [transitionRunId, setTransitionRunId] = (0,
|
|
2208
|
-
const [snapshot, setSnapshot] = (0,
|
|
2209
|
-
const [recoveryKey, setRecoveryKey] = (0,
|
|
2210
|
-
const requestedSceneIdRef = (0,
|
|
2211
|
-
const captureReasonRef = (0,
|
|
2212
|
-
const interactionReleaseRef = (0,
|
|
2213
|
-
const slotsRef = (0,
|
|
2214
|
-
const phaseRef = (0,
|
|
2215
|
-
const liveSlotRef = (0,
|
|
2216
|
-
const transitionFromRef = (0,
|
|
3856
|
+
const [activeSlot, setActiveSlot] = (0, import_react15.useState)(0);
|
|
3857
|
+
const [liveSlot, setLiveSlot] = (0, import_react15.useState)(0);
|
|
3858
|
+
const [pendingSlot, setPendingSlot] = (0, import_react15.useState)(null);
|
|
3859
|
+
const [phase, setPhase] = (0, import_react15.useState)(initialScene ? "idle" : "preloading");
|
|
3860
|
+
const [captureRequestId, setCaptureRequestId] = (0, import_react15.useState)(0);
|
|
3861
|
+
const [transitionRunId, setTransitionRunId] = (0, import_react15.useState)(0);
|
|
3862
|
+
const [snapshot, setSnapshot] = (0, import_react15.useState)(null);
|
|
3863
|
+
const [recoveryKey, setRecoveryKey] = (0, import_react15.useState)(0);
|
|
3864
|
+
const requestedSceneIdRef = (0, import_react15.useRef)(activeSceneId);
|
|
3865
|
+
const captureReasonRef = (0, import_react15.useRef)("start");
|
|
3866
|
+
const interactionReleaseRef = (0, import_react15.useRef)(null);
|
|
3867
|
+
const slotsRef = (0, import_react15.useRef)(slots);
|
|
3868
|
+
const phaseRef = (0, import_react15.useRef)(phase);
|
|
3869
|
+
const liveSlotRef = (0, import_react15.useRef)(liveSlot);
|
|
3870
|
+
const transitionFromRef = (0, import_react15.useRef)(initialScene);
|
|
2217
3871
|
slotsRef.current = slots;
|
|
2218
3872
|
phaseRef.current = phase;
|
|
2219
3873
|
liveSlotRef.current = liveSlot;
|
|
2220
|
-
const releaseInteraction = (0,
|
|
3874
|
+
const releaseInteraction = (0, import_react15.useCallback)(() => {
|
|
2221
3875
|
interactionReleaseRef.current?.();
|
|
2222
3876
|
interactionReleaseRef.current = null;
|
|
2223
3877
|
}, []);
|
|
2224
|
-
const reportError = (0,
|
|
3878
|
+
const reportError = (0, import_react15.useCallback)((sceneId, error) => {
|
|
2225
3879
|
onTransitionError?.({ sceneId, error });
|
|
2226
3880
|
}, [onTransitionError]);
|
|
2227
|
-
const startPreload = (0,
|
|
3881
|
+
const startPreload = (0, import_react15.useCallback)((scene) => {
|
|
2228
3882
|
const nextSlot = 1 - liveSlotRef.current;
|
|
2229
3883
|
requestedSceneIdRef.current = scene.id;
|
|
2230
3884
|
setSlots((current) => {
|
|
@@ -2235,7 +3889,7 @@ function PanoramaScenesController({
|
|
|
2235
3889
|
setPendingSlot(nextSlot);
|
|
2236
3890
|
setPhase("preloading");
|
|
2237
3891
|
}, []);
|
|
2238
|
-
(0,
|
|
3892
|
+
(0, import_react15.useEffect)(() => {
|
|
2239
3893
|
const requested = sceneMap.get(activeSceneId);
|
|
2240
3894
|
if (!requested) {
|
|
2241
3895
|
reportError(activeSceneId, new Error(`Unknown panorama scene: ${activeSceneId}`));
|
|
@@ -2259,7 +3913,7 @@ function PanoramaScenesController({
|
|
|
2259
3913
|
}
|
|
2260
3914
|
startPreload(requested);
|
|
2261
3915
|
}, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
|
|
2262
|
-
const handleReady = (0,
|
|
3916
|
+
const handleReady = (0, import_react15.useCallback)((slot) => {
|
|
2263
3917
|
if (slot !== pendingSlot || phaseRef.current !== "preloading") {
|
|
2264
3918
|
return;
|
|
2265
3919
|
}
|
|
@@ -2290,7 +3944,7 @@ function PanoramaScenesController({
|
|
|
2290
3944
|
setPhase("capturing");
|
|
2291
3945
|
setCaptureRequestId((current) => current + 1);
|
|
2292
3946
|
}, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
|
|
2293
|
-
const handleCaptured = (0,
|
|
3947
|
+
const handleCaptured = (0, import_react15.useCallback)((nextSnapshot) => {
|
|
2294
3948
|
setSnapshot((current) => {
|
|
2295
3949
|
current?.dispose();
|
|
2296
3950
|
return nextSnapshot;
|
|
@@ -2312,7 +3966,7 @@ function PanoramaScenesController({
|
|
|
2312
3966
|
setPhase("transitioning");
|
|
2313
3967
|
setTransitionRunId((current) => current + 1);
|
|
2314
3968
|
}, [manager, pendingSlot, sceneMap, startPreload]);
|
|
2315
|
-
const finishTransition = (0,
|
|
3969
|
+
const finishTransition = (0, import_react15.useCallback)(() => {
|
|
2316
3970
|
const nextSlot = pendingSlot;
|
|
2317
3971
|
const previousScene = transitionFromRef.current;
|
|
2318
3972
|
const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
|
|
@@ -2337,12 +3991,12 @@ function PanoramaScenesController({
|
|
|
2337
3991
|
});
|
|
2338
3992
|
}
|
|
2339
3993
|
}, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
|
|
2340
|
-
(0,
|
|
3994
|
+
(0, import_react15.useEffect)(() => () => {
|
|
2341
3995
|
releaseInteraction();
|
|
2342
3996
|
snapshot?.dispose();
|
|
2343
3997
|
}, [releaseInteraction, snapshot]);
|
|
2344
|
-
const { gl } = (0,
|
|
2345
|
-
(0,
|
|
3998
|
+
const { gl } = (0, import_fiber12.useThree)();
|
|
3999
|
+
(0, import_react15.useEffect)(() => {
|
|
2346
4000
|
const canvas = gl.domElement;
|
|
2347
4001
|
const onContextLost = (event) => {
|
|
2348
4002
|
event.preventDefault();
|
|
@@ -2366,8 +4020,8 @@ function PanoramaScenesController({
|
|
|
2366
4020
|
}, [gl, manager, releaseInteraction, snapshot]);
|
|
2367
4021
|
const renderedScene = slots[liveSlot];
|
|
2368
4022
|
const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
|
|
2369
|
-
return /* @__PURE__ */ (0,
|
|
2370
|
-
/* @__PURE__ */ (0,
|
|
4023
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react15.Fragment, { children: [
|
|
4024
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2371
4025
|
SnapshotCapture,
|
|
2372
4026
|
{
|
|
2373
4027
|
requestId: captureRequestId,
|
|
@@ -2379,7 +4033,7 @@ function PanoramaScenesController({
|
|
|
2379
4033
|
const source = slots[slot];
|
|
2380
4034
|
const isLive = slot === liveSlot;
|
|
2381
4035
|
const isPending = slot === pendingSlot;
|
|
2382
|
-
return /* @__PURE__ */ (0,
|
|
4036
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2383
4037
|
SceneSource,
|
|
2384
4038
|
{
|
|
2385
4039
|
scene: source,
|
|
@@ -2395,7 +4049,7 @@ function PanoramaScenesController({
|
|
|
2395
4049
|
`${slot}:${source?.id ?? "empty"}:${recoveryKey}`
|
|
2396
4050
|
);
|
|
2397
4051
|
}),
|
|
2398
|
-
snapshotVisible ? /* @__PURE__ */ (0,
|
|
4052
|
+
snapshotVisible ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2399
4053
|
SnapshotOverlay,
|
|
2400
4054
|
{
|
|
2401
4055
|
snapshot,
|
|
@@ -2413,26 +4067,26 @@ function PanoramaScenes({
|
|
|
2413
4067
|
maxConcurrentTileLoads,
|
|
2414
4068
|
...props
|
|
2415
4069
|
}) {
|
|
2416
|
-
return /* @__PURE__ */ (0,
|
|
4070
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2417
4071
|
TileTextureManagerProvider,
|
|
2418
4072
|
{
|
|
2419
4073
|
maxTextureMemoryMb,
|
|
2420
4074
|
maxConcurrentLoads: maxConcurrentTileLoads,
|
|
2421
|
-
children: /* @__PURE__ */ (0,
|
|
4075
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanoramaScenesController, { ...props })
|
|
2422
4076
|
}
|
|
2423
4077
|
);
|
|
2424
4078
|
}
|
|
2425
4079
|
|
|
2426
4080
|
// src/hotspot/image-hotspot.tsx
|
|
2427
|
-
var
|
|
2428
|
-
var
|
|
4081
|
+
var import_fiber14 = require("@react-three/fiber");
|
|
4082
|
+
var import_react17 = require("react");
|
|
2429
4083
|
var import_three10 = require("three");
|
|
2430
4084
|
|
|
2431
4085
|
// src/hotspot/hotspot-anchor.tsx
|
|
2432
|
-
var
|
|
2433
|
-
var
|
|
4086
|
+
var import_fiber13 = require("@react-three/fiber");
|
|
4087
|
+
var import_react16 = require("react");
|
|
2434
4088
|
var import_three9 = require("three");
|
|
2435
|
-
var
|
|
4089
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2436
4090
|
var DEFAULT_FLOATING_DISTANCE = 10;
|
|
2437
4091
|
var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
|
|
2438
4092
|
var PANORAMA_SURFACE_INSET = 4;
|
|
@@ -2449,6 +4103,9 @@ function angularSizeToWorldSize(size, radius) {
|
|
|
2449
4103
|
const safeSize = Number.isFinite(size) ? import_three9.MathUtils.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
|
|
2450
4104
|
return 2 * radius * Math.tan(import_three9.MathUtils.degToRad(safeSize) / 2);
|
|
2451
4105
|
}
|
|
4106
|
+
function resolveScale(scale) {
|
|
4107
|
+
return Number.isFinite(scale) && scale > 0 ? scale : 1;
|
|
4108
|
+
}
|
|
2452
4109
|
function resolveDistance(placement, distance, width, height) {
|
|
2453
4110
|
const maximumDistance = maximumHotspotDistance(width, height);
|
|
2454
4111
|
if (placement === "surface") {
|
|
@@ -2513,6 +4170,7 @@ function HotspotAnchor({
|
|
|
2513
4170
|
position,
|
|
2514
4171
|
width,
|
|
2515
4172
|
height,
|
|
4173
|
+
scale = 1,
|
|
2516
4174
|
useAngularScale = true,
|
|
2517
4175
|
focusContent,
|
|
2518
4176
|
mode = "billboard",
|
|
@@ -2534,25 +4192,33 @@ function HotspotAnchor({
|
|
|
2534
4192
|
onPositionChange,
|
|
2535
4193
|
onDragEnd
|
|
2536
4194
|
}) {
|
|
2537
|
-
const controlsRef = (0,
|
|
2538
|
-
const groupRef = (0,
|
|
2539
|
-
const dragStateRef = (0,
|
|
2540
|
-
const suppressNextClickRef = (0,
|
|
4195
|
+
const controlsRef = (0, import_react16.useContext)(PanoramaViewContext);
|
|
4196
|
+
const groupRef = (0, import_react16.useRef)(null);
|
|
4197
|
+
const dragStateRef = (0, import_react16.useRef)(null);
|
|
4198
|
+
const suppressNextClickRef = (0, import_react16.useRef)(false);
|
|
2541
4199
|
const normalizedPosition = normalizePanoPosition(position);
|
|
2542
4200
|
const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
|
|
2543
4201
|
const { orientation: orientation2, placement } = rendering;
|
|
2544
|
-
const
|
|
2545
|
-
const
|
|
4202
|
+
const resolvedScale = resolveScale(scale);
|
|
4203
|
+
const scaledWidth = width * resolvedScale;
|
|
4204
|
+
const scaledHeight = height * resolvedScale;
|
|
4205
|
+
const resolvedDistance = resolveDistance(
|
|
4206
|
+
placement,
|
|
4207
|
+
distance,
|
|
4208
|
+
scaledWidth,
|
|
4209
|
+
scaledHeight
|
|
4210
|
+
);
|
|
4211
|
+
const worldPosition = (0, import_react16.useMemo)(
|
|
2546
4212
|
() => panoPositionToVector3(normalizedPosition, resolvedDistance),
|
|
2547
4213
|
[normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
|
|
2548
4214
|
);
|
|
2549
|
-
const surfaceQuaternion3 = (0,
|
|
4215
|
+
const surfaceQuaternion3 = (0, import_react16.useMemo)(
|
|
2550
4216
|
() => createSurfaceQuaternion(normalizedPosition),
|
|
2551
4217
|
[normalizedPosition.pitch, normalizedPosition.yaw]
|
|
2552
4218
|
);
|
|
2553
|
-
const worldWidth = angularSizeToWorldSize(
|
|
2554
|
-
const worldHeight = angularSizeToWorldSize(
|
|
2555
|
-
const focusGeometry = (0,
|
|
4219
|
+
const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
|
|
4220
|
+
const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
|
|
4221
|
+
const focusGeometry = (0, import_react16.useMemo)(() => {
|
|
2556
4222
|
const planeGeometry = new import_three9.PlaneGeometry(1, 1);
|
|
2557
4223
|
const geometry = new import_three9.EdgesGeometry(planeGeometry);
|
|
2558
4224
|
planeGeometry.dispose();
|
|
@@ -2570,15 +4236,15 @@ function HotspotAnchor({
|
|
|
2570
4236
|
});
|
|
2571
4237
|
} : void 0
|
|
2572
4238
|
});
|
|
2573
|
-
(0,
|
|
4239
|
+
(0, import_react16.useEffect)(
|
|
2574
4240
|
() => () => {
|
|
2575
4241
|
dragStateRef.current?.releaseInteractionLock();
|
|
2576
4242
|
dragStateRef.current = null;
|
|
2577
4243
|
},
|
|
2578
4244
|
[]
|
|
2579
4245
|
);
|
|
2580
|
-
(0,
|
|
2581
|
-
(0,
|
|
4246
|
+
(0, import_react16.useEffect)(() => () => focusGeometry.dispose(), [focusGeometry]);
|
|
4247
|
+
(0, import_fiber13.useFrame)(({ camera }) => {
|
|
2582
4248
|
if (!groupRef.current) {
|
|
2583
4249
|
return;
|
|
2584
4250
|
}
|
|
@@ -2589,7 +4255,7 @@ function HotspotAnchor({
|
|
|
2589
4255
|
}
|
|
2590
4256
|
groupRef.current.rotateOnAxis(
|
|
2591
4257
|
LOCAL_FORWARD,
|
|
2592
|
-
import_three9.MathUtils.degToRad(rotation)
|
|
4258
|
+
import_three9.MathUtils.degToRad(-rotation)
|
|
2593
4259
|
);
|
|
2594
4260
|
const scaleFactor = scaleMode === "fixed" && camera instanceof import_three9.PerspectiveCamera ? fixedScaleFactor(camera.fov, referenceFov) : 1;
|
|
2595
4261
|
groupRef.current.scale.set(
|
|
@@ -2624,7 +4290,7 @@ function HotspotAnchor({
|
|
|
2624
4290
|
dragState.releaseInteractionLock();
|
|
2625
4291
|
dragStateRef.current = null;
|
|
2626
4292
|
};
|
|
2627
|
-
return /* @__PURE__ */ (0,
|
|
4293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2628
4294
|
"group",
|
|
2629
4295
|
{
|
|
2630
4296
|
ref: groupRef,
|
|
@@ -2694,14 +4360,14 @@ function HotspotAnchor({
|
|
|
2694
4360
|
} : void 0,
|
|
2695
4361
|
onPointerUp: interactive ? (event) => releasePointer(event, true) : void 0,
|
|
2696
4362
|
children: [
|
|
2697
|
-
focused ? focusContent ?? /* @__PURE__ */ (0,
|
|
4363
|
+
focused ? focusContent ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2698
4364
|
"lineSegments",
|
|
2699
4365
|
{
|
|
2700
4366
|
geometry: focusGeometry,
|
|
2701
4367
|
position: [0, 0, 0.01],
|
|
2702
4368
|
renderOrder: renderOrder + 1,
|
|
2703
4369
|
scale: [1.12, 1.12, 1],
|
|
2704
|
-
children: /* @__PURE__ */ (0,
|
|
4370
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
|
|
2705
4371
|
}
|
|
2706
4372
|
) : null,
|
|
2707
4373
|
children
|
|
@@ -2711,7 +4377,7 @@ function HotspotAnchor({
|
|
|
2711
4377
|
}
|
|
2712
4378
|
|
|
2713
4379
|
// src/hotspot/image-hotspot.tsx
|
|
2714
|
-
var
|
|
4380
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2715
4381
|
function clampOpacity(opacity) {
|
|
2716
4382
|
if (!Number.isFinite(opacity)) {
|
|
2717
4383
|
return 1;
|
|
@@ -2719,13 +4385,13 @@ function clampOpacity(opacity) {
|
|
|
2719
4385
|
return Math.max(0, Math.min(opacity, 1));
|
|
2720
4386
|
}
|
|
2721
4387
|
function useHotspotImageTexture(src, onLoad, onError) {
|
|
2722
|
-
const gl = (0,
|
|
2723
|
-
const [texture, setTexture] = (0,
|
|
2724
|
-
const onLoadRef = (0,
|
|
2725
|
-
const onErrorRef = (0,
|
|
4388
|
+
const gl = (0, import_fiber14.useThree)((state) => state.gl);
|
|
4389
|
+
const [texture, setTexture] = (0, import_react17.useState)(null);
|
|
4390
|
+
const onLoadRef = (0, import_react17.useRef)(onLoad);
|
|
4391
|
+
const onErrorRef = (0, import_react17.useRef)(onError);
|
|
2726
4392
|
onLoadRef.current = onLoad;
|
|
2727
4393
|
onErrorRef.current = onError;
|
|
2728
|
-
(0,
|
|
4394
|
+
(0, import_react17.useEffect)(() => {
|
|
2729
4395
|
let active = true;
|
|
2730
4396
|
setTexture(null);
|
|
2731
4397
|
if (!src) {
|
|
@@ -2763,7 +4429,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
|
|
|
2763
4429
|
active = false;
|
|
2764
4430
|
};
|
|
2765
4431
|
}, [gl, src]);
|
|
2766
|
-
(0,
|
|
4432
|
+
(0, import_react17.useEffect)(
|
|
2767
4433
|
() => () => {
|
|
2768
4434
|
texture?.dispose();
|
|
2769
4435
|
},
|
|
@@ -2781,9 +4447,9 @@ function ImageHotspot({
|
|
|
2781
4447
|
...anchorProps
|
|
2782
4448
|
}) {
|
|
2783
4449
|
const texture = useHotspotImageTexture(src, onLoad, onError);
|
|
2784
|
-
return /* @__PURE__ */ (0,
|
|
2785
|
-
/* @__PURE__ */ (0,
|
|
2786
|
-
/* @__PURE__ */ (0,
|
|
4450
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("mesh", { children: [
|
|
4451
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("planeGeometry", { args: [1, 1] }),
|
|
4452
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2787
4453
|
"meshBasicMaterial",
|
|
2788
4454
|
{
|
|
2789
4455
|
map: texture,
|
|
@@ -2797,13 +4463,13 @@ function ImageHotspot({
|
|
|
2797
4463
|
}
|
|
2798
4464
|
|
|
2799
4465
|
// src/hotspot/polygon-hotspot.tsx
|
|
2800
|
-
var
|
|
2801
|
-
var
|
|
4466
|
+
var import_fiber15 = require("@react-three/fiber");
|
|
4467
|
+
var import_react18 = require("react");
|
|
2802
4468
|
var import_three11 = require("three");
|
|
2803
4469
|
var import_Line2 = require("three/addons/lines/Line2.js");
|
|
2804
4470
|
var import_LineGeometry = require("three/addons/lines/LineGeometry.js");
|
|
2805
4471
|
var import_LineMaterial = require("three/addons/lines/LineMaterial.js");
|
|
2806
|
-
var
|
|
4472
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2807
4473
|
var POLYGON_RADIUS = 990;
|
|
2808
4474
|
var MAX_FILL_SEGMENT_DEGREES = 7;
|
|
2809
4475
|
function cloneVertices(vertices) {
|
|
@@ -3076,38 +4742,38 @@ function PolygonHotspot({
|
|
|
3076
4742
|
onInvalid,
|
|
3077
4743
|
...anchorProps
|
|
3078
4744
|
}) {
|
|
3079
|
-
const gl = (0,
|
|
3080
|
-
const size = (0,
|
|
3081
|
-
const issues = (0,
|
|
4745
|
+
const gl = (0, import_fiber15.useThree)((state) => state.gl);
|
|
4746
|
+
const size = (0, import_fiber15.useThree)((state) => state.size);
|
|
4747
|
+
const issues = (0, import_react18.useMemo)(() => validatePolygonVertices(vertices), [vertices]);
|
|
3082
4748
|
const valid = issues.length === 0;
|
|
3083
|
-
const center = (0,
|
|
3084
|
-
const fillGeometry = (0,
|
|
4749
|
+
const center = (0, import_react18.useMemo)(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
|
|
4750
|
+
const fillGeometry = (0, import_react18.useMemo)(
|
|
3085
4751
|
() => valid && center ? makeFillGeometry(vertices, center) : null,
|
|
3086
4752
|
[center, valid, vertices]
|
|
3087
4753
|
);
|
|
3088
|
-
const strokePositions = (0,
|
|
4754
|
+
const strokePositions = (0, import_react18.useMemo)(
|
|
3089
4755
|
() => valid && center ? makeStrokePositions(vertices, center) : null,
|
|
3090
4756
|
[center, valid, vertices]
|
|
3091
4757
|
);
|
|
3092
4758
|
const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3093
4759
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3094
|
-
const strokeLine = (0,
|
|
4760
|
+
const strokeLine = (0, import_react18.useMemo)(
|
|
3095
4761
|
() => strokePositions ? makeStrokeLine(strokePositions, stroke, strokeWidth * devicePixelRatio, resolvedStrokeOpacity) : null,
|
|
3096
4762
|
[devicePixelRatio, resolvedStrokeOpacity, stroke, strokePositions, strokeWidth]
|
|
3097
4763
|
);
|
|
3098
|
-
const focusStrokeLine = (0,
|
|
4764
|
+
const focusStrokeLine = (0, import_react18.useMemo)(
|
|
3099
4765
|
() => strokePositions ? makeStrokeLine(strokePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3100
4766
|
[devicePixelRatio, strokePositions, strokeWidth]
|
|
3101
4767
|
);
|
|
3102
|
-
const startVerticesRef = (0,
|
|
4768
|
+
const startVerticesRef = (0, import_react18.useRef)(null);
|
|
3103
4769
|
const issuesKey = issues.map((issue) => issue.code).join(",");
|
|
3104
|
-
(0,
|
|
4770
|
+
(0, import_react18.useEffect)(() => {
|
|
3105
4771
|
if (!valid) onInvalid?.(issues);
|
|
3106
4772
|
}, [issues, issuesKey, onInvalid, valid]);
|
|
3107
|
-
(0,
|
|
3108
|
-
(0,
|
|
3109
|
-
(0,
|
|
3110
|
-
(0,
|
|
4773
|
+
(0, import_react18.useEffect)(() => () => fillGeometry?.dispose(), [fillGeometry]);
|
|
4774
|
+
(0, import_react18.useEffect)(() => () => disposeStrokeLine(strokeLine), [strokeLine]);
|
|
4775
|
+
(0, import_react18.useEffect)(() => () => disposeStrokeLine(focusStrokeLine), [focusStrokeLine]);
|
|
4776
|
+
(0, import_react18.useEffect)(() => {
|
|
3111
4777
|
const width = gl.domElement.width;
|
|
3112
4778
|
const height = gl.domElement.height;
|
|
3113
4779
|
for (const line of [strokeLine, focusStrokeLine]) {
|
|
@@ -3126,7 +4792,7 @@ function PolygonHotspot({
|
|
|
3126
4792
|
startPosition: event.startPosition
|
|
3127
4793
|
});
|
|
3128
4794
|
};
|
|
3129
|
-
return /* @__PURE__ */ (0,
|
|
4795
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
3130
4796
|
HotspotAnchor,
|
|
3131
4797
|
{
|
|
3132
4798
|
...anchorProps,
|
|
@@ -3141,7 +4807,7 @@ function PolygonHotspot({
|
|
|
3141
4807
|
draggable,
|
|
3142
4808
|
interactive,
|
|
3143
4809
|
useAngularScale: false,
|
|
3144
|
-
focusContent: /* @__PURE__ */ (0,
|
|
4810
|
+
focusContent: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("primitive", { object: focusStrokeLine, renderOrder: renderOrder + 2 }),
|
|
3145
4811
|
onDragStart: (event) => {
|
|
3146
4812
|
startVerticesRef.current = cloneVertices(vertices);
|
|
3147
4813
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3152,7 +4818,7 @@ function PolygonHotspot({
|
|
|
3152
4818
|
startVerticesRef.current = null;
|
|
3153
4819
|
},
|
|
3154
4820
|
children: [
|
|
3155
|
-
/* @__PURE__ */ (0,
|
|
4821
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3156
4822
|
"meshBasicMaterial",
|
|
3157
4823
|
{
|
|
3158
4824
|
color: fill,
|
|
@@ -3162,20 +4828,20 @@ function PolygonHotspot({
|
|
|
3162
4828
|
transparent: true
|
|
3163
4829
|
}
|
|
3164
4830
|
) }),
|
|
3165
|
-
/* @__PURE__ */ (0,
|
|
4831
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("primitive", { object: strokeLine, renderOrder: renderOrder + 1 })
|
|
3166
4832
|
]
|
|
3167
4833
|
}
|
|
3168
4834
|
);
|
|
3169
4835
|
}
|
|
3170
4836
|
|
|
3171
4837
|
// src/hotspot/polyline-hotspot.tsx
|
|
3172
|
-
var
|
|
3173
|
-
var
|
|
4838
|
+
var import_fiber16 = require("@react-three/fiber");
|
|
4839
|
+
var import_react19 = require("react");
|
|
3174
4840
|
var import_three12 = require("three");
|
|
3175
4841
|
var import_Line22 = require("three/addons/lines/Line2.js");
|
|
3176
4842
|
var import_LineGeometry2 = require("three/addons/lines/LineGeometry.js");
|
|
3177
4843
|
var import_LineMaterial2 = require("three/addons/lines/LineMaterial.js");
|
|
3178
|
-
var
|
|
4844
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
3179
4845
|
var POLYLINE_RADIUS = 990;
|
|
3180
4846
|
var MAX_SEGMENT_DEGREES = 7;
|
|
3181
4847
|
function cloneVertices2(vertices) {
|
|
@@ -3312,32 +4978,32 @@ function PolylineHotspot({
|
|
|
3312
4978
|
onInvalid,
|
|
3313
4979
|
...anchorProps
|
|
3314
4980
|
}) {
|
|
3315
|
-
const gl = (0,
|
|
3316
|
-
const size = (0,
|
|
3317
|
-
const issues = (0,
|
|
4981
|
+
const gl = (0, import_fiber16.useThree)((state) => state.gl);
|
|
4982
|
+
const size = (0, import_fiber16.useThree)((state) => state.size);
|
|
4983
|
+
const issues = (0, import_react19.useMemo)(() => validatePolylineVertices(vertices), [vertices]);
|
|
3318
4984
|
const valid = issues.length === 0;
|
|
3319
|
-
const center = (0,
|
|
3320
|
-
const linePositions = (0,
|
|
4985
|
+
const center = (0, import_react19.useMemo)(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
|
|
4986
|
+
const linePositions = (0, import_react19.useMemo)(
|
|
3321
4987
|
() => valid && center ? makeLinePositions(vertices, center) : null,
|
|
3322
4988
|
[center, valid, vertices]
|
|
3323
4989
|
);
|
|
3324
4990
|
const devicePixelRatio = gl.getPixelRatio();
|
|
3325
4991
|
const opacity = Math.max(0, Math.min(strokeOpacity, 1));
|
|
3326
|
-
const line = (0,
|
|
4992
|
+
const line = (0, import_react19.useMemo)(
|
|
3327
4993
|
() => linePositions ? makeLine(linePositions, stroke, strokeWidth * devicePixelRatio, opacity) : null,
|
|
3328
4994
|
[devicePixelRatio, linePositions, opacity, stroke, strokeWidth]
|
|
3329
4995
|
);
|
|
3330
|
-
const focusLine = (0,
|
|
4996
|
+
const focusLine = (0, import_react19.useMemo)(
|
|
3331
4997
|
() => linePositions ? makeLine(linePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
|
|
3332
4998
|
[devicePixelRatio, linePositions, strokeWidth]
|
|
3333
4999
|
);
|
|
3334
|
-
const startVerticesRef = (0,
|
|
3335
|
-
(0,
|
|
5000
|
+
const startVerticesRef = (0, import_react19.useRef)(null);
|
|
5001
|
+
(0, import_react19.useEffect)(() => {
|
|
3336
5002
|
if (!valid) onInvalid?.(issues);
|
|
3337
5003
|
}, [issues, onInvalid, valid]);
|
|
3338
|
-
(0,
|
|
3339
|
-
(0,
|
|
3340
|
-
(0,
|
|
5004
|
+
(0, import_react19.useEffect)(() => () => disposeLine(line), [line]);
|
|
5005
|
+
(0, import_react19.useEffect)(() => () => disposeLine(focusLine), [focusLine]);
|
|
5006
|
+
(0, import_react19.useEffect)(() => {
|
|
3341
5007
|
const width = gl.domElement.width;
|
|
3342
5008
|
const height = gl.domElement.height;
|
|
3343
5009
|
for (const nextLine of [line, focusLine]) {
|
|
@@ -3355,7 +5021,7 @@ function PolylineHotspot({
|
|
|
3355
5021
|
startPosition: event.startPosition
|
|
3356
5022
|
});
|
|
3357
5023
|
};
|
|
3358
|
-
return /* @__PURE__ */ (0,
|
|
5024
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3359
5025
|
HotspotAnchor,
|
|
3360
5026
|
{
|
|
3361
5027
|
...anchorProps,
|
|
@@ -3369,7 +5035,7 @@ function PolylineHotspot({
|
|
|
3369
5035
|
renderOrder,
|
|
3370
5036
|
draggable,
|
|
3371
5037
|
useAngularScale: false,
|
|
3372
|
-
focusContent: /* @__PURE__ */ (0,
|
|
5038
|
+
focusContent: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("primitive", { object: focusLine, renderOrder: renderOrder + 2 }),
|
|
3373
5039
|
onDragStart: (event) => {
|
|
3374
5040
|
startVerticesRef.current = cloneVertices2(vertices);
|
|
3375
5041
|
emitVerticesChange(event, onDragStart);
|
|
@@ -3379,15 +5045,15 @@ function PolylineHotspot({
|
|
|
3379
5045
|
emitVerticesChange(event, onDragEnd);
|
|
3380
5046
|
startVerticesRef.current = null;
|
|
3381
5047
|
},
|
|
3382
|
-
children: /* @__PURE__ */ (0,
|
|
5048
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("primitive", { object: line, renderOrder: renderOrder + 1 })
|
|
3383
5049
|
}
|
|
3384
5050
|
);
|
|
3385
5051
|
}
|
|
3386
5052
|
|
|
3387
5053
|
// src/hotspot/graphic-hotspot.tsx
|
|
3388
|
-
var
|
|
5054
|
+
var import_react20 = require("react");
|
|
3389
5055
|
var import_three13 = require("three");
|
|
3390
|
-
var
|
|
5056
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3391
5057
|
var GRAPHIC_TEXTURE_SIZE = 512;
|
|
3392
5058
|
function clampOpacity2(opacity) {
|
|
3393
5059
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3398,14 +5064,34 @@ function clampOpacity2(opacity) {
|
|
|
3398
5064
|
function resolveStrokeWidth(value) {
|
|
3399
5065
|
return Number.isFinite(value) ? Math.max(value, 0) : 8;
|
|
3400
5066
|
}
|
|
3401
|
-
function
|
|
3402
|
-
|
|
5067
|
+
function resolveCornerRadius(value) {
|
|
5068
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(value, 0.5)) : 0;
|
|
5069
|
+
}
|
|
5070
|
+
function roundedRectanglePath(context, x, y, width, height, radiusRatio, aspectWidth, aspectHeight) {
|
|
5071
|
+
const safeAspectWidth = Number.isFinite(aspectWidth) && aspectWidth > 0 ? aspectWidth : 1;
|
|
5072
|
+
const safeAspectHeight = Number.isFinite(aspectHeight) && aspectHeight > 0 ? aspectHeight : 1;
|
|
5073
|
+
const shortSide = Math.min(safeAspectWidth, safeAspectHeight);
|
|
5074
|
+
const safeRadiusRatio = resolveCornerRadius(radiusRatio);
|
|
5075
|
+
const radiusX = Math.min(width / 2, width * safeRadiusRatio * shortSide / safeAspectWidth);
|
|
5076
|
+
const radiusY = Math.min(height / 2, height * safeRadiusRatio * shortSide / safeAspectHeight);
|
|
5077
|
+
context.beginPath();
|
|
5078
|
+
context.moveTo(x + radiusX, y);
|
|
5079
|
+
context.lineTo(x + width - radiusX, y);
|
|
5080
|
+
context.ellipse(x + width - radiusX, y + radiusY, radiusX, radiusY, 0, -Math.PI / 2, 0);
|
|
5081
|
+
context.lineTo(x + width, y + height - radiusY);
|
|
5082
|
+
context.ellipse(x + width - radiusX, y + height - radiusY, radiusX, radiusY, 0, 0, Math.PI / 2);
|
|
5083
|
+
context.lineTo(x + radiusX, y + height);
|
|
5084
|
+
context.ellipse(x + radiusX, y + height - radiusY, radiusX, radiusY, 0, Math.PI / 2, Math.PI);
|
|
5085
|
+
context.lineTo(x, y + radiusY);
|
|
5086
|
+
context.ellipse(x + radiusX, y + radiusY, radiusX, radiusY, 0, Math.PI, Math.PI * 1.5);
|
|
5087
|
+
context.closePath();
|
|
5088
|
+
}
|
|
5089
|
+
function polygonPath(context, points) {
|
|
3403
5090
|
context.beginPath();
|
|
3404
|
-
context.moveTo(
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
context.arcTo(x, y, x + width, y, safeRadius);
|
|
5091
|
+
context.moveTo(points[0][0], points[0][1]);
|
|
5092
|
+
for (const [x, y] of points.slice(1)) {
|
|
5093
|
+
context.lineTo(x, y);
|
|
5094
|
+
}
|
|
3409
5095
|
context.closePath();
|
|
3410
5096
|
}
|
|
3411
5097
|
function paintPath(context, graphic, path) {
|
|
@@ -3419,7 +5105,7 @@ function paintPath(context, graphic, path) {
|
|
|
3419
5105
|
path ? context.stroke(path) : context.stroke();
|
|
3420
5106
|
}
|
|
3421
5107
|
}
|
|
3422
|
-
function drawGraphic(context, graphic) {
|
|
5108
|
+
function drawGraphic(context, graphic, aspectWidth, aspectHeight) {
|
|
3423
5109
|
const padding = Math.max(resolveStrokeWidth(graphic.strokeWidth) / 2 + 2, 4);
|
|
3424
5110
|
const drawableSize = GRAPHIC_TEXTURE_SIZE - padding * 2;
|
|
3425
5111
|
context.clearRect(0, 0, GRAPHIC_TEXTURE_SIZE, GRAPHIC_TEXTURE_SIZE);
|
|
@@ -3437,6 +5123,63 @@ function drawGraphic(context, graphic) {
|
|
|
3437
5123
|
paintPath(context, graphic);
|
|
3438
5124
|
return;
|
|
3439
5125
|
}
|
|
5126
|
+
if (graphic.kind === "triangle") {
|
|
5127
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5128
|
+
polygonPath(context, [
|
|
5129
|
+
[center, padding],
|
|
5130
|
+
[padding + drawableSize, padding + drawableSize],
|
|
5131
|
+
[padding, padding + drawableSize]
|
|
5132
|
+
]);
|
|
5133
|
+
paintPath(context, graphic);
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
if (graphic.kind === "diamond") {
|
|
5137
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5138
|
+
polygonPath(context, [
|
|
5139
|
+
[center, padding],
|
|
5140
|
+
[padding + drawableSize, center],
|
|
5141
|
+
[center, padding + drawableSize],
|
|
5142
|
+
[padding, center]
|
|
5143
|
+
]);
|
|
5144
|
+
paintPath(context, graphic);
|
|
5145
|
+
return;
|
|
5146
|
+
}
|
|
5147
|
+
if (graphic.kind === "star") {
|
|
5148
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5149
|
+
const outerRadius = drawableSize / 2;
|
|
5150
|
+
const innerRadius = outerRadius * 0.45;
|
|
5151
|
+
const points = [];
|
|
5152
|
+
for (let index = 0; index < 10; index += 1) {
|
|
5153
|
+
const angle = -Math.PI / 2 + index * Math.PI / 5;
|
|
5154
|
+
const radius = index % 2 === 0 ? outerRadius : innerRadius;
|
|
5155
|
+
points.push([
|
|
5156
|
+
center + Math.cos(angle) * radius,
|
|
5157
|
+
center + Math.sin(angle) * radius
|
|
5158
|
+
]);
|
|
5159
|
+
}
|
|
5160
|
+
polygonPath(context, points);
|
|
5161
|
+
paintPath(context, graphic);
|
|
5162
|
+
return;
|
|
5163
|
+
}
|
|
5164
|
+
if (graphic.kind === "arrow") {
|
|
5165
|
+
const center = GRAPHIC_TEXTURE_SIZE / 2;
|
|
5166
|
+
const top = padding;
|
|
5167
|
+
const bottom = padding + drawableSize;
|
|
5168
|
+
const headBottom = top + drawableSize * 0.43;
|
|
5169
|
+
const headHalfWidth = drawableSize * 0.38;
|
|
5170
|
+
const stemHalfWidth = drawableSize * 0.14;
|
|
5171
|
+
polygonPath(context, [
|
|
5172
|
+
[center, top],
|
|
5173
|
+
[center + headHalfWidth, headBottom],
|
|
5174
|
+
[center + stemHalfWidth, headBottom],
|
|
5175
|
+
[center + stemHalfWidth, bottom],
|
|
5176
|
+
[center - stemHalfWidth, bottom],
|
|
5177
|
+
[center - stemHalfWidth, headBottom],
|
|
5178
|
+
[center - headHalfWidth, headBottom]
|
|
5179
|
+
]);
|
|
5180
|
+
paintPath(context, graphic);
|
|
5181
|
+
return;
|
|
5182
|
+
}
|
|
3440
5183
|
if (graphic.kind === "rectangle") {
|
|
3441
5184
|
roundedRectanglePath(
|
|
3442
5185
|
context,
|
|
@@ -3444,7 +5187,9 @@ function drawGraphic(context, graphic) {
|
|
|
3444
5187
|
padding,
|
|
3445
5188
|
drawableSize,
|
|
3446
5189
|
drawableSize,
|
|
3447
|
-
graphic.cornerRadius ?? 0
|
|
5190
|
+
graphic.cornerRadius ?? 0,
|
|
5191
|
+
aspectWidth,
|
|
5192
|
+
aspectHeight
|
|
3448
5193
|
);
|
|
3449
5194
|
paintPath(context, graphic);
|
|
3450
5195
|
return;
|
|
@@ -3483,7 +5228,7 @@ function drawGraphic(context, graphic) {
|
|
|
3483
5228
|
paintPath(context, graphic, path);
|
|
3484
5229
|
context.restore();
|
|
3485
5230
|
}
|
|
3486
|
-
function createGraphicTexture(graphic) {
|
|
5231
|
+
function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
|
|
3487
5232
|
const canvas = document.createElement("canvas");
|
|
3488
5233
|
canvas.width = GRAPHIC_TEXTURE_SIZE;
|
|
3489
5234
|
canvas.height = GRAPHIC_TEXTURE_SIZE;
|
|
@@ -3491,7 +5236,7 @@ function createGraphicTexture(graphic) {
|
|
|
3491
5236
|
if (!context) {
|
|
3492
5237
|
throw new Error("Canvas 2D is unavailable for this graphic hotspot.");
|
|
3493
5238
|
}
|
|
3494
|
-
drawGraphic(context, graphic);
|
|
5239
|
+
drawGraphic(context, graphic, aspectWidth, aspectHeight);
|
|
3495
5240
|
const texture = new import_three13.CanvasTexture(canvas);
|
|
3496
5241
|
texture.colorSpace = import_three13.SRGBColorSpace;
|
|
3497
5242
|
texture.wrapS = import_three13.ClampToEdgeWrapping;
|
|
@@ -3502,16 +5247,16 @@ function createGraphicTexture(graphic) {
|
|
|
3502
5247
|
texture.needsUpdate = true;
|
|
3503
5248
|
return texture;
|
|
3504
5249
|
}
|
|
3505
|
-
function useGraphicTexture(graphic, onLoad, onError) {
|
|
3506
|
-
const [texture, setTexture] = (0,
|
|
3507
|
-
const onLoadRef = (0,
|
|
3508
|
-
const onErrorRef = (0,
|
|
5250
|
+
function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
|
|
5251
|
+
const [texture, setTexture] = (0, import_react20.useState)(null);
|
|
5252
|
+
const onLoadRef = (0, import_react20.useRef)(onLoad);
|
|
5253
|
+
const onErrorRef = (0, import_react20.useRef)(onError);
|
|
3509
5254
|
onLoadRef.current = onLoad;
|
|
3510
5255
|
onErrorRef.current = onError;
|
|
3511
|
-
(0,
|
|
5256
|
+
(0, import_react20.useEffect)(() => {
|
|
3512
5257
|
let nextTexture = null;
|
|
3513
5258
|
try {
|
|
3514
|
-
nextTexture = createGraphicTexture(graphic);
|
|
5259
|
+
nextTexture = createGraphicTexture(graphic, aspectWidth, aspectHeight);
|
|
3515
5260
|
setTexture(nextTexture);
|
|
3516
5261
|
onLoadRef.current?.(nextTexture);
|
|
3517
5262
|
} catch (error) {
|
|
@@ -3521,7 +5266,7 @@ function useGraphicTexture(graphic, onLoad, onError) {
|
|
|
3521
5266
|
return () => {
|
|
3522
5267
|
nextTexture?.dispose();
|
|
3523
5268
|
};
|
|
3524
|
-
}, [graphic]);
|
|
5269
|
+
}, [aspectHeight, aspectWidth, graphic]);
|
|
3525
5270
|
return texture;
|
|
3526
5271
|
}
|
|
3527
5272
|
function CanvasGraphicHotspot({
|
|
@@ -3533,10 +5278,10 @@ function CanvasGraphicHotspot({
|
|
|
3533
5278
|
onError,
|
|
3534
5279
|
...anchorProps
|
|
3535
5280
|
}) {
|
|
3536
|
-
const texture = useGraphicTexture(graphic, onLoad, onError);
|
|
3537
|
-
return /* @__PURE__ */ (0,
|
|
3538
|
-
/* @__PURE__ */ (0,
|
|
3539
|
-
/* @__PURE__ */ (0,
|
|
5281
|
+
const texture = useGraphicTexture(graphic, width, height, onLoad, onError);
|
|
5282
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("mesh", { children: [
|
|
5283
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("planeGeometry", { args: [1, 1] }),
|
|
5284
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3540
5285
|
"meshBasicMaterial",
|
|
3541
5286
|
{
|
|
3542
5287
|
map: texture,
|
|
@@ -3552,16 +5297,16 @@ function GraphicHotspot(props) {
|
|
|
3552
5297
|
const { graphic, ...restProps } = props;
|
|
3553
5298
|
if (graphic.kind === "svg") {
|
|
3554
5299
|
const imageProps = restProps;
|
|
3555
|
-
return /* @__PURE__ */ (0,
|
|
5300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ImageHotspot, { ...imageProps, src: graphic.src });
|
|
3556
5301
|
}
|
|
3557
|
-
return /* @__PURE__ */ (0,
|
|
5302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CanvasGraphicHotspot, { ...restProps, graphic });
|
|
3558
5303
|
}
|
|
3559
5304
|
|
|
3560
5305
|
// src/hotspot/sequence-hotspot.tsx
|
|
3561
|
-
var
|
|
3562
|
-
var
|
|
5306
|
+
var import_fiber17 = require("@react-three/fiber");
|
|
5307
|
+
var import_react21 = require("react");
|
|
3563
5308
|
var import_three14 = require("three");
|
|
3564
|
-
var
|
|
5309
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3565
5310
|
var DEFAULT_SEQUENCE_FPS = 12;
|
|
3566
5311
|
function clampOpacity3(opacity) {
|
|
3567
5312
|
if (!Number.isFinite(opacity)) {
|
|
@@ -3581,13 +5326,13 @@ function useSpriteTexture({
|
|
|
3581
5326
|
onError,
|
|
3582
5327
|
onLoadProgress
|
|
3583
5328
|
}) {
|
|
3584
|
-
const gl = (0,
|
|
3585
|
-
const [texture, setTexture] = (0,
|
|
3586
|
-
const onErrorRef = (0,
|
|
3587
|
-
const onLoadProgressRef = (0,
|
|
5329
|
+
const gl = (0, import_fiber17.useThree)((state) => state.gl);
|
|
5330
|
+
const [texture, setTexture] = (0, import_react21.useState)(null);
|
|
5331
|
+
const onErrorRef = (0, import_react21.useRef)(onError);
|
|
5332
|
+
const onLoadProgressRef = (0, import_react21.useRef)(onLoadProgress);
|
|
3588
5333
|
onErrorRef.current = onError;
|
|
3589
5334
|
onLoadProgressRef.current = onLoadProgress;
|
|
3590
|
-
(0,
|
|
5335
|
+
(0, import_react21.useEffect)(() => {
|
|
3591
5336
|
let active = true;
|
|
3592
5337
|
setTexture(null);
|
|
3593
5338
|
if (!src) {
|
|
@@ -3628,7 +5373,7 @@ function useSpriteTexture({
|
|
|
3628
5373
|
active = false;
|
|
3629
5374
|
};
|
|
3630
5375
|
}, [gl, id, src]);
|
|
3631
|
-
(0,
|
|
5376
|
+
(0, import_react21.useEffect)(
|
|
3632
5377
|
() => () => {
|
|
3633
5378
|
texture?.dispose();
|
|
3634
5379
|
},
|
|
@@ -3657,24 +5402,24 @@ function SequenceHotspot({
|
|
|
3657
5402
|
const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
|
|
3658
5403
|
const frameCount = resolveFrameCount(requestedFrameCount);
|
|
3659
5404
|
const safeFps = resolveFps(fps);
|
|
3660
|
-
const [frameIndex, setFrameIndex] = (0,
|
|
3661
|
-
const elapsedRef = (0,
|
|
3662
|
-
const frameIndexRef = (0,
|
|
3663
|
-
const endedRef = (0,
|
|
3664
|
-
const lastPlaybackStateRef = (0,
|
|
3665
|
-
const onFrameChangeRef = (0,
|
|
3666
|
-
const onPlaybackStateChangeRef = (0,
|
|
3667
|
-
const onEndedRef = (0,
|
|
5405
|
+
const [frameIndex, setFrameIndex] = (0, import_react21.useState)(0);
|
|
5406
|
+
const elapsedRef = (0, import_react21.useRef)(0);
|
|
5407
|
+
const frameIndexRef = (0, import_react21.useRef)(0);
|
|
5408
|
+
const endedRef = (0, import_react21.useRef)(false);
|
|
5409
|
+
const lastPlaybackStateRef = (0, import_react21.useRef)(null);
|
|
5410
|
+
const onFrameChangeRef = (0, import_react21.useRef)(onFrameChange);
|
|
5411
|
+
const onPlaybackStateChangeRef = (0, import_react21.useRef)(onPlaybackStateChange);
|
|
5412
|
+
const onEndedRef = (0, import_react21.useRef)(onEnded);
|
|
3668
5413
|
onFrameChangeRef.current = onFrameChange;
|
|
3669
5414
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3670
5415
|
onEndedRef.current = onEnded;
|
|
3671
|
-
(0,
|
|
5416
|
+
(0, import_react21.useEffect)(() => {
|
|
3672
5417
|
elapsedRef.current = 0;
|
|
3673
5418
|
frameIndexRef.current = 0;
|
|
3674
5419
|
endedRef.current = false;
|
|
3675
5420
|
setFrameIndex(0);
|
|
3676
5421
|
}, [frameCount, frameDirection, src]);
|
|
3677
|
-
(0,
|
|
5422
|
+
(0, import_react21.useEffect)(() => {
|
|
3678
5423
|
if (!texture) {
|
|
3679
5424
|
return;
|
|
3680
5425
|
}
|
|
@@ -3688,13 +5433,13 @@ function SequenceHotspot({
|
|
|
3688
5433
|
);
|
|
3689
5434
|
texture.needsUpdate = true;
|
|
3690
5435
|
}, [frameCount, frameDirection, frameIndex, texture]);
|
|
3691
|
-
(0,
|
|
5436
|
+
(0, import_react21.useEffect)(() => {
|
|
3692
5437
|
if (!texture) {
|
|
3693
5438
|
return;
|
|
3694
5439
|
}
|
|
3695
5440
|
onFrameChangeRef.current?.({ id, index: 0, src });
|
|
3696
5441
|
}, [id, src, texture]);
|
|
3697
|
-
(0,
|
|
5442
|
+
(0, import_react21.useEffect)(() => {
|
|
3698
5443
|
if (!texture) {
|
|
3699
5444
|
return;
|
|
3700
5445
|
}
|
|
@@ -3713,7 +5458,7 @@ function SequenceHotspot({
|
|
|
3713
5458
|
onPlaybackStateChangeRef.current?.(nextState);
|
|
3714
5459
|
}
|
|
3715
5460
|
}, [playing, texture]);
|
|
3716
|
-
(0,
|
|
5461
|
+
(0, import_fiber17.useFrame)((_, deltaSeconds) => {
|
|
3717
5462
|
if (!texture || !playing || endedRef.current) {
|
|
3718
5463
|
return;
|
|
3719
5464
|
}
|
|
@@ -3743,9 +5488,9 @@ function SequenceHotspot({
|
|
|
3743
5488
|
onFrameChangeRef.current?.({ id, index: nextIndex, src });
|
|
3744
5489
|
}
|
|
3745
5490
|
});
|
|
3746
|
-
return /* @__PURE__ */ (0,
|
|
3747
|
-
/* @__PURE__ */ (0,
|
|
3748
|
-
/* @__PURE__ */ (0,
|
|
5491
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("mesh", { children: [
|
|
5492
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("planeGeometry", { args: [1, 1] }),
|
|
5493
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3749
5494
|
"meshBasicMaterial",
|
|
3750
5495
|
{
|
|
3751
5496
|
map: texture,
|
|
@@ -3759,10 +5504,10 @@ function SequenceHotspot({
|
|
|
3759
5504
|
}
|
|
3760
5505
|
|
|
3761
5506
|
// src/hotspot/video-hotspot.tsx
|
|
3762
|
-
var
|
|
3763
|
-
var
|
|
5507
|
+
var import_fiber18 = require("@react-three/fiber");
|
|
5508
|
+
var import_react22 = require("react");
|
|
3764
5509
|
var import_three15 = require("three");
|
|
3765
|
-
var
|
|
5510
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3766
5511
|
function clampOpacity4(opacity) {
|
|
3767
5512
|
if (!Number.isFinite(opacity)) {
|
|
3768
5513
|
return 1;
|
|
@@ -3776,11 +5521,11 @@ function clampVolume(volume) {
|
|
|
3776
5521
|
return Math.max(0, Math.min(volume, 1));
|
|
3777
5522
|
}
|
|
3778
5523
|
function usePosterTexture(id, poster, onError) {
|
|
3779
|
-
const gl = (0,
|
|
3780
|
-
const [texture, setTexture] = (0,
|
|
3781
|
-
const onErrorRef = (0,
|
|
5524
|
+
const gl = (0, import_fiber18.useThree)((state) => state.gl);
|
|
5525
|
+
const [texture, setTexture] = (0, import_react22.useState)(null);
|
|
5526
|
+
const onErrorRef = (0, import_react22.useRef)(onError);
|
|
3782
5527
|
onErrorRef.current = onError;
|
|
3783
|
-
(0,
|
|
5528
|
+
(0, import_react22.useEffect)(() => {
|
|
3784
5529
|
let active = true;
|
|
3785
5530
|
setTexture(null);
|
|
3786
5531
|
if (!poster) {
|
|
@@ -3817,7 +5562,7 @@ function usePosterTexture(id, poster, onError) {
|
|
|
3817
5562
|
active = false;
|
|
3818
5563
|
};
|
|
3819
5564
|
}, [gl, id, poster]);
|
|
3820
|
-
(0,
|
|
5565
|
+
(0, import_react22.useEffect)(
|
|
3821
5566
|
() => () => {
|
|
3822
5567
|
texture?.dispose();
|
|
3823
5568
|
},
|
|
@@ -3835,14 +5580,14 @@ function useVideoResource({
|
|
|
3835
5580
|
poster,
|
|
3836
5581
|
src
|
|
3837
5582
|
}) {
|
|
3838
|
-
const [resource, setResource] = (0,
|
|
3839
|
-
const onEndedRef = (0,
|
|
3840
|
-
const onErrorRef = (0,
|
|
3841
|
-
const onPlaybackStateChangeRef = (0,
|
|
5583
|
+
const [resource, setResource] = (0, import_react22.useState)(null);
|
|
5584
|
+
const onEndedRef = (0, import_react22.useRef)(onEnded);
|
|
5585
|
+
const onErrorRef = (0, import_react22.useRef)(onError);
|
|
5586
|
+
const onPlaybackStateChangeRef = (0, import_react22.useRef)(onPlaybackStateChange);
|
|
3842
5587
|
onEndedRef.current = onEnded;
|
|
3843
5588
|
onErrorRef.current = onError;
|
|
3844
5589
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3845
|
-
(0,
|
|
5590
|
+
(0, import_react22.useEffect)(() => {
|
|
3846
5591
|
if (!src) {
|
|
3847
5592
|
setResource(null);
|
|
3848
5593
|
return;
|
|
@@ -3943,11 +5688,11 @@ function VideoHotspot({
|
|
|
3943
5688
|
src
|
|
3944
5689
|
});
|
|
3945
5690
|
const posterTexture = usePosterTexture(id, poster, onError);
|
|
3946
|
-
const onPlaybackErrorRef = (0,
|
|
3947
|
-
const onPlaybackStateChangeRef = (0,
|
|
5691
|
+
const onPlaybackErrorRef = (0, import_react22.useRef)(onPlaybackError);
|
|
5692
|
+
const onPlaybackStateChangeRef = (0, import_react22.useRef)(onPlaybackStateChange);
|
|
3948
5693
|
onPlaybackErrorRef.current = onPlaybackError;
|
|
3949
5694
|
onPlaybackStateChangeRef.current = onPlaybackStateChange;
|
|
3950
|
-
(0,
|
|
5695
|
+
(0, import_react22.useEffect)(() => {
|
|
3951
5696
|
if (!resource) {
|
|
3952
5697
|
return;
|
|
3953
5698
|
}
|
|
@@ -3957,7 +5702,7 @@ function VideoHotspot({
|
|
|
3957
5702
|
resource.video.playsInline = playsInline;
|
|
3958
5703
|
resource.video.preload = preload;
|
|
3959
5704
|
}, [loop, muted, playsInline, preload, resource, volume]);
|
|
3960
|
-
(0,
|
|
5705
|
+
(0, import_react22.useEffect)(() => {
|
|
3961
5706
|
if (!resource) {
|
|
3962
5707
|
return;
|
|
3963
5708
|
}
|
|
@@ -3980,9 +5725,9 @@ function VideoHotspot({
|
|
|
3980
5725
|
};
|
|
3981
5726
|
}, [playing, resource]);
|
|
3982
5727
|
const activeTexture = resource?.ready ? resource.texture : posterTexture;
|
|
3983
|
-
return /* @__PURE__ */ (0,
|
|
3984
|
-
/* @__PURE__ */ (0,
|
|
3985
|
-
/* @__PURE__ */ (0,
|
|
5728
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("mesh", { children: [
|
|
5729
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("planeGeometry", { args: [1, 1] }),
|
|
5730
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3986
5731
|
"meshBasicMaterial",
|
|
3987
5732
|
{
|
|
3988
5733
|
map: activeTexture,
|
|
@@ -3999,7 +5744,12 @@ function VideoHotspot({
|
|
|
3999
5744
|
AutoRotate,
|
|
4000
5745
|
GraphicHotspot,
|
|
4001
5746
|
ImageHotspot,
|
|
5747
|
+
KeyboardControls,
|
|
4002
5748
|
MAX_HOTSPOT_PITCH,
|
|
5749
|
+
MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY,
|
|
5750
|
+
MouseControls,
|
|
5751
|
+
PanoContextMenu,
|
|
5752
|
+
PanoEvents,
|
|
4003
5753
|
PanoView,
|
|
4004
5754
|
PanoramaScenes,
|
|
4005
5755
|
PolygonHotspot,
|
|
@@ -4007,12 +5757,19 @@ function VideoHotspot({
|
|
|
4007
5757
|
SequenceHotspot,
|
|
4008
5758
|
Sphere,
|
|
4009
5759
|
Tile,
|
|
5760
|
+
TouchControls,
|
|
4010
5761
|
VideoHotspot,
|
|
4011
5762
|
clampPanoPitch,
|
|
5763
|
+
composePanoContextMenuItems,
|
|
5764
|
+
createDefaultPanoContextMenuItems,
|
|
5765
|
+
createPanoContextMenuPresets,
|
|
5766
|
+
cycleSceneId,
|
|
4012
5767
|
normalizePanoPosition,
|
|
4013
5768
|
normalizePanoYaw,
|
|
4014
5769
|
panoPositionToVector3,
|
|
5770
|
+
resolvePanoContextMenuEntries,
|
|
4015
5771
|
unwrapPolygonVertices,
|
|
5772
|
+
usePanoEvents,
|
|
4016
5773
|
validatePolygonVertices,
|
|
4017
5774
|
validatePolylineVertices,
|
|
4018
5775
|
vector3ToPanoPosition
|