@@ -1,4 +1,4 @@
|
|
1
|
-
import { getErrorCount } from "./debug_overlay.js";
|
1
|
+
import { getErrorCount, makeErrorsVisibleForDevelopment } from "./debug_overlay.js";
|
2
2
|
import { getParam, isMobileDevice, isQuest } from "../engine_utils.js";
|
3
3
|
import { isLocalNetwork } from "../engine_networking_utils.js";
|
4
4
|
import { isDevEnvironment } from "./debug.js";
|
@@ -25,6 +25,9 @@
|
|
25
25
|
}
|
26
26
|
const isMobile = isMobileDevice() || (isQuest() && isDevEnvironment());
|
27
27
|
if (isMobile) {
|
28
|
+
// we need to invoke this here - otherwise we will miss errors that happen after the console is loaded
|
29
|
+
// and calling the method from the root needle-engine.ts import is evaluated later (if we import the method from the toplevel file and then invoke it)
|
30
|
+
makeErrorsVisibleForDevelopment();
|
28
31
|
beginWatchingLogs();
|
29
32
|
createConsole(true);
|
30
33
|
if (isMobile) {
|
@@ -192,7 +195,7 @@
|
|
192
195
|
}
|
193
196
|
`;
|
194
197
|
consoleHtmlElement?.prepend(styles);
|
195
|
-
if (startHidden === true)
|
198
|
+
if (startHidden === true && getErrorCount() <= 0)
|
196
199
|
hideDebugConsole();
|
197
200
|
console.log("🌵 Debug console has loaded");
|
198
201
|
}
|
@@ -15,7 +15,7 @@
|
|
15
15
|
}
|
16
16
|
|
17
17
|
export function getErrorCount() {
|
18
|
-
return
|
18
|
+
return _errorCount;
|
19
19
|
}
|
20
20
|
|
21
21
|
const originalConsoleError = console.error;
|
@@ -37,9 +37,10 @@
|
|
37
37
|
if (hide) return;
|
38
38
|
const isLocal = isLocalNetwork();
|
39
39
|
if (debug) console.log("Is this a local network?", isLocal);
|
40
|
-
if (isLocal)
|
40
|
+
if (isLocal)
|
41
|
+
{
|
41
42
|
if (debug)
|
42
|
-
console.
|
43
|
+
console.warn("Patch console", window.location.hostname);
|
43
44
|
console.error = patchedConsoleError;
|
44
45
|
window.addEventListener("error", (event) => {
|
45
46
|
if (hide) return;
|
@@ -66,10 +67,10 @@
|
|
66
67
|
}
|
67
68
|
|
68
69
|
|
69
|
-
let
|
70
|
+
let _errorCount = 0;
|
70
71
|
|
71
72
|
function onReceivedError() {
|
72
|
-
|
73
|
+
_errorCount += 1;
|
73
74
|
}
|
74
75
|
|
75
76
|
function onParseError(args: Array<any>) {
|
@@ -3,7 +3,8 @@
|
|
3
3
|
import { Vector3, Quaternion, Object3D } from "three";
|
4
4
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
5
|
import { InstantiateOptions } from "../engine/engine_gameobject.js";
|
6
|
-
import { IPointerEventHandler,
|
6
|
+
import { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
7
|
+
import { ObjectRaycaster } from "./ui/Raycaster.js";
|
7
8
|
|
8
9
|
export class Duplicatable extends Behaviour implements IPointerEventHandler {
|
9
10
|
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import { Color, DirectionalLight, OrthographicCamera } from "three";
|
8
8
|
import { WebARSessionRoot } from "./webxr/WebARSessionRoot.js";
|
9
9
|
import type { ILight } from "../engine/engine_types.js";
|
10
|
-
import { NeedleXREventArgs } from "../
|
10
|
+
import { NeedleXREventArgs } from "../engine/xr/index.js";
|
11
11
|
|
12
12
|
// https://threejs.org/examples/webgl_shadowmap_csm.html
|
13
13
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
import { makeErrorsVisibleForDevelopment } from "./engine/debug/debug_overlay.js";
|
2
|
-
makeErrorsVisibleForDevelopment();
|
3
|
-
|
4
1
|
import "./engine/engine_element.js";
|
5
2
|
import "./engine/engine_setup.js";
|
6
3
|
export * from "./engine/api.js";
|
@@ -116,7 +116,9 @@
|
|
116
116
|
get hitTestSource() { return this._hitTestSource; }
|
117
117
|
private _hitTestSource: XRTransientInputHitTestSource | undefined = undefined;
|
118
118
|
|
119
|
-
/** Perform a hit test against the XR planes or meshes. shorthand for `xr.getHitTest(controller)`
|
119
|
+
/** Perform a hit test against the XR planes or meshes. shorthand for `xr.getHitTest(controller)`
|
120
|
+
* @returns the hit test result (with position and rotation in worldspace) or null if no hit was found
|
121
|
+
*/
|
120
122
|
getHitTest(): NeedleXRHitTestResult | null {
|
121
123
|
return this.xr.getHitTest(this);
|
122
124
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { Mesh, Object3D, PerspectiveCamera, Quaternion, Vector3, PlaneHelper, PlaneGeometry, MeshBasicMaterial, Camera, WebXRArrayCamera, DoubleSide } from "three";
|
2
2
|
import { Context, FrameEvent } from "../engine_context.js";
|
3
3
|
import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
|
4
4
|
import { Gizmos } from "../engine_gizmos.js";
|
@@ -13,11 +13,18 @@
|
|
13
13
|
import { registerFrameEventCallback, unregisterFrameEventCallback } from "../engine_lifecycle_functions_internal.js";
|
14
14
|
import { isDestroyed } from "../engine_gameobject.js";
|
15
15
|
import { TemporaryXRContext } from "./TempXRContext.js";
|
16
|
+
import { Mathf } from "../engine_math.js";
|
16
17
|
|
17
18
|
export type NeedleXREventArgs = { xr: NeedleXRSession }
|
18
19
|
export type SessionChangedEvt = (args: NeedleXREventArgs) => void;
|
19
20
|
export type SessionRequestedEvent = (args: { mode: XRSessionMode, init: XRSessionInit }) => void;
|
20
21
|
export type SessionRequestedEndEvent = (args: { mode: XRSessionMode, init: XRSessionInit, newSession: XRSession | null }) => void;
|
22
|
+
|
23
|
+
/** Result of a XR hit-test
|
24
|
+
* @property {XRHitTestResult} hit The original XRHitTestResult
|
25
|
+
* @property {Vector3} position The hit position in world space
|
26
|
+
* @property {Quaternion} quaternion The hit rotation in world space
|
27
|
+
*/
|
21
28
|
export type NeedleXRHitTestResult = { hit: XRHitTestResult, position: Vector3, quaternion: Quaternion };
|
22
29
|
|
23
30
|
const debug = getParam("debugwebxr");
|
@@ -675,8 +682,8 @@
|
|
675
682
|
this._controllerRemoved = extra.controller_removed;
|
676
683
|
|
677
684
|
registerFrameEventCallback(this.onBefore, FrameEvent.LateUpdate);
|
678
|
-
|
679
|
-
this.context.post_render_callbacks.push(this.
|
685
|
+
this.context.pre_render_callbacks.push(this.onBeforeRender);
|
686
|
+
this.context.post_render_callbacks.push(this.onAfterRender);
|
680
687
|
|
681
688
|
|
682
689
|
if (extra.init.optionalFeatures?.includes("hit-test") || extra.init.requiredFeatures?.includes("hit-test")) {
|
@@ -763,15 +770,15 @@
|
|
763
770
|
|
764
771
|
deleteSessionInfo();
|
765
772
|
|
766
|
-
this.
|
773
|
+
this.onAfterRender();
|
767
774
|
this.revertCustomForward();
|
768
775
|
this._didStart = false;
|
769
776
|
this._previousCameraParent = null;
|
770
777
|
|
771
|
-
// const index = this.context.pre_render_callbacks.indexOf(this.onBefore);
|
772
|
-
// if (index >= 0) this.context.pre_render_callbacks.splice(index, 1);
|
773
778
|
unregisterFrameEventCallback(this.onBefore, FrameEvent.LateUpdate);
|
774
|
-
const
|
779
|
+
const index = this.context.pre_render_callbacks.indexOf(this.onBeforeRender);
|
780
|
+
if (index >= 0) this.context.pre_render_callbacks.splice(index, 1);
|
781
|
+
const index2 = this.context.post_render_callbacks.indexOf(this.onAfterRender);
|
775
782
|
if (index2 >= 0) this.context.post_render_callbacks.splice(index2, 1);
|
776
783
|
|
777
784
|
this.context.xr = null;
|
@@ -999,7 +1006,14 @@
|
|
999
1006
|
}
|
1000
1007
|
}
|
1001
1008
|
|
1002
|
-
private
|
1009
|
+
private onBeforeRender = () => {
|
1010
|
+
if (this.context.mainCamera)
|
1011
|
+
this.updateFade(this.context.mainCamera);
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
private onAfterRender = () => {
|
1015
|
+
this.onUpdateFade_PostRender();
|
1016
|
+
|
1003
1017
|
// render spectator view if we're in VR using Link
|
1004
1018
|
if (isDesktop()) {
|
1005
1019
|
const renderer = this.context.renderer;
|
@@ -1178,4 +1192,73 @@
|
|
1178
1192
|
this._transformOrientation.set(transform.orientation.x, transform.orientation.y, transform.orientation.z, transform.orientation.w);
|
1179
1193
|
}
|
1180
1194
|
}
|
1195
|
+
|
1196
|
+
// TODO: for scene transitions (e.g. SceneSwitcher) where creating the scene might take a few moments we might want more control over when/how this fading occurs and how long the scene stays black
|
1197
|
+
|
1198
|
+
/** Call to fade rendering to black for a short moment (the returned promise will be resolved when fully black)
|
1199
|
+
* This can be used to mask scene transitions or teleportation
|
1200
|
+
* @returns a promise that is resolved when the screen is fully black
|
1201
|
+
* @example `fadeTransition().then(() => { <fully_black> })`
|
1202
|
+
*/
|
1203
|
+
fadeTransition() {
|
1204
|
+
if (this._transitionPromise) return this._transitionPromise;
|
1205
|
+
this._requestedFadeValue = 1;
|
1206
|
+
const promise = new Promise<void>(resolve => {
|
1207
|
+
this._transitionResolve = resolve;
|
1208
|
+
});
|
1209
|
+
this._transitionPromise = promise;
|
1210
|
+
return promise;
|
1211
|
+
}
|
1212
|
+
|
1213
|
+
private _requestedFadeValue: number = 0;
|
1214
|
+
private _transitionPromise: Promise<void> | null = null;
|
1215
|
+
private _transitionResolve: (() => void) | null = null;
|
1216
|
+
private _fadeToColorQuad: Mesh | null = null;
|
1217
|
+
private _fadeToColorMaterial!: MeshBasicMaterial;
|
1218
|
+
|
1219
|
+
/** e.g. FadeToBlack */
|
1220
|
+
private updateFade(camera: Camera) {
|
1221
|
+
if (!(camera instanceof PerspectiveCamera)) return;
|
1222
|
+
if (!this._fadeToColorQuad || !this._fadeToColorMaterial) {
|
1223
|
+
this._fadeToColorMaterial = new MeshBasicMaterial({
|
1224
|
+
color: 0x000000,
|
1225
|
+
transparent: true,
|
1226
|
+
depthTest: false,
|
1227
|
+
fog: false,
|
1228
|
+
side: DoubleSide,
|
1229
|
+
});
|
1230
|
+
this._fadeToColorQuad = new Mesh(new PlaneGeometry(10, 10), this._fadeToColorMaterial);
|
1231
|
+
}
|
1232
|
+
|
1233
|
+
// ensure that the quad is setup with the right properties
|
1234
|
+
const quad = this._fadeToColorQuad;
|
1235
|
+
const mat = this._fadeToColorMaterial;
|
1236
|
+
|
1237
|
+
// make sure the quad is in the scene
|
1238
|
+
if (quad.parent !== camera && mat.opacity > 0) {
|
1239
|
+
camera!.add(quad);
|
1240
|
+
}
|
1241
|
+
else if (mat.opacity === 0) {
|
1242
|
+
quad.removeFromParent();
|
1243
|
+
}
|
1244
|
+
quad.layers.set(2);
|
1245
|
+
quad.material = this._fadeToColorMaterial!;
|
1246
|
+
quad.position.z = -1;
|
1247
|
+
// perform the fade
|
1248
|
+
const fadeValue = this._requestedFadeValue;
|
1249
|
+
mat.opacity = Mathf.lerp(mat.opacity, fadeValue, this.context.time.deltaTime / .03);
|
1250
|
+
// check if we're close enough to the desired value:
|
1251
|
+
if (Math.abs(mat.opacity - fadeValue) <= .01) {
|
1252
|
+
if (this._transitionResolve) {
|
1253
|
+
this._transitionResolve();
|
1254
|
+
this._transitionResolve = null;
|
1255
|
+
this._transitionPromise = null;
|
1256
|
+
this._requestedFadeValue = 0;
|
1257
|
+
}
|
1258
|
+
}
|
1259
|
+
}
|
1260
|
+
|
1261
|
+
private onUpdateFade_PostRender() {
|
1262
|
+
this._fadeToColorQuad?.removeFromParent();
|
1263
|
+
}
|
1181
1264
|
}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import * as THREE from "three";
|
4
4
|
import { WaitForSeconds } from "../engine/engine_coroutine.js";
|
5
5
|
import { PlayerState } from "../engine-components-experimental/networking/PlayerSync.js";
|
6
|
-
import { AvatarMarker } from "./
|
6
|
+
import { AvatarMarker } from "./webxr/WebXRAvatar.js";
|
7
7
|
|
8
8
|
|
9
9
|
export class PlayerColor extends Behaviour {
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
import { Object3D } from "three";
|
10
10
|
import { EventList } from "../../engine-components/EventList.js";
|
11
|
-
import { IGameObject } from "../../
|
11
|
+
import { IGameObject } from "../../engine/engine_types.js";
|
12
12
|
|
13
13
|
|
14
14
|
const debug = getParam("debugplayersync");
|
@@ -267,7 +267,7 @@
|
|
267
267
|
if (this.createARButton || this.createVRButton || this.useQuicklookExport) {
|
268
268
|
// Quicklook / iOS
|
269
269
|
if ((isiOS() && isSafari()) || debugQuicklook) {
|
270
|
-
if (this.
|
270
|
+
if (this.useQuicklookExport) {
|
271
271
|
this.getButtonsContainer().createQuicklookButton();
|
272
272
|
}
|
273
273
|
}
|
@@ -34,6 +34,10 @@
|
|
34
34
|
@serializable()
|
35
35
|
useTeleportTarget = false;
|
36
36
|
|
37
|
+
/** Enable to fade out the scene when teleporting */
|
38
|
+
@serializable()
|
39
|
+
useTeleportFade = false;
|
40
|
+
|
37
41
|
/** enable to visualize controller rays in the 3D scene */
|
38
42
|
@serializable()
|
39
43
|
showRays: boolean = true;
|
@@ -137,8 +141,16 @@
|
|
137
141
|
const teleportTarget = GameObject.getComponentInParent(hit.object, TeleportTarget);
|
138
142
|
if (!teleportTarget) return;
|
139
143
|
}
|
140
|
-
rig.worldPosition = hit.point;
|
141
144
|
if (debug) Gizmos.DrawSphere(hit.point, .025, 0xff0000, 5);
|
145
|
+
const point = hit.point.clone();
|
146
|
+
if (this.useTeleportFade) {
|
147
|
+
controller.xr.fadeTransition()?.then(() => {
|
148
|
+
rig.worldPosition = point;
|
149
|
+
})
|
150
|
+
}
|
151
|
+
else {
|
152
|
+
rig.worldPosition = point;
|
153
|
+
}
|
142
154
|
}
|
143
155
|
else {
|
144
156
|
// TODO: add option to allow teleportation on current ground plane
|