@@ -41,7 +41,7 @@
|
|
41
41
|
marker.avatar = this.gameObject;
|
42
42
|
marker.connectionId = playerstate.owner;
|
43
43
|
}
|
44
|
-
else console.error("No player state found for avatar", this);
|
44
|
+
else if(this.context.connection.isConnected) console.error("No player state found for avatar", this);
|
45
45
|
}
|
46
46
|
|
47
47
|
onLeaveXR(_args: NeedleXREventArgs): void {
|
@@ -12,6 +12,7 @@
|
|
12
12
|
import { getParam } from "../../engine/engine_utils.js";
|
13
13
|
import { LayoutGroup } from "./Layout.js";
|
14
14
|
import { Mathf } from "../../engine/engine_math.js";
|
15
|
+
import { NeedleXREventArgs } from "../../engine/xr/index.js";
|
15
16
|
|
16
17
|
export enum RenderMode {
|
17
18
|
ScreenSpaceOverlay = 0,
|
@@ -200,19 +201,30 @@
|
|
200
201
|
}
|
201
202
|
}
|
202
203
|
|
204
|
+
onEnterXR(args: NeedleXREventArgs): void {
|
205
|
+
if (this.screenspace) {
|
206
|
+
if (args.xr.isVR || args.xr.isPassThrough) {
|
207
|
+
this.gameObject.visible = false;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
onLeaveXR(args: NeedleXREventArgs): void {
|
212
|
+
if (this.screenspace) {
|
213
|
+
if (args.xr.isVR || args.xr.isPassThrough) {
|
214
|
+
this.gameObject.visible = true;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
203
219
|
onBeforeRenderRoutine = () => {
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
this.
|
209
|
-
this.shadowComponent?.updateWorldMatrix(true, true);
|
210
|
-
this.invokeBeforeRenderEvents();
|
211
|
-
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
|
220
|
+
this.previousParent = this.gameObject.parent;
|
221
|
+
if ((this.context.xr?.isVR || this.context.xr?.isPassThrough) && this.screenspace) {
|
222
|
+
// see https://linear.app/needle/issue/NE-4114
|
223
|
+
this.gameObject.visible = false;
|
224
|
+
this.gameObject.removeFromParent();
|
212
225
|
return;
|
213
226
|
}
|
214
227
|
|
215
|
-
this.previousParent = this.gameObject.parent;
|
216
228
|
// console.log(this.previousParent?.name + "/" + this.gameObject.name);
|
217
229
|
|
218
230
|
if (this.renderOnTop || this.screenspace) {
|
@@ -231,7 +243,12 @@
|
|
231
243
|
}
|
232
244
|
|
233
245
|
onAfterRenderRoutine = () => {
|
234
|
-
if(this.context.
|
246
|
+
if ((this.context.xr?.isVR || this.context.xr?.isPassThrough) && this.screenspace) {
|
247
|
+
this.previousParent?.add(this.gameObject);
|
248
|
+
// this is currently causing an error during XR (https://linear.app/needle/issue/NE-4114)
|
249
|
+
// this.gameObject.visible = true;
|
250
|
+
return;
|
251
|
+
}
|
235
252
|
if ((this.screenspace || this.renderOnTop) && this.previousParent && this.context.mainCamera) {
|
236
253
|
if (this.screenspace) {
|
237
254
|
const camObj = this.context.mainCamera;
|
@@ -276,7 +293,7 @@
|
|
276
293
|
for (const ch of this._rectTransforms) {
|
277
294
|
if (matrixWorldChanged) ch.markDirty();
|
278
295
|
let layout = this._layoutGroups.get(ch.gameObject);
|
279
|
-
if(ch.isDirty && !layout){
|
296
|
+
if (ch.isDirty && !layout) {
|
280
297
|
layout = ch.gameObject.getComponentInParent(LayoutGroup) as LayoutGroup;
|
281
298
|
}
|
282
299
|
if (ch.isDirty || layout?.isDirty) {
|
@@ -6,7 +6,7 @@
|
|
6
6
|
import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate.js";
|
7
7
|
import type { ConstructorConcrete, SourceIdentifier, IComponent, IGameObject, Constructor, GuidsMap, Collision, ICollider } from "../engine/engine_types.js";
|
8
8
|
import { addNewComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components.js";
|
9
|
-
import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive, isDestroyed } from "../engine/engine_gameobject.js";
|
9
|
+
import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive, isDestroyed, IInstantiateOptions } from "../engine/engine_gameobject.js";
|
10
10
|
|
11
11
|
import { Euler, Object3D, Quaternion, Scene, Vector3 } from "three";
|
12
12
|
import { showBalloonWarning, isDevEnvironment } from "../engine/debug/index.js";
|
@@ -74,7 +74,7 @@
|
|
74
74
|
* @param instance object to instantiate
|
75
75
|
* @param opts options for the instantiation
|
76
76
|
*/
|
77
|
-
public static instantiateSynced(instance: GameObject | Object3D | null, opts:
|
77
|
+
public static instantiateSynced(instance: GameObject | Object3D | null, opts: IInstantiateOptions): GameObject | null {
|
78
78
|
if (!instance) return null;
|
79
79
|
return syncInstantiate(instance as any, opts) as GameObject | null;
|
80
80
|
}
|
@@ -83,7 +83,7 @@
|
|
83
83
|
* @param instance object to instantiate
|
84
84
|
* @param opts options for the instantiation (e.g. with what parent, position, etc.)
|
85
85
|
*/
|
86
|
-
public static instantiate(instance: GameObject | Object3D | null, opts:
|
86
|
+
public static instantiate(instance: GameObject | Object3D | null, opts: IInstantiateOptions | null = null): GameObject | null {
|
87
87
|
return instantiate(instance, opts) as GameObject | null;
|
88
88
|
}
|
89
89
|
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import { download } from "./engine_web_api.js";
|
8
8
|
import { getLoader } from "./engine_gltf.js";
|
9
9
|
import type { IComponent, IGameObject, SourceIdentifier } from "./engine_types.js";
|
10
|
-
import { destroy, instantiate, InstantiateOptions, isDestroyed } from "./engine_gameobject.js";
|
10
|
+
import { destroy, IInstantiateOptions, instantiate, InstantiateOptions, isDestroyed } from "./engine_gameobject.js";
|
11
11
|
|
12
12
|
const debug = getParam("debugaddressables");
|
13
13
|
|
@@ -245,12 +245,12 @@
|
|
245
245
|
}
|
246
246
|
|
247
247
|
/** loads and returns a new instance of `asset` */
|
248
|
-
async instantiate(parent?: Object3D |
|
248
|
+
async instantiate(parent?: Object3D | IInstantiateOptions) {
|
249
249
|
return this.onInstantiate(parent, false);
|
250
250
|
}
|
251
251
|
|
252
252
|
/** loads and returns a new instance of `asset` - this call is networked so an instance will be created on all connected users */
|
253
|
-
async instantiateSynced(parent?: Object3D |
|
253
|
+
async instantiateSynced(parent?: Object3D | IInstantiateOptions, saveOnServer: boolean = true) {
|
254
254
|
return this.onInstantiate(parent, true, saveOnServer);
|
255
255
|
}
|
256
256
|
|
@@ -26,7 +26,7 @@
|
|
26
26
|
import { LightDataRegistry, type ILightDataRegistry } from './engine_lightdata.js';
|
27
27
|
import { PlayerViewManager } from './engine_playerview.js';
|
28
28
|
|
29
|
-
import { type CoroutineData, type GLTF, type ICamera, type IComponent, type IContext, type ILight, type LoadedGLTF } from "./engine_types.js";
|
29
|
+
import { INeedleXRSession, type CoroutineData, type GLTF, type ICamera, type IComponent, type IContext, type ILight, type LoadedGLTF } from "./engine_types.js";
|
30
30
|
import { destroy, foreachComponent } from './engine_gameobject.js';
|
31
31
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
32
32
|
import { delay, getParam } from './engine_utils.js';
|
@@ -247,10 +247,14 @@
|
|
247
247
|
return this._domY;
|
248
248
|
}
|
249
249
|
get isInXR() { return this.renderer?.xr?.isPresenting || false; }
|
250
|
-
|
251
|
-
|
250
|
+
/** shorthand for `NeedleXRSession.active`
|
251
|
+
* Automatically set by NeedleXRSession when a XR session is active */
|
252
|
+
xr: INeedleXRSession | null = null;
|
253
|
+
get xrSessionMode() { return this.xr?.mode; }
|
252
254
|
get isInVR() { return this.xrSessionMode === "immersive-vr"; }
|
253
255
|
get isInAR() { return this.xrSessionMode === "immersive-ar"; }
|
256
|
+
/** If a XR session is active and in pass through mode (immersive-ar on e.g. Quest) */
|
257
|
+
get isInPassThrough() { return this.xr ? this.xr.isPassThrough : false; }
|
254
258
|
/** access the raw `XRSession` object (shorthand for `context.renderer.xr.getSession()`). For more control use `NeedleXRSession.active` */
|
255
259
|
get xrSession() { return this.renderer?.xr?.getSession(); }
|
256
260
|
/** @returns the latest XRFrame (if a XRSession is currently active)
|
@@ -438,7 +442,7 @@
|
|
438
442
|
|
439
443
|
private _disposeCallbacks: Function[] = [];
|
440
444
|
|
441
|
-
|
445
|
+
|
442
446
|
/** will request a renderer size update the next render call (will call updateSize the next update) */
|
443
447
|
requestSizeUpdate() { this._sizeChanged = true; }
|
444
448
|
|
@@ -495,7 +499,7 @@
|
|
495
499
|
async create(opts?: ContextCreateArgs) {
|
496
500
|
try {
|
497
501
|
this._isCreating = true;
|
498
|
-
if(opts !== this._originalCreationArgs)
|
502
|
+
if (opts !== this._originalCreationArgs)
|
499
503
|
this._originalCreationArgs = utils.deepClone(opts);
|
500
504
|
window.addEventListener("unhandledrejection", this.onUnhandledRejection)
|
501
505
|
const res = await this.internalOnCreate(opts);
|
@@ -725,7 +729,7 @@
|
|
725
729
|
private async internalOnCreate(opts?: ContextCreateArgs) {
|
726
730
|
const createId = ++this._createId;
|
727
731
|
|
728
|
-
if(debug) console.log("Creating context", this.name, opts);
|
732
|
+
if (debug) console.log("Creating context", this.name, opts);
|
729
733
|
|
730
734
|
this.clear();
|
731
735
|
// stop the animation loop if its running during creation
|
@@ -920,7 +924,7 @@
|
|
920
924
|
}
|
921
925
|
|
922
926
|
args?.onLoadingStart?.call(this, i, file);
|
923
|
-
if(debug) console.log("Context Load " + file);
|
927
|
+
if (debug) console.log("Context Load " + file);
|
924
928
|
const res = await loader.loadSync(this, file, file, loadingHash, prog => {
|
925
929
|
progressArg.name = file;
|
926
930
|
progressArg.progress = prog;
|
@@ -996,7 +1000,7 @@
|
|
996
1000
|
catch (err) {
|
997
1001
|
this._renderlooperrors += 1;
|
998
1002
|
if ((isDevEnvironment() || debug) && (err instanceof Error || err instanceof TypeError))
|
999
|
-
showBalloonMessage(
|
1003
|
+
showBalloonMessage(`Caught unhandled exception during render-loop - see console for details.`, LogType.Error);
|
1000
1004
|
console.error(err);
|
1001
1005
|
if (this._renderlooperrors > 10) {
|
1002
1006
|
console.warn("Stopping render loop due to error")
|
@@ -28,20 +28,28 @@
|
|
28
28
|
HideAndDontSave = DontSave | NotEditable | HideInHierarchy, // 0x0000003D
|
29
29
|
}
|
30
30
|
|
31
|
+
export type IInstantiateOptions = {
|
32
|
+
idProvider?: UIDProvider;
|
33
|
+
//** parent guid or object */
|
34
|
+
parent?: string | Object3D;
|
35
|
+
position?: Vector3;
|
36
|
+
/** for duplicatable parenting */
|
37
|
+
keepWorldPosition?: boolean;
|
38
|
+
rotation?: Quaternion;
|
39
|
+
scale?: Vector3;
|
40
|
+
/** if the instantiated object should be visible */
|
41
|
+
visible?: boolean;
|
42
|
+
context?: Context;
|
43
|
+
}
|
31
44
|
|
32
|
-
export class InstantiateOptions {
|
45
|
+
export class InstantiateOptions implements IInstantiateOptions {
|
33
46
|
idProvider?: UIDProvider | undefined;
|
34
|
-
|
35
|
-
//** parent guid */
|
36
47
|
parent?: string | undefined | Object3D;
|
37
|
-
/** for duplicatable parenting */
|
38
48
|
keepWorldPosition?: boolean
|
39
49
|
position?: Vector3 | undefined;
|
40
50
|
rotation?: Quaternion | undefined;
|
41
51
|
scale?: Vector3 | undefined;
|
42
|
-
|
43
52
|
visible?: boolean | undefined;
|
44
|
-
|
45
53
|
context?: Context | undefined;
|
46
54
|
}
|
47
55
|
|
@@ -13,7 +13,7 @@
|
|
13
13
|
ray?: Ray;
|
14
14
|
/** The control object for this input. In the case of spatial devices the controller,
|
15
15
|
* otherwise a generated object in screen space. The object may not be in the scene. */
|
16
|
-
device:
|
16
|
+
device: IGameObject;
|
17
17
|
buttonName: ButtonName | "none";
|
18
18
|
}
|
19
19
|
|
@@ -26,7 +26,7 @@
|
|
26
26
|
/** A ray in worldspace for the event */
|
27
27
|
readonly ray?: Ray;
|
28
28
|
/** The device space (this object is not necessarily rendered in the scene but you can access or copy the matrix) */
|
29
|
-
readonly space:
|
29
|
+
readonly space: IGameObject;
|
30
30
|
|
31
31
|
isClick: boolean = false;
|
32
32
|
isDoubleClick: boolean = false;
|
@@ -335,7 +335,7 @@
|
|
335
335
|
private _pointerEvent: Event[] = [];
|
336
336
|
private _pointerUsed: boolean[] = [];
|
337
337
|
/** This is added/updated for pointers. screenspace pointers set this to the camera near plane */
|
338
|
-
private _pointerSpace:
|
338
|
+
private _pointerSpace: IGameObject[] = [];
|
339
339
|
|
340
340
|
getKeyDown(): string | null {
|
341
341
|
for (const key in this.keysPressed) {
|
@@ -658,10 +658,10 @@
|
|
658
658
|
private readonly tempNearPlaneVector = new Vector3();
|
659
659
|
private readonly tempFarPlaneVector = new Vector3();
|
660
660
|
private readonly tempLookMatrix = new Matrix4();
|
661
|
-
private getAndUpdateSpatialObjectForScreenPosition(id: number, screenX: number, screenY: number):
|
661
|
+
private getAndUpdateSpatialObjectForScreenPosition(id: number, screenX: number, screenY: number): IGameObject {
|
662
662
|
let space = this._pointerSpace[id]
|
663
663
|
if (!space) {
|
664
|
-
space = new Object3D();
|
664
|
+
space = new Object3D() as unknown as IGameObject;
|
665
665
|
this._pointerSpace[id] = space;
|
666
666
|
}
|
667
667
|
this._pointerSpace[id] = space;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
const defaultNetworkingBackendUrlProvider = "https://urls.needle.tools/default-networking-backend/index";
|
2
2
|
let serverUrl: string | undefined = "wss://needle-tiny-starter.glitch.me/socket";
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { type Websocket } from 'websocket-ts';
|
5
5
|
// import { Networking } from '../engine-components/Networking.js';
|
6
6
|
import { Context } from './engine_setup.js';
|
7
7
|
import * as utils from "./engine_utils.js";
|
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
export const debugNet = utils.getParam("debugnet") ? true : false;
|
16
16
|
export const debugOwner = debugNet || utils.getParam("debugowner") ? true : false;
|
17
|
+
const debugnetBin = utils.getParam("debugnetbin");
|
17
18
|
|
18
19
|
export interface INetworkingWebsocketUrlProvider {
|
19
20
|
getWebsocketUrl(): string | null;
|
@@ -389,7 +390,7 @@
|
|
389
390
|
|
390
391
|
/** Send a binary message to the server (broadcasted to all connected users) */
|
391
392
|
public sendBinary(bin: Uint8Array) {
|
392
|
-
if (
|
393
|
+
if (debugnetBin) console.log("<< send binary", this.context.time.frame, (bin.length / 1024) + " KB");
|
393
394
|
this._ws?.send(bin);
|
394
395
|
}
|
395
396
|
|
@@ -547,10 +548,11 @@
|
|
547
548
|
console.error("⊠Websocket error", i, ev);
|
548
549
|
resolve(false);
|
549
550
|
})
|
550
|
-
.onMessage(this.onMessage.bind(this))
|
551
551
|
.onRetry(() => { console.log("Retry connecting to networking websocket") })
|
552
552
|
.build();
|
553
|
-
|
553
|
+
ws.addEventListener(pkg.WebsocketEvent.message, (socket, msg) => {
|
554
|
+
this.onMessage(socket, msg);
|
555
|
+
});
|
554
556
|
});
|
555
557
|
}
|
556
558
|
|
@@ -581,6 +583,7 @@
|
|
581
583
|
}
|
582
584
|
|
583
585
|
private async handleIncomingBinaryMessage(blob: Blob) {
|
586
|
+
if (debugnetBin) console.log("<< bin", this.context.time.frame);
|
584
587
|
const buf = await blob.arrayBuffer();
|
585
588
|
var data = new Uint8Array(buf);
|
586
589
|
const bb = new flatbuffers.ByteBuffer(data);
|
@@ -97,6 +97,16 @@
|
|
97
97
|
stopAllCoroutinesFrom(script: IComponent);
|
98
98
|
}
|
99
99
|
|
100
|
+
export interface INeedleXRSession {
|
101
|
+
get running(): boolean;
|
102
|
+
readonly mode: XRSessionMode;
|
103
|
+
readonly session: XRSession;
|
104
|
+
|
105
|
+
get isVR();
|
106
|
+
get isAR();
|
107
|
+
get isPassThrough();
|
108
|
+
}
|
109
|
+
|
100
110
|
export declare interface INeedleEngineComponent extends HTMLElement {
|
101
111
|
getAROverlayContainer(): HTMLElement;
|
102
112
|
onEnterAR(session: XRSession, overlayContainer: HTMLElement);
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import { AxesHelper, Object3D, Quaternion, Ray, Vector3 } from "three";
|
1
|
+
import { AxesHelper, Int8BufferAttribute, Object3D, Quaternion, Ray, Vector3 } from "three";
|
2
2
|
import { MotionController, fetchProfile } from "@webxr-input-profiles/motion-controllers";
|
3
|
-
import type { ButtonName, Vec3, XRControllerButtonName } from "../engine_types.js";
|
3
|
+
import type { ButtonName, IGameObject, Vec3, XRControllerButtonName } from "../engine_types.js";
|
4
4
|
import { Context } from "../engine_context.js";
|
5
5
|
import { Gizmos } from "../engine_gizmos.js";
|
6
6
|
import { InputEvents, NEPointerEventInit, PointerType, NEPointerEvent } from "../engine_input.js";
|
@@ -178,7 +178,7 @@
|
|
178
178
|
* Children will be automatically detached and put into the scene when the controller disconnects
|
179
179
|
*/
|
180
180
|
get object() { return this._object; }
|
181
|
-
private readonly _object:
|
181
|
+
private readonly _object: IGameObject;
|
182
182
|
|
183
183
|
private readonly _debugAxesHelper = new AxesHelper(.03);
|
184
184
|
|
@@ -191,7 +191,7 @@
|
|
191
191
|
this.xr = session;
|
192
192
|
this.inputSource = device;
|
193
193
|
this.index = index;
|
194
|
-
this._object = new Object3D();
|
194
|
+
this._object = new Object3D() as unknown as IGameObject;
|
195
195
|
if (debug)
|
196
196
|
this._object.add(this._debugAxesHelper);
|
197
197
|
this.xr.context.scene.add(this._object);
|
@@ -321,7 +321,7 @@
|
|
321
321
|
* @param key the controller button name e.g. x-button
|
322
322
|
* @returns the gamepad button if it exists on the controller - otherwise undefined
|
323
323
|
*/
|
324
|
-
getButton(key: ButtonName | "primary-button" | "primary"):
|
324
|
+
getButton(key: ButtonName | "primary-button" | "primary"): NeedleGamepadButton | undefined {
|
325
325
|
if (!this._layout) return undefined;
|
326
326
|
|
327
327
|
switch (key) {
|
@@ -331,21 +331,21 @@
|
|
331
331
|
else return undefined;
|
332
332
|
break;
|
333
333
|
case "primary":
|
334
|
-
return this.
|
334
|
+
return this.toNeedleGamepadButton(0);
|
335
335
|
}
|
336
336
|
|
337
337
|
|
338
|
-
if (this._buttonMap.has(key))
|
339
|
-
return this._buttonMap.get(key)
|
338
|
+
if (this._buttonMap.has(key)) {
|
339
|
+
return this.toNeedleGamepadButton(this._buttonMap.get(key)!);
|
340
|
+
}
|
340
341
|
const componentModel = this._layout?.components[key];
|
341
342
|
if (componentModel?.gamepadIndices) {
|
342
343
|
switch (componentModel.type) {
|
343
344
|
case "button":
|
344
345
|
if (this.inputSource.gamepad) {
|
345
346
|
const index = componentModel.gamepadIndices!.button!;
|
346
|
-
|
347
|
-
this.
|
348
|
-
return button;
|
347
|
+
this._buttonMap.set(key, index);
|
348
|
+
return this.toNeedleGamepadButton(index);
|
349
349
|
}
|
350
350
|
break;
|
351
351
|
default:
|
@@ -357,6 +357,25 @@
|
|
357
357
|
return undefined;
|
358
358
|
}
|
359
359
|
|
360
|
+
private readonly _needleGamepadButtons = new Array<NeedleGamepadButton>();
|
361
|
+
/** combine the InputState information + the GamepadButton information (since GamepadButtons can not be extended) */
|
362
|
+
private toNeedleGamepadButton(index: number): NeedleGamepadButton {
|
363
|
+
const button = this.inputSource.gamepad?.buttons[index];
|
364
|
+
const state = this.states[index];
|
365
|
+
const needleButton = this._needleGamepadButtons[index] || new NeedleGamepadButton();
|
366
|
+
if (button) {
|
367
|
+
needleButton.pressed = button.pressed;
|
368
|
+
needleButton.value = button.value;
|
369
|
+
needleButton.touched = button.touched;
|
370
|
+
}
|
371
|
+
if (state) {
|
372
|
+
needleButton.isDown = state.isDown;
|
373
|
+
needleButton.isUp = state.isUp;
|
374
|
+
}
|
375
|
+
this._needleGamepadButtons[index] = needleButton;
|
376
|
+
return needleButton;
|
377
|
+
}
|
378
|
+
|
360
379
|
/**
|
361
380
|
* Get the values of a controller joystick
|
362
381
|
* @link https://github.com/immersive-web/webxr-gamepads-module/blob/main/gamepads-module-explainer.md
|
@@ -394,7 +413,7 @@
|
|
394
413
|
}
|
395
414
|
|
396
415
|
|
397
|
-
private readonly _buttonMap = new Map<ButtonName,
|
416
|
+
private readonly _buttonMap = new Map<ButtonName, number>();
|
398
417
|
|
399
418
|
// the motion controller contains the controller scheme, we use this to simplify button access
|
400
419
|
private _motioncontroller?: MotionController;
|
@@ -506,11 +525,19 @@
|
|
506
525
|
// is down
|
507
526
|
if (button.pressed && !state.pressed) {
|
508
527
|
inputEvent = InputEvents.PointerDown;
|
528
|
+
state.isDown = true;
|
529
|
+
state.isUp = false;
|
509
530
|
}
|
510
531
|
// is up
|
511
532
|
else if (!button.pressed && state.pressed) {
|
512
533
|
inputEvent = InputEvents.PointerUp;
|
534
|
+
state.isDown = false;
|
535
|
+
state.isUp = true;
|
513
536
|
}
|
537
|
+
else {
|
538
|
+
state.isDown = false;
|
539
|
+
state.isUp = false;
|
540
|
+
}
|
514
541
|
|
515
542
|
state.value = button.value;
|
516
543
|
state.pressed = button.pressed;
|
@@ -553,6 +580,22 @@
|
|
553
580
|
}
|
554
581
|
|
555
582
|
class InputState {
|
583
|
+
/** if the button was pressed the last update */
|
584
|
+
isDown: boolean = false;
|
585
|
+
/** if the button was released the last update */
|
586
|
+
isUp: boolean = false;
|
587
|
+
|
556
588
|
pressed: boolean = false;
|
557
589
|
value: number = 0;
|
558
|
-
}
|
590
|
+
};
|
591
|
+
|
592
|
+
/** Enhanced GamepadButton with `isDown` and `isUp` information */
|
593
|
+
class NeedleGamepadButton {
|
594
|
+
touched: boolean = false;
|
595
|
+
pressed: boolean = false;
|
596
|
+
value: number = 0;
|
597
|
+
/** was the button just pressed down the last update */
|
598
|
+
isDown: boolean = false;
|
599
|
+
/** was the button just released the last update */
|
600
|
+
isUp: boolean = false;
|
601
|
+
}
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import type { IXRRig } from "./XRRig.js";
|
8
8
|
import { ImplictXRRig, flipForwardMatrix, flipForwardQuaternion } from "./internal.js";
|
9
9
|
import { delay, getParam, isDesktop, isQuest } from "../engine_utils.js";
|
10
|
-
import { ICamera, IComponent } from "../engine_types.js";
|
10
|
+
import { ICamera, IComponent, INeedleXRSession } from "../engine_types.js";
|
11
11
|
import { NeedleXRSync } from "./NeedleXRSync.js";
|
12
12
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../debug/index.js";
|
13
13
|
import { registerFrameEventCallback, unregisterFrameEventCallback } from "../engine_lifecycle_functions_internal.js";
|
@@ -131,7 +131,7 @@
|
|
131
131
|
* - Listen to XRSession controller removed events with `NeedleXRSession.onControllerRemoved(...)`
|
132
132
|
*
|
133
133
|
*/
|
134
|
-
export class NeedleXRSession {
|
134
|
+
export class NeedleXRSession implements INeedleXRSession {
|
135
135
|
|
136
136
|
private static _sync: NeedleXRSync | null = null;
|
137
137
|
static getXRSync(context: Context) {
|
@@ -386,7 +386,7 @@
|
|
386
386
|
}
|
387
387
|
|
388
388
|
/** Returns true if the xr session is still active */
|
389
|
-
get running() { return !this._ended && this.session; }
|
389
|
+
get running(): boolean { return !this._ended && this.session != null; }
|
390
390
|
|
391
391
|
/**
|
392
392
|
* @link https://developer.mozilla.org/en-US/docs/Web/API/XRSession
|
@@ -438,6 +438,9 @@
|
|
438
438
|
if (this.controllers.some(c => c.inputSource.targetRayMode === "tracked-pointer"))
|
439
439
|
return true;
|
440
440
|
}
|
441
|
+
if (isDevEnvironment() && isDesktop() && this.mode === "immersive-ar") {
|
442
|
+
return true;
|
443
|
+
}
|
441
444
|
return false;
|
442
445
|
}
|
443
446
|
get isAR() { return this.mode === 'immersive-ar'; }
|
@@ -658,9 +661,9 @@
|
|
658
661
|
this.session = session;
|
659
662
|
this.mode = mode;
|
660
663
|
this.context = context;
|
661
|
-
this.context.xrSessionMode = this.mode;
|
662
664
|
this.context.renderer.xr.enabled = true;
|
663
665
|
this.context.renderer.xr.setSession(this.session);
|
666
|
+
this.context.xr = this;
|
664
667
|
|
665
668
|
this._xr_scripts = [...extra.scripts];
|
666
669
|
this._xr_update_scripts = this._xr_scripts.filter(e => typeof e.onUpdateXR === "function");
|
@@ -767,8 +770,8 @@
|
|
767
770
|
const index2 = this.context.post_render_callbacks.indexOf(this.onAfter);
|
768
771
|
if (index2 >= 0) this.context.post_render_callbacks.splice(index2, 1);
|
769
772
|
|
773
|
+
this.context.xr = null;
|
770
774
|
this.context.renderer.xr.enabled = false;
|
771
|
-
this.context.xrSessionMode = undefined;
|
772
775
|
this.context.mainCameraComponent?.applyClearFlags();
|
773
776
|
|
774
777
|
// make sure we disconnect all controllers
|
@@ -834,6 +837,9 @@
|
|
834
837
|
const frame = context.xrFrame;
|
835
838
|
if (!frame) return;
|
836
839
|
|
840
|
+
// ensure that XR is always set to a running session
|
841
|
+
this.context.xr = this;
|
842
|
+
|
837
843
|
// ensure that we always have the correct main camera reference
|
838
844
|
// we need to store the main camera reference here because the main camera can be disabled and then it's removed from the context
|
839
845
|
// but in XR we want to ensure we always have a active camera (or at least parent the camera to the active rig)
|
@@ -911,6 +917,7 @@
|
|
911
917
|
continue;
|
912
918
|
}
|
913
919
|
if (!script.activeAndEnabled) {
|
920
|
+
this.context.new_scripts_xr.splice(i, 1);
|
914
921
|
this.markInactive(script);
|
915
922
|
continue;
|
916
923
|
}
|
@@ -1017,10 +1024,10 @@
|
|
1017
1024
|
|
1018
1025
|
/** mark a script as inactive and invokes callbacks */
|
1019
1026
|
private markInactive(script: INeedleXRSessionEventReceiver) {
|
1020
|
-
if (this._inactive_scripts.
|
1027
|
+
if (this._inactive_scripts.indexOf(script) >= 0) return;
|
1028
|
+
// inactive scripts should not receive any regular callbacks anymore
|
1029
|
+
this.removeScript(script, false);
|
1021
1030
|
this._inactive_scripts.push(script);
|
1022
|
-
// inactive scripts should not receive any regular callbacks anymore
|
1023
|
-
this.removeScript(script);
|
1024
1031
|
// inactive scripts receive callbacks as if the XR session has ended
|
1025
1032
|
for (const ctrl of this.controllers) this.invokeCallback_ControllerRemoved(script, ctrl);
|
1026
1033
|
this.invokeCallback_LeaveXR(script);
|
@@ -1041,14 +1048,16 @@
|
|
1041
1048
|
|
1042
1049
|
private readonly _script_to_remove: INeedleXRSessionEventReceiver[] = [];
|
1043
1050
|
|
1044
|
-
private removeScript(script: INeedleXRSessionEventReceiver) {
|
1051
|
+
private removeScript(script: INeedleXRSessionEventReceiver, removeCompletely: boolean = true) {
|
1045
1052
|
if (debug) console.log("Remove XRScript", script);
|
1046
1053
|
const index = this._xr_scripts.indexOf(script);
|
1047
1054
|
if (index >= 0) this._xr_scripts.splice(index, 1);
|
1048
1055
|
const index2 = this._xr_update_scripts.indexOf(script);
|
1049
1056
|
if (index2 >= 0) this._xr_update_scripts.splice(index2, 1);
|
1050
|
-
|
1051
|
-
|
1057
|
+
if (removeCompletely) {
|
1058
|
+
const index3 = this._inactive_scripts.indexOf(script);
|
1059
|
+
if (index3 >= 0) this._inactive_scripts.splice(index3, 1);
|
1060
|
+
}
|
1052
1061
|
}
|
1053
1062
|
|
1054
1063
|
private invokeCallback_EnterXR(script: INeedleXRSessionEventReceiver) {
|
@@ -10,6 +10,7 @@
|
|
10
10
|
import { Transform } from '../engine-schemes/transform.js';
|
11
11
|
import { registerBinaryType } from '../engine-schemes/schemes.js';
|
12
12
|
import { setWorldEuler } from '../engine/engine_three_utils.js';
|
13
|
+
import { onUpdate } from '../engine/engine_lifecycle_api.js';
|
13
14
|
|
14
15
|
const debug = utils.getParam("debugsync");
|
15
16
|
export const SyncedTransformIdentifier = "STRS";
|
@@ -35,8 +36,19 @@
|
|
35
36
|
}
|
36
37
|
|
37
38
|
|
39
|
+
let FAST_ACTIVE_SYNCTRANSFORMS = 0;
|
40
|
+
let FAST_INTERVAL = 0;
|
41
|
+
onUpdate((ctx) => {
|
42
|
+
const isRunningOnGlitch = ctx.connection.currentServerUrl?.includes("glitch");
|
43
|
+
const threshold = isRunningOnGlitch ? 10 : 40;
|
44
|
+
FAST_INTERVAL = Math.floor(FAST_ACTIVE_SYNCTRANSFORMS / threshold);
|
45
|
+
FAST_ACTIVE_SYNCTRANSFORMS = 0;
|
46
|
+
if(debug && FAST_INTERVAL > 0) console.log("Sync Transform Fast Interval", FAST_INTERVAL);
|
47
|
+
})
|
48
|
+
|
38
49
|
export class SyncedTransform extends Behaviour {
|
39
50
|
|
51
|
+
|
40
52
|
// public autoOwnership: boolean = true;
|
41
53
|
public overridePhysics: boolean = true
|
42
54
|
public interpolatePosition: boolean = true;
|
@@ -293,8 +305,12 @@
|
|
293
305
|
|
294
306
|
const updateInterval = 10;
|
295
307
|
const fastUpdate = this.rb || this.fastMode;
|
308
|
+
|
296
309
|
if (this._needsUpdate && (updateInterval <= 0 || updateInterval > 0 && this.context.time.frameCount % updateInterval === 0 || fastUpdate)) {
|
297
310
|
|
311
|
+
FAST_ACTIVE_SYNCTRANSFORMS++;
|
312
|
+
if (fastUpdate && FAST_INTERVAL > 0 && this.context.time.frameCount % FAST_INTERVAL !== 0) return;
|
313
|
+
|
298
314
|
if (debug)
|
299
315
|
console.log("send update", this.context.connection.connectionId, this.guid, this.gameObject.name, this.gameObject.guid);
|
300
316
|
|
@@ -108,13 +108,17 @@
|
|
108
108
|
}
|
109
109
|
|
110
110
|
private async handleOfferSession() {
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
if (this.createVRButton) {
|
112
|
+
const hasVRSupport = await NeedleXRSession.isVRSupported();
|
113
|
+
if (hasVRSupport && this.createVRButton) {
|
114
|
+
return NeedleXRSession.offerSession("immersive-vr", "default", this.context);
|
115
|
+
}
|
114
116
|
}
|
115
|
-
|
116
|
-
|
117
|
-
|
117
|
+
if (this.createARButton) {
|
118
|
+
const hasARSupport = await NeedleXRSession.isARSupported();
|
119
|
+
if (hasARSupport && this.createARButton) {
|
120
|
+
return NeedleXRSession.offerSession("immersive-ar", "default", this.context);
|
121
|
+
}
|
118
122
|
}
|
119
123
|
return false;
|
120
124
|
}
|