@needle-tools/engine 2.27.0-pre → 2.29.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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/needle-engine.d.ts +193 -132
  3. package/dist/needle-engine.js +345 -345
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +19 -19
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine_input.d.ts +13 -1
  8. package/lib/engine/engine_input.js +47 -16
  9. package/lib/engine/engine_input.js.map +1 -1
  10. package/lib/engine/engine_networking_utils.js +1 -2
  11. package/lib/engine/engine_networking_utils.js.map +1 -1
  12. package/lib/engine/engine_physics.d.ts +1 -0
  13. package/lib/engine/engine_physics.js +2 -1
  14. package/lib/engine/engine_physics.js.map +1 -1
  15. package/lib/engine/engine_playerview.d.ts +26 -0
  16. package/lib/engine/engine_playerview.js +65 -0
  17. package/lib/engine/engine_playerview.js.map +1 -0
  18. package/lib/engine/engine_serialization_core.js +5 -0
  19. package/lib/engine/engine_serialization_core.js.map +1 -1
  20. package/lib/engine/engine_setup.d.ts +8 -1
  21. package/lib/engine/engine_setup.js +38 -3
  22. package/lib/engine/engine_setup.js.map +1 -1
  23. package/lib/engine/extensions/NEEDLE_lighting_settings.js +6 -2
  24. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  25. package/lib/engine/extensions/NEEDLE_techniques_webgl.js +1 -1
  26. package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
  27. package/lib/engine-components/Component.d.ts +1 -1
  28. package/lib/engine-components/Component.js.map +1 -1
  29. package/lib/engine-components/Light.js +1 -0
  30. package/lib/engine-components/Light.js.map +1 -1
  31. package/lib/engine-components/OrbitControls.js +1 -2
  32. package/lib/engine-components/OrbitControls.js.map +1 -1
  33. package/lib/engine-components/ParticleSystem.d.ts +0 -1
  34. package/lib/engine-components/ParticleSystem.js +24 -27
  35. package/lib/engine-components/ParticleSystem.js.map +1 -1
  36. package/lib/engine-components/PlayerColor.js +1 -2
  37. package/lib/engine-components/PlayerColor.js.map +1 -1
  38. package/lib/engine-components/SpectatorCamera.d.ts +24 -17
  39. package/lib/engine-components/SpectatorCamera.js +410 -181
  40. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  41. package/lib/engine-components/SyncedCamera.d.ts +8 -4
  42. package/lib/engine-components/SyncedCamera.js +15 -18
  43. package/lib/engine-components/SyncedCamera.js.map +1 -1
  44. package/lib/engine-components/WebXR.js +1 -0
  45. package/lib/engine-components/WebXR.js.map +1 -1
  46. package/lib/engine-components/WebXRAvatar.d.ts +3 -0
  47. package/lib/engine-components/WebXRAvatar.js +16 -0
  48. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  49. package/lib/engine-components/WebXRController.js +1 -1
  50. package/lib/engine-components/WebXRController.js.map +1 -1
  51. package/lib/engine-components/WebXRSync.js +3 -3
  52. package/lib/engine-components/WebXRSync.js.map +1 -1
  53. package/lib/engine-components/XRFlag.d.ts +2 -1
  54. package/lib/engine-components/XRFlag.js +1 -0
  55. package/lib/engine-components/XRFlag.js.map +1 -1
  56. package/lib/engine-components/ui/InputField.js +2 -2
  57. package/package.json +1 -1
  58. package/src/engine/engine_components.js +16 -0
  59. package/src/engine/engine_input.ts +62 -20
  60. package/src/engine/engine_networking_utils.ts +1 -2
  61. package/src/engine/engine_physics.ts +2 -1
  62. package/src/engine/engine_playerview.ts +80 -0
  63. package/src/engine/engine_serialization_core.ts +8 -0
  64. package/src/engine/engine_setup.ts +37 -3
  65. package/src/engine/extensions/NEEDLE_lighting_settings.ts +4 -2
  66. package/src/engine/extensions/NEEDLE_techniques_webgl.ts +1 -1
  67. package/src/engine-components/Component.ts +1 -1
  68. package/src/engine-components/Light.ts +3 -0
  69. package/src/engine-components/OrbitControls.ts +1 -2
  70. package/src/engine-components/ParticleSystem.ts +25 -26
  71. package/src/engine-components/PlayerColor.ts +1 -1
  72. package/src/engine-components/SpectatorCamera.ts +466 -194
  73. package/src/engine-components/SyncedCamera.ts +23 -22
  74. package/src/engine-components/WebXR.ts +1 -0
  75. package/src/engine-components/WebXRAvatar.ts +22 -2
  76. package/src/engine-components/WebXRController.ts +1 -1
  77. package/src/engine-components/WebXRSync.ts +3 -3
  78. package/src/engine-components/XRFlag.ts +1 -0
  79. package/src/engine-components/ui/InputField.ts +2 -2
