@needle-tools/engine 2.28.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 (130) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/needle-engine.d.ts +232 -139
  3. package/dist/needle-engine.js +349 -345
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +24 -20
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine.d.ts +1 -0
  8. package/lib/engine/engine_input.d.ts +13 -1
  9. package/lib/engine/engine_input.js +47 -16
  10. package/lib/engine/engine_input.js.map +1 -1
  11. package/lib/engine/engine_physics.d.ts +1 -0
  12. package/lib/engine/engine_physics.js +2 -1
  13. package/lib/engine/engine_physics.js.map +1 -1
  14. package/lib/engine/engine_playerview.d.ts +26 -0
  15. package/lib/engine/engine_playerview.js +65 -0
  16. package/lib/engine/engine_playerview.js.map +1 -0
  17. package/lib/engine/engine_serialization.d.ts +1 -0
  18. package/lib/engine/engine_serialization.js +1 -0
  19. package/lib/engine/engine_serialization.js.map +1 -1
  20. package/lib/engine/engine_serialization_core.js +5 -0
  21. package/lib/engine/engine_serialization_core.js.map +1 -1
  22. package/lib/engine/engine_setup.d.ts +8 -0
  23. package/lib/engine/engine_setup.js +23 -0
  24. package/lib/engine/engine_setup.js.map +1 -1
  25. package/lib/engine/engine_utils.d.ts +1 -1
  26. package/lib/engine/engine_utils.js +25 -8
  27. package/lib/engine/engine_utils.js.map +1 -1
  28. package/lib/engine/extensions/NEEDLE_deferred_texture.d.ts +1 -1
  29. package/lib/engine/extensions/NEEDLE_deferred_texture.js +26 -14
  30. package/lib/engine/extensions/NEEDLE_deferred_texture.js.map +1 -1
  31. package/lib/engine/extensions/NEEDLE_lighting_settings.js +6 -2
  32. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  33. package/lib/engine/extensions/extension_utils.js +24 -13
  34. package/lib/engine/extensions/extension_utils.js.map +1 -1
  35. package/lib/engine/extensions/extensions.js +3 -1
  36. package/lib/engine/extensions/extensions.js.map +1 -1
  37. package/lib/engine-components/Camera.js +7 -0
  38. package/lib/engine-components/Camera.js.map +1 -1
  39. package/lib/engine-components/Component.d.ts +1 -1
  40. package/lib/engine-components/Component.js.map +1 -1
  41. package/lib/engine-components/Light.js +1 -0
  42. package/lib/engine-components/Light.js.map +1 -1
  43. package/lib/engine-components/OrbitControls.js +3 -3
  44. package/lib/engine-components/OrbitControls.js.map +1 -1
  45. package/lib/engine-components/ParticleSystem.d.ts +0 -1
  46. package/lib/engine-components/ParticleSystem.js +24 -27
  47. package/lib/engine-components/ParticleSystem.js.map +1 -1
  48. package/lib/engine-components/PlayerColor.js +1 -2
  49. package/lib/engine-components/PlayerColor.js.map +1 -1
  50. package/lib/engine-components/Renderer.d.ts +1 -0
  51. package/lib/engine-components/Renderer.js +10 -3
  52. package/lib/engine-components/Renderer.js.map +1 -1
  53. package/lib/engine-components/ScreenCapture.d.ts +1 -0
  54. package/lib/engine-components/ScreenCapture.js +265 -1
  55. package/lib/engine-components/ScreenCapture.js.map +1 -1
  56. package/lib/engine-components/SpectatorCamera.d.ts +24 -17
  57. package/lib/engine-components/SpectatorCamera.js +435 -182
  58. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  59. package/lib/engine-components/SyncedCamera.d.ts +8 -4
  60. package/lib/engine-components/SyncedCamera.js +15 -18
  61. package/lib/engine-components/SyncedCamera.js.map +1 -1
  62. package/lib/engine-components/SyncedRoom.js +2 -0
  63. package/lib/engine-components/SyncedRoom.js.map +1 -1
  64. package/lib/engine-components/VideoPlayer.d.ts +10 -1
  65. package/lib/engine-components/VideoPlayer.js +64 -15
  66. package/lib/engine-components/VideoPlayer.js.map +1 -1
  67. package/lib/engine-components/Volume.d.ts +4 -0
  68. package/lib/engine-components/Volume.js +44 -3
  69. package/lib/engine-components/Volume.js.map +1 -1
  70. package/lib/engine-components/WebARSessionRoot.d.ts +9 -2
  71. package/lib/engine-components/WebARSessionRoot.js +69 -24
  72. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  73. package/lib/engine-components/WebXR.d.ts +6 -3
  74. package/lib/engine-components/WebXR.js +43 -7
  75. package/lib/engine-components/WebXR.js.map +1 -1
  76. package/lib/engine-components/WebXRAvatar.d.ts +3 -0
  77. package/lib/engine-components/WebXRAvatar.js +20 -0
  78. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  79. package/lib/engine-components/WebXRController.js +14 -8
  80. package/lib/engine-components/WebXRController.js.map +1 -1
  81. package/lib/engine-components/WebXRSync.js +3 -3
  82. package/lib/engine-components/WebXRSync.js.map +1 -1
  83. package/lib/engine-components/XRFlag.d.ts +2 -1
  84. package/lib/engine-components/XRFlag.js +1 -0
  85. package/lib/engine-components/XRFlag.js.map +1 -1
  86. package/lib/engine-components/ui/CanvasGroup.d.ts +1 -0
  87. package/lib/engine-components/ui/CanvasGroup.js +1 -0
  88. package/lib/engine-components/ui/CanvasGroup.js.map +1 -1
  89. package/lib/engine-components/ui/EventSystem.js +13 -4
  90. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  91. package/lib/engine-components/ui/Graphic.d.ts +1 -0
  92. package/lib/engine-components/ui/Graphic.js +2 -0
  93. package/lib/engine-components/ui/Graphic.js.map +1 -1
  94. package/lib/engine-components/ui/Interfaces.d.ts +2 -0
  95. package/package.json +2 -2
  96. package/src/engine/engine_components.js +16 -0
  97. package/src/engine/engine_input.ts +62 -20
  98. package/src/engine/engine_physics.ts +2 -1
  99. package/src/engine/engine_playerview.ts +80 -0
  100. package/src/engine/engine_serialization.ts +3 -1
  101. package/src/engine/engine_serialization_core.ts +8 -0
  102. package/src/engine/engine_setup.ts +24 -0
  103. package/src/engine/engine_utils.ts +34 -8
  104. package/src/engine/extensions/NEEDLE_deferred_texture.ts +25 -19
  105. package/src/engine/extensions/NEEDLE_lighting_settings.ts +4 -2
  106. package/src/engine/extensions/extension_utils.ts +24 -12
  107. package/src/engine/extensions/extensions.ts +3 -2
  108. package/src/engine-components/Camera.ts +9 -1
  109. package/src/engine-components/Component.ts +1 -1
  110. package/src/engine-components/Light.ts +3 -0
  111. package/src/engine-components/OrbitControls.ts +3 -3
  112. package/src/engine-components/ParticleSystem.ts +25 -26
  113. package/src/engine-components/PlayerColor.ts +1 -1
  114. package/src/engine-components/Renderer.ts +11 -3
  115. package/src/engine-components/ScreenCapture.ts +312 -2
  116. package/src/engine-components/SpectatorCamera.ts +490 -195
  117. package/src/engine-components/SyncedCamera.ts +23 -22
  118. package/src/engine-components/SyncedRoom.ts +1 -0
  119. package/src/engine-components/VideoPlayer.ts +97 -21
  120. package/src/engine-components/Volume.ts +47 -4
  121. package/src/engine-components/WebARSessionRoot.ts +78 -28
  122. package/src/engine-components/WebXR.ts +51 -15
  123. package/src/engine-components/WebXRAvatar.ts +27 -2
  124. package/src/engine-components/WebXRController.ts +21 -15
  125. package/src/engine-components/WebXRSync.ts +3 -3
  126. package/src/engine-components/XRFlag.ts +1 -0
  127. package/src/engine-components/ui/CanvasGroup.ts +2 -0
  128. package/src/engine-components/ui/EventSystem.ts +21 -15
  129. package/src/engine-components/ui/Graphic.ts +3 -0
  130. package/src/engine-components/ui/Interfaces.ts +2 -0
