@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.
- package/CHANGELOG.md +16 -0
- package/dist/needle-engine.d.ts +193 -132
- package/dist/needle-engine.js +345 -345
- package/dist/needle-engine.js.map +4 -4
- package/dist/needle-engine.min.js +19 -19
- package/dist/needle-engine.min.js.map +4 -4
- package/lib/engine/engine_input.d.ts +13 -1
- package/lib/engine/engine_input.js +47 -16
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_networking_utils.js +1 -2
- package/lib/engine/engine_networking_utils.js.map +1 -1
- package/lib/engine/engine_physics.d.ts +1 -0
- package/lib/engine/engine_physics.js +2 -1
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_playerview.d.ts +26 -0
- package/lib/engine/engine_playerview.js +65 -0
- package/lib/engine/engine_playerview.js.map +1 -0
- package/lib/engine/engine_serialization_core.js +5 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_setup.d.ts +8 -1
- package/lib/engine/engine_setup.js +38 -3
- package/lib/engine/engine_setup.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lighting_settings.js +6 -2
- package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_techniques_webgl.js +1 -1
- package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
- package/lib/engine-components/Component.d.ts +1 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/Light.js +1 -0
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/OrbitControls.js +1 -2
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/ParticleSystem.d.ts +0 -1
- package/lib/engine-components/ParticleSystem.js +24 -27
- package/lib/engine-components/ParticleSystem.js.map +1 -1
- package/lib/engine-components/PlayerColor.js +1 -2
- package/lib/engine-components/PlayerColor.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.d.ts +24 -17
- package/lib/engine-components/SpectatorCamera.js +410 -181
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/SyncedCamera.d.ts +8 -4
- package/lib/engine-components/SyncedCamera.js +15 -18
- package/lib/engine-components/SyncedCamera.js.map +1 -1
- package/lib/engine-components/WebXR.js +1 -0
- package/lib/engine-components/WebXR.js.map +1 -1
- package/lib/engine-components/WebXRAvatar.d.ts +3 -0
- package/lib/engine-components/WebXRAvatar.js +16 -0
- package/lib/engine-components/WebXRAvatar.js.map +1 -1
- package/lib/engine-components/WebXRController.js +1 -1
- package/lib/engine-components/WebXRController.js.map +1 -1
- package/lib/engine-components/WebXRSync.js +3 -3
- package/lib/engine-components/WebXRSync.js.map +1 -1
- package/lib/engine-components/XRFlag.d.ts +2 -1
- package/lib/engine-components/XRFlag.js +1 -0
- package/lib/engine-components/XRFlag.js.map +1 -1
- package/lib/engine-components/ui/InputField.js +2 -2
- package/package.json +1 -1
- package/src/engine/engine_components.js +16 -0
- package/src/engine/engine_input.ts +62 -20
- package/src/engine/engine_networking_utils.ts +1 -2
- package/src/engine/engine_physics.ts +2 -1
- package/src/engine/engine_playerview.ts +80 -0
- package/src/engine/engine_serialization_core.ts +8 -0
- package/src/engine/engine_setup.ts +37 -3
- package/src/engine/extensions/NEEDLE_lighting_settings.ts +4 -2
- package/src/engine/extensions/NEEDLE_techniques_webgl.ts +1 -1
- package/src/engine-components/Component.ts +1 -1
- package/src/engine-components/Light.ts +3 -0
- package/src/engine-components/OrbitControls.ts +1 -2
- package/src/engine-components/ParticleSystem.ts +25 -26
- package/src/engine-components/PlayerColor.ts +1 -1
- package/src/engine-components/SpectatorCamera.ts +466 -194
- package/src/engine-components/SyncedCamera.ts +23 -22
- package/src/engine-components/WebXR.ts +1 -0
- package/src/engine-components/WebXRAvatar.ts +22 -2
- package/src/engine-components/WebXRController.ts +1 -1
- package/src/engine-components/WebXRSync.ts +3 -3
- package/src/engine-components/XRFlag.ts +1 -0
- 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 {
|
|
9
|
-
import {
|
|
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
|
-
|
|
13
|
-
get
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
30
|
-
this.
|
|
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
|
-
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
141
|
-
|
|
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.
|
|
168
|
-
|
|
138
|
+
if (!this.isSupportedPlatform())
|
|
139
|
+
return;
|
|
169
140
|
if (this.context.mainCamera) {
|
|
170
|
-
|
|
171
|
-
this.setupFollowMode(cam);
|
|
141
|
+
this.followSelf();
|
|
172
142
|
}
|
|
173
143
|
}
|
|
174
144
|
onXRSessionEnded(_evt) {
|
|
175
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|