@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
@@ -10,13 +10,20 @@ declare type DependencyInfo = {
10
10
  dependencyName: string,
11
11
  }
12
12
 
13
- const rootExtensionPrefix = "/extensions/";
13
+ const rootExtensionPrefix = ["/extensions/", "extensions/"];
14
14
  const defaultDependencies = [
15
15
  { prefix: "/nodes/", dependencyName: "node" },
16
16
  { prefix: "/meshes/", dependencyName: "mesh" },
17
17
  { prefix: "/materials/", dependencyName: "material" },
18
18
  { prefix: "/textures/", dependencyName: "texture" },
19
- { prefix: "/animations/", dependencyName: "animation" }
19
+ { prefix: "/animations/", dependencyName: "animation" },
20
+
21
+ // legacy support
22
+ { prefix: "nodes/", dependencyName: "node" },
23
+ { prefix: "meshes/", dependencyName: "mesh" },
24
+ { prefix: "materials/", dependencyName: "material" },
25
+ { prefix: "textures/", dependencyName: "texture" },
26
+ { prefix: "animations/", dependencyName: "animation" },
20
27
  ]
21
28
 
22
29
  export async function resolveReferences(parser: GLTFParser, obj) {
@@ -95,16 +102,21 @@ function internalResolve(paths: DependencyInfo[], parser: GLTFParser, obj, promi
95
102
 
96
103
 
97
104
  function resolveExtension(parser: GLTFParser, str): Promise<void> | null {
98
- if (parser && parser.plugins && typeof str === "string" && str.startsWith(rootExtensionPrefix)) {
99
- let name = str.substring(rootExtensionPrefix.length);
100
- const endIndex = name.indexOf("/");
101
- if (endIndex >= 0) name = name.substring(0, endIndex);
102
- const ext = parser.plugins[name] as IExtensionReferenceResolver;
103
- if (debugExtension)
104
- console.log(name, ext);
105
- if (typeof ext?.resolve === "function") {
106
- const path = str.substring(rootExtensionPrefix.length + name.length + 1);
107
- return ext.resolve(parser, path);
105
+ if (parser && parser.plugins && typeof str === "string") {
106
+ for (const prefix of rootExtensionPrefix) {
107
+ if (str.startsWith(prefix)) {
108
+ let name = str.substring(prefix.length);
109
+ const endIndex = name.indexOf("/");
110
+ if (endIndex >= 0) name = name.substring(0, endIndex);
111
+ const ext = parser.plugins[name] as IExtensionReferenceResolver;
112
+ if (debugExtension)
113
+ console.log(name, ext);
114
+ if (typeof ext?.resolve === "function") {
115
+ const path = str.substring(prefix.length + name.length + 1);
116
+ return ext.resolve(parser, path);
117
+ }
118
+ break;
119
+ }
108
120
  }
109
121
  }
110
122
  return null;
@@ -26,8 +26,9 @@ export function registerComponentExtension(loader: GLTFLoader): NEEDLE_component
26
26
  class PointerResolver {
27
27
  resolvePath(path: string) {
28
28
  if (path.includes('/extensions/builtin_components/'))
29
- path = path.replace('/extensions/builtin_components/', '/userData/components/');
30
-
29
+ return path.replace('/extensions/builtin_components/', '/userData/components/');
30
+ if (path.includes('extensions/builtin_components/'))
31
+ return path.replace('extensions/builtin_components/', '/userData/components/');
31
32
  return path;
32
33
  }
33
34
  }
@@ -5,6 +5,7 @@ import { getParam } from "../engine/engine_utils";
5
5
  import { serializeable } from "../engine/engine_serialization_decorator";
6
6
  import { RGBAColor } from "./js-extensions/RGBAColor";
7
7
  import { PerspectiveCamera } from "three";
8
+ import { XRSessionMode } from "../engine/engine_setup";
8
9
 
9
10
  export enum ClearFlags {
10
11
  Skybox = 1,
@@ -192,13 +193,20 @@ export class Camera extends Behaviour {
192
193
  }
193
194
 
194
195
  private environmentIsTransparent(): boolean {
195
- const session = this.context.renderer.xr?.getSession()
196
+ const session = this.context.renderer.xr?.getSession();
196
197
  if (!session) return false;
197
198
  const environmentBlendMode = session.environmentBlendMode;
198
199
  const transparent = environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend';
200
+ // workaround for Quest 2 returning opaque when it should be alpha-blend
201
+ // check user agent if this is the Quest browser and return true if so
202
+
203
+ if (environmentBlendMode === "opaque" && navigator.userAgent?.includes("OculusBrowser")) {
204
+ if (this.context.xrSessionMode === XRSessionMode.ImmersiveAR) return true;
205
+ }
199
206
  return transparent;
200
207
  }
201
208
 
209
+
202
210
  private enableSkybox() {
203
211
  if (!this._skybox)
204
212
  this._skybox = new CameraSkybox(this);
@@ -102,7 +102,8 @@ export class OrbitControls extends Behaviour {
102
102
  if (this._controls) {
103
103
  const camGo = GameObject.getComponent(this.gameObject, Camera);
104
104
  if (camGo && !this.setFromTargetPosition()) {
105
- console.log("NO TARGET");
105
+ if(this.debugLog)
106
+ console.log("NO TARGET");
106
107
  const forward = new THREE.Vector3(0, 0, -1).applyMatrix4(camGo.cam.matrixWorld);
107
108
  this.setTarget(forward, true);
108
109
  }
@@ -13,6 +13,7 @@ const suppressInstancing = getParam("noInstancing");
13
13
  const debugLightmap = getParam("debuglightmaps") ? true : false;
14
14
  const debugInstancing = getParam("debuginstancing");
15
15
  const debugProgressiveLoading = getParam("debugprogressiveload");
16
+ const suppressProgressiveLoading = getParam("noprogressiveload");
16
17
 
17
18
  export class FieldWithDefault {
18
19
  public path: string | null = null;
@@ -172,6 +173,8 @@ export class Renderer extends Behaviour {
172
173
  }
173
174
 
174
175
  awake() {
176
+ this.clearInstancingState();
177
+
175
178
  const type = this.gameObject.type;
176
179
  if (type === "Group") {
177
180
  for (const child of this.gameObject.children) {
@@ -245,6 +248,11 @@ export class Renderer extends Behaviour {
245
248
  private handles: InstanceHandle[] | null | undefined = undefined;
246
249
  private prevLayers: number[] | null | undefined = undefined;
247
250
 
251
+ private clearInstancingState() {
252
+ this._isInstancingEnabled = false;
253
+ this.handles = undefined;
254
+ this.prevLayers = undefined;
255
+ }
248
256
  setInstancingEnabled(enabled: boolean): boolean {
249
257
  if (this._isInstancingEnabled === enabled) return enabled && (this.handles === undefined || this.handles != null && this.handles.length > 0);
250
258
  this._isInstancingEnabled = enabled;
@@ -338,16 +346,16 @@ export class Renderer extends Behaviour {
338
346
  onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
339
347
 
340
348
  // progressive load before rendering so we only load textures for visible materials
341
- if (material._didRequestTextureLOD === undefined) {
349
+ if (!suppressProgressiveLoading && material._didRequestTextureLOD === undefined) {
342
350
  material._didRequestTextureLOD = 0;
343
351
  if (debugProgressiveLoading) {
344
352
  console.log("Load material LOD (with delay)", material.name);
345
353
  setTimeout(() => {
346
- NEEDLE_deferred_texture.assignTextureLOD(this.context, material);
354
+ NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
347
355
  }, 2000);
348
356
  }
349
357
  else {
350
- NEEDLE_deferred_texture.assignTextureLOD(this.context, material);
358
+ NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
351
359
  }
352
360
  }
353
361
 
@@ -1,11 +1,19 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import { VideoPlayer } from "./VideoPlayer";
3
+ import Peer from "peerjs"
4
+ import { Context } from "../engine/engine_setup";
5
+ import { RoomEvents } from "../engine/engine_networking";
6
+ import { UserJoinedOrLeftRoomModel } from "../engine/engine_networking";
7
+ import { serializeable } from "../engine/engine_serialization";
8
+ import { IModel } from "../engine/engine_networking";
3
9
 
4
10
  export class ScreenCapture extends Behaviour {
5
11
 
12
+ @serializeable()
6
13
  streamOnAwake: boolean = true;
7
14
 
8
15
  start() {
16
+ console.warn("EXPERIMENTAL screen capture", this);
9
17
  if (this.streamOnAwake)
10
18
  this.open();
11
19
  }
@@ -22,7 +30,7 @@ export class ScreenCapture extends Behaviour {
22
30
  if (player) {
23
31
  const displayMediaOptions: DisplayMediaStreamConstraints = {
24
32
  video: true,
25
- audio: false
33
+ audio: true
26
34
  };
27
35
  this.captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
28
36
  if (this.captureStream && this.requestOpen) {
@@ -30,6 +38,7 @@ export class ScreenCapture extends Behaviour {
30
38
  player.clip = this.captureStream;
31
39
  player.create(true);
32
40
  }
41
+ this.handleNetworkedVideo();
33
42
  }
34
43
  } catch (err) {
35
44
  console.error("Error: " + err);
@@ -44,4 +53,305 @@ export class ScreenCapture extends Behaviour {
44
53
  this.captureStream = null;
45
54
  }
46
55
  }
47
- }
56
+
57
+ private handleNetworkedVideo() {
58
+ if (!this.context.connection.isConnected) {
59
+ console.warn("Will not stream video - not connected to room");
60
+ return;
61
+ }
62
+ if (this.captureStream) {
63
+ const handle = PeerHandle.getOrCreate(this.context, this.guid);
64
+ handle.enable();
65
+ // if (!this.peer)
66
+ // this.peer = new Peer();
67
+ const net = new NetworkedVideo(this.context, handle);
68
+ net.enable();
69
+ net.startSendingVideo(this.captureStream);
70
+ }
71
+ }
72
+
73
+ }
74
+
75
+
76
+ enum PeerHandleEvent {
77
+ Connected = "peer-user-connected",
78
+ }
79
+
80
+ class PeerUserConnectedModel implements IModel {
81
+ /** the peer handle id */
82
+ readonly guid: string;
83
+ readonly peerId: string;
84
+ // internal so server doesnt save it to persistent storage
85
+ readonly dontSave: boolean = true;
86
+ constructor(handle: PeerHandle, peerId: string) {
87
+ this.guid = handle.id;
88
+ this.peerId = peerId;
89
+ }
90
+ }
91
+
92
+ enum CallDirection {
93
+ Incoming = "incoming",
94
+ Outgoing = "outgoing",
95
+ }
96
+
97
+ class CallHandle {
98
+ readonly direction: CallDirection;
99
+ readonly call: Peer.MediaConnection;
100
+ stream: MediaStream | null;
101
+
102
+ constructor(call: Peer.MediaConnection, direction: CallDirection) {
103
+ this.call = call;
104
+ this.direction = direction;
105
+ this.stream = null;
106
+ call.on("stream", stream => {
107
+ this.stream = stream;
108
+ });
109
+ }
110
+ }
111
+
112
+ class PeerHandle {
113
+
114
+ private static readonly instances: Map<string, PeerHandle> = new Map();
115
+
116
+ static getOrCreate(context: Context, guid: string): PeerHandle {
117
+ // if (id === undefined) {
118
+ // // randomId
119
+ // id = Math.random().toFixed(5);
120
+ // }
121
+ if (PeerHandle.instances.has(guid))
122
+ return PeerHandle.instances.get(guid)!;
123
+ const peer = new PeerHandle(context, guid);
124
+ PeerHandle.instances.set(guid, peer);
125
+ return peer;
126
+ }
127
+
128
+ getPeerIdFromUserId(userConnectionId: string): string {
129
+ // we build the peer id ourselves so we dont need to wait for peer to report it
130
+ return this.id + "-" + userConnectionId;
131
+ }
132
+
133
+ getUserIdFromPeerId(peerId: string): string {
134
+ return peerId.substring(this.id.length + 1);
135
+ }
136
+
137
+ makeCall(peerId: string, stream: MediaStream): CallHandle | undefined {
138
+ // if (userId === this.context.connection.connectionId) {
139
+ // console.warn("Can not call self");
140
+ // return;
141
+ // }
142
+ // const peerId = this.getUserPeerId(userId);
143
+ console.log("CALL", peerId);
144
+ const call = this._peer?.call(peerId, stream);
145
+ if (call)
146
+ return this.registerCall(call, CallDirection.Outgoing);
147
+ return undefined;
148
+ }
149
+
150
+ get peer(): Peer | undefined { return this._peer; }
151
+
152
+ readonly id: string;
153
+ readonly context: Context;
154
+ private _peer: Peer | undefined;
155
+ private _incomingCalls: CallHandle[] = [];
156
+ private _outgoingCalls: CallHandle[] = [];
157
+
158
+ private constructor(context: Context, id: string) {
159
+ this.context = context;
160
+ this.id = id;
161
+ this.setupPeer();
162
+ navigator["getUserMedia"] = (
163
+ navigator["getUserMedia"] || navigator["webkitGetUserMedia"] ||
164
+ navigator["mozGetUserMedia"] || navigator["msGetUserMedia"]
165
+ );
166
+ }
167
+
168
+ private _enabled: boolean = false;
169
+ private _enabledPeer: boolean = false;
170
+ private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
171
+ private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
172
+ private onPeerConnectFn: (id) => void = this.onPeerConnect.bind(this);
173
+ private onPeerReceiveCallFn: (call) => void = this.onPeerReceivingCall.bind(this);
174
+ // private _connectionPeerIdMap : Map<string, string> = new Map();
175
+
176
+ enable() {
177
+ if (this._enabled) return;
178
+ this._enabled = true;
179
+ this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
180
+ this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
181
+ this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
182
+ this.subscribePeerEvents();
183
+ }
184
+
185
+ disable() {
186
+ if (!this._enabled) return;
187
+ this._enabled = false;
188
+ this.context.connection.stopListening(RoomEvents.JoinedRoom, this.onConnectRoomFn);
189
+ this.context.connection.stopListening(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
190
+ this.context.connection.stopListening(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
191
+ this.unsubscribePeerEvents();
192
+ }
193
+
194
+ private onConnectRoom(): void {
195
+ this.setupPeer();
196
+ };
197
+
198
+ private onUserJoinedOrLeftRoom(_: UserJoinedOrLeftRoomModel): void {
199
+ };
200
+
201
+ private setupPeer() {
202
+ if (!this.context.connection.connectionId) return;
203
+ if (this._enabledPeer) return;
204
+ this._enabledPeer = true;
205
+ if (!this._peer) {
206
+ const peerId = this.getPeerIdFromUserId(this.context.connection.connectionId);
207
+ this._peer = new Peer(peerId);
208
+ }
209
+ if (this._enabled)
210
+ this.subscribePeerEvents();
211
+ }
212
+
213
+ private subscribePeerEvents() {
214
+ if (!this._peer) return;
215
+ this._peer.on("open", this.onPeerConnectFn);
216
+ this._peer.on("call", this.onPeerReceiveCallFn);
217
+ // this.context.connection.beginListen(PeerEvent.Connected, this.onRemotePeerConnect.bind(this));
218
+ }
219
+
220
+ private unsubscribePeerEvents() {
221
+ }
222
+
223
+ private onPeerConnect(id): void {
224
+ console.log("connect", id);
225
+ this.context.connection.send(PeerHandleEvent.Connected, new PeerUserConnectedModel(this, id));
226
+ }
227
+
228
+ private onPeerReceivingCall(call: Peer.MediaConnection): void {
229
+ console.log("RECEIVE CALL");
230
+ call.answer();
231
+ this.registerCall(call, CallDirection.Incoming);
232
+ }
233
+
234
+ private registerCall(call: Peer.MediaConnection, direction: CallDirection): CallHandle {
235
+ const arr = direction === CallDirection.Incoming ? this._incomingCalls : this._outgoingCalls;
236
+ const handle = new CallHandle(call, direction);
237
+ arr.push(handle);
238
+ call.on("error", err => {
239
+ console.error(err);
240
+ });
241
+ call.on("close", () => {
242
+ const index = arr.indexOf(handle);
243
+ if (index !== -1)
244
+ arr.splice(index, 1);
245
+ });
246
+ return handle;
247
+ }
248
+
249
+ // private onRemotePeerConnect(user: PeerUserConnectedModel) {
250
+ // console.log("other user connected", user);
251
+ // }
252
+ }
253
+
254
+
255
+ type UserVideoCall = {
256
+ call: Peer.MediaConnection;
257
+ stream: MediaStream;
258
+ userId: string;
259
+ }
260
+
261
+ class NetworkedVideo {
262
+
263
+ private readonly context: Context;
264
+ private readonly peer: PeerHandle;
265
+
266
+ private _sendingVideoStreams: Map<MediaStream, UserVideoCall[]> = new Map();
267
+
268
+ constructor(context: Context, peer: PeerHandle) {
269
+ this.context = context;
270
+ this.peer = peer;
271
+
272
+ // this.peer.on("open", (id) => {
273
+ // console.log("PEERID", id);
274
+ // });
275
+ // // this.peer.on("call", (call) => {
276
+ // // });
277
+ // // this.peer.on("connection", (conn) => {
278
+ // // });
279
+ // // this.peer.on("error", (err) => {
280
+ // // });
281
+ // // this.peer.on("disconnected", () => {
282
+ // // });
283
+ // // this.peer.on("close", () => {
284
+ // // });
285
+ }
286
+
287
+ startSendingVideo(stream: MediaStream) {
288
+ if (!this._sendingVideoStreams.has(stream)) {
289
+ this._sendingVideoStreams.set(stream, []);
290
+ this.updateSendingCalls();
291
+ };
292
+ }
293
+
294
+ stopSendingVideo(_steam: MediaStream) {
295
+
296
+ }
297
+
298
+ private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
299
+ private onUserConnectedFn: Function = this.onUserConnected.bind(this);
300
+ private onUserLeftFn: Function = this.onUserLeft.bind(this);
301
+
302
+ enable() {
303
+ this.context.connection.beginListen(PeerHandleEvent.Connected, this.onUserConnectedFn);
304
+ }
305
+
306
+ disable() {
307
+ // this.context.connection.stopListening(RoomEvents.UserJoinedRoom, this.onUserConnectedFn);
308
+ // this.context.connection.stopListening(RoomEvents.UserLeftRoom, this.onUserLeftFn);
309
+ }
310
+
311
+ private onConnectRoom() {
312
+
313
+ }
314
+
315
+ private onUserConnected(user: PeerUserConnectedModel) {
316
+ // console.log(this.peer.id, user.guid)
317
+ if (this.peer.id === user.guid) {
318
+ console.log("USER CONNECTED", user);
319
+ const userId = this.peer.getUserIdFromPeerId(user.peerId);
320
+ console.log(userId);
321
+ // this.updateSendingCalls();
322
+ const call = this.peer.makeCall(user.peerId, this._sendingVideoStreams.keys().next().value);
323
+
324
+ // for (const userId of this.context.connection.usersInRoom()) {
325
+ // const id = this.peer.getPeerIdFromUserId(userId);
326
+ // if(id === user.peerId) console.log(userId);
327
+ // }
328
+ }
329
+ }
330
+
331
+ private onUserLeft(_: UserJoinedOrLeftRoomModel) {
332
+
333
+ }
334
+
335
+ private updateSendingCalls() {
336
+ // for (const str of this._sendingVideoStreams.keys()) {
337
+ // const calls = this._sendingVideoStreams.get(str) || [];
338
+ // for (const userId of this.context.connection.usersInRoom()) {
339
+ // const existing = calls.find(c => c.userId === userId);
340
+ // if (!existing) {
341
+ // const call = this.peer.makeCall(userId, str);
342
+ // console.log(call);
343
+ // if (call) {
344
+ // calls.push({
345
+ // call: call,
346
+ // stream: str,
347
+ // userId: userId
348
+ // });
349
+ // }
350
+ // }
351
+ // }
352
+ // this._sendingVideoStreams.set(str, calls);
353
+ // }
354
+ }
355
+
356
+ // const call = peer.call(peerId, stream);
357
+ }
@@ -170,6 +170,7 @@ export class SpectatorCamera extends Behaviour {
170
170
 
171
171
  private onXRSessionStart(_evt) {
172
172
  if (!this.isSupportedPlatform()) return;
173
+ if (debug) console.log(this.context.mainCamera);
173
174
  if (this.context.mainCamera) {
174
175
  this.followSelf();
175
176
  }
@@ -188,6 +189,7 @@ export class SpectatorCamera extends Behaviour {
188
189
 
189
190
  private followSelf() {
190
191
  this.target = this.context.players.getPlayerView(this.context.connection.connectionId);
192
+ if (debug) console.log("Follow self", this.target);
191
193
  }
192
194
 
193
195
  // TODO: only show Spectator cam for DesktopVR;
@@ -219,6 +221,8 @@ export class SpectatorCamera extends Behaviour {
219
221
 
220
222
  this.setAvatarFlagsBeforeRender();
221
223
 
224
+ const mainCam = this.context.mainCameraComponent;
225
+
222
226
  // these should not be needed if we don't override viewport/scissor
223
227
  // renderer.getViewport(this.currentViewport);
224
228
  // renderer.getScissor(this.currentScissor);
@@ -231,7 +235,17 @@ export class SpectatorCamera extends Behaviour {
231
235
  // renderer.setViewport(left, bottom, width, height);
232
236
  // renderer.setScissor(left, bottom, width, height);
233
237
  // renderer.setScissorTest(true);
234
- renderer.setClearColor(new THREE.Color(1, 1, 1));
238
+ if (mainCam) {
239
+ const backgroundColor = mainCam.backgroundColor;
240
+ if (backgroundColor)
241
+ renderer.setClearColor(backgroundColor, backgroundColor.alpha);
242
+ this.cam.backgroundColor = backgroundColor;
243
+ this.cam.clearFlags = mainCam.clearFlags;
244
+ this.cam.nearClipPlane = mainCam.nearClipPlane;
245
+ this.cam.farClipPlane = mainCam.farClipPlane;
246
+ }
247
+ else
248
+ renderer.setClearColor(new THREE.Color(1, 1, 1));
235
249
  renderer.setRenderTarget(null); // null: direct to Canvas
236
250
  renderer.xr.enabled = false;
237
251
  const cam = this.cam?.cam;
@@ -384,8 +398,16 @@ class SpectatorHandler implements ISpectatorHandler {
384
398
  if (!target || !this.follow) return;
385
399
  switch (mode) {
386
400
  case SpectatorMode.FirstPerson:
387
- this.follow.followFactor = 20;
388
- this.follow.rotateFactor = 20;
401
+ if (this.view?.viewDevice !== ViewDevice.Browser) {
402
+ // soft follow for AR and VR
403
+ this.follow.followFactor = 5;
404
+ this.follow.rotateFactor = 5;
405
+ }
406
+ else {
407
+ // snappy follow for desktop
408
+ this.follow.followFactor = 50;
409
+ this.follow.rotateFactor = 50;
410
+ }
389
411
  target.position.set(0, 0, 0);
390
412
  break;
391
413
  case SpectatorMode.ThirdPerson:
@@ -427,10 +449,11 @@ class SpectatorSelectionController {
427
449
  downTime = this.context.time.time;
428
450
  });
429
451
  this.context.input.addEventListener(InputEvents.PointerUp, _ => {
430
- if (this.context.time.time - downTime > 1) {
452
+ const dt = this.context.time.time - downTime;
453
+ if (dt > 1) {
431
454
  this.spectator.stopSpectating();
432
455
  }
433
- else
456
+ else if (this.context.input.getPointerClicked(0) && dt < .3)
434
457
  this.trySelectObject();
435
458
  });
436
459
  }
@@ -44,6 +44,7 @@ export class SyncedRoom extends Behaviour {
44
44
  }
45
45
 
46
46
  tryJoinRoom(call: number = 0): boolean {
47
+ if(call === undefined) call = 0;
47
48
  let hasRoomParameter = false;
48
49
  if (this.urlParameterName) {
49
50
  const val = utils.getParam(this.urlParameterName);