@@ -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,45 @@ 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;
140
+ if (debug)
141
+ console.log(this.context.mainCamera);
169
142
  if (this.context.mainCamera) {
170
- const cam = this.context.renderer.xr.getCamera(this.context.mainCamera);
171
- this.setupFollowMode(cam);
143
+ this.followSelf();
172
144
  }
173
145
  }
174
146
  onXRSessionEnded(_evt) {
175
- this._sessionHasStarted = false;
176
- this._firstPersonIsSetup = false;
177
- this.spectatorUIDomElement?.classList.add("hidden");
147
+ this.context.removeCamera(this.cam);
178
148
  GameObject.setActive(this.gameObject, false);
149
+ if (this.orbit)
150
+ this.orbit.enabled = true;
151
+ this._handler?.set(undefined);
152
+ this._handler?.disable();
153
+ if (this.isSpectatingSelf)
154
+ this.stopSpectating();
155
+ }
156
+ followSelf() {
157
+ this.target = this.context.players.getPlayerView(this.context.connection.connectionId);
158
+ if (debug)
159
+ console.log("Follow self", this.target);
179
160
  }
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
161
  // TODO: only show Spectator cam for DesktopVR;
187
162
  // don't show for AR, don't show on Quest
188
163
  // TODO: properly align cameras on enter/exit VR, seems currently spectator cam breaks alignment
