@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.
- package/CHANGELOG.md +19 -0
- package/dist/needle-engine.d.ts +232 -139
- package/dist/needle-engine.js +349 -345
- package/dist/needle-engine.js.map +4 -4
- package/dist/needle-engine.min.js +24 -20
- package/dist/needle-engine.min.js.map +4 -4
- package/lib/engine/engine.d.ts +1 -0
- 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_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.d.ts +1 -0
- package/lib/engine/engine_serialization.js +1 -0
- package/lib/engine/engine_serialization.js.map +1 -1
- 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 -0
- package/lib/engine/engine_setup.js +23 -0
- package/lib/engine/engine_setup.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +25 -8
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_deferred_texture.d.ts +1 -1
- package/lib/engine/extensions/NEEDLE_deferred_texture.js +26 -14
- package/lib/engine/extensions/NEEDLE_deferred_texture.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/extension_utils.js +24 -13
- package/lib/engine/extensions/extension_utils.js.map +1 -1
- package/lib/engine/extensions/extensions.js +3 -1
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine-components/Camera.js +7 -0
- package/lib/engine-components/Camera.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 +3 -3
- 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/Renderer.d.ts +1 -0
- package/lib/engine-components/Renderer.js +10 -3
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/ScreenCapture.d.ts +1 -0
- package/lib/engine-components/ScreenCapture.js +265 -1
- package/lib/engine-components/ScreenCapture.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.d.ts +24 -17
- package/lib/engine-components/SpectatorCamera.js +435 -182
- 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/SyncedRoom.js +2 -0
- package/lib/engine-components/SyncedRoom.js.map +1 -1
- package/lib/engine-components/VideoPlayer.d.ts +10 -1
- package/lib/engine-components/VideoPlayer.js +64 -15
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/Volume.d.ts +4 -0
- package/lib/engine-components/Volume.js +44 -3
- package/lib/engine-components/Volume.js.map +1 -1
- package/lib/engine-components/WebARSessionRoot.d.ts +9 -2
- package/lib/engine-components/WebARSessionRoot.js +69 -24
- package/lib/engine-components/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/WebXR.d.ts +6 -3
- package/lib/engine-components/WebXR.js +43 -7
- package/lib/engine-components/WebXR.js.map +1 -1
- package/lib/engine-components/WebXRAvatar.d.ts +3 -0
- package/lib/engine-components/WebXRAvatar.js +20 -0
- package/lib/engine-components/WebXRAvatar.js.map +1 -1
- package/lib/engine-components/WebXRController.js +14 -8
- 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/CanvasGroup.d.ts +1 -0
- package/lib/engine-components/ui/CanvasGroup.js +1 -0
- package/lib/engine-components/ui/CanvasGroup.js.map +1 -1
- package/lib/engine-components/ui/EventSystem.js +13 -4
- package/lib/engine-components/ui/EventSystem.js.map +1 -1
- package/lib/engine-components/ui/Graphic.d.ts +1 -0
- package/lib/engine-components/ui/Graphic.js +2 -0
- package/lib/engine-components/ui/Graphic.js.map +1 -1
- package/lib/engine-components/ui/Interfaces.d.ts +2 -0
- package/package.json +2 -2
- package/src/engine/engine_components.js +16 -0
- package/src/engine/engine_input.ts +62 -20
- package/src/engine/engine_physics.ts +2 -1
- package/src/engine/engine_playerview.ts +80 -0
- package/src/engine/engine_serialization.ts +3 -1
- package/src/engine/engine_serialization_core.ts +8 -0
- package/src/engine/engine_setup.ts +24 -0
- package/src/engine/engine_utils.ts +34 -8
- package/src/engine/extensions/NEEDLE_deferred_texture.ts +25 -19
- package/src/engine/extensions/NEEDLE_lighting_settings.ts +4 -2
- package/src/engine/extensions/extension_utils.ts +24 -12
- package/src/engine/extensions/extensions.ts +3 -2
- package/src/engine-components/Camera.ts +9 -1
- package/src/engine-components/Component.ts +1 -1
- package/src/engine-components/Light.ts +3 -0
- package/src/engine-components/OrbitControls.ts +3 -3
- package/src/engine-components/ParticleSystem.ts +25 -26
- package/src/engine-components/PlayerColor.ts +1 -1
- package/src/engine-components/Renderer.ts +11 -3
- package/src/engine-components/ScreenCapture.ts +312 -2
- package/src/engine-components/SpectatorCamera.ts +490 -195
- package/src/engine-components/SyncedCamera.ts +23 -22
- package/src/engine-components/SyncedRoom.ts +1 -0
- package/src/engine-components/VideoPlayer.ts +97 -21
- package/src/engine-components/Volume.ts +47 -4
- package/src/engine-components/WebARSessionRoot.ts +78 -28
- package/src/engine-components/WebXR.ts +51 -15
- package/src/engine-components/WebXRAvatar.ts +27 -2
- package/src/engine-components/WebXRController.ts +21 -15
- package/src/engine-components/WebXRSync.ts +3 -3
- package/src/engine-components/XRFlag.ts +1 -0
- package/src/engine-components/ui/CanvasGroup.ts +2 -0
- package/src/engine-components/ui/EventSystem.ts +21 -15
- package/src/engine-components/ui/Graphic.ts +3 -0
- 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 {
|
|
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,45 @@ 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;
|
|
140
|
+
if (debug)
|
|
141
|
+
console.log(this.context.mainCamera);
|
|
169
142
|
if (this.context.mainCamera) {
|
|
170
|
-
|
|
171
|
-
this.setupFollowMode(cam);
|
|
143
|
+
this.followSelf();
|
|
172
144
|
}
|
|
173
145
|
}
|
|
174
146
|
onXRSessionEnded(_evt) {
|
|
175
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|