@needle-tools/engine 4.12.0-next.0b39d59 → 4.12.0-next.1eb80e5
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 +4 -2
- package/components.needle.json +1 -1
- package/dist/generateMeshBVH.worker-iyfPIK6R.js +21 -0
- package/dist/{gltf-progressive-Rs-ojtXy.umd.cjs → gltf-progressive-Bfpfaz84.umd.cjs} +1 -1
- package/dist/{gltf-progressive-DnLBuGK5.js → gltf-progressive-DPunMlEM.js} +1 -1
- package/dist/{gltf-progressive-BmSygnAC.min.js → gltf-progressive-hFPACYio.min.js} +1 -1
- package/dist/{needle-engine.bundle-BktxajaZ.js → needle-engine.bundle-BBMMqqcl.js} +9311 -8878
- package/dist/{needle-engine.bundle-C6Ko3O_C.min.js → needle-engine.bundle-C__2LBOp.min.js} +156 -156
- package/dist/needle-engine.bundle-CgknXW5o.umd.cjs +1647 -0
- package/dist/needle-engine.d.ts +53 -14
- package/dist/needle-engine.js +47 -47
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-BWZIqm3N.umd.cjs → postprocessing-BHQvwehB.umd.cjs} +5 -5
- package/dist/{postprocessing-BkY94MUG.min.js → postprocessing-ClLv0reO.min.js} +61 -61
- package/dist/{postprocessing-CLbPDsD8.js → postprocessing-DLI2N3LL.js} +426 -435
- package/dist/{three-examples-y2GeYlze.js → three-examples-D4rE49Ui.js} +10 -2
- package/dist/{three-examples-MsJjauyk.min.js → three-examples-DB5Uoja4.min.js} +2 -2
- package/dist/{three-examples-Dho7cuu4.umd.cjs → three-examples-Djbk6WA4.umd.cjs} +2 -2
- 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 +161 -9
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking.js +20 -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_three_utils.js +2 -2
- package/lib/engine/engine_three_utils.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/extensions/extensions.d.ts +29 -7
- package/lib/engine/extensions/extensions.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-spatial.js +2 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.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 +45 -7
- 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/DropListener.d.ts +1 -0
- package/lib/engine-components/DropListener.js +26 -8
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.js +4 -1
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +3 -2
- package/lib/engine-components/SceneSwitcher.js +24 -11
- package/lib/engine-components/SceneSwitcher.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/export/usdz/extensions/behavior/BehaviourComponents.js +8 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.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/WebARSessionRoot.d.ts +5 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js +5 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.d.ts +3 -1
- package/lib/engine-components/webxr/WebXR.js +21 -13
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/package.json +4 -4
- 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 +178 -9
- package/src/engine/engine_networking.ts +20 -5
- package/src/engine/engine_physics.ts +3 -3
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -1
- package/src/engine/engine_three_utils.ts +4 -2
- package/src/engine/engine_utils.ts +23 -4
- package/src/engine/extensions/extensions.ts +30 -6
- package/src/engine/webcomponents/WebXRButtons.ts +15 -5
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -1
- package/src/engine/webcomponents/needle menu/needle-menu.ts +48 -8
- 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/DropListener.ts +29 -8
- package/src/engine-components/EventList.ts +5 -1
- package/src/engine-components/SceneSwitcher.ts +26 -13
- package/src/engine-components/SpectatorCamera.ts +21 -10
- package/src/engine-components/api.ts +1 -1
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +11 -0
- package/src/engine-components/webxr/Avatar.ts +4 -0
- package/src/engine-components/webxr/WebARSessionRoot.ts +7 -3
- package/src/engine-components/webxr/WebXR.ts +23 -13
- package/dist/generateMeshBVH.worker-BvGEI0r7.js +0 -21
- package/dist/needle-engine.bundle-BZJhFSnI.umd.cjs +0 -1647
|
@@ -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;
|
|
@@ -10,7 +10,7 @@ import { BlobStorage } from "../engine/engine_networking_blob.js";
|
|
|
10
10
|
import { PreviewHelper } from "../engine/engine_networking_files.js";
|
|
11
11
|
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
|
|
12
12
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
13
|
-
import { fitObjectIntoVolume, getBoundingBox, placeOnSurface } from "../engine/engine_three_utils.js";
|
|
13
|
+
import { fitObjectIntoVolume, getBoundingBox, getWorldScale, placeOnSurface } from "../engine/engine_three_utils.js";
|
|
14
14
|
import { Model, Vec3 } from "../engine/engine_types.js";
|
|
15
15
|
import { getParam, setParamWithoutReload } from "../engine/engine_utils.js";
|
|
16
16
|
import { determineMimeTypeFromExtension } from "../engine/engine_utils_format.js";
|
|
@@ -111,6 +111,7 @@ const blobKeyName = "blob";
|
|
|
111
111
|
|
|
112
112
|
/** The DropListener component is used to listen for drag and drop events in the browser and add the dropped files to the scene
|
|
113
113
|
* It can be used to allow users to drag and drop glTF files into the scene to add new objects.
|
|
114
|
+
* Existing child objects will behave like placeholders and will be removed when new files are dropped.
|
|
114
115
|
*
|
|
115
116
|
* If {@link useNetworking} is enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
116
117
|
* Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
|
|
@@ -522,22 +523,42 @@ export class DropListener extends Behaviour {
|
|
|
522
523
|
|
|
523
524
|
const obj = model.scene;
|
|
524
525
|
|
|
525
|
-
|
|
526
|
-
this.gameObject
|
|
527
|
-
obj.position.set(0, 0, 0);
|
|
528
|
-
obj.quaternion.identity();
|
|
526
|
+
obj.position.copy(this.gameObject.worldPosition);
|
|
527
|
+
const scale = getWorldScale(this.gameObject);
|
|
529
528
|
|
|
530
|
-
|
|
531
|
-
|
|
529
|
+
let localPos = new Vector3(0,0,0);
|
|
530
|
+
scale.x = Math.abs(scale.x);
|
|
531
|
+
scale.y = Math.abs(scale.y);
|
|
532
|
+
scale.z = Math.abs(scale.z);
|
|
533
|
+
let localScale =obj.scale.clone();
|
|
534
|
+
|
|
535
|
+
// TODOs: handle rotation when Gizmos APIs has changed to support it
|
|
532
536
|
|
|
533
|
-
const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize);
|
|
537
|
+
const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * scale.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize.clone().multiply(scale));
|
|
534
538
|
if (debug) Gizmos.DrawWireBox3(volume, 0x0000ff, 5);
|
|
535
539
|
if (this.fitIntoVolume) {
|
|
540
|
+
|
|
536
541
|
fitObjectIntoVolume(obj, volume, {
|
|
537
542
|
position: !this.placeAtHitPosition
|
|
538
543
|
});
|
|
544
|
+
|
|
545
|
+
// to match parent scale later, divide by it
|
|
546
|
+
localScale = obj.scale.clone().divide(scale);
|
|
547
|
+
// just take the computed offset from fitting
|
|
548
|
+
localPos = obj.worldPosition.clone().sub(this.gameObject.worldPosition).divide(scale);
|
|
549
|
+
if (debug) Gizmos.DrawSphere(localPos, 0.1, 0xff0000, 5);
|
|
539
550
|
}
|
|
540
551
|
|
|
552
|
+
// use attach to ignore the DropListener scale (e.g. if the parent object scale is not uniform)
|
|
553
|
+
this.gameObject.attach(obj);
|
|
554
|
+
obj.position.copy(localPos);
|
|
555
|
+
obj.quaternion.identity();
|
|
556
|
+
obj.scale.copy(localScale);
|
|
557
|
+
if (debug) Gizmos.DrawArrow(this.gameObject.worldPosition, obj.getWorldPosition(new Vector3()), 0x00ff00, 5);
|
|
558
|
+
|
|
559
|
+
this._addedObjects.push(obj);
|
|
560
|
+
this._addedModels.push(model);
|
|
561
|
+
|
|
541
562
|
if (this.placeAtHitPosition && ctx && ctx.screenposition) {
|
|
542
563
|
obj.visible = false; // < don't raycast on the placed object
|
|
543
564
|
const rc = this.context.physics.raycast({ screenPoint: this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone()) });
|
|
@@ -78,7 +78,11 @@ export class CallInfo {
|
|
|
78
78
|
// If the target is a property
|
|
79
79
|
else {
|
|
80
80
|
if (this.arguments) {
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
if (args !== undefined && args.length > 0)
|
|
83
|
+
this.target[this.methodName] = args[0];
|
|
84
|
+
else
|
|
85
|
+
this.target[this.methodName] = this.arguments[0];
|
|
82
86
|
}
|
|
83
87
|
else {
|
|
84
88
|
this.target[this.methodName] = args[0];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EquirectangularReflectionMapping, Object3D, Scene, Texture } from "three";
|
|
2
2
|
|
|
3
3
|
import { AssetReference } from "../engine/engine_addressables.js";
|
|
4
|
-
import { destroy } from "../engine/engine_gameobject.js";
|
|
4
|
+
import { destroy, instantiate } from "../engine/engine_gameobject.js";
|
|
5
5
|
import { InputEvents } from "../engine/engine_input.js";
|
|
6
6
|
import { isLocalNetwork } from "../engine/engine_networking_utils.js";
|
|
7
7
|
import { serializable } from "../engine/engine_serialization.js";
|
|
@@ -247,14 +247,23 @@ export class SceneSwitcher extends Behaviour {
|
|
|
247
247
|
|
|
248
248
|
private _currentIndex: number = -1;
|
|
249
249
|
private _currentScene: AssetReference | undefined = undefined;
|
|
250
|
+
private _currentSceneAsset: Object3D | undefined = undefined;
|
|
250
251
|
private _engineElementOverserver: MutationObserver | undefined = undefined;
|
|
251
252
|
|
|
252
253
|
private _preloadScheduler?: PreLoadScheduler;
|
|
253
254
|
|
|
254
255
|
private _menuButtons?: HTMLElement[];
|
|
255
256
|
|
|
257
|
+
// this is the scene that was requested last
|
|
258
|
+
private __lastSwitchScene?: AssetReference;
|
|
259
|
+
private __lastSwitchScenePromise?: Promise<boolean>;
|
|
260
|
+
|
|
256
261
|
/** @internal */
|
|
257
262
|
awake(): void {
|
|
263
|
+
this._currentScene = undefined;
|
|
264
|
+
this._lastLoadingScene = undefined;
|
|
265
|
+
this.__lastSwitchScenePromise = undefined;
|
|
266
|
+
|
|
258
267
|
if (this.scenes === undefined) this.scenes = [];
|
|
259
268
|
// remove all scenes from the url that are in the scenes array at startup
|
|
260
269
|
for (const scene of this.scenes) {
|
|
@@ -538,10 +547,6 @@ export class SceneSwitcher extends Behaviour {
|
|
|
538
547
|
return false;
|
|
539
548
|
}
|
|
540
549
|
|
|
541
|
-
// this is the scene that was requested last
|
|
542
|
-
private __lastSwitchScene?: AssetReference;
|
|
543
|
-
private __lastSwitchScenePromise?: Promise<boolean>;
|
|
544
|
-
|
|
545
550
|
/**
|
|
546
551
|
* Switch to a scene by its AssetReference.
|
|
547
552
|
* If the scene is already loaded it will be unloaded and the new scene will be loaded.
|
|
@@ -601,13 +606,15 @@ export class SceneSwitcher extends Behaviour {
|
|
|
601
606
|
const res = sceneListener.sceneClosing();
|
|
602
607
|
if (res instanceof Promise) await res;
|
|
603
608
|
}
|
|
604
|
-
|
|
605
|
-
//
|
|
606
|
-
|
|
609
|
+
|
|
610
|
+
// // if the current scene has a URL (so it can be reloaded)
|
|
611
|
+
// // then we unload it
|
|
612
|
+
if (current.hasUrl) {
|
|
607
613
|
current.unload();
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
614
|
+
if (this._currentSceneAsset) destroy(this._currentSceneAsset, true, false);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
GameObject.remove(this._currentSceneAsset);
|
|
611
618
|
}
|
|
612
619
|
}
|
|
613
620
|
}
|
|
@@ -658,7 +665,6 @@ export class SceneSwitcher extends Behaviour {
|
|
|
658
665
|
if (debug) console.log("[SceneSwitcher] ADD", scene.url);
|
|
659
666
|
this._currentScene = scene;
|
|
660
667
|
|
|
661
|
-
|
|
662
668
|
// Experimental: replace the whole content of the scene
|
|
663
669
|
if (experimental_clearSceneOnLoad) {
|
|
664
670
|
const camera = this.context.mainCameraComponent?.gameObject || this.context.mainCamera;
|
|
@@ -672,7 +678,14 @@ export class SceneSwitcher extends Behaviour {
|
|
|
672
678
|
}
|
|
673
679
|
}
|
|
674
680
|
|
|
675
|
-
|
|
681
|
+
// @TODO: if multiple scene switcher or scenes use this asset already it will be moved
|
|
682
|
+
if (!scene.asset.parent) {
|
|
683
|
+
this._currentSceneAsset = scene.asset;
|
|
684
|
+
GameObject.add(scene.asset, this.gameObject);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
this._currentSceneAsset = instantiate(scene.asset, { parent: this.gameObject });
|
|
688
|
+
}
|
|
676
689
|
|
|
677
690
|
if (this.useSceneLighting)
|
|
678
691
|
this.context.sceneLighting.enable(scene);
|
|
@@ -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"
|
|
@@ -11,6 +11,7 @@ import { Animation } from "../../../../Animation.js";
|
|
|
11
11
|
import { Animator } from "../../../../Animator.js";
|
|
12
12
|
import { AudioSource } from "../../../../AudioSource.js";
|
|
13
13
|
import { Behaviour, GameObject } from "../../../../Component.js";
|
|
14
|
+
import { Rigidbody } from "../../../../RigidBody.js";
|
|
14
15
|
import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
|
|
15
16
|
import { ObjectRaycaster,Raycaster } from "../../../../ui/Raycaster.js";
|
|
16
17
|
import { makeNameSafeForUSD,USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
|
|
@@ -68,6 +69,16 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
onPointerClick(args: PointerEventData) {
|
|
72
|
+
|
|
73
|
+
const rbs = this.object?.getComponentsInChildren(Rigidbody);
|
|
74
|
+
|
|
75
|
+
if (rbs){
|
|
76
|
+
for (const rb of rbs) {
|
|
77
|
+
rb.resetVelocities();
|
|
78
|
+
rb.resetForcesAndTorques();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
args.use();
|
|
72
83
|
if (this.coroutine) this.stopCoroutine(this.coroutine);
|
|
73
84
|
if (!this.relativeMotion)
|