@@ -1,134 +1,127 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import { Camera } from "./Camera";
3
3
  import * as THREE from "three";
4
+ import { OrbitControls } from "./OrbitControls";
4
5
  import { WebXR, WebXREvent } from "./WebXR";
5
6
  import { AvatarMarker } from "./WebXRAvatar";
6
7
  import { XRStateFlag } from "./XRFlag";
7
8
  import { SmoothFollow } from "./SmoothFollow";
8
- import { setWorldPosition, setWorldQuaternion, getWorldPosition, getWorldQuaternion } from "../engine/engine_three_utils";
9
- import { KeyCode } from "../engine/engine_input";
9
+ import { InputEvents } from "../engine/engine_input";
10
+ import { getParam } from "../engine/engine_utils";
11
+ import { ViewDevice } from "../engine/engine_playerview";
12
+ import { RaycastOptions } from "../engine/engine_physics";
13
+ import { RoomEvents } from "../engine/engine_networking";
14
+ export var SpectatorMode;
15
+ (function (SpectatorMode) {
16
+ SpectatorMode[SpectatorMode["FirstPerson"] = 0] = "FirstPerson";
17
+ SpectatorMode[SpectatorMode["ThirdPerson"] = 1] = "ThirdPerson";
18
+ })(SpectatorMode || (SpectatorMode = {}));
19
+ const debug = getParam("debugspectator");
10
20
  export class SpectatorCamera extends Behaviour {
11
21
  cam = null;
12
- _firstPersonMode = true;
13
- get firstPersonMode() {
14
- return this._firstPersonMode ?? false;
15
- }
16
- set firstPersonMode(val) {
17
- this._firstPersonMode = val;
18
- // if (this._firstPersonMode) this.enableFirstPersonMode();
19
- // else this.enableThirdPersonMode();
20
- }
21
- enableFirstPersonMode() {
22
- this._firstPersonMode = true;
23
- this.updateUI();
24
- if (this.cam) {
25
- if (this.firstPersonFollow) {
26
- this.firstPersonFollow.enabled = true;
27
- this.firstPersonFollow.updateNow(true);
22
+ _mode = SpectatorMode.FirstPerson;
23
+ get mode() { return this._mode; }
24
+ set mode(val) {
25
+ this._mode = val;
26
+ }
27
+ /** if this user is currently spectating someone else */
28
+ get isSpectating() {
29
+ return this._handler?.currentTarget !== undefined;
30
+ }
31
+ isSpectatingUser(userId) {
32
+ return this.target?.userId === userId;
33
+ }
34
+ isFollowedBy(userId) {
35
+ return this.followers?.includes(userId);
36
+ }
37
+ /** list of other users that are following me */
38
+ get followers() {
39
+ return this._networking.followers;
40
+ }
41
+ stopSpectating() {
42
+ if (this.context.isInXR) {
43
+ this.followSelf();
44
+ return;
45
+ }
46
+ this.target = undefined;
47
+ }
48
+ /** player view to follow */
49
+ set target(target) {
50
+ if (this._handler) {
51
+ // if (this.target?.userId) {
52
+ // const isFollowedByThisUser = this.followers.includes(this.target.userId);
53
+ // if (isFollowedByThisUser) {
54
+ // console.warn("Can not follow follower");
55
+ // target = undefined;
56
+ // }
57
+ // }
58
+ const prev = this._handler.currentTarget?.userId;
59
+ const self = this.context.players.getPlayerView(this.context.connection.connectionId);
60
+ // if user is in XR and sets target to self disable it
61
+ if (target === undefined || (this.context.isInXR === false && self?.currentObject === target.currentObject)) {
62
+ if (this._handler.currentTarget !== undefined) {
63
+ this._handler.disable();
64
+ GameObject.setActive(this.gameObject, false);
65
+ if (this.orbit)
66
+ this.orbit.enabled = true;
67
+ this._networking.onSpectatedObjectChanged(target, prev);
68
+ }
28
69
  }
29
- if (this.orbit) {
30
- this.orbit.enabled = false;
70
+ else if (this._handler.currentTarget !== target) {
71
+ this._handler.set(target);
72
+ GameObject.setActive(this.gameObject, true);
73
+ if (this.orbit)
74
+ this.orbit.enabled = false;
75
+ this._networking.onSpectatedObjectChanged(target, prev);
31
76
  }
32
77
  }
33
78
  }
34
- enableThirdPersonMode() {
35
- this._firstPersonMode = false;
36
- this.updateUI();
37
- if (this.firstPersonFollow) {
38
- this.firstPersonFollow.enabled = false;
39
- }
40
- if (!this.cam)
41
- return;
42
- setWorldPosition(this.cam.cam, this._orbitStartPos);
43
- setWorldQuaternion(this.cam.cam, this._orbitStartRot);
44
- setWorldPosition(this.cam.gameObject, this._orbitStartPos2);
45
- setWorldQuaternion(this.cam.gameObject, this._orbitStartRot2);
46
- if (this.orbit) {
47
- this.orbit.enabled = true;
48
- this.orbit.setFromTargetPosition();
49
- }
79
+ get target() {
80
+ return this._handler?.currentTarget;
81
+ }
82
+ requestAllFollowMe() {
83
+ this._networking.onRequestFollowMe();
84
+ }
85
+ get isSpectatingSelf() {
86
+ return this.isSpectating && this.target?.currentObject === this.context.players.getPlayerView(this.context.connection.connectionId)?.currentObject;
50
87
  }
51
88
  // private currentViewport : THREE.Vector4 = new THREE.Vector4();
52
89
  // private currentScissor : THREE.Vector4 = new THREE.Vector4();
53
90
  // private currentScissorTest : boolean = false;
54
91
  orbit = null;
55
- firstPersonFollow = null;
56
- spectatorUIDomElement = null;
92
+ _handler;
57
93
  eventSub_WebXRRequestStartEvent = null;
58
94
  eventSub_WebXRStartEvent = null;
59
95
  eventSub_WebXREndEvent = null;
96
+ _debug;
97
+ _networking;
60
98
  awake() {
99
+ this._debug = new SpectatorSelectionController(this.context, this);
100
+ this._networking = new SpectatorCamNetworking(this.context, this);
101
+ this._networking.awake();
61
102
  GameObject.setActive(this.gameObject, false);
62
- if (!this.isSupportedPlatform()) {
63
- console.log("Disable spectator cam", window.navigator.userAgent, this);
64
- return;
65
- }
66
103
  this.cam = GameObject.getComponent(this.gameObject, Camera);
67
104
  if (!this.cam) {
68
- // TODO: adding camera component does currently not work properly
69
105
  console.error("Spectator camera needs camera component", this);
70
106
  return;
71
- // this.cam = GameObject.addNewComponent(this.gameObject, Camera) as Camera;
72
- }
73
- const uiQuery = "#spectator-camera-ui";
74
- this.spectatorUIDomElement = this.context.domElement.querySelector(uiQuery);
75
- if (!this.spectatorUIDomElement) {
76
- console.warn("Could not find spectator camera UI element", uiQuery);
77
- // this.spectatorUIDomElement = document.createElement("div");
78
- // this.spectatorUIDomElement.id = "spectator-camera-ui";
79
- // this.spectatorUIDomElement.classList.add("desktop");
80
- // this.context.domElement.appendChild(this.spectatorUIDomElement);
81
- // const toggle = document.createElement("button");
82
- // toggle.id = "toggle-spectator-view";
83
- // this.spectatorUIDomElement.appendChild(toggle);
84
- }
85
- this.spectatorUIDomElement?.classList.add("hidden");
86
- if (this.cam) {
87
- this.cam.enabled = true;
88
- this._orbitStartPos.copy(getWorldPosition(this.cam.cam));
89
- this._orbitStartRot.copy(getWorldQuaternion(this.cam.cam));
90
- this._orbitStartPos2.copy(getWorldPosition(this.cam.gameObject));
91
- this._orbitStartRot2.copy(getWorldQuaternion(this.cam.gameObject));
92
107
  }
108
+ if (!this._handler && this.cam)
109
+ this._handler = new SpectatorHandler(this.context, this.cam, this);
93
110
  this.eventSub_WebXRRequestStartEvent = this.onXRSessionRequestStart.bind(this);
94
111
  this.eventSub_WebXRStartEvent = this.onXRSessionStart.bind(this);
95
112
  this.eventSub_WebXREndEvent = this.onXRSessionEnded.bind(this);
96
113
  WebXR.addEventListener(WebXREvent.RequestVRSession, this.eventSub_WebXRRequestStartEvent);
97
114
  WebXR.addEventListener(WebXREvent.XRStarted, this.eventSub_WebXRStartEvent);
98
115
  WebXR.addEventListener(WebXREvent.XRStopped, this.eventSub_WebXREndEvent);
99
- this.context.domElement.querySelector("button#toggle-spectator-view")?.addEventListener("click", this.toggleView.bind(this));
100
- this.updateUI();
116
+ this.orbit = GameObject.getComponent(this.context.mainCamera, OrbitControls);
101
117
  }
102
118
  onDestroy() {
119
+ this.stopSpectating();
103
120
  WebXR.removeEventListener(WebXREvent.RequestVRSession, this.eventSub_WebXRStartEvent);
104
121
  WebXR.removeEventListener(WebXREvent.XRStarted, this.eventSub_WebXRStartEvent);
105
122
  WebXR.removeEventListener(WebXREvent.XRStopped, this.eventSub_WebXREndEvent);
106
- }
107
- toggleView() {
108
- this.firstPersonMode = !this.firstPersonMode;
109
- }
110
- updateUI() {
111
- const el = this.context.domElement.querySelector("button#toggle-spectator-view");
112
- if (!el)
113
- return;
114
- el.disabled = true; // !this._sessionHasStarted;
115
- if (el.firstChild) {
116
- if (!this._sessionHasStarted) {
117
- el.firstChild.textContent = "Waiting for VR session";
118
- }
119
- else
120
- el.firstChild.textContent = this.firstPersonMode ? "👁️" : "📷";
121
- }
122
- if (el.children?.length > 1) {
123
- const tooltipElement = el.children[1];
124
- tooltipElement.style.display = !this._sessionHasStarted ? "none" : "block";
125
- if (this.firstPersonMode) {
126
- tooltipElement.textContent = "First-Person View. See what the person in VR sees";
127
- }
128
- else {
129
- tooltipElement.textContent = "Third-Person View. Freely move the camera around";
130
- }
131
- }
123
+ this._handler?.destroy();
124
+ this._networking.destroy();
132
125
  }
133
126
  isSupportedPlatform() {
134
127
  const ua = window.navigator.userAgent;
@@ -137,69 +130,41 @@ export class SpectatorCamera extends Behaviour {
137
130
  return standalone && !isHololens;
138
131
  }
139
132
  onXRSessionRequestStart(_evt) {
140
- this._sessionHasStarted = false;
141
- this.spectatorUIDomElement?.classList.remove("hidden");
133
+ if (!this.isSupportedPlatform())
134
+ return;
142
135
  GameObject.setActive(this.gameObject, true);
143
- // if (!this.orbit && this.cam) {
144
- // const lookAt = GameObject.addNewComponent(this.gameObject, LookAtConstraint);
145
- // lookAt.constraintActive = true;
146
- // const center = new THREE.Object3D();
147
- // this.context.scene.add(center);
148
- // lookAt.sources = [center];
149
- // this.orbit = GameObject.addNewComponent(this.cam?.gameObject, OrbitControls);
150
- // this.orbit.lookAtConstraint = lookAt;
151
- // this.orbit.debugLog = true;
152
- // this.orbit.enabled = !this.firstPersonMode;
153
- // }
154
- if (this.cam && this.cam.cam) {
155
- setWorldPosition(this.cam.cam, this._orbitStartPos);
156
- setWorldQuaternion(this.cam.cam, this._orbitStartRot);
157
- setWorldPosition(this.cam.gameObject, this._orbitStartPos2);
158
- setWorldQuaternion(this.cam.gameObject, this._orbitStartRot2);
159
- }
160
- if (this.firstPersonMode) {
161
- this.enableFirstPersonMode();
162
- }
163
- else
164
- this.enableThirdPersonMode();
165
136
  }
166
137
  onXRSessionStart(_evt) {
167
- this._sessionHasStarted = true;
168
- this.updateUI();
138
+ if (!this.isSupportedPlatform())
139
+ return;
169
140
  if (this.context.mainCamera) {
170
- const cam = this.context.renderer.xr.getCamera(this.context.mainCamera);
171
- this.setupFollowMode(cam);
141
+ this.followSelf();
172
142
  }
173
143
  }
174
144
  onXRSessionEnded(_evt) {
175
- this._sessionHasStarted = false;
176
- this._firstPersonIsSetup = false;
177
- this.spectatorUIDomElement?.classList.add("hidden");
145
+ this.context.removeCamera(this.cam);
178
146
  GameObject.setActive(this.gameObject, false);
147
+ if (this.orbit)
148
+ this.orbit.enabled = true;
149
+ this._handler?.set(undefined);
150
+ this._handler?.disable();
151
+ if (this.isSpectatingSelf)
152
+ this.stopSpectating();
153
+ }
154
+ followSelf() {
155
+ this.target = this.context.players.getPlayerView(this.context.connection.connectionId);
179
156
  }
180
- _sessionHasStarted = false;
181
- _firstPersonIsSetup = false;
182
- _orbitStartPos = new THREE.Vector3();
183
- _orbitStartRot = new THREE.Quaternion();
184
- _orbitStartPos2 = new THREE.Vector3();
185
- _orbitStartRot2 = new THREE.Quaternion();
186
157
  // TODO: only show Spectator cam for DesktopVR;
187
158
  // don't show for AR, don't show on Quest
188
159
  // TODO: properly align cameras on enter/exit VR, seems currently spectator cam breaks alignment
189
160
  onAfterRender() {
190
161
  if (!this.cam)
191
162
  return;
192
- if (this.context.input.isKeyDown(KeyCode.KEY_S))
193
- this.firstPersonMode = !this.firstPersonMode;
194
- this.updateFollowSettings();
195
163
  const renderer = this.context.renderer;
196
164
  const xrWasEnabled = renderer.xr.enabled;
197
- // these should not be needed if we don't override viewport/scissor
198
- // renderer.getViewport(this.currentViewport);
199
- // renderer.getScissor(this.currentScissor);
200
- // this.currentScissorTest = renderer.getScissorTest();
201
- if (!renderer.xr.isPresenting)
165
+ if (!renderer.xr.isPresenting && !this._handler?.currentTarget)
202
166
  return;
167
+ this._handler?.update(this._mode);
203
168
  // remember XR render target so we can restore later
204
169
  const previousRenderTarget = renderer.getRenderTarget();
205
170
  let oldFramebuffer = null;
@@ -212,6 +177,10 @@ export class SpectatorCamera extends Behaviour {
212
177
  renderer.state.bindXRFramebuffer(null);
213
178
  }
214
179
  this.setAvatarFlagsBeforeRender();
180
+ // these should not be needed if we don't override viewport/scissor
181
+ // renderer.getViewport(this.currentViewport);
182
+ // renderer.getScissor(this.currentScissor);
183
+ // this.currentScissorTest = renderer.getScissorTest();
215
184
  // for scissor rendering (e.g. just a part of the screen / viewport, multiplayer split view)
216
185
  // let left = 0;
217
186
  // let bottom = 100;
@@ -241,53 +210,13 @@ export class SpectatorCamera extends Behaviour {
241
210
  renderer.state.bindXRFramebuffer(oldFramebuffer);
242
211
  this.resetAvatarFlags();
243
212
  }
244
- setupFollowMode(object) {
245
- if (!object)
246
- return;
247
- if (!this.cam)
248
- return;
249
- if (this._firstPersonIsSetup)
250
- return;
251
- this._firstPersonIsSetup = true;
252
- this.firstPersonFollow = GameObject.addNewComponent(this.cam.gameObject, SmoothFollow);
253
- const target = new THREE.Object3D();
254
- object.add(target);
255
- target.add(new THREE.AxesHelper(.2));
256
- this.firstPersonFollow.target = target;
257
- this.updateFollowSettings();
258
- const perspectiveCamera = this.context.mainCamera;
259
- if (perspectiveCamera) {
260
- this.cam.cam.near = perspectiveCamera.near;
261
- this.cam.cam.far = perspectiveCamera.far;
262
- this.cam.cam.updateProjectionMatrix();
263
- }
264
- if (this.orbit)
265
- this.orbit.enabled = false;
266
- }
267
- updateFollowSettings() {
268
- const target = this.firstPersonFollow?.target;
269
- if (!target || !this.firstPersonFollow)
270
- return;
271
- if (this.firstPersonMode === false) {
272
- this.firstPersonFollow.followFactor = 3;
273
- this.firstPersonFollow.rotateFactor = 2;
274
- this.firstPersonFollow.flipForward = false;
275
- target.position.set(0, .5, 1.5);
276
- target.quaternion.identity();
277
- // lookAtInverse(target, new THREE.Vector3(0, 0, 0));
278
- }
279
- else {
280
- target.position.set(0, 0, 0);
281
- target.quaternion.identity();
282
- this.firstPersonFollow.followFactor = 12;
283
- this.firstPersonFollow.rotateFactor = 5;
284
- this.firstPersonFollow.flipForward = false;
285
- }
286
- }
287
213
  setAvatarFlagsBeforeRender() {
214
+ const isFirstPersonMode = this._mode === SpectatorMode.FirstPerson;
288
215
  for (const av of AvatarMarker.instances) {
289
216
  if (av.avatar && "isLocalAvatar" in av.avatar) {
290
- const mask = this.firstPersonMode && this._firstPersonIsSetup && av.avatar.isLocalAvatar ? XRStateFlag.FirstPerson : XRStateFlag.ThirdPerson;
217
+ let mask = XRStateFlag.All;
218
+ if (this.isSpectatingSelf)
219
+ mask = isFirstPersonMode && av.avatar.isLocalAvatar ? XRStateFlag.FirstPerson : XRStateFlag.ThirdPerson;
291
220
  const flags = av.avatar.flags;
292
221
  if (!flags)
293
222
  continue;
@@ -315,4 +244,304 @@ export class SpectatorCamera extends Behaviour {
315
244
  }
316
245
  }
317
246
  }
247
+ class SpectatorHandler {
248
+ context;
249
+ cam;
250
+ spectator;
251
+ follow;
252
+ target;
253
+ view;
254
+ currentObject;
255
+ get currentTarget() {
256
+ return this.view;
257
+ }
258
+ constructor(context, cam, spectator) {
259
+ this.context = context;
260
+ this.cam = cam;
261
+ this.spectator = spectator;
262
+ }
263
+ set(view) {
264
+ const followObject = view?.currentObject;
265
+ if (!followObject) {
266
+ this.spectator.stopSpectating();
267
+ return;
268
+ }
269
+ if (followObject === this.currentObject)
270
+ return;
271
+ this.currentObject = followObject;
272
+ this.view = view;
273
+ if (!this.follow)
274
+ this.follow = GameObject.addNewComponent(this.cam.gameObject, SmoothFollow);
275
+ if (!this.target)
276
+ this.target = new THREE.Object3D();
277
+ followObject.add(this.target);
278
+ this.follow.enabled = true;
279
+ this.follow.target = this.target;
280
+ // this.context.setCurrentCamera(this.cam);
281
+ if (debug)
282
+ console.log("FOLLOW", followObject);
283
+ if (!this.context.isInXR) {
284
+ this.context.setCurrentCamera(this.cam);
285
+ }
286
+ else
287
+ this.context.removeCamera(this.cam);
288
+ }
289
+ disable() {
290
+ if (debug)
291
+ console.log("STOP FOLLOW", this.currentObject);
292
+ this.view = undefined;
293
+ this.currentObject = undefined;
294
+ this.context.removeCamera(this.cam);
295
+ if (this.follow)
296
+ this.follow.enabled = false;
297
+ }
298
+ destroy() {
299
+ this.target?.removeFromParent();
300
+ if (this.follow)
301
+ GameObject.destroy(this.follow);
302
+ }
303
+ update(mode) {
304
+ if (this.currentTarget?.isConnected === false || this.currentTarget?.removed === true) {
305
+ if (debug)
306
+ console.log("Target disconnected or timeout", this.currentTarget);
307
+ this.spectator.stopSpectating();
308
+ return;
309
+ }
310
+ if (this.currentTarget && this.currentTarget?.currentObject !== this.currentObject) {
311
+ if (debug)
312
+ console.log("Target changed", this.currentObject, "to", this.currentTarget.currentObject);
313
+ this.set(this.currentTarget);
314
+ }
315
+ const perspectiveCamera = this.context.mainCamera;
316
+ if (perspectiveCamera) {
317
+ if (this.cam.cam.near !== perspectiveCamera.near || this.cam.cam.far !== perspectiveCamera.far) {
318
+ this.cam.cam.near = perspectiveCamera.near;
319
+ this.cam.cam.far = perspectiveCamera.far;
320
+ this.cam.cam.updateProjectionMatrix();
321
+ }
322
+ }
323
+ const target = this.follow?.target;
324
+ if (!target || !this.follow)
325
+ return;
326
+ switch (mode) {
327
+ case SpectatorMode.FirstPerson:
328
+ this.follow.followFactor = 20;
329
+ this.follow.rotateFactor = 20;
330
+ target.position.set(0, 0, 0);
331
+ break;
332
+ case SpectatorMode.ThirdPerson:
333
+ this.follow.followFactor = 3;
334
+ this.follow.rotateFactor = 2;
335
+ target.position.set(0, .5, 1.5);
336
+ break;
337
+ }
338
+ this.follow.flipForward = false;
339
+ // console.log(this.view);
340
+ if (this.view?.viewDevice !== ViewDevice.Browser)
341
+ target.quaternion.copy(_inverseYQuat);
342
+ else
343
+ target.quaternion.identity();
344
+ }
345
+ }
346
+ const _inverseYQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
347
+ class SpectatorSelectionController {
348
+ context;
349
+ spectator;
350
+ constructor(context, spectator) {
351
+ this.context = context;
352
+ this.spectator = spectator;
353
+ console.log("Click other avatars or cameras to follow them. Press ESC to exit spectator mode.");
354
+ window.addEventListener("keydown", (evt) => {
355
+ const key = evt.key;
356
+ if (key === "Escape") {
357
+ this.spectator.stopSpectating();
358
+ }
359
+ });
360
+ let downTime = 0;
361
+ this.context.input.addEventListener(InputEvents.PointerDown, _ => {
362
+ downTime = this.context.time.time;
363
+ });
364
+ this.context.input.addEventListener(InputEvents.PointerUp, _ => {
365
+ if (this.context.time.time - downTime > 1) {
366
+ this.spectator.stopSpectating();
367
+ }
368
+ else
369
+ this.trySelectObject();
370
+ });
371
+ }
372
+ trySelectObject() {
373
+ const opts = new RaycastOptions();
374
+ opts.setMask(0xffffff);
375
+ // opts.cam = this.spectator.cam?.cam;
376
+ const hits = this.context.physics.raycast(opts);
377
+ if (debug)
378
+ console.log(...hits);
379
+ if (hits?.length) {
380
+ for (const hit of hits) {
381
+ if (hit.distance < .2)
382
+ continue;
383
+ const obj = hit.object;
384
+ const avatar = GameObject.getComponentInParent(obj, AvatarMarker);
385
+ const id = avatar?.connectionId;
386
+ if (id) {
387
+ const view = this.context.players.getPlayerView(id);
388
+ this.spectator.target = view;
389
+ if (debug)
390
+ console.log("spectate", id, avatar);
391
+ break;
392
+ }
393
+ }
394
+ }
395
+ }
396
+ }
397
+ class SpectatorFollowerChangedEventModel {
398
+ /** the user that is following */
399
+ guid;
400
+ dontSave = true;
401
+ /** the user being followed */
402
+ targetUserId;
403
+ stoppedFollowing;
404
+ constructor(connectionId, userId, stoppedFollowing) {
405
+ this.guid = connectionId;
406
+ this.targetUserId = userId;
407
+ this.stoppedFollowing = stoppedFollowing;
408
+ }
409
+ }
410
+ class SpectatorFollowEventModel {
411
+ guid;
412
+ userId;
413
+ constructor(comp, userId) {
414
+ this.guid = comp.guid;
415
+ this.userId = userId;
416
+ }
417
+ }
418
+ class SpectatorCamNetworking {
419
+ followers = [];
420
+ context;
421
+ spectator;
422
+ _followerEventMethod;
423
+ _requestFollowMethod;
424
+ _joinedRoomMethod;
425
+ constructor(context, spectator) {
426
+ this.context = context;
427
+ this.spectator = spectator;
428
+ this._followerEventMethod = this.onFollowerEvent.bind(this);
429
+ this._requestFollowMethod = this.onRequestFollowEvent.bind(this);
430
+ this._joinedRoomMethod = this.onUserJoinedRoom.bind(this);
431
+ }
432
+ awake() {
433
+ this.context.connection.beginListen("spectator-follower-changed", this._followerEventMethod);
434
+ this.context.connection.beginListen("spectator-request-follow", this._requestFollowMethod);
435
+ this.context.connection.beginListen(RoomEvents.JoinedRoom, this._joinedRoomMethod);
436
+ document.addEventListener("keydown", evt => {
437
+ if (evt.key === "f") {
438
+ this.onRequestFollowMe();
439
+ }
440
+ else if (evt.key === "Escape") {
441
+ this.onRequestFollowMe(true);
442
+ }
443
+ });
444
+ }
445
+ destroy() {
446
+ this.context.connection.stopListening("spectator-follower-changed", this._followerEventMethod);
447
+ this.context.connection.stopListening("spectator-request-follow", this._requestFollowMethod);
448
+ this.context.connection.stopListening(RoomEvents.JoinedRoom, this._joinedRoomMethod);
449
+ }
450
+ onSpectatedObjectChanged(target, _prevId) {
451
+ if (debug)
452
+ console.log(this.context.connection.connectionId, "onSpectatedObjectChanged", target, _prevId);
453
+ if (this.context.connection.connectionId) {
454
+ const stopped = target?.userId === undefined;
455
+ const userId = stopped ? _prevId : target?.userId;
456
+ const evt = new SpectatorFollowerChangedEventModel(this.context.connection.connectionId, userId, stopped);
457
+ this.context.connection.send("spectator-follower-changed", evt);
458
+ }
459
+ }
460
+ onRequestFollowMe(stop = false) {
461
+ if (debug)
462
+ console.log("Request follow", this.context.connection.connectionId);
463
+ if (this.context.connection.connectionId) {
464
+ this.spectator.stopSpectating();
465
+ const id = stop ? undefined : this.context.connection.connectionId;
466
+ const model = new SpectatorFollowEventModel(this.spectator, id);
467
+ this.context.connection.send("spectator-request-follow", model);
468
+ }
469
+ }
470
+ onUserJoinedRoom() {
471
+ if (getParam("followme")) {
472
+ this.onRequestFollowMe();
473
+ }
474
+ }
475
+ onFollowerEvent(evt) {
476
+ const userBeingFollowed = evt.targetUserId;
477
+ const userThatIsFollowing = evt.guid;
478
+ if (debug)
479
+ console.log(evt);
480
+ if (userBeingFollowed === this.context.connection.connectionId) {
481
+ if (evt.stoppedFollowing) {
482
+ const index = this.followers.indexOf(userThatIsFollowing);
483
+ if (index !== -1) {
484
+ this.followers.splice(index, 1);
485
+ this.removeDisconnectedFollowers();
486
+ console.log(userThatIsFollowing, "unfollows you", this.followers.length);
487
+ }
488
+ }
489
+ else {
490
+ if (!this.followers.includes(userThatIsFollowing)) {
491
+ this.followers.push(userThatIsFollowing);
492
+ this.removeDisconnectedFollowers();
493
+ console.log(userThatIsFollowing, "follows you", this.followers.length);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ removeDisconnectedFollowers() {
499
+ for (let i = this.followers.length - 1; i >= 0; i--) {
500
+ const id = this.followers[i];
501
+ if (this.context.connection.userIsInRoom(id) === false) {
502
+ this.followers.splice(i, 1);
503
+ }
504
+ }
505
+ }
506
+ _lastRequestFollowUser;
507
+ onRequestFollowEvent(evt) {
508
+ this._lastRequestFollowUser = evt;
509
+ if (evt.userId === this.context.connection.connectionId) {
510
+ this.spectator.stopSpectating();
511
+ }
512
+ else if (evt.userId === undefined) {
513
+ // this will currently also stop spectating if the user is not following you
514
+ this.spectator.stopSpectating();
515
+ }
516
+ else {
517
+ const view = this.context.players.getPlayerView(evt.userId);
518
+ if (view) {
519
+ this.spectator.target = view;
520
+ }
521
+ else {
522
+ if (debug)
523
+ console.warn("Could not find view", evt.userId);
524
+ this.enforceFollow();
525
+ return false;
526
+ }
527
+ }
528
+ return true;
529
+ }
530
+ _enforceFollowInterval;
531
+ enforceFollow() {
532
+ if (this._enforceFollowInterval)
533
+ return;
534
+ this._enforceFollowInterval = setInterval(() => {
535
+ if (this._lastRequestFollowUser === undefined || this._lastRequestFollowUser.userId && this.spectator.isFollowedBy(this._lastRequestFollowUser.userId)) {
536
+ clearInterval(this._enforceFollowInterval);
537
+ this._enforceFollowInterval = undefined;
538
+ }
539
+ else {
540
+ if (debug)
541
+ console.log("REQUEST FOLLOW AGAIN", this._lastRequestFollowUser.userId);
542
+ this.onRequestFollowEvent(this._lastRequestFollowUser);
543
+ }
544
+ }, 1000);
545
+ }
546
+ }
318
547
  //# sourceMappingURL=SpectatorCamera.js.map