@needle-tools/engine 2.29.0-pre → 2.30.0-pre

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.d.ts +44 -8
  3. package/dist/needle-engine.js +347 -343
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +20 -16
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine.d.ts +1 -0
  8. package/lib/engine/engine_serialization.d.ts +1 -0
  9. package/lib/engine/engine_serialization.js +1 -0
  10. package/lib/engine/engine_serialization.js.map +1 -1
  11. package/lib/engine/engine_setup.d.ts +5 -0
  12. package/lib/engine/engine_setup.js +6 -0
  13. package/lib/engine/engine_setup.js.map +1 -1
  14. package/lib/engine/engine_utils.d.ts +1 -1
  15. package/lib/engine/engine_utils.js +25 -8
  16. package/lib/engine/engine_utils.js.map +1 -1
  17. package/lib/engine/extensions/NEEDLE_deferred_texture.d.ts +1 -1
  18. package/lib/engine/extensions/NEEDLE_deferred_texture.js +26 -14
  19. package/lib/engine/extensions/NEEDLE_deferred_texture.js.map +1 -1
  20. package/lib/engine/extensions/extension_utils.js +24 -13
  21. package/lib/engine/extensions/extension_utils.js.map +1 -1
  22. package/lib/engine/extensions/extensions.js +3 -1
  23. package/lib/engine/extensions/extensions.js.map +1 -1
  24. package/lib/engine-components/Camera.js +7 -0
  25. package/lib/engine-components/Camera.js.map +1 -1
  26. package/lib/engine-components/OrbitControls.js +2 -1
  27. package/lib/engine-components/OrbitControls.js.map +1 -1
  28. package/lib/engine-components/Renderer.d.ts +1 -0
  29. package/lib/engine-components/Renderer.js +10 -3
  30. package/lib/engine-components/Renderer.js.map +1 -1
  31. package/lib/engine-components/ScreenCapture.d.ts +1 -0
  32. package/lib/engine-components/ScreenCapture.js +265 -1
  33. package/lib/engine-components/ScreenCapture.js.map +1 -1
  34. package/lib/engine-components/SpectatorCamera.js +29 -5
  35. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  36. package/lib/engine-components/SyncedRoom.js +2 -0
  37. package/lib/engine-components/SyncedRoom.js.map +1 -1
  38. package/lib/engine-components/VideoPlayer.d.ts +10 -1
  39. package/lib/engine-components/VideoPlayer.js +64 -15
  40. package/lib/engine-components/VideoPlayer.js.map +1 -1
  41. package/lib/engine-components/Volume.d.ts +4 -0
  42. package/lib/engine-components/Volume.js +44 -3
  43. package/lib/engine-components/Volume.js.map +1 -1
  44. package/lib/engine-components/WebARSessionRoot.d.ts +9 -2
  45. package/lib/engine-components/WebARSessionRoot.js +69 -24
  46. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  47. package/lib/engine-components/WebXR.d.ts +6 -3
  48. package/lib/engine-components/WebXR.js +42 -7
  49. package/lib/engine-components/WebXR.js.map +1 -1
  50. package/lib/engine-components/WebXRAvatar.js +4 -0
  51. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  52. package/lib/engine-components/WebXRController.js +13 -7
  53. package/lib/engine-components/WebXRController.js.map +1 -1
  54. package/lib/engine-components/ui/CanvasGroup.d.ts +1 -0
  55. package/lib/engine-components/ui/CanvasGroup.js +1 -0
  56. package/lib/engine-components/ui/CanvasGroup.js.map +1 -1
  57. package/lib/engine-components/ui/EventSystem.js +13 -4
  58. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  59. package/lib/engine-components/ui/Graphic.d.ts +1 -0
  60. package/lib/engine-components/ui/Graphic.js +2 -0
  61. package/lib/engine-components/ui/Graphic.js.map +1 -1
  62. package/lib/engine-components/ui/Interfaces.d.ts +2 -0
  63. package/package.json +2 -2
  64. package/src/engine/engine_serialization.ts +3 -1
  65. package/src/engine/engine_setup.ts +6 -0
  66. package/src/engine/engine_utils.ts +34 -8
  67. package/src/engine/extensions/NEEDLE_deferred_texture.ts +25 -19
  68. package/src/engine/extensions/extension_utils.ts +24 -12
  69. package/src/engine/extensions/extensions.ts +3 -2
  70. package/src/engine-components/Camera.ts +9 -1
  71. package/src/engine-components/OrbitControls.ts +2 -1
  72. package/src/engine-components/Renderer.ts +11 -3
  73. package/src/engine-components/ScreenCapture.ts +312 -2
  74. package/src/engine-components/SpectatorCamera.ts +28 -5
  75. package/src/engine-components/SyncedRoom.ts +1 -0
  76. package/src/engine-components/VideoPlayer.ts +97 -21
  77. package/src/engine-components/Volume.ts +47 -4
  78. package/src/engine-components/WebARSessionRoot.ts +78 -28
  79. package/src/engine-components/WebXR.ts +50 -15
  80. package/src/engine-components/WebXRAvatar.ts +5 -0
  81. package/src/engine-components/WebXRController.ts +20 -14
  82. package/src/engine-components/ui/CanvasGroup.ts +2 -0
  83. package/src/engine-components/ui/EventSystem.ts +21 -15
  84. package/src/engine-components/ui/Graphic.ts +3 -0
  85. package/src/engine-components/ui/Interfaces.ts +2 -0