189
164
  onAfterRender() {
190
165
  if (!this.cam)
191
166
  return;
192
- if (this.context.input.isKeyDown(KeyCode.KEY_S))
193
- this.firstPersonMode = !this.firstPersonMode;
194
- this.updateFollowSettings();
195
167
  const renderer = this.context.renderer;
196
168
  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)
169
+ if (!renderer.xr.isPresenting && !this._handler?.currentTarget)
202
170
  return;
171
+ this._handler?.update(this._mode);
203
172
  // remember XR render target so we can restore later
204
173
  const previousRenderTarget = renderer.getRenderTarget();
205
174
  let oldFramebuffer = null;
@@ -212,6 +181,11 @@ export class SpectatorCamera extends Behaviour {
212
181
  renderer.state.bindXRFramebuffer(null);
213
182
  }
214
183
  this.setAvatarFlagsBeforeRender();
184
+ const mainCam = this.context.mainCameraComponent;
185
+ // these should not be needed if we don't override viewport/scissor
186
+ // renderer.getViewport(this.currentViewport);
187
+ // renderer.getScissor(this.currentScissor);
188
+ // this.currentScissorTest = renderer.getScissorTest();
215
189
  // for scissor rendering (e.g. just a part of the screen / viewport, multiplayer split view)
216
190
  // let left = 0;
217
191
  // let bottom = 100;
