@needle-tools/engine 4.12.0-next.c6c0281 → 4.12.0-next.da19dd0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generateMeshBVH.worker-iyfPIK6R.js +21 -0
- package/dist/needle-engine.bundle-BoOkD4mG.umd.cjs +1647 -0
- package/dist/{needle-engine.bundle-ThW_g39D.min.js → needle-engine.bundle-DDdH38o3.min.js} +151 -150
- package/dist/{needle-engine.bundle-BVdPECIB.js → needle-engine.bundle-DeqWDtMx.js} +8997 -8592
- package/dist/needle-engine.d.ts +27 -18
- package/dist/needle-engine.js +46 -46
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-C_oHRUjX.js → vendor-DMZcbVO1.js} +2644 -2487
- package/dist/{vendor-BiJQtqow.min.js → vendor-sURMCFSI.min.js} +41 -41
- package/dist/{vendor-DN-NsXVB.umd.cjs → vendor-tyBvnMF-.umd.cjs} +36 -36
- package/lib/engine/debug/debug_console.js +403 -1
- package/lib/engine/debug/debug_console.js.map +1 -1
- package/lib/engine/engine_components.js +3 -3
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.js +2 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_input.d.ts +5 -0
- package/lib/engine/engine_input.js +6 -0
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_license.d.ts +18 -0
- package/lib/engine/engine_license.js +155 -9
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking.js +5 -5
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +4 -1
- package/lib/engine/engine_utils.js +28 -4
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/webcomponents/WebXRButtons.js +13 -5
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -0
- package/lib/engine/webcomponents/needle menu/needle-menu.js +33 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +4 -0
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +1 -1
- package/lib/engine/xr/NeedleXRSession.js +106 -22
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +12 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine/xr/usdz.js +6 -2
- package/lib/engine/xr/usdz.js.map +1 -1
- package/lib/engine-components/Camera.js +4 -1
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/Component.d.ts +2 -1
- package/lib/engine-components/Component.js +3 -2
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.js +1 -1
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.js +15 -7
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -1
- package/lib/engine-components/api.js +1 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/webxr/Avatar.js +2 -0
- package/lib/engine-components/webxr/Avatar.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.js +18 -12
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/package.json +3 -3
- package/plugins/vite/poster-client.js +8 -1
- package/src/engine/debug/debug_console.ts +449 -1
- package/src/engine/engine_components.ts +4 -4
- package/src/engine/engine_context.ts +2 -0
- package/src/engine/engine_input.ts +7 -0
- package/src/engine/engine_license.ts +171 -9
- package/src/engine/engine_networking.ts +5 -5
- package/src/engine/engine_physics.ts +3 -3
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -1
- package/src/engine/engine_utils.ts +23 -4
- package/src/engine/webcomponents/WebXRButtons.ts +15 -5
- package/src/engine/webcomponents/needle menu/needle-menu.ts +35 -2
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +6 -0
- package/src/engine/xr/NeedleXRSession.ts +120 -24
- package/src/engine/xr/TempXRContext.ts +12 -2
- package/src/engine/xr/usdz.ts +6 -1
- package/src/engine-components/Camera.ts +4 -1
- package/src/engine-components/Component.ts +5 -4
- package/src/engine-components/DragControls.ts +1 -1
- package/src/engine-components/SpectatorCamera.ts +21 -10
- package/src/engine-components/api.ts +1 -1
- package/src/engine-components/webxr/Avatar.ts +4 -0
- package/src/engine-components/webxr/WebXR.ts +19 -11
- package/dist/generateMeshBVH.worker-BvGEI0r7.js +0 -21
- package/dist/needle-engine.bundle-Dk2U2sIu.umd.cjs +0 -1646
|
@@ -6,6 +6,7 @@ import { Context, FrameEvent } from "../engine_context.js";
|
|
|
6
6
|
import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
|
|
7
7
|
import { isDestroyed } from "../engine_gameobject.js";
|
|
8
8
|
import { Gizmos } from "../engine_gizmos.js";
|
|
9
|
+
import { Telemetry } from "../engine_license.js";
|
|
9
10
|
import { registerFrameEventCallback, unregisterFrameEventCallback } from "../engine_lifecycle_functions_internal.js";
|
|
10
11
|
import { getBoundingBox, getTempQuaternion, getTempVector, getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../engine_three_utils.js";
|
|
11
12
|
import type { ICamera, IComponent, INeedleXRSession } from "../engine_types.js";
|
|
@@ -82,6 +83,34 @@ function getDOMOverlayElement(domElement: HTMLElement) {
|
|
|
82
83
|
handleSessionGranted();
|
|
83
84
|
async function handleSessionGranted() {
|
|
84
85
|
|
|
86
|
+
// await delay(400);
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
let defaultMode: XRSessionMode = "immersive-vr";
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// In app clips we default to AR
|
|
93
|
+
if (DeviceUtilities.isNeedleAppClip()) {
|
|
94
|
+
defaultMode = "immersive-ar";
|
|
95
|
+
}
|
|
96
|
+
// Check if VR is even supported, otherwise try AR
|
|
97
|
+
else if (!(await navigator.xr?.isSessionSupported("immersive-vr"))) {
|
|
98
|
+
defaultMode = "immersive-ar";
|
|
99
|
+
}
|
|
100
|
+
// Check if AR is supported, otherwise we can't do anything
|
|
101
|
+
if (!(await navigator.xr?.isSessionSupported("immersive-ar")) && defaultMode === "immersive-ar") {
|
|
102
|
+
// console.warn("[NeedleXRSession:granted] Neither VR nor AR supported, aborting session start.");
|
|
103
|
+
// showBalloonMessage("NeidleXRSession: Neither VR nor AR supported, aborting session start.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.debug("[NeedleXRSession:granted] Error while checking XR support:", e);
|
|
108
|
+
// showBalloonWarning("NeedleXRSession: Error while checking XR support: " + (e as Error).message);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// showBalloonMessage("sessiongranted: " + defaultMode);
|
|
113
|
+
|
|
85
114
|
// TODO: asap session granted doesnt handle the pre-room yet
|
|
86
115
|
if (getParam("debugasap")) {
|
|
87
116
|
let asapSession = globalThis["needle:XRSession"] as XRSession | undefined | Promise<XRSession>;
|
|
@@ -94,11 +123,11 @@ async function handleSessionGranted() {
|
|
|
94
123
|
enableSpatialConsole(true);
|
|
95
124
|
const session = await asapSession;
|
|
96
125
|
if (session) {
|
|
97
|
-
const sessionInit = NeedleXRSession.getDefaultSessionInit(
|
|
98
|
-
NeedleXRSession.setSession(
|
|
126
|
+
const sessionInit = NeedleXRSession.getDefaultSessionInit(defaultMode);
|
|
127
|
+
NeedleXRSession.setSession(defaultMode, session, sessionInit, cb.context);
|
|
99
128
|
}
|
|
100
129
|
else {
|
|
101
|
-
console.error("NeedleXRSession: ASAP session was rejected");
|
|
130
|
+
console.error("[NeedleXRSession:granted] ASAP session was rejected");
|
|
102
131
|
}
|
|
103
132
|
asapSession = undefined;
|
|
104
133
|
});
|
|
@@ -106,6 +135,9 @@ async function handleSessionGranted() {
|
|
|
106
135
|
}
|
|
107
136
|
}
|
|
108
137
|
|
|
138
|
+
// console.log("Attaching sessiongranted handler...", { haveXR: 'xr' in navigator });
|
|
139
|
+
// setTimeout(() => console.log("Session Granted handler attached.", { haveXR: 'xr' in navigator }), 1000);
|
|
140
|
+
|
|
109
141
|
if ('xr' in navigator) {
|
|
110
142
|
// WebXRViewer (based on Firefox) has a bug where addEventListener
|
|
111
143
|
// throws a silent exception and aborts execution entirely.
|
|
@@ -115,10 +147,8 @@ async function handleSessionGranted() {
|
|
|
115
147
|
}
|
|
116
148
|
|
|
117
149
|
navigator.xr?.addEventListener('sessiongranted', async () => {
|
|
118
|
-
enableSpatialConsole(true);
|
|
150
|
+
// enableSpatialConsole(true);
|
|
119
151
|
|
|
120
|
-
console.log("Received Session Granted...")
|
|
121
|
-
await delay(100);
|
|
122
152
|
|
|
123
153
|
const lastSessionMode = sessionStorage.getItem("needle_xr_session_mode") as XRSessionMode;
|
|
124
154
|
const lastSessionInit = sessionStorage.getItem("needle_xr_session_init") ?? null;
|
|
@@ -126,7 +156,8 @@ async function handleSessionGranted() {
|
|
|
126
156
|
|
|
127
157
|
let info: SessionInfo | null = null;
|
|
128
158
|
if (contextIsLoading()) {
|
|
129
|
-
await TemporaryXRContext.start(lastSessionMode ||
|
|
159
|
+
await TemporaryXRContext.start(lastSessionMode || defaultMode, init || NeedleXRSession.getDefaultSessionInit(defaultMode))
|
|
160
|
+
.catch(e => console.warn("[NeedleXRSession:granted] TemporaryXRContext start failed:", e));
|
|
130
161
|
await waitForContextLoadingFinished();
|
|
131
162
|
info = await TemporaryXRContext.handoff();
|
|
132
163
|
}
|
|
@@ -134,17 +165,19 @@ async function handleSessionGranted() {
|
|
|
134
165
|
NeedleXRSession.setSession(info.mode, info.session, info.init, Context.Current);
|
|
135
166
|
}
|
|
136
167
|
else if (lastSessionMode && lastSessionInit) {
|
|
137
|
-
console.log("
|
|
168
|
+
console.log("[NeedleXRSession:granted] Restore last session")
|
|
138
169
|
const init = JSON.parse(lastSessionInit);
|
|
139
170
|
NeedleXRSession.start(lastSessionMode as XRSessionMode, init).catch(e => console.warn(e));
|
|
140
171
|
}
|
|
141
172
|
else {
|
|
142
173
|
// if no session was found we start VR by default
|
|
143
|
-
NeedleXRSession.start(
|
|
174
|
+
NeedleXRSession.start(defaultMode).catch(e => console.warn("[NeedleXRSession:granted] failed:", e));
|
|
144
175
|
}
|
|
145
176
|
// make sure we only subscribe to the event once
|
|
146
177
|
}, { once: true });
|
|
147
|
-
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// showBalloonWarning("NeedleXRSession: WebXR not available in this browser.");
|
|
148
181
|
}
|
|
149
182
|
}
|
|
150
183
|
function saveSessionInfo(mode: XRSessionMode, init: XRSessionInit) {
|
|
@@ -419,27 +452,78 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
419
452
|
* @param init The XRSessionInit to use (optional), docs: https://developer.mozilla.org/en-US/docs/Web/API/XRSessionInit
|
|
420
453
|
* @param context The Needle Engine context to use
|
|
421
454
|
*/
|
|
422
|
-
static async start(mode: XRSessionMode | "ar", init?: XRSessionInit, context?: Context): Promise<NeedleXRSession | null> {
|
|
455
|
+
static async start(mode: XRSessionMode | "ar" | "quicklook", init?: XRSessionInit, context?: Context): Promise<NeedleXRSession | null> {
|
|
423
456
|
|
|
424
|
-
// handle iOS platform where "immersive-ar" is
|
|
457
|
+
// handle iOS platform where "immersive-ar" is special:
|
|
458
|
+
// - we either launch QuickLook
|
|
459
|
+
// - or forward to the Needle App Clip experience for WebXR AR
|
|
425
460
|
// TODO: should we add a separate mode (e.g. "AR")? https://linear.app/needle/issue/NE-5303
|
|
426
461
|
if (DeviceUtilities.isiOS()) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
462
|
+
|
|
463
|
+
const arSupported = await this.isARSupported().catch(() => false);
|
|
464
|
+
|
|
465
|
+
// On VisionOS, we use QuickLook for AR experiences; no AppClip support for now.
|
|
466
|
+
if (DeviceUtilities.isVisionOS() && !arSupported && (mode === "ar" || mode === "immersive-ar")) {
|
|
467
|
+
mode = "quicklook";
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (mode === "quicklook") {
|
|
471
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
472
|
+
action: "quicklook_export",
|
|
473
|
+
source: "NeedleXRSession.start",
|
|
474
|
+
});
|
|
475
|
+
InternalUSDZRegistry.exportAndOpen();
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!arSupported && (mode === "immersive-ar" || mode === "ar")) {
|
|
480
|
+
// const debugAppClip = getParam("debugappclip")
|
|
481
|
+
// Forward to the AppClip experience (Using the apple.com url the appclip overlay shows immediately)
|
|
482
|
+
// const url =`https://appclip.needle.tools/ar?url=${(location.href)}`;
|
|
483
|
+
const url = new URL("https://appclip.apple.com/id?p=tools.needle.launch-app.Clip");
|
|
484
|
+
url.searchParams.set("url", location.href);
|
|
485
|
+
|
|
486
|
+
const urlStr = url.toString();
|
|
487
|
+
|
|
488
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
489
|
+
action: "app_clip_launch",
|
|
490
|
+
source: "NeedleXRSession.start",
|
|
491
|
+
url: urlStr,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// if we are in an iframe, we need to navigate the top window
|
|
495
|
+
const topWindow = window.top || window;
|
|
496
|
+
try {
|
|
497
|
+
console.debug("iOS device detected - opening Needle App Clip for AR experience", { mode, init, url });
|
|
498
|
+
// navigate to app clip url but keep the current url in history, open in same tab
|
|
499
|
+
// eslint-disable-next-line xss/no-location-href-assign
|
|
500
|
+
topWindow.location.href = urlStr;
|
|
432
501
|
}
|
|
433
|
-
|
|
434
|
-
|
|
502
|
+
catch (e) {
|
|
503
|
+
console.warn("Error navigating to AppClip " + urlStr + "\n", e);
|
|
504
|
+
// if top window navigation fails and we are in an iframe, we try to navigate the top window directly
|
|
505
|
+
const weAreInIframe = window !== window.top;
|
|
506
|
+
if (weAreInIframe) {
|
|
507
|
+
// we can try to open a new tab as a fallback
|
|
508
|
+
window.open(urlStr, "_blank");
|
|
509
|
+
}
|
|
510
|
+
// eslint-disable-next-line xss/no-location-href-assign
|
|
511
|
+
else window.location.href = urlStr;
|
|
435
512
|
}
|
|
513
|
+
|
|
514
|
+
return null;
|
|
436
515
|
}
|
|
437
516
|
}
|
|
438
|
-
else if (mode == "ar") {
|
|
439
|
-
mode = "immersive-ar";
|
|
440
|
-
}
|
|
441
517
|
|
|
518
|
+
if (mode === "quicklook") {
|
|
519
|
+
console.warn("QuickLook mode is only supported on iOS devices");
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
442
522
|
|
|
523
|
+
// Since we now know we are not on iOS, ar mode becomes "immersive-ar"
|
|
524
|
+
if (mode == "ar") {
|
|
525
|
+
mode = "immersive-ar";
|
|
526
|
+
}
|
|
443
527
|
|
|
444
528
|
if (isDevEnvironment() && getParam("debugxrpreroom")) {
|
|
445
529
|
console.warn("Debug: Starting temporary XR session");
|
|
@@ -535,12 +619,18 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
535
619
|
listener({ mode, init });
|
|
536
620
|
}
|
|
537
621
|
if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
|
|
622
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
623
|
+
action: "session_request",
|
|
624
|
+
mode: mode,
|
|
625
|
+
features: ((init.requiredFeatures ?? []).concat(init.optionalFeatures ?? [])).join(","),
|
|
626
|
+
source: "NeedleXRSession.start",
|
|
627
|
+
});
|
|
538
628
|
this._currentSessionRequest = navigator?.xr?.requestSession(mode, init);
|
|
539
629
|
this._currentSessionRequestMode = mode;
|
|
540
630
|
/**@type {XRSystem} */
|
|
541
631
|
const newSession = await (this._currentSessionRequest)?.catch(e => {
|
|
542
|
-
console.error(e, "Code: " + e
|
|
543
|
-
if (e
|
|
632
|
+
console.error(e, "Code: " + e?.code);
|
|
633
|
+
if (e?.code === 9) showBalloonWarning("Couldn't start XR session. Make sure you allow the required permissions.")
|
|
544
634
|
console.log("If the specified XR configuration is not supported (e.g. entering AR doesnt work) - make sure you access the website on a secure connection (HTTPS) and your device has the required permissions (e.g. camera access)");
|
|
545
635
|
const notSecure = location.protocol === 'http:';
|
|
546
636
|
if (notSecure) showBalloonWarning("XR requires a secure connection (HTTPS)");
|
|
@@ -1111,6 +1201,12 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1111
1201
|
|
|
1112
1202
|
console.debug("XR Session ended");
|
|
1113
1203
|
|
|
1204
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
1205
|
+
action: "session_end",
|
|
1206
|
+
mode: this.mode,
|
|
1207
|
+
source: "NeedleXRSession.onEnd",
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1114
1210
|
deleteSessionInfo();
|
|
1115
1211
|
|
|
1116
1212
|
this.onAfterRender();
|
|
@@ -32,7 +32,13 @@ export class TemporaryXRContext {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
this._requestInFlight = true;
|
|
35
|
-
const session = await navigator.xr.requestSession(mode, init)
|
|
35
|
+
const session = await navigator.xr.requestSession(mode, init).catch(err => {
|
|
36
|
+
console.error("Failed to start temporary XR session:", err);
|
|
37
|
+
});
|
|
38
|
+
if (!session) {
|
|
39
|
+
this._requestInFlight = false;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
36
42
|
session.addEventListener("end", () => {
|
|
37
43
|
this._active = null;
|
|
38
44
|
});
|
|
@@ -82,10 +88,14 @@ export class TemporaryXRContext {
|
|
|
82
88
|
this._session = session;
|
|
83
89
|
this._session.addEventListener("end", this.onEnd);
|
|
84
90
|
|
|
85
|
-
this._renderer = new WebGLRenderer({ alpha: true });
|
|
91
|
+
this._renderer = new WebGLRenderer({ alpha: true, antialias: true });
|
|
92
|
+
this._renderer.outputColorSpace = 'srgb';
|
|
86
93
|
this._renderer.setAnimationLoop(this.onFrame);
|
|
87
94
|
this._renderer.xr.setSession(session);
|
|
88
95
|
this._renderer.xr.enabled = true;
|
|
96
|
+
// Set pixel ratio and size
|
|
97
|
+
this._renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
|
|
98
|
+
this._renderer.setSize(window.innerWidth, window.innerHeight);
|
|
89
99
|
this._camera = new PerspectiveCamera();
|
|
90
100
|
this._scene = new Scene();
|
|
91
101
|
this._scene.fog = new Fog(0x444444, 10, 250);
|
package/src/engine/xr/usdz.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDevEnvironment } from "../debug/index.js";
|
|
1
2
|
|
|
2
3
|
declare type USDZExporter = {
|
|
3
4
|
exportAndOpen(): Promise<any>,
|
|
@@ -11,7 +12,11 @@ export namespace InternalUSDZRegistry {
|
|
|
11
12
|
const usdzExporter: USDZExporter[] = [];
|
|
12
13
|
|
|
13
14
|
export function exportAndOpen(): boolean {
|
|
14
|
-
if (!usdzExporter?.length)
|
|
15
|
+
if (!usdzExporter?.length) {
|
|
16
|
+
if (isDevEnvironment()) {
|
|
17
|
+
console.warn("No USDZ exporters found – cannot export USDZ for QuickLook.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
15
20
|
for (const exp of usdzExporter) {
|
|
16
21
|
exp.exportAndOpen();
|
|
17
22
|
}
|
|
@@ -8,7 +8,7 @@ import { Context } from "../engine/engine_setup.js";
|
|
|
8
8
|
import { RenderTexture } from "../engine/engine_texture.js";
|
|
9
9
|
import { getTempColor, getWorldPosition } from "../engine/engine_three_utils.js";
|
|
10
10
|
import type { ICamera } from "../engine/engine_types.js"
|
|
11
|
-
import { getParam } from "../engine/engine_utils.js";
|
|
11
|
+
import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
|
|
12
12
|
import { NeedleXREventArgs } from "../engine/engine_xr.js";
|
|
13
13
|
import { RGBAColor } from "../engine/js-extensions/index.js";
|
|
14
14
|
import { Behaviour, GameObject } from "./Component.js";
|
|
@@ -664,6 +664,9 @@ export class Camera extends Behaviour implements ICamera {
|
|
|
664
664
|
else if (navigator.userAgent?.includes("Mozilla") && navigator.userAgent?.includes("Mobile WebXRViewer/v2")) {
|
|
665
665
|
transparent = true;
|
|
666
666
|
}
|
|
667
|
+
else if(DeviceUtilities.isNeedleAppClip()) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
667
670
|
}
|
|
668
671
|
}
|
|
669
672
|
|
|
@@ -476,10 +476,11 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
|
|
|
476
476
|
* Gets a component of the specified type in the gameObject's children hierarchy
|
|
477
477
|
* @param go GameObject to search in
|
|
478
478
|
* @param typeName Constructor of the component type
|
|
479
|
+
* @param includeInactive Whether to include inactive objects in the search
|
|
479
480
|
* @returns The first matching component if found, otherwise null
|
|
480
481
|
*/
|
|
481
|
-
public static getComponentInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T
|
|
482
|
-
return getComponentInChildren(go, typeName);
|
|
482
|
+
public static getComponentInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>, includeInactive: boolean = false): T | null {
|
|
483
|
+
return getComponentInChildren(go, typeName, includeInactive);
|
|
483
484
|
}
|
|
484
485
|
|
|
485
486
|
/**
|
|
@@ -588,7 +589,7 @@ export abstract class Component implements IComponent, EventTarget,
|
|
|
588
589
|
*/
|
|
589
590
|
get [$componentName]() { return TypeStore.getKey(this.constructor as any) || undefined; }
|
|
590
591
|
|
|
591
|
-
|
|
592
|
+
|
|
592
593
|
private __context: Context | undefined;
|
|
593
594
|
|
|
594
595
|
/**
|
|
@@ -1106,7 +1107,7 @@ export abstract class Component implements IComponent, EventTarget,
|
|
|
1106
1107
|
this.dispatchEvent(new CustomEvent("destroyed", { detail: this }));
|
|
1107
1108
|
}
|
|
1108
1109
|
destroyComponentInstance(this as any);
|
|
1109
|
-
if(isHotReloadEnabled()) unregisterHotReloadType(this);
|
|
1110
|
+
if (isHotReloadEnabled()) unregisterHotReloadType(this);
|
|
1110
1111
|
}
|
|
1111
1112
|
|
|
1112
1113
|
/**
|
|
@@ -447,7 +447,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
447
447
|
if (!this || !this._isDragging) return;
|
|
448
448
|
this._isDragging = false;
|
|
449
449
|
for (const rb of this._draggingRigidbodies) {
|
|
450
|
-
rb.setVelocity(rb.smoothedVelocity);
|
|
450
|
+
rb.setVelocity(rb.smoothedVelocity.multiplyScalar(this.context.time.deltaTime));
|
|
451
451
|
}
|
|
452
452
|
this._draggingRigidbodies.length = 0;
|
|
453
453
|
this._targetObject = null;
|
|
@@ -8,7 +8,8 @@ import { PlayerView, ViewDevice } from "../engine/engine_playerview.js";
|
|
|
8
8
|
import { serializable } from "../engine/engine_serialization.js";
|
|
9
9
|
import { Context } from "../engine/engine_setup.js";
|
|
10
10
|
import type { ICamera } from "../engine/engine_types.js";
|
|
11
|
-
import { getParam } from "../engine/engine_utils.js";
|
|
11
|
+
import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
|
|
12
|
+
import { PlayerState } from "../engine-components-experimental/networking/PlayerSync.js";
|
|
12
13
|
import { Camera } from "./Camera.js";
|
|
13
14
|
import { Behaviour, Component, GameObject } from "./Component.js";
|
|
14
15
|
import { OrbitControls } from "./OrbitControls.js";
|
|
@@ -98,7 +99,7 @@ export class SpectatorCamera extends Behaviour {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/** Gets the local player's connection ID */
|
|
101
|
-
private get localId()
|
|
102
|
+
private get localId(): string {
|
|
102
103
|
return this.context.connection.connectionId ?? "local";
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -196,9 +197,8 @@ export class SpectatorCamera extends Behaviour {
|
|
|
196
197
|
*/
|
|
197
198
|
private isSupportedPlatform() {
|
|
198
199
|
const ua = window.navigator.userAgent;
|
|
199
|
-
const standalone = /Windows|MacOS/.test(ua);
|
|
200
200
|
const isHololens = /Windows NT/.test(ua) && /Edg/.test(ua) && !/Win64/.test(ua);
|
|
201
|
-
return
|
|
201
|
+
return DeviceUtilities.isDesktop() && !DeviceUtilities.isMobileDevice() && !isHololens;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
@@ -268,11 +268,16 @@ export class SpectatorCamera extends Behaviour {
|
|
|
268
268
|
const previousRenderTarget = renderer.getRenderTarget();
|
|
269
269
|
let oldFramebuffer: WebGLFramebuffer | null = null;
|
|
270
270
|
|
|
271
|
+
|
|
271
272
|
const webglState = renderer.state as WebGLState & { bindXRFramebuffer?: Function };
|
|
272
273
|
|
|
274
|
+
|
|
273
275
|
// seems that in some cases, renderer.getRenderTarget returns null
|
|
274
276
|
// even when we're rendering to a headset.
|
|
275
|
-
if (!previousRenderTarget
|
|
277
|
+
if (!previousRenderTarget ||
|
|
278
|
+
// Prevent rendering if in XR - @TODO: check if we need to allow this for VR?
|
|
279
|
+
previousRenderTarget["isXRRenderTarget"] === true)
|
|
280
|
+
{
|
|
276
281
|
if (!renderer.state.bindFramebuffer || !webglState.bindXRFramebuffer)
|
|
277
282
|
return;
|
|
278
283
|
|
|
@@ -557,12 +562,18 @@ class SpectatorSelectionController {
|
|
|
557
562
|
for (const hit of hits) {
|
|
558
563
|
if (hit.distance < .2) continue;
|
|
559
564
|
const obj = hit.object;
|
|
560
|
-
|
|
561
|
-
const
|
|
565
|
+
// For WebXR
|
|
566
|
+
const state = PlayerState.getFor(obj);
|
|
567
|
+
let id = state?.owner;
|
|
568
|
+
// for SpectatorCamera
|
|
569
|
+
if (!id) {
|
|
570
|
+
const avatar = GameObject.getComponentInParent(obj, AvatarMarker);
|
|
571
|
+
id = avatar?.connectionId;
|
|
572
|
+
}
|
|
562
573
|
if (id) {
|
|
563
574
|
const view = this.context.players.getPlayerView(id);
|
|
564
575
|
this.spectator.target = view;
|
|
565
|
-
if (debug) console.log("spectate", id,
|
|
576
|
+
if (debug) console.log("spectate", id, state);
|
|
566
577
|
break;
|
|
567
578
|
}
|
|
568
579
|
}
|
|
@@ -633,7 +644,7 @@ class SpectatorCamNetworking {
|
|
|
633
644
|
this.context.connection.beginListen("spectator-request-follow", this._requestFollowMethod);
|
|
634
645
|
this.context.connection.beginListen(RoomEvents.JoinedRoom, this._joinedRoomMethod);
|
|
635
646
|
this.context.domElement.addEventListener("keydown", evt => {
|
|
636
|
-
if(!this.spectator.useKeys) return;
|
|
647
|
+
if (!this.spectator.useKeys) return;
|
|
637
648
|
if (evt.key === "f") {
|
|
638
649
|
this.onRequestFollowMe();
|
|
639
650
|
}
|
|
@@ -767,7 +778,7 @@ class SpectatorCamNetworking {
|
|
|
767
778
|
}
|
|
768
779
|
|
|
769
780
|
private _enforceFollowInterval: any;
|
|
770
|
-
|
|
781
|
+
|
|
771
782
|
/**
|
|
772
783
|
* Periodically retries following a user if the initial attempt failed
|
|
773
784
|
*/
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
export * from "./codegen/components.js";
|
|
38
|
-
export { Behaviour, Component, GameObject } from "./Component.js";
|
|
39
38
|
export { Collider } from "./Collider.js"; // export abstract type
|
|
39
|
+
export { Behaviour, Component, GameObject } from "./Component.js";
|
|
40
40
|
|
|
41
41
|
// We dont want to export everything in the extensions
|
|
42
42
|
export { ClearFlags } from "./Camera.js"
|
|
@@ -2,6 +2,7 @@ import { Mesh, Object3D, Quaternion, Vector3 } from "three";
|
|
|
2
2
|
|
|
3
3
|
import { AssetReference } from "../../engine/engine_addressables.js";
|
|
4
4
|
import { ObjectUtils, PrimitiveType } from "../../engine/engine_create_objects.js";
|
|
5
|
+
import { ViewDevice } from "../../engine/engine_playerview.js";
|
|
5
6
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
|
6
7
|
import type { IGameObject } from "../../engine/engine_types.js";
|
|
7
8
|
import { getParam, PromiseAllWithErrors } from "../../engine/engine_utils.js";
|
|
@@ -55,10 +56,13 @@ export class Avatar extends Behaviour {
|
|
|
55
56
|
const marker = this.gameObject.addComponent(AvatarMarker)!;
|
|
56
57
|
marker.avatar = this.gameObject;
|
|
57
58
|
marker.connectionId = playerstate.owner;
|
|
59
|
+
|
|
60
|
+
this.context.players.setPlayerView(playerstate.owner, this.head?.asset, ViewDevice.Headset);
|
|
58
61
|
}
|
|
59
62
|
else if (this.context.connection.isConnected) console.error("No player state found for avatar", this);
|
|
60
63
|
// don't destroy the avatar when entering XR and not connected to a networking backend
|
|
61
64
|
else if (playerstate && !this.context.connection.isConnected) playerstate.dontDestroy = true;
|
|
65
|
+
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
onLeaveXR(_args: NeedleXREventArgs): void {
|
|
@@ -210,17 +210,25 @@ export class WebXR extends Behaviour {
|
|
|
210
210
|
showBalloonWarning("<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API\" target=\"_blank\">WebXR</a> only works on secure connections (https).");
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
213
|
+
// Showing the QuickLook button depends on whether we're on iOS or visionOS –
|
|
214
|
+
// on iOS we have AppClip support, so we don't need QuickLook button there,
|
|
215
|
+
// while on visionOS we use QuickLook for AR experiences for now (unless it happens to have WebXR support by then).
|
|
216
|
+
navigator.xr?.isSessionSupported("immersive-ar").catch(() => false).then((arSupported) => {
|
|
217
|
+
|
|
218
|
+
const isVisionOSFallback = DeviceUtilities.isVisionOS() && !arSupported;
|
|
219
|
+
|
|
220
|
+
if (this.useQuicklookExport || isVisionOSFallback) {
|
|
221
|
+
const existingUSDZExporter = GameObject.findObjectOfType(USDZExporter);
|
|
222
|
+
if (!existingUSDZExporter) {
|
|
223
|
+
// if no USDZ Exporter is found we add one and assign the scene to be exported
|
|
224
|
+
if (debug) console.log("WebXR: Adding USDZExporter");
|
|
225
|
+
this._usdzExporter = GameObject.addComponent(this.gameObject, USDZExporter);
|
|
226
|
+
this._usdzExporter.objectToExport = this.context.scene;
|
|
227
|
+
this._usdzExporter.autoExportAnimations = true;
|
|
228
|
+
this._usdzExporter.autoExportAudioSources = true;
|
|
229
|
+
}
|
|
222
230
|
}
|
|
223
|
-
}
|
|
231
|
+
});
|
|
224
232
|
|
|
225
233
|
this.handleCreatingHTML();
|
|
226
234
|
this.handleOfferSession();
|
|
@@ -360,7 +368,7 @@ export class WebXR extends Behaviour {
|
|
|
360
368
|
|
|
361
369
|
// Handle AR session root
|
|
362
370
|
if (args.xr.isAR) {
|
|
363
|
-
let sessionroot = GameObject.findObjectOfType(WebARSessionRoot);
|
|
371
|
+
let sessionroot = GameObject.findObjectOfType(WebARSessionRoot, this.context, false);
|
|
364
372
|
// Only create a WebARSessionRoot if none is in the scene already
|
|
365
373
|
if (!sessionroot) {
|
|
366
374
|
if (this.usePlacementReticle) {
|