@@ -2,9 +2,10 @@ import { Behaviour } from "./Component";
2
2
  import * as THREE from "three";
3
3
  import { Material } from "material/Material";
4
4
  import { serializeable } from "../engine/engine_serialization_decorator";
5
- import { Object3D } from "three";
5
+ import { LinearFilter, Object3D, Texture } from "three";
6
6
  import { awaitInput } from "../engine/engine_input_utils";
7
7
  import { getParam } from "../engine/engine_utils";
8
+ import { Renderer } from "./Renderer";
8
9
 
9
10
  const debug = getParam("debugvideo");
10
11
 
@@ -20,22 +21,17 @@ export enum VideoSource {
20
21
  }
21
22
 
22
23
  export enum VideoAudioOutputMode {
23
- /// <summary>
24
- /// <para>Disable the embedded audio.</para>
25
- /// </summary>
26
- None,
27
- /// <summary>
28
- /// <para>Send the embedded audio into a specified AudioSource.</para>
29
- /// </summary>
30
- AudioSource,
31
- /// <summary>
32
- /// <para>Send the embedded audio direct to the platform's audio hardware.</para>
33
- /// </summary>
34
- Direct,
35
- /// <summary>
36
- /// <para>Send the embedded audio to the associated AudioSampleProvider.</para>
37
- /// </summary>
38
- APIOnly,
24
+ None = 0,
25
+ AudioSource = 1,
26
+ Direct = 2,
27
+ APIOnly = 3,
28
+ }
29
+
30
+ export enum VideoRenderMode {
31
+ CameraFarPlane = 0,
32
+ CameraNearPlane = 1,
33
+ RenderTexture = 2,
34
+ MaterialOverride = 3,
39
35
  }
40
36
 