@@ -220,7 +194,17 @@ export class SpectatorCamera extends Behaviour {
220
194
  // renderer.setViewport(left, bottom, width, height);
221
195
  // renderer.setScissor(left, bottom, width, height);
222
196
  // renderer.setScissorTest(true);
223
- renderer.setClearColor(new THREE.Color(1, 1, 1));
197
+ if (mainCam) {
198
+ const backgroundColor = mainCam.backgroundColor;
199
+ if (backgroundColor)
200
+ renderer.setClearColor(backgroundColor, backgroundColor.alpha);
201
+ this.cam.backgroundColor = backgroundColor;
202
+ this.cam.clearFlags = mainCam.clearFlags;
203
+ this.cam.nearClipPlane = mainCam.nearClipPlane;
204
+ this.cam.farClipPlane = mainCam.farClipPlane;
205
+ }
206
+ else
207
+ renderer.setClearColor(new THREE.Color(1, 1, 1));
224
208
  renderer.setRenderTarget(null); // null: direct to Canvas
225
209
  renderer.xr.enabled = false;
226
210
  const cam = this.cam?.cam;
@@ -241,53 +225,13 @@ export class SpectatorCamera extends Behaviour {
241
225
  renderer.state.bindXRFramebuffer(oldFramebuffer);
242
226
  this.resetAvatarFlags();
243
227
  }
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
228
  setAvatarFlagsBeforeRender() {
229
+ const isFirstPersonMode = this._mode === SpectatorMode.FirstPerson;
288
230
  for (const av of AvatarMarker.instances) {
289
231
  if (av.avatar && "isLocalAvatar" in av.avatar) {
290
- const mask = this.firstPersonMode && this._firstPersonIsSetup && av.avatar.isLocalAvatar ? XRStateFlag.FirstPerson : XRStateFlag.ThirdPerson;
232
+ let mask = XRStateFlag.All;
233
+ if (this.isSpectatingSelf)
234
+ mask = isFirstPersonMode && av.avatar.isLocalAvatar ? XRStateFlag.FirstPerson : XRStateFlag.ThirdPerson;
291
235
  const flags = av.avatar.flags;
292
236
  if (!flags)
293
237
  continue;
@@ -315,4 +259,313 @@ export class SpectatorCamera extends Behaviour {
315
259
  }
316
260
  }
317
261
  }
262
+ class SpectatorHandler {
263
+ context;
264
+ cam;
265
+ spectator;
266
+ follow;
267
+ target;
268
+ view;
269
+ currentObject;
270
+ get currentTarget() {
271
+ return this.view;
272
+ }
273
+ constructor(context, cam, spectator) {
274
+ this.context = context;
275
+ this.cam = cam;
276
+ this.spectator = spectator;
277
+ }
278
+ set(view) {
279
+ const followObject = view?.currentObject;
280
+ if (!followObject) {
281
+ this.spectator.stopSpectating();
282
+ return;
283
+ }
284
+ if (followObject === this.currentObject)
285
+ return;
286
+ this.currentObject = followObject;
287
+ this.view = view;
288
+ if (!this.follow)
289
+ this.follow = GameObject.addNewComponent(this.cam.gameObject, SmoothFollow);
290
+ if (!this.target)
291
+ this.target = new THREE.Object3D();
292
+ followObject.add(this.target);
293
+ this.follow.enabled = true;
294
+ this.follow.target = this.target;
295
+ // this.context.setCurrentCamera(this.cam);
296
+ if (debug)
297
+ console.log("FOLLOW", followObject);
298
+ if (!this.context.isInXR) {
299
+ this.context.setCurrentCamera(this.cam);
300
+ }
301
+ else
302
+ this.context.removeCamera(this.cam);
303
+ }
304
+ disable() {
305
+ if (debug)
306
+ console.log("STOP FOLLOW", this.currentObject);
307
+ this.view = undefined;
308
+ this.currentObject = undefined;
309
+ this.context.removeCamera(this.cam);
310
+ if (this.follow)
311
+ this.follow.enabled = false;
312
+ }
313
+ destroy() {
314
+ this.target?.removeFromParent();
315
+ if (this.follow)
316
+ GameObject.destroy(this.follow);
317
+ }
318
+ update(mode) {
319
+ if (this.currentTarget?.isConnected === false || this.currentTarget?.removed === true) {
320
+ if (debug)
321
+ console.log("Target disconnected or timeout", this.currentTarget);
322
+ this.spectator.stopSpectating();
323
+ return;
324
+ }
325
+ if (this.currentTarget && this.currentTarget?.currentObject !== this.currentObject) {
326
+ if (debug)
327
+ console.log("Target changed", this.currentObject, "to", this.currentTarget.currentObject);
328
+ this.set(this.currentTarget);
329
+ }
330
+ const perspectiveCamera = this.context.mainCamera;
331
+ if (perspectiveCamera) {
332
+ if (this.cam.cam.near !== perspectiveCamera.near || this.cam.cam.far !== perspectiveCamera.far) {
333
+ this.cam.cam.near = perspectiveCamera.near;
334
+ this.cam.cam.far = perspectiveCamera.far;
335
+ this.cam.cam.updateProjectionMatrix();
336
+ }
337
+ }
338
+ const target = this.follow?.target;
339
+ if (!target || !this.follow)
340
+ return;
341
+ switch (mode) {
342
+ case SpectatorMode.FirstPerson:
343
+ if (this.view?.viewDevice !== ViewDevice.Browser) {
344
+ // soft follow for AR and VR
345
+ this.follow.followFactor = 5;
346
+ this.follow.rotateFactor = 5;
347
+ }
348
+ else {
349
+ // snappy follow for desktop
350
+ this.follow.followFactor = 50;
351
+ this.follow.rotateFactor = 50;
352
+ }
353
+ target.position.set(0, 0, 0);
354
+ break;
355
+ case SpectatorMode.ThirdPerson:
356
+ this.follow.followFactor = 3;
357
+ this.follow.rotateFactor = 2;
358
+ target.position.set(0, .5, 1.5);
359
+ break;
360
+ }
361
+ this.follow.flipForward = false;
362
+ // console.log(this.view);
363
+ if (this.view?.viewDevice !== ViewDevice.Browser)
364
+ target.quaternion.copy(_inverseYQuat);
365
+ else
366
+ target.quaternion.identity();
367
+ }
368
+ }
369
+ const _inverseYQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
370
+ class SpectatorSelectionController {
371
+ context;
372
+ spectator;
373
+ constructor(context, spectator) {
374
+ this.context = context;
375
+ this.spectator = spectator;
376
+ console.log("Click other avatars or cameras to follow them. Press ESC to exit spectator mode.");
377
+ window.addEventListener("keydown", (evt) => {
378
+ const key = evt.key;
379
+ if (key === "Escape") {
380
+ this.spectator.stopSpectating();
381
+ }
382
+ });
383
+ let downTime = 0;
384
+ this.context.input.addEventListener(InputEvents.PointerDown, _ => {
385
+ downTime = this.context.time.time;
386
+ });
387
+ this.context.input.addEventListener(InputEvents.PointerUp, _ => {
388
+ const dt = this.context.time.time - downTime;
389
+ if (dt > 1) {
390
+ this.spectator.stopSpectating();
391
+ }
392
+ else if (this.context.input.getPointerClicked(0) && dt < .3)
393
+ this.trySelectObject();
394
+ });
395
+ }
396
+ trySelectObject() {
397
+ const opts = new RaycastOptions();
398
+ opts.setMask(0xffffff);
399
+ // opts.cam = this.spectator.cam?.cam;
400
+ const hits = this.context.physics.raycast(opts);
401
+ if (debug)
402
+ console.log(...hits);
403
+ if (hits?.length) {
404
+ for (const hit of hits) {
405
+ if (hit.distance < .2)
406
+ continue;
407
+ const obj = hit.object;
408
+ const avatar = GameObject.getComponentInParent(obj, AvatarMarker);
409
+ const id = avatar?.connectionId;
410
+ if (id) {
411
+ const view = this.context.players.getPlayerView(id);
412
+ this.spectator.target = view;
413
+ if (debug)
414
+ console.log("spectate", id, avatar);
415
+ break;
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ class SpectatorFollowerChangedEventModel {
422
+ /** the user that is following */
423
+ guid;
424
+ dontSave = true;
425
+ /** the user being followed */
426
+ targetUserId;
427
+ stoppedFollowing;
428
+ constructor(connectionId, userId, stoppedFollowing) {
429
+ this.guid = connectionId;
430
+ this.targetUserId = userId;
431
+ this.stoppedFollowing = stoppedFollowing;
432
+ }
433
+ }
434
+ class SpectatorFollowEventModel {
435
+ guid;
436
+ userId;
437
+ constructor(comp, userId) {
438
+ this.guid = comp.guid;
439
+ this.userId = userId;
440
+ }
441
+ }
442
+ class SpectatorCamNetworking {
443
+ followers = [];
444
+ context;
445
+ spectator;
446
+ _followerEventMethod;
447
+ _requestFollowMethod;
448
+ _joinedRoomMethod;
449
+ constructor(context, spectator) {
450
+ this.context = context;
451
+ this.spectator = spectator;
452
+ this._followerEventMethod = this.onFollowerEvent.bind(this);
453
+ this._requestFollowMethod = this.onRequestFollowEvent.bind(this);
454
+ this._joinedRoomMethod = this.onUserJoinedRoom.bind(this);
455
+ }
456
+ awake() {
457
+ this.context.connection.beginListen("spectator-follower-changed", this._followerEventMethod);
458
+ this.context.connection.beginListen("spectator-request-follow", this._requestFollowMethod);
459
+ this.context.connection.beginListen(RoomEvents.JoinedRoom, this._joinedRoomMethod);
460
+ document.addEventListener("keydown", evt => {
461
+ if (evt.key === "f") {
462
+ this.onRequestFollowMe();
463
+ }
464
+ else if (evt.key === "Escape") {
465
+ this.onRequestFollowMe(true);
466
+ }
467
+ });
468
+ }
469
+ destroy() {
470
+ this.context.connection.stopListening("spectator-follower-changed", this._followerEventMethod);
471
+ this.context.connection.stopListening("spectator-request-follow", this._requestFollowMethod);
472
+ this.context.connection.stopListening(RoomEvents.JoinedRoom, this._joinedRoomMethod);
473
+ }
474
+ onSpectatedObjectChanged(target, _prevId) {
475
+ if (debug)
476
+ console.log(this.context.connection.connectionId, "onSpectatedObjectChanged", target, _prevId);
477
+ if (this.context.connection.connectionId) {
478
+ const stopped = target?.userId === undefined;
479
+ const userId = stopped ? _prevId : target?.userId;
480
+ const evt = new SpectatorFollowerChangedEventModel(this.context.connection.connectionId, userId, stopped);
481
+ this.context.connection.send("spectator-follower-changed", evt);
482
+ }
483
+ }
484
+ onRequestFollowMe(stop = false) {
485
+ if (debug)
486
+ console.log("Request follow", this.context.connection.connectionId);
487
+ if (this.context.connection.connectionId) {
488
+ this.spectator.stopSpectating();
489
+ const id = stop ? undefined : this.context.connection.connectionId;
490
+ const model = new SpectatorFollowEventModel(this.spectator, id);
491
+ this.context.connection.send("spectator-request-follow", model);
492
+ }
493
+ }
494
+ onUserJoinedRoom() {
495
+ if (getParam("followme")) {
496
+ this.onRequestFollowMe();
497
+ }
498
+ }
499
+ onFollowerEvent(evt) {
500
+ const userBeingFollowed = evt.targetUserId;
501
+ const userThatIsFollowing = evt.guid;
502
+ if (debug)
503
+ console.log(evt);
504
+ if (userBeingFollowed === this.context.connection.connectionId) {
505
+ if (evt.stoppedFollowing) {
506
+ const index = this.followers.indexOf(userThatIsFollowing);
507
+ if (index !== -1) {
508
+ this.followers.splice(index, 1);
509
+ this.removeDisconnectedFollowers();
510
+ console.log(userThatIsFollowing, "unfollows you", this.followers.length);
511
+ }
512
+ }
513
+ else {
514
+ if (!this.followers.includes(userThatIsFollowing)) {
515
+ this.followers.push(userThatIsFollowing);
516
+ this.removeDisconnectedFollowers();
517
+ console.log(userThatIsFollowing, "follows you", this.followers.length);
518
+ }
519
+ }
520
+ }
521
+ }
522
+ removeDisconnectedFollowers() {
523
+ for (let i = this.followers.length - 1; i >= 0; i--) {
524
+ const id = this.followers[i];
525
+ if (this.context.connection.userIsInRoom(id) === false) {
526
+ this.followers.splice(i, 1);
527
+ }
528
+ }
529
+ }
530
+ _lastRequestFollowUser;
531
+ onRequestFollowEvent(evt) {
532
+ this._lastRequestFollowUser = evt;
533
+ if (evt.userId === this.context.connection.connectionId) {
534
+ this.spectator.stopSpectating();
535
+ }
536
+ else if (evt.userId === undefined) {
537
+ // this will currently also stop spectating if the user is not following you
538
+ this.spectator.stopSpectating();
539
+ }
540
+ else {
541
+ const view = this.context.players.getPlayerView(evt.userId);
542
+ if (view) {
543
+ this.spectator.target = view;
544
+ }
545
+ else {
546
+ if (debug)
547
+ console.warn("Could not find view", evt.userId);
548
+ this.enforceFollow();
549
+ return false;
550
+ }
551
+ }
552
+ return true;
553
+ }
554
+ _enforceFollowInterval;
555
+ enforceFollow() {
556
+ if (this._enforceFollowInterval)
557
+ return;
558
+ this._enforceFollowInterval = setInterval(() => {
559
+ if (this._lastRequestFollowUser === undefined || this._lastRequestFollowUser.userId && this.spectator.isFollowedBy(this._lastRequestFollowUser.userId)) {
560
+ clearInterval(this._enforceFollowInterval);
561
+ this._enforceFollowInterval = undefined;
562
+ }
563
+ else {
564
+ if (debug)
565
+ console.log("REQUEST FOLLOW AGAIN", this._lastRequestFollowUser.userId);
566
+ this.onRequestFollowEvent(this._lastRequestFollowUser);
567
+ }
568
+ }, 1000);
569
+ }
570
+ }
318
571
  //# sourceMappingURL=SpectatorCamera.js.map