41
37
  export class VideoPlayer extends Behaviour {
@@ -48,7 +44,16 @@ export class VideoPlayer extends Behaviour {
48
44
  playOnEnable?: boolean;
49
45
 
50
46
  @serializeable()
51
- targetMaterialProperty?: string;
47
+ private renderMode?: VideoRenderMode;
48
+
49
+ @serializeable()
50
+ private targetMaterialProperty?: string;
51
+
52
+ @serializeable(Renderer)
53
+ private targetMaterialRenderer?: Renderer;
54
+
55
+ @serializeable(Texture)
56
+ private targetTexture?: Texture;
52
57
 
53
58
  @serializeable()
54
59
  private time: number = 0;
@@ -250,11 +255,26 @@ export class VideoPlayer extends Behaviour {
250
255
  if (!this.enabled) return;
251
256
  if (!this.videoElement) return;
252
257
 
253
- const mat = this.gameObject["material"];
258
+ let target: Object3D | undefined = this.gameObject;
259
+
260
+ switch (this.renderMode) {
261
+ case VideoRenderMode.MaterialOverride:
262
+ target = this.targetMaterialRenderer?.gameObject;
263
+ break;
264
+ case VideoRenderMode.RenderTexture:
265
+ console.error("VideoPlayer renderTexture not implemented yet. Please use material override instead");
266
+ return;
267
+ }
268
+
269
+ if (!target) {
270
+ console.error("Missing target for video material renderer", this);
271
+ return;
272
+ }
273
+ const mat = target["material"];
254
274
  if (mat) {
255
275
  if (mat !== this.videoMaterial) {
256
276
  this.videoMaterial = mat.clone();
257
- this.gameObject["material"] = this.videoMaterial;
277
+ target["material"] = this.videoMaterial;
258
278
  }
259
279
 
260
280
  if (!this.targetMaterialProperty) {
@@ -305,4 +325,60 @@ export class VideoPlayer extends Behaviour {
305
325
  this.videoElement.style.visibility = "hidden";
306
326
  this.videoElement.style.display = "none";
307
327
  }
308
- }
328
+ }
329
+
330
+
331
+
332
+
333
+ // class VideoTexture extends Texture {
334
+
335
+ // get isVideoTexture() { return true; }
336
+
337
+ // constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
338
+
339
+ // super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
340
+
341
+ // this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
342
+ // this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
343
+
344
+ // this.generateMipmaps = false;
345
+
346
+ // const scope = this;
347
+
348
+ // function updateVideo() {
349
+
350
+ // scope.needsUpdate = true;
351
+ // video.requestVideoFrameCallback( updateVideo );
352
+
353
+ // }
354
+
355
+ // if ( 'requestVideoFrameCallback' in video ) {
356
+
357
+ // video.requestVideoFrameCallback( updateVideo );
358
+
359
+ // }
360
+
361
+ // }
362
+
363
+ // // clone() {
364
+
365
+ // // return new this.constructor( this.image ).copy( this );
366
+
367
+ // // }
368
+
369
+ // update() {
370
+
371
+ // const video = this.image;
372
+ // const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
373
+
374
+ // if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
375
+
376
+ // this.needsUpdate = true;
377
+
378
+ // }
379
+
380
+ // }
381
+
382
+ // }
383
+
384
+ // export { VideoTexture };
@@ -2,6 +2,9 @@ import { Behaviour } from "./Component";
2
2
  import { NoToneMapping, LinearToneMapping, ACESFilmicToneMapping, ReinhardToneMapping } from "three";
3
3
  import { serializeable } from "../engine/engine_serialization_decorator";
4
4
  import { Context } from "../engine/engine_setup";
5
+ import { getParam } from "../engine/engine_utils";
6
+
7
+ const debug = getParam("debugvolume");
5
8
 
6
9
  export enum TonemappingMode {
7
10
  None = 0,
@@ -35,21 +38,41 @@ function resolveComponentType(data) {
35
38
  return VolumeComponent;
36
39
  }
37
40
 
41
+ const volumeKey = Symbol("volumeprofile");
42
+
38
43
  export class VolumeProfile {
39
44
  @serializeable([d => resolveComponentType(d), VolumeComponent])
40
45
  components?: VolumeComponent[];
41
46
 
42
-
43
47
  apply(context: Context) {
48
+ this.onUpdate(context, false);
49
+ }
50
+
51
+ unapply(context: Context) {
52
+ this.onUpdate(context, true);
53
+ }
54
+
55
+ private onUpdate(context: Context, remove: boolean) {
44
56
  if (!this.components) return;
57
+ const renderer = context.renderer;
58
+ const currentProfile = renderer[volumeKey];
59
+ const isActive = currentProfile !== undefined;
60
+ if (remove) {
61
+ // can not remove volume profile that is not active
62
+ if (!isActive) return;
63
+ }
64
+ else {
65
+ renderer[volumeKey] = this;
66
+ }
45
67
  for (const component of this.components) {
46
68
 
47
69
  if (component instanceof ToneMapping) {
48
70
  const tonemapping = component as ToneMapping;
49
- if (!component.active) {
71
+ if (!component.active || remove) {
50
72
  context.renderer.toneMapping = LinearToneMapping;
51
73
  continue;
52
74
  }
75
+ if (debug) console.log("VOLUME:", TonemappingMode[tonemapping.mode?.value ?? 0]);
53
76
  switch (tonemapping.mode?.value ?? 0) {
54
77
  case TonemappingMode.None:
55
78
  context.renderer.toneMapping = LinearToneMapping;
@@ -66,9 +89,11 @@ export class VolumeProfile {
66
89
  const colorAdjustments = component as ColorAdjustments;
67
90
  // unity range goes from -15..15
68
91
  // three.js range goes from 0..inf
69
- console.log(colorAdjustments.postExposure);
92
+ if (debug)
93
+ console.log(colorAdjustments.postExposure);
70
94
  const exposure = Math.pow(2, colorAdjustments.postExposure?.value ?? 0);
71
- context.renderer.toneMappingExposure = colorAdjustments.postExposure?.overrideState ? exposure : 1;
95
+ const useExposure = colorAdjustments.postExposure?.overrideState && !remove;
96
+ context.renderer.toneMappingExposure = useExposure ? exposure : 1;
72
97
  }
73
98
  }
74
99
  }
@@ -80,7 +105,25 @@ export class Volume extends Behaviour {
80
105
  @serializeable(VolumeProfile)
81
106
  sharedProfile?: VolumeProfile;
82
107
 
108
+ awake() {
109
+ if (debug) {
110
+ console.log(this);
111
+ console.log("Press P to toggle post processing");
112
+ window.addEventListener("keydown", (e) => {
113
+ if (e.key === "p") {
114
+ console.log("Toggle volume: " + this.name, !this.enabled);
115
+ this.enabled = !this.enabled;
116
+ }
117
+ });
118
+ }
119
+ }
120
+
83
121
  onEnable() {
122
+ if (debug) console.log("APPLY VOLUME", this)
84
123
  this.sharedProfile?.apply(this.context);
85
124
  }
125
+
126
+ onDisable() {
127
+ this.sharedProfile?.unapply(this.context);
128
+ }
86
129
  }
@@ -1,7 +1,7 @@
1
- import { Behaviour } from "./Component";
1
+ import { Behaviour, GameObject } from "./Component";
2
2
  import * as THREE from 'three'
3
- import { Object3D, XRPose } from "three";
4
- import { WebAR } from "./WebXR";
3
+ import { Matrix4, Object3D, XRPose } from "three";
4
+ import { WebAR, WebXR } from "./WebXR";
5
5
  import { InstancingUtil } from "./Renderer";
6
6
  import { serializeable } from "../engine/engine_serialization_decorator";
7
7
 
@@ -11,6 +11,10 @@ export class WebARSessionRoot extends Behaviour {
11
11
 
12
12
  webAR: WebAR | null = null;
13
13
 
14
+ get rig(): THREE.Object3D | undefined {
15
+ return this.webAR?.webxr.Rig;
16
+ }
17
+
14
18
  @serializeable()
15
19
  invertForward: boolean = false;
16
20
 
@@ -24,46 +28,57 @@ export class WebARSessionRoot extends Behaviour {
24
28
  this.setScale(val);
25
29
  }
26
30
 
31
+ private readonly _initalMatrix = new THREE.Matrix4();
32
+ private readonly _selectStartFn = this.onSelectStart.bind(this);
33
+ private readonly _selectEndFn = this.onSelectEnd.bind(this);
34
+
35
+ start() {
36
+ const xr = GameObject.findObjectOfType(WebXR);
37
+ if (xr) {
38
+ xr.Rig.updateMatrix();
39
+ this._initalMatrix.copy(xr.Rig.matrix);
40
+ }
41
+ }
42
+
27
43
  private _arScale: number = 5;
28
44
  private _rig: THREE.Object3D | null = null;
29
45
  private _startPose: THREE.Matrix4 | null = null;
30
46
  private _placementPose: THREE.Matrix4 | null = null;
31
47
  private _isTouching: boolean = false;
48
+ private _rigStartPose: THREE.Matrix4 | undefined | null = null;
32
49
 
33
50
  onBegin(session: THREE.XRSession) {
34
51
  this._placementPose = null;
35
52
  this.gameObject.visible = false;
36
53
  this.gameObject.matrixAutoUpdate = false;
37
54
  this._startPose = this.gameObject.matrix.clone();
38
- session.addEventListener('selectstart', this.onSelectStart.bind(this));
39
- session.addEventListener('selectend', this.onSelectEnd.bind(this));
40
- setTimeout(() => this.gameObject.visible = false, 1000);
55
+ this._rigStartPose = this.rig?.matrix.clone();
56
+ session.addEventListener('selectstart', this._selectStartFn);
57
+ session.addEventListener('selectend', this._selectEndFn);
58
+ // setTimeout(() => this.gameObject.visible = false, 1000); // TODO test on phone AR and Hololens if this was still needed
59
+
60
+ // console.log(this.rig?.position, this.rig?.quaternion, this.rig?.scale);
61
+ this.gameObject.visible = false;
62
+
63
+ if (this.rig) {
64
+ // reset rig to initial pose, this is helping the mix of immersive AR and immersive VR that we now have on quest
65
+ // where the rig can be moved and scaled by the user in VR mode and we use the rig position when entering
66
+ // immersive Ar right now to place the user/offset the session
67
+ this.rig.matrixAutoUpdate = true;
68
+ this._initalMatrix.decompose(this.rig.position, this.rig.quaternion, this.rig.scale);
69
+ }
41
70
  }
42
71
 
43
- onUpdate(rig: THREE.Object3D | null, _session: THREE.XRSession, pose: XRPose | null | undefined) {
72
+ onUpdate(rig: THREE.Object3D | null, _session: THREE.XRSession, pose: XRPose | null | undefined): boolean {
44
73
 
45
74
  if (pose && !this._placementPose) {
46
75
  if (this._isTouching) {
47
76
  if (this.webAR) this.webAR.setReticleActive(false);
48
- this._placementPose = new THREE.Matrix4().fromArray(pose.transform.matrix).invert();
49
- if (rig) {
50
-
51
- if (this.invertForward) {
52
- const rot = new THREE.Matrix4().makeRotationY(Math.PI);
53
- this._placementPose.premultiply(rot);
54
- }
55
- this._rig = rig;
56
-
57
- this.setScale(this.arScale);
58
- }
59
- else this._rig = null;
60
- // this.gameObject.matrix.copy(this._placementPose);
61
- // if (rig) {
62
- // this.gameObject.matrix.premultiply(rig.matrixWorld)
63
- // }
64
- this.gameObject.visible = true;
77
+ this.placeAt(rig, new THREE.Matrix4().fromArray(pose.transform.matrix).invert());
78
+ return true;
65
79
  }
66
80
  }
81
+ return false;
67
82
 
68
83
  // if (this._placementPose) {
69
84
  // this.gameObject.matrixAutoUpdate = false;
@@ -75,16 +90,47 @@ export class WebARSessionRoot extends Behaviour {
75
90
  // }
76
91
  }
77
92
 
93
+ placeAt(rig: THREE.Object3D | null, mat: Matrix4) {
94
+ if (!this._placementPose) this._placementPose = new THREE.Matrix4();
95
+ this._placementPose.copy(mat);
96
+ if (rig) {
97
+
98
+ if (this.invertForward) {
99
+ const rot = new THREE.Matrix4().makeRotationY(Math.PI);
100
+ this._placementPose.premultiply(rot);
101
+ }
102
+ this._rig = rig;
103
+
104
+ this.setScale(this.arScale);
105
+ }
106
+ else this._rig = null;
107
+ // this.gameObject.matrix.copy(this._placementPose);
108
+ // if (rig) {
109
+ // this.gameObject.matrix.premultiply(rig.matrixWorld)
110
+ // }
111
+ this.gameObject.visible = true;
112
+ }
113
+
78
114
  onEnd(rig: THREE.Object3D | null, _session: THREE.XRSession) {
79
115
  this._placementPose = null;
80
116
  this.gameObject.visible = false;
81
- if (this._startPose)
117
+ this.gameObject.matrixAutoUpdate = false;
118
+ if (this._startPose) {
82
119
  this.gameObject.matrix.copy(this._startPose);
83
- this.gameObject.matrixAutoUpdate = true;
84
- if (rig) rig.matrixAutoUpdate = true;
120
+ }
121
+ if (rig) {
122
+ rig.matrixAutoUpdate = true;
123
+ if (this._rigStartPose) {
124
+ this._rigStartPose.decompose(rig.position, rig.quaternion, rig.scale);
125
+ // console.log(rig.position, rig.quaternion, rig.scale);
126
+ }
127
+ }
85
128
  InstancingUtil.markDirty(this.gameObject, true);
86
129
  // HACK to fix physics being not in correct place after exiting AR
87
- setTimeout(() => this.gameObject.visible = true, 100);
130
+ setTimeout(() => {
131
+ this.gameObject.matrixAutoUpdate = true;
132
+ this.gameObject.visible = true;
133
+ }, 100);
88
134
  }
89
135
 
90
136
 
@@ -101,9 +147,13 @@ export class WebARSessionRoot extends Behaviour {
101
147
  if (!rig || !this._placementPose) {
102
148
  return;
103
149
  }
150
+ if (!this._rigStartPose) {
151
+ this._rigStartPose = rig.matrix.clone();
152
+ }
104
153
  rig.matrixAutoUpdate = false;
105
154
  rig.matrix.multiplyMatrices(new THREE.Matrix4().makeScale(scale, scale, scale), this._placementPose);
106
155
  rig.matrix.decompose(rig.position, rig.quaternion, rig.scale);
107
156
  rig.updateMatrixWorld();
157
+ console.log("Place", rig.position);
108
158
  }
109
159
  }
@@ -4,7 +4,7 @@ import { ARButton } from '../include/three/ARButton.js';
4
4
  import { VRButton } from '../include/three/VRButton.js';
5
5
 
6
6
  import * as THREE from 'three'
7
- import { ArrayCamera, XRHitTestSource } from 'three';
7
+ import { ArrayCamera, Matrix4, Vector3, XRHitTestSource } from 'three';
8
8
 
9
9
  import * as utils from "../engine/engine_utils";
10
10
  import { noVoip } from "./Voip";
@@ -17,6 +17,7 @@ import { EngineElement } from "../engine/engine_element";
17
17
  import { AssetReference } from "../engine/engine_addressables";
18
18
  import { serializeable } from "../engine/engine_serialization_decorator";
19
19
  import { WebXRSync } from "./WebXRSync";
20
+ import { XRSessionMode } from "../engine/engine_setup";
20
21
 
21
22
 
22
23
  export async function detectARSupport() {
@@ -73,7 +74,7 @@ export class WebXR extends Behaviour {
73
74
  this.events.addEventListener(type, listener);
74
75
  return listener;
75
76
  }
76
- public static removeEventListener(type: string, listener: any) : any {
77
+ public static removeEventListener(type: string, listener: any): any {
77
78
  this.events.removeEventListener(type, listener);
78
79
  return listener;
79
80
  }
@@ -94,10 +95,10 @@ export class WebXR extends Behaviour {
94
95
 
95
96
  public static createARButton(webXR: WebXR, opts?: CreateButtonOptions): HTMLButtonElement {
96
97
  const domOverlayRoot = webXR.webAR?.getAROverlayContainer();
97
- const features: any = { requiredFeatures: ['hit-test'] };
98
+ const features: any = {};
98
99
  if (domOverlayRoot) {
99
100
  features.domOverlay = { root: domOverlayRoot };
100
- features.optionalFeatures = ['dom-overlay'];
101
+ features.optionalFeatures = ['hit-test', 'dom-overlay'];
101
102
  }
102
103
  const arButton = ARButton.createButton(webXR.context.renderer, features);
103
104
  arButton.classList.add('webxr-ar-button');
@@ -120,7 +121,8 @@ export class WebXR extends Behaviour {
120
121
  if (session) session.end();
121
122
  }
122
123
 
123
- public get Rig(): THREE.Object3D | null {
124
+ public get Rig(): THREE.Object3D {
125
+ if(!this.rig) this.ensureRig();
124
126
  return this.rig;
125
127
  }
126
128
 
@@ -256,8 +258,6 @@ export class WebXR extends Behaviour {
256
258
  WebXR.events.dispatchEvent({ type: WebXREvent.XRUpdate, frame: frame, xr: this.context.renderer.xr, rig: this.rig });
257
259
  }
258
260
 
259
-
260
-
261
261
  private onClickedARButton() {
262
262
  if (!this._isInAR) {
263
263
  this._requestedAR = true;
@@ -361,10 +361,12 @@ export class WebXR extends Behaviour {
361
361
 
362
362
  switch (flag) {
363
363
  case XRStateFlag.AR:
364
+ this.context.xrSessionMode = XRSessionMode.ImmersiveAR;
364
365
  this._isInAR = true;
365
366
  this.webAR?.onBegin(session);
366
367
  break;
367
368
  case XRStateFlag.VR:
369
+ this.context.xrSessionMode = XRSessionMode.ImmersiveVR;
368
370
  this._isInVR = true;
369
371
  this.onEnterVR(session);
370
372
  break;
@@ -391,6 +393,7 @@ export class WebXR extends Behaviour {
391
393
  this._isInVR = false;
392
394
  this._requestedAR = false;
393
395
  this._requestedVR = false;
396
+ this.context.xrSessionMode = undefined;
394
397
 
395
398
  if (this.xrMirrorWindow) {
396
399
  this.xrMirrorWindow.close();
@@ -448,7 +451,10 @@ export class WebXR extends Behaviour {
448
451
  // not sure if this should be a behaviour.
449
452
  // for now we dont really need it to go through the usual update loop
450
453
  export class WebAR {
451
- private webxr: WebXR;
454
+
455
+ get webxr(): WebXR { return this._webxr; }
456
+
457
+ private _webxr: WebXR;
452
458
 
453
459
  private reticle: THREE.Object3D | null = null;
454
460
  private hitTestSource: XRHitTestSource | null = null;
@@ -468,11 +474,13 @@ export class WebAR {
468
474
  private get context() { return this.webxr.context; }
469
475
 
470
476
  constructor(webxr: WebXR) {
471
- this.webxr = webxr;
477
+ this._webxr = webxr;
472
478
  }
473
479
 
474
480
  private arDomOverlay: EngineElement | null = null;
475
481
  private arOverlayElement: HTMLElement | null = null;
482
+ private noHitTestAvailable: boolean = false;
483
+ private didPlaceARSessionRoot: boolean = false;
476
484
 
477
485
  getAROverlayContainer(): HTMLElement | null {
478
486
  this.arDomOverlay = this.webxr.context.domElement as EngineElement;
@@ -484,12 +492,14 @@ export class WebAR {
484
492
  this.reticleActive = active;
485
493
  }
486
494
 
487
- onBegin(session: THREE.XRSession) {
495
+ async onBegin(session: THREE.XRSession) {
488
496
  const context = this.webxr.context;
489
497
  this.reticleActive = true;
498
+ this.didPlaceARSessionRoot = false;
490
499
 
500
+ const deviceType = navigator.userAgent?.includes("OculusBrowser") ? ControllerType.PhysicalDevice : ControllerType.Touch;
491
501
  for (let i = 0; i < 4; i++) {
492
- WebXRController.Create(this.webxr, i, this.webxr.gameObject, ControllerType.Touch)
502
+ WebXRController.Create(this.webxr, i, this.webxr.gameObject, deviceType)
493
503
  }
494
504
 
495
505
  if (!this.sessionRoot || this.sessionRoot.destroyed || !this.sessionRoot.activeAndEnabled)
@@ -502,6 +512,9 @@ export class WebAR {
502
512
  session.requestReferenceSpace('viewer').then((referenceSpace) => {
503
513
  session.requestHitTestSource({ space: referenceSpace }).then((source) => {
504
514
  this.hitTestSource = source;
515
+ }).catch((_) => {
516
+ this.noHitTestAvailable = true;
517
+ console.warn("WebXR: Hit test not supported");
505
518
  });
506
519
  });
507
520
 
@@ -534,7 +547,7 @@ export class WebAR {
534
547
  if (this.arDomOverlay && this.arOverlayElement) {
535
548
  this.arDomOverlay.onEnterAR(session, this.arOverlayElement);
536
549
  }
537
-
550
+
538
551
  this.context.mainCameraComponent?.applyClearFlagsIfIsActiveCamera();
539
552
  }
540
553
 
@@ -552,11 +565,29 @@ export class WebAR {
552
565
  this.sessionRoot.onEnd(this.webxr.Rig, _session);
553
566
  }
554
567
  if (this.arDomOverlay) this.arDomOverlay.onExitAR(_session);
555
-
568
+
556
569
  this.context.mainCameraComponent?.applyClearFlagsIfIsActiveCamera();
557
570
  }
558
571
 
559
572
  onUpdate(session: THREE.XRSession, frame: THREE.XRFrame) {
573
+
574
+ if (this.noHitTestAvailable === true) {
575
+ if (this.reticle)
576
+ this.reticle.visible = false;
577
+ if (!this.didPlaceARSessionRoot) {
578
+ this.didPlaceARSessionRoot = true;
579
+ const rig = this.webxr.Rig;
580
+ const placementMatrix = arPlacementWithoutHitTestMatrix.clone();
581
+ if (rig) {
582
+ const positionFromRig = new Vector3(0, 0, 0).add(rig.position).divideScalar(this.sessionRoot?.arScale ?? 1);
583
+ placementMatrix.multiply(new Matrix4().makeTranslation(positionFromRig.x, positionFromRig.y, positionFromRig.z));
584
+ // placementMatrix.setPosition(positionFromRig);
585
+ }
586
+ this.sessionRoot?.placeAt(rig, placementMatrix);
587
+ }
588
+ return;
589
+ }
590
+
560
591
  if (!this.hitTestSource) return;
561
592
  const hitTestResults = frame.getHitTestResults(this.hitTestSource);
562
593
  if (hitTestResults.length) {
@@ -564,7 +595,11 @@ export class WebAR {
564
595
  const referenceSpace = this.webxr.context.renderer.xr.getReferenceSpace();
565
596
  if (referenceSpace) {
566
597
  const pose = hit.getPose(referenceSpace);
567
- this.sessionRoot?.onUpdate(this.webxr.Rig, session, pose);
598
+
599
+ if (this.sessionRoot) {
600
+ const didPlace = this.sessionRoot.onUpdate(this.webxr.Rig, session, pose);
601
+ this.didPlaceARSessionRoot = didPlace;
602
+ }
568
603
 
569
604
  if (this.reticle) {
570
605
  this.reticle.visible = this.reticleActive;
@@ -587,4 +622,4 @@ export class WebAR {
587
622
  }
588
623
  }
589
624
 
590
-
625
+ const arPlacementWithoutHitTestMatrix = new THREE.Matrix4().identity().makeTranslation(0, -0.5, 0);
@@ -11,6 +11,7 @@ import { VRUserState } from "./WebXRSync";
11
11
  import { getParam } from "../engine/engine_utils";
12
12
  import { serializeable } from "../engine/engine_serialization_decorator";
13
13
  import { ViewDevice } from "../engine/engine_playerview";
14
+ import { InstancingUtil } from "./Renderer";
14
15
 
15
16
  export const debug = getParam("debugavatar");
16
17
 
@@ -168,6 +169,8 @@ export class WebXRAvatar {
168
169
  // }
169
170
  this.context.players.setPlayerView(state.guid, viewObj, device);
170
171
 
172
+ InstancingUtil.markDirty(this.head);
173
+
171
174
  this._canInterpolate = true;
172
175
  const ht = this.isLocalAvatar ? this.head : this._headTarget;
173
176
  ht.position.set(state.position.x, state.position.y, state.position.z);
@@ -183,6 +186,7 @@ export class WebXRAvatar {
183
186
  ht.quaternion.multiply(WebXRAvatar.invertRotation);
184
187
  ht.scale.set(state.scale, state.scale, state.scale);
185
188
  ht.scale.multiply(this.handLeftScale);
189
+ InstancingUtil.markDirty(this.handLeft);
186
190
  }
187
191
 
188
192
  if (this.handRight) {
@@ -192,6 +196,7 @@ export class WebXRAvatar {
192
196
  ht.quaternion.multiply(WebXRAvatar.invertRotation);
193
197
  ht.scale.set(state.scale, state.scale, state.scale);
194
198
  ht.scale.multiply(this.handRightScale);
199
+ InstancingUtil.markDirty(this.handRight);
195
200
  }
196
201
  }
197
202
  }