@needle-tools/engine 3.1.0-alpha.2 → 3.2.1-alpha
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.js +9332 -9066
- package/dist/needle-engine.min.js +264 -264
- package/dist/needle-engine.umd.cjs +266 -266
- package/lib/engine/codegen/register_types.js +2 -0
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/debug/debug_overlay.js +2 -0
- package/lib/engine/debug/debug_overlay.js.map +1 -1
- package/lib/engine/engine_context_registry.d.ts +2 -0
- package/lib/engine/engine_context_registry.js +3 -0
- package/lib/engine/engine_context_registry.js.map +1 -1
- package/lib/engine/engine_gltf.js +4 -0
- package/lib/engine/engine_gltf.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +2 -0
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_license.js +1 -1
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_lightdata.js +1 -1
- package/lib/engine/engine_lightdata.js.map +1 -1
- package/lib/engine/engine_networking_auto.d.ts +3 -3
- package/lib/engine/engine_networking_auto.js +7 -3
- package/lib/engine/engine_networking_auto.js.map +1 -1
- package/lib/engine/engine_rendererdata.d.ts +1 -0
- package/lib/engine/engine_rendererdata.js +39 -15
- package/lib/engine/engine_rendererdata.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +10 -1
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lighting_settings.js +14 -11
- package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_progressive.js +11 -4
- package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -1
- package/lib/engine/extensions/extensions.js +4 -0
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +1 -0
- package/lib/engine-components/AudioSource.js +6 -3
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Camera.js +1 -1
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/Renderer.d.ts +5 -0
- package/lib/engine-components/Renderer.js +64 -17
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/RendererLightmap.d.ts +1 -2
- package/lib/engine-components/RendererLightmap.js +8 -10
- package/lib/engine-components/RendererLightmap.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +33 -0
- package/lib/engine-components/SceneSwitcher.js +244 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -0
- package/lib/engine-components/ScreenCapture.js +1 -0
- package/lib/engine-components/ScreenCapture.js.map +1 -1
- package/lib/engine-components/VideoPlayer.d.ts +2 -0
- package/lib/engine-components/VideoPlayer.js +7 -3
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/XRFlag.js +3 -0
- package/lib/engine-components/XRFlag.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.js +6 -4
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +5 -7
- package/lib/engine-components/timeline/SignalAsset.js +38 -6
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.js +40 -12
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/engine/codegen/register_types.js +2 -0
- package/src/engine/debug/debug_overlay.ts +1 -0
- package/src/engine/engine_context_registry.ts +3 -0
- package/src/engine/engine_gltf.ts +6 -2
- package/src/engine/engine_gltf_builtin_components.ts +3 -0
- package/src/engine/engine_license.ts +1 -1
- package/src/engine/engine_lightdata.ts +1 -1
- package/src/engine/engine_networking_auto.ts +13 -6
- package/src/engine/engine_rendererdata.ts +35 -16
- package/src/engine/engine_serialization_builtin_serializer.ts +10 -1
- package/src/engine/extensions/NEEDLE_lighting_settings.ts +15 -13
- package/src/engine/extensions/NEEDLE_lightmaps.ts +3 -2
- package/src/engine/extensions/NEEDLE_progressive.ts +9 -3
- package/src/engine/extensions/extensions.ts +6 -0
- package/src/engine-components/AudioSource.ts +6 -3
- package/src/engine-components/Camera.ts +1 -1
- package/src/engine-components/Component.ts +0 -1
- package/src/engine-components/Renderer.ts +69 -20
- package/src/engine-components/RendererLightmap.ts +7 -11
- package/src/engine-components/SceneSwitcher.ts +240 -0
- package/src/engine-components/ScreenCapture.ts +1 -0
- package/src/engine-components/VideoPlayer.ts +16 -11
- package/src/engine-components/XRFlag.ts +3 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/timeline/PlayableDirector.ts +13 -11
- package/src/engine-components/timeline/SignalAsset.ts +35 -8
- package/src/engine-components/timeline/TimelineTracks.ts +43 -13
|
@@ -14,7 +14,7 @@ export class RendererLightmap {
|
|
|
14
14
|
set lightmap(tex: Texture | null) {
|
|
15
15
|
if (tex !== this.lightmapTexture) {
|
|
16
16
|
this.lightmapTexture = tex;
|
|
17
|
-
this.
|
|
17
|
+
this.applyLightmap();
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -27,8 +27,6 @@ export class RendererLightmap {
|
|
|
27
27
|
private lightmapScaleOffsetUniform = { value: new THREE.Vector4(1, 1, 0, 0) };
|
|
28
28
|
private lightmapUniform: { value: THREE.Texture | null } = { value: null };
|
|
29
29
|
|
|
30
|
-
private beforeRenderCallback?: OnBeforeRenderCallback;
|
|
31
|
-
|
|
32
30
|
constructor(gameObject: GameObject, context: Context) {
|
|
33
31
|
this.gameObject = gameObject;
|
|
34
32
|
this.context = context;
|
|
@@ -44,20 +42,19 @@ export class RendererLightmap {
|
|
|
44
42
|
|
|
45
43
|
const debugLightmaps = debug;
|
|
46
44
|
if (debugLightmaps) this.setLightmapDebugMaterial();
|
|
47
|
-
this.
|
|
45
|
+
this.applyLightmap();
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
bindOnBeforeRender() {
|
|
51
|
-
this.
|
|
52
|
-
this.context.addBeforeRenderListener(this.gameObject, this.
|
|
53
|
-
// this.gameObject.onBeforeRender = this.onBeforeRenderThreeComplete.bind(this);
|
|
49
|
+
this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThreeComplete);
|
|
50
|
+
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThreeComplete);
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
private onBeforeRenderThreeComplete(_renderer, _scene, _camera, _geometry, material, _group) {
|
|
53
|
+
private onBeforeRenderThreeComplete = (_renderer, _scene, _camera, _geometry, material, _group) => {
|
|
57
54
|
this.onBeforeRenderThree(material);
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
private
|
|
57
|
+
private applyLightmap() {
|
|
61
58
|
|
|
62
59
|
if (this.gameObject.type === "Object3D") {
|
|
63
60
|
// console.warn("Can not add lightmap. Is this object missing a renderer?");
|
|
@@ -65,13 +62,12 @@ export class RendererLightmap {
|
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
if (this.gameObject.type === "Group") {
|
|
68
|
-
console.warn("Lightmap on multimaterial object is not supported yet... please
|
|
65
|
+
console.warn("Lightmap on multimaterial object is not supported yet... please open a feature request on https://github.com/needle-tools/needle-engine-support if your project requires it");
|
|
69
66
|
return;
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
console.assert(this.gameObject.type === "Mesh", "Lightmap only works on meshes", this);
|
|
73
70
|
|
|
74
|
-
|
|
75
71
|
const mesh = this.gameObject as unknown as THREE.Mesh;
|
|
76
72
|
// TODO: ensure uv2 exists
|
|
77
73
|
if (!mesh.geometry.getAttribute("uv2"))
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { AssetReference } from "../engine/engine_addressables";
|
|
2
|
+
import { NeedleEngineHTMLElement } from "../engine/engine_element";
|
|
3
|
+
import { InputEvents } from "../engine/engine_input";
|
|
4
|
+
import { isLocalNetwork } from "../engine/engine_networking_utils";
|
|
5
|
+
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry";
|
|
6
|
+
import { getParam, isMobileDevice, setParamWithoutReload } from "../engine/engine_utils";
|
|
7
|
+
import { serializable } from "../engine/engine_serialization";
|
|
8
|
+
import { Behaviour, GameObject } from "./Component";
|
|
9
|
+
|
|
10
|
+
const debug = getParam("debugsceneswitcher");
|
|
11
|
+
|
|
12
|
+
const ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME = "scene";
|
|
13
|
+
|
|
14
|
+
ContextRegistry.registerCallback(ContextEvent.ContextRegistered, _ => {
|
|
15
|
+
if (!NeedleEngineHTMLElement.observedAttributes.includes(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME))
|
|
16
|
+
NeedleEngineHTMLElement.observedAttributes.push(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const couldNotLoadScenePromise = Promise.resolve(false);
|
|
20
|
+
|
|
21
|
+
export class SceneSwitcher extends Behaviour {
|
|
22
|
+
|
|
23
|
+
@serializable(AssetReference)
|
|
24
|
+
scenes!: AssetReference[];
|
|
25
|
+
|
|
26
|
+
/** the url parameter that is set/used to store the currently loaded scene in, set to "" to disable */
|
|
27
|
+
@serializable()
|
|
28
|
+
queryParameterName: string = "scene";
|
|
29
|
+
|
|
30
|
+
@serializable()
|
|
31
|
+
clamp: boolean = true;
|
|
32
|
+
|
|
33
|
+
/** when enabled the new scene is pushed to the browser navigation history, only works with a valid query parameter set */
|
|
34
|
+
@serializable()
|
|
35
|
+
useHistory: boolean = true;
|
|
36
|
+
|
|
37
|
+
/** when enabled you can switch between scenes using keyboard left, right, A and D or number keys */
|
|
38
|
+
@serializable()
|
|
39
|
+
useKeyboard: boolean = true;
|
|
40
|
+
|
|
41
|
+
/** when enabled you can switch between scenes using swipe (mobile only) */
|
|
42
|
+
@serializable()
|
|
43
|
+
useSwipe: boolean = true;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
private _currentIndex: number = -1;
|
|
47
|
+
private _currentScene: AssetReference | undefined = undefined;
|
|
48
|
+
private _engineElementOverserver: MutationObserver | undefined = undefined;
|
|
49
|
+
|
|
50
|
+
start() {
|
|
51
|
+
if (!this.tryLoadFromQueryParam()) {
|
|
52
|
+
const value = this.context.domElement.getAttribute(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
|
|
53
|
+
// let locked = this.lock;
|
|
54
|
+
try {
|
|
55
|
+
// this.lock = false;
|
|
56
|
+
if (value === null || !this.trySelectSceneFromValue(value))
|
|
57
|
+
this.select(0);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
// this.lock = locked;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onEnable(): void {
|
|
66
|
+
globalThis.addEventListener("popstate", this.onPopState);
|
|
67
|
+
this.context.input.addEventListener(InputEvents.KeyDown, this.onKeyDown);
|
|
68
|
+
this.context.input.addEventListener(InputEvents.PointerMove, this.onPointerMove);
|
|
69
|
+
this.context.input.addEventListener(InputEvents.PointerUp, this.onPointerUp);
|
|
70
|
+
|
|
71
|
+
if (!this._engineElementOverserver) {
|
|
72
|
+
this._engineElementOverserver = new MutationObserver((mutations) => {
|
|
73
|
+
for (const mut of mutations) {
|
|
74
|
+
if (mut.type === "attributes" && mut.attributeName === ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME) {
|
|
75
|
+
const value = this.context.domElement.getAttribute(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
|
|
76
|
+
if (value !== null) {
|
|
77
|
+
this.trySelectSceneFromValue(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this._engineElementOverserver.observe(this.context.domElement, {
|
|
85
|
+
attributes: true
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onDisable(): void {
|
|
90
|
+
globalThis.removeEventListener("popstate", this.onPopState);
|
|
91
|
+
this.context.input.removeEventListener(InputEvents.KeyDown, this.onKeyDown);
|
|
92
|
+
this.context.input.removeEventListener(InputEvents.PointerMove, this.onPointerMove);
|
|
93
|
+
this.context.input.removeEventListener(InputEvents.PointerUp, this.onPointerUp);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private onPopState = async (_state: PopStateEvent) => {
|
|
97
|
+
if (!this.useHistory) return;
|
|
98
|
+
let wasUsingHistory = this.useHistory;
|
|
99
|
+
try {
|
|
100
|
+
this.useHistory = false;
|
|
101
|
+
await this.tryLoadFromQueryParam();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
this.useHistory = wasUsingHistory;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private normalizedSwipeThresholdX = 0.1;
|
|
109
|
+
private _didSwipe: boolean = false;
|
|
110
|
+
private onPointerMove = (e: any) => {
|
|
111
|
+
if (!this._didSwipe && e.button === 0 && e.pointerType === "touch" && this.context.input.getPointerPressedCount() === 1) {
|
|
112
|
+
const delta = this.context.input.getPointerPositionDelta(e.button);
|
|
113
|
+
if (delta) {
|
|
114
|
+
const normalizedX = delta.x / this.context.domWidth;
|
|
115
|
+
if (normalizedX >= this.normalizedSwipeThresholdX) {
|
|
116
|
+
this._didSwipe = true;
|
|
117
|
+
this.selectPrev();
|
|
118
|
+
}
|
|
119
|
+
else if (normalizedX <= -this.normalizedSwipeThresholdX) {
|
|
120
|
+
this._didSwipe = true;
|
|
121
|
+
this.selectNext();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private onPointerUp = (e: any) => {
|
|
128
|
+
if (e.button === 0) {
|
|
129
|
+
this._didSwipe = false;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
private onKeyDown = (e: any) => {
|
|
134
|
+
if (!this.useKeyboard) return;
|
|
135
|
+
if (!this.scenes) return;
|
|
136
|
+
const key = e.key.toLowerCase();
|
|
137
|
+
if (!key) return;
|
|
138
|
+
const index = parseInt(key) - 1;
|
|
139
|
+
if (index >= 0) {
|
|
140
|
+
this.trySelectSceneFromValue(index);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
switch (key) {
|
|
144
|
+
case "arrowright":
|
|
145
|
+
case "d":
|
|
146
|
+
this.selectNext();
|
|
147
|
+
break;
|
|
148
|
+
case "arrowleft":
|
|
149
|
+
case "a":
|
|
150
|
+
this.selectPrev();
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
selectNext(): Promise<boolean> {
|
|
156
|
+
return this.select(this._currentIndex + 1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
selectPrev(): Promise<boolean> {
|
|
160
|
+
return this.select(this._currentIndex - 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
select(index: number): Promise<boolean> {
|
|
164
|
+
if (debug) console.log("select", index)
|
|
165
|
+
if (!this.scenes?.length) return couldNotLoadScenePromise;
|
|
166
|
+
if (index < 0) {
|
|
167
|
+
if (this.clamp) return couldNotLoadScenePromise;
|
|
168
|
+
index = this.scenes.length - 1;
|
|
169
|
+
}
|
|
170
|
+
else if (index >= this.scenes.length) {
|
|
171
|
+
if (this.clamp) return couldNotLoadScenePromise;
|
|
172
|
+
index = 0;
|
|
173
|
+
}
|
|
174
|
+
const scene = this.scenes[index];
|
|
175
|
+
return this.switchScene(scene);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async switchScene(scene: AssetReference): Promise<boolean> {
|
|
179
|
+
if (scene === this._currentScene) return true;
|
|
180
|
+
if (this._currentScene)
|
|
181
|
+
this._currentScene.unload();
|
|
182
|
+
const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
|
|
183
|
+
this._currentScene = scene;
|
|
184
|
+
try {
|
|
185
|
+
await scene.loadAssetAsync();
|
|
186
|
+
if (!scene.asset) {
|
|
187
|
+
if (debug) console.warn("Failed loading scene:", scene);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (this._currentIndex === index) {
|
|
191
|
+
GameObject.add(scene.asset, this.gameObject);
|
|
192
|
+
// save the loaded scene as an url parameter
|
|
193
|
+
if (this.queryParameterName?.length)
|
|
194
|
+
setParamWithoutReload(this.queryParameterName, index.toString(), this.useHistory);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error(err);
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private tryLoadFromQueryParam() {
|
|
205
|
+
if (!this.queryParameterName?.length) return false;
|
|
206
|
+
// try restore the scene from the url
|
|
207
|
+
const value = getParam(this.queryParameterName);
|
|
208
|
+
if (typeof value === "boolean") return false;
|
|
209
|
+
return this.trySelectSceneFromValue(value);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** try to select a scene from a string or index */
|
|
213
|
+
private trySelectSceneFromValue(value: string | number) {
|
|
214
|
+
|
|
215
|
+
if (typeof value === "string") {
|
|
216
|
+
const index = parseInt(value as string);
|
|
217
|
+
if (index >= 0 && index < this.scenes.length) {
|
|
218
|
+
return this.select(index);;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Try to find a scene with a matching name
|
|
222
|
+
for (let i = 0; i < this.scenes.length; i++) {
|
|
223
|
+
const scene = this.scenes[i];
|
|
224
|
+
if (scene.uri.toLowerCase().includes(value)) {
|
|
225
|
+
return this.select(i);;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (typeof value === "number") {
|
|
231
|
+
if (value >= 0 && value < this.scenes.length) {
|
|
232
|
+
return this.select(value);;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (isLocalNetwork()) console.warn("Unknown scene value or index: \"" + value + "\"", this)
|
|
237
|
+
|
|
238
|
+
return couldNotLoadScenePromise;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -96,6 +96,7 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
|
|
|
96
96
|
console.log("Screensharing", this.name, this);
|
|
97
97
|
AudioSource.registerWaitForAllowAudio(() => {
|
|
98
98
|
if (this.videoPlayer && this._currentStream && this._currentMode === ScreenCaptureMode.Receiving) {
|
|
99
|
+
this.videoPlayer.playInBackground = true;
|
|
99
100
|
this.videoPlayer.setVideo(this._currentStream);
|
|
100
101
|
}
|
|
101
102
|
});
|
|
@@ -52,7 +52,7 @@ export class VideoPlayer extends Behaviour {
|
|
|
52
52
|
|
|
53
53
|
@serializable()
|
|
54
54
|
aspectMode: AspectMode = AspectMode.None;
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
@serializable(URL)
|
|
57
57
|
private clip?: string | MediaStream | null = null;
|
|
58
58
|
|
|
@@ -106,12 +106,12 @@ export class VideoPlayer extends Behaviour {
|
|
|
106
106
|
get isPlaying(): boolean {
|
|
107
107
|
const video = this._videoElement;
|
|
108
108
|
if (video) {
|
|
109
|
-
if(video.currentTime > 0 && !video.paused && !video.ended
|
|
109
|
+
if (video.currentTime > 0 && !video.paused && !video.ended
|
|
110
110
|
&& video.readyState > video.HAVE_CURRENT_DATA)
|
|
111
111
|
return true;
|
|
112
|
-
else if(video.srcObject){
|
|
112
|
+
else if (video.srcObject) {
|
|
113
113
|
const stream = video.srcObject as MediaStream;
|
|
114
|
-
if(stream.active) return true;
|
|
114
|
+
if (stream.active) return true;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
return false;
|
|
@@ -149,6 +149,9 @@ export class VideoPlayer extends Behaviour {
|
|
|
149
149
|
}
|
|
150
150
|
private _muted: boolean = false;
|
|
151
151
|
|
|
152
|
+
/** Set this to false to pause video playback while the tab is not active */
|
|
153
|
+
playInBackground: boolean = true;
|
|
154
|
+
|
|
152
155
|
private _crossOrigin: string | null = "anonymous";
|
|
153
156
|
|
|
154
157
|
private audioOutputMode: VideoAudioOutputMode = VideoAudioOutputMode.AudioSource;
|
|
@@ -198,11 +201,13 @@ export class VideoPlayer extends Behaviour {
|
|
|
198
201
|
window.addEventListener('visibilitychange', _evt => {
|
|
199
202
|
switch (document.visibilityState) {
|
|
200
203
|
case "hidden":
|
|
201
|
-
this.
|
|
202
|
-
|
|
204
|
+
if(!this.playInBackground){
|
|
205
|
+
this.wasPlaying = this._isPlaying;
|
|
206
|
+
this.pause();
|
|
207
|
+
}
|
|
203
208
|
break;
|
|
204
209
|
case "visible":
|
|
205
|
-
if (this.wasPlaying) this.play();
|
|
210
|
+
if (this.wasPlaying && !this._isPlaying) this.play();
|
|
206
211
|
break;
|
|
207
212
|
}
|
|
208
213
|
});
|
|
@@ -260,7 +265,7 @@ export class VideoPlayer extends Behaviour {
|
|
|
260
265
|
this.updateVideoElementSettings();
|
|
261
266
|
this._videoElement?.play().catch(err => {
|
|
262
267
|
// https://developer.chrome.com/blog/play-request-was-interrupted/
|
|
263
|
-
if(debug)
|
|
268
|
+
if (debug)
|
|
264
269
|
console.error("Error playing video", err, "CODE=" + err.code, this.videoElement?.src, this);
|
|
265
270
|
setTimeout(() => {
|
|
266
271
|
if (this._isPlaying && !this.destroyed && this.activeAndEnabled)
|
|
@@ -318,7 +323,7 @@ export class VideoPlayer extends Behaviour {
|
|
|
318
323
|
this._videoTexture.flipY = false;
|
|
319
324
|
this._videoTexture.encoding = THREE.sRGBEncoding;
|
|
320
325
|
this.handleBeginPlaying(playAutomatically);
|
|
321
|
-
if(debug)
|
|
326
|
+
if (debug)
|
|
322
327
|
console.log(this);
|
|
323
328
|
}
|
|
324
329
|
|
|
@@ -420,7 +425,7 @@ export class VideoPlayer extends Behaviour {
|
|
|
420
425
|
// dont open in fullscreen on ios
|
|
421
426
|
this._videoElement.playsInline = true;
|
|
422
427
|
let muted = !this._receivedInput && this.audioOutputMode !== VideoAudioOutputMode.None;
|
|
423
|
-
if(!muted && this._muted) muted = true;
|
|
428
|
+
if (!muted && this._muted) muted = true;
|
|
424
429
|
this._videoElement.muted = muted;
|
|
425
430
|
if (this.playOnAwake)
|
|
426
431
|
this._videoElement.autoplay = true;
|
|
@@ -540,7 +545,7 @@ class VideoOverlay {
|
|
|
540
545
|
if (!this._screenspaceModeQuad) return;
|
|
541
546
|
this._screenspaceModeQuad.geometry.scale(2, 2, 2);
|
|
542
547
|
}
|
|
543
|
-
|
|
548
|
+
|
|
544
549
|
const quad = this._screenspaceModeQuad;
|
|
545
550
|
this.context.scene.add(quad);
|
|
546
551
|
this.updateScreenspaceMaterialUniforms();
|
|
@@ -114,6 +114,7 @@ export { RGBAColor } from "../js-extensions/RGBAColor";
|
|
|
114
114
|
export { Rigidbody } from "../RigidBody";
|
|
115
115
|
export { RotationBySpeedModule } from "../ParticleSystemModules";
|
|
116
116
|
export { RotationOverLifetimeModule } from "../ParticleSystemModules";
|
|
117
|
+
export { SceneSwitcher } from "../SceneSwitcher";
|
|
117
118
|
export { ScreenCapture } from "../ScreenCapture";
|
|
118
119
|
export { ScreenSpaceAmbientOcclusion } from "../postprocessing/Effects/ScreenspaceAmbientOcclusion";
|
|
119
120
|
export { ShadowCatcher } from "../ShadowCatcher";
|
|
@@ -172,7 +172,7 @@ export class PlayableDirector extends Behaviour {
|
|
|
172
172
|
await Promise.all(promises);
|
|
173
173
|
if (!this._isPlaying) return;
|
|
174
174
|
}
|
|
175
|
-
while(this._audioTracks.length > 0 && this._isPlaying && !AudioSource.userInteractionRegistered && this.waitForAudio)
|
|
175
|
+
while (this._audioTracks.length > 0 && this._isPlaying && !AudioSource.userInteractionRegistered && this.waitForAudio)
|
|
176
176
|
await delay(200);
|
|
177
177
|
}
|
|
178
178
|
this._internalUpdateRoutine = this.startCoroutine(this.internalUpdate());
|
|
@@ -211,9 +211,9 @@ export class PlayableDirector extends Behaviour {
|
|
|
211
211
|
let t = this._time;
|
|
212
212
|
switch (this.extrapolationMode) {
|
|
213
213
|
case DirectorWrapMode.Hold:
|
|
214
|
-
if(this._speed > 0)
|
|
214
|
+
if (this._speed > 0)
|
|
215
215
|
t = Math.min(t, this._duration);
|
|
216
|
-
else if(this._speed < 0)
|
|
216
|
+
else if (this._speed < 0)
|
|
217
217
|
t = Math.max(t, 0);
|
|
218
218
|
this._time = t;
|
|
219
219
|
break;
|
|
@@ -242,7 +242,7 @@ export class PlayableDirector extends Behaviour {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
get audioTracks()
|
|
245
|
+
get audioTracks(): Tracks.AudioTrackHandler[] {
|
|
246
246
|
return this._audioTracks;
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -362,7 +362,7 @@ export class PlayableDirector extends Behaviour {
|
|
|
362
362
|
if (debug)
|
|
363
363
|
console.log("Resolved binding", binding, "to", obj);
|
|
364
364
|
track.outputs[i] = obj;
|
|
365
|
-
if(obj instanceof Animator) {
|
|
365
|
+
if (obj instanceof Animator) {
|
|
366
366
|
// TODO: should disable? animator but this is not the animator that is currently on the object? needs investigation
|
|
367
367
|
// console.log("DISABLE ANIMATOR", obj, obj.name, binding, this._guidsMap);
|
|
368
368
|
// obj.enabled = false;
|
|
@@ -376,7 +376,7 @@ export class PlayableDirector extends Behaviour {
|
|
|
376
376
|
continue;
|
|
377
377
|
}
|
|
378
378
|
// if the binding is missing remove it to avoid unnecessary loops
|
|
379
|
-
if (track.type !== Models.TrackType.Audio && track.type !== Models.TrackType.Control && track.type !== Models.TrackType.Marker)
|
|
379
|
+
if (track.type !== Models.TrackType.Audio && track.type !== Models.TrackType.Control && track.type !== Models.TrackType.Marker && track.type !== Models.TrackType.Signal)
|
|
380
380
|
console.warn("Missing binding", binding, track.name, track.type, this.name, this.playableAsset.name);
|
|
381
381
|
}
|
|
382
382
|
}
|
|
@@ -416,8 +416,11 @@ export class PlayableDirector extends Behaviour {
|
|
|
416
416
|
for (const clip of track.clips) {
|
|
417
417
|
if (clip.end > this._duration) this._duration = clip.end;
|
|
418
418
|
}
|
|
419
|
+
for(const marker of track.markers){
|
|
420
|
+
if (marker.time > this._duration) this._duration = marker.time + .001;
|
|
421
|
+
}
|
|
419
422
|
}
|
|
420
|
-
// console.log("timeline duration", this._duration);
|
|
423
|
+
// console.log("timeline duration", this._duration, this.playableAsset);
|
|
421
424
|
}
|
|
422
425
|
|
|
423
426
|
private setupAndCreateTrackHandlers() {
|
|
@@ -442,17 +445,16 @@ export class PlayableDirector extends Behaviour {
|
|
|
442
445
|
// only handle animation tracks
|
|
443
446
|
if (track.type === Models.TrackType.Animation) {
|
|
444
447
|
if (track.clips.length <= 0) {
|
|
445
|
-
if(debug) console.warn("Animation track has no clips", track);
|
|
448
|
+
if (debug) console.warn("Animation track has no clips", track);
|
|
446
449
|
continue;
|
|
447
450
|
}
|
|
448
451
|
// loop outputs / bindings, they should contain animator references
|
|
449
452
|
for (let i = track.outputs.length - 1; i >= 0; i--) {
|
|
450
453
|
let binding = track.outputs[i] as Animator;
|
|
451
|
-
if(binding instanceof Object3D){
|
|
454
|
+
if (binding instanceof Object3D) {
|
|
452
455
|
const anim = GameObject.getOrAddComponent(binding, Animator);
|
|
453
|
-
if(anim) binding = anim;
|
|
456
|
+
if (anim) binding = anim;
|
|
454
457
|
}
|
|
455
|
-
if (typeof binding.enabled === "boolean") binding.enabled = false;
|
|
456
458
|
const animationClips = binding?.gameObject?.animations;
|
|
457
459
|
if (animationClips) {
|
|
458
460
|
const handler = new Tracks.AnimationTrackHandler();
|
|
@@ -7,28 +7,55 @@ import { serializable } from "../../engine/engine_serialization_decorator";
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
export class SignalAsset {
|
|
10
|
+
@serializable()
|
|
10
11
|
guid!: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export class SignalReceiverEvent
|
|
14
|
+
export class SignalReceiverEvent {
|
|
15
|
+
@serializable(SignalAsset)
|
|
14
16
|
signal!: SignalAsset;
|
|
17
|
+
@serializable(EventList)
|
|
15
18
|
reaction!: EventList;
|
|
16
|
-
|
|
17
|
-
$serializedTypes = {
|
|
18
|
-
signal: SignalAsset,
|
|
19
|
-
reaction: EventList
|
|
20
|
-
}
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export class SignalReceiver extends Behaviour {
|
|
24
22
|
|
|
23
|
+
private static receivers: { [key: string]: SignalReceiver[] } = {};
|
|
24
|
+
|
|
25
|
+
static invoke(guid: string) {
|
|
26
|
+
if (SignalReceiver.receivers[guid]) {
|
|
27
|
+
const receivers = SignalReceiver.receivers[guid];
|
|
28
|
+
if (!receivers) return;
|
|
29
|
+
for (const rec of receivers)
|
|
30
|
+
rec.invoke(guid);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
|
|
26
36
|
@serializable(SignalReceiverEvent)
|
|
27
37
|
events?: SignalReceiverEvent[];
|
|
28
38
|
|
|
39
|
+
onEnable(): void {
|
|
40
|
+
if (this.events) {
|
|
41
|
+
for (const evt of this.events) {
|
|
42
|
+
if (!SignalReceiver.receivers[evt.signal.guid])
|
|
43
|
+
SignalReceiver.receivers[evt.signal.guid] = [];
|
|
44
|
+
SignalReceiver.receivers[evt.signal.guid].push(this);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
onDisable(): void {
|
|
50
|
+
if (this.events) {
|
|
51
|
+
for (const evt of this.events) {
|
|
52
|
+
if (SignalReceiver.receivers[evt.signal.guid]) {
|
|
53
|
+
const idx = SignalReceiver.receivers[evt.signal.guid].indexOf(this);
|
|
54
|
+
if (idx >= 0)
|
|
55
|
+
SignalReceiver.receivers[evt.signal.guid].splice(idx, 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
invoke(sig: SignalAsset | string) {
|
|
@@ -145,7 +145,7 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
145
145
|
onPauseChanged() {
|
|
146
146
|
// When the timeline is paused the original animator will be enabled again if it was before
|
|
147
147
|
if (this._animator && this._animatorWasEnabled !== undefined) {
|
|
148
|
-
this._animator.enabled = this.director.
|
|
148
|
+
this._animator.enabled = this.director.isPlaying === false ? this._animatorWasEnabled : false;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -225,7 +225,11 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
225
225
|
// We need to disable the animator component in case it also animates
|
|
226
226
|
// which overrides the timeline
|
|
227
227
|
this._animator = GameObject.getComponent(this.target, Animator) ?? null;
|
|
228
|
-
|
|
228
|
+
if (this._animator) {
|
|
229
|
+
this._animatorWasEnabled = this._animator.enabled;
|
|
230
|
+
this._animator.enabled = false;
|
|
231
|
+
}
|
|
232
|
+
else this._animatorWasEnabled = false;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
// Clip Offsets
|
|
@@ -323,9 +327,10 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
323
327
|
// const clip = this.clips[i];
|
|
324
328
|
let weight = 1;
|
|
325
329
|
weight *= this.evaluateWeight(time, i, this.models, isActive);
|
|
330
|
+
weight *= this.director.weight;
|
|
326
331
|
|
|
327
332
|
let handleLoop = isInTimeRange;
|
|
328
|
-
if(doPreExtrapolate){
|
|
333
|
+
if (doPreExtrapolate) {
|
|
329
334
|
if (preExtrapolation !== Models.ClipExtrapolation.Hold) {
|
|
330
335
|
time += model.start;
|
|
331
336
|
handleLoop = true;
|
|
@@ -370,7 +375,7 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
370
375
|
else action.time = t;
|
|
371
376
|
|
|
372
377
|
action.timeScale = 0;
|
|
373
|
-
const effectiveWeight = weight
|
|
378
|
+
const effectiveWeight = weight;
|
|
374
379
|
action.weight = effectiveWeight;
|
|
375
380
|
action.clampWhenFinished = true;
|
|
376
381
|
if (!action.isRunning())
|
|
@@ -660,7 +665,7 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
660
665
|
public static dispose() {
|
|
661
666
|
AudioTrackHandler._currentlyLoading.clear();
|
|
662
667
|
}
|
|
663
|
-
|
|
668
|
+
|
|
664
669
|
private handleAudioLoading(model: Models.ClipModel, audio: THREE.Audio): Promise<AudioBuffer | null> | null {
|
|
665
670
|
if (!this._audioLoader) {
|
|
666
671
|
this._audioLoader = new THREE.AudioLoader();
|
|
@@ -675,7 +680,7 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
675
680
|
});
|
|
676
681
|
return promise;
|
|
677
682
|
}
|
|
678
|
-
|
|
683
|
+
|
|
679
684
|
if (debug) console.warn("LOAD audio track", path, this.director.sourceId);
|
|
680
685
|
const loadingPromise = new Promise<AudioBuffer | null>((resolve, _reject) => {
|
|
681
686
|
this._audioLoader!.load(path,
|
|
@@ -699,23 +704,48 @@ export class SignalTrackHandler extends TrackHandler {
|
|
|
699
704
|
didTrigger: boolean[] = [];
|
|
700
705
|
receivers: Array<SignalReceiver | null> = [];
|
|
701
706
|
|
|
707
|
+
// private _lastTime: number = -1;
|
|
708
|
+
|
|
702
709
|
evaluate(time: number) {
|
|
703
|
-
if (this.receivers.length <= 0) return;
|
|
704
710
|
if (this.track.muted) return;
|
|
711
|
+
|
|
712
|
+
// let lastTime = this._lastTime;
|
|
713
|
+
// if (lastTime === -1) lastTime = time;
|
|
714
|
+
// this._lastTime = time;
|
|
715
|
+
|
|
705
716
|
for (let i = 0; i < this.models.length; i++) {
|
|
706
717
|
const model = this.models[i];
|
|
707
718
|
const wasTriggered = this.didTrigger[i];
|
|
708
719
|
const td = model.time - time;
|
|
709
|
-
let isActive =
|
|
720
|
+
let isActive = false;
|
|
721
|
+
if (model.retroActive) {
|
|
722
|
+
isActive = td <= 0.000001;
|
|
723
|
+
}
|
|
724
|
+
// TODO: handle signal asset at time 0 (only trigger when time is 0)
|
|
725
|
+
// else if (model.time < .001) {
|
|
726
|
+
// if (td <= 0.000001 && lastTime > time)
|
|
727
|
+
// isActive = true;
|
|
728
|
+
// }
|
|
729
|
+
else {
|
|
730
|
+
const abs = Math.abs(td);
|
|
731
|
+
if (abs >= .00001 && abs < .1) {
|
|
732
|
+
isActive = true;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// console.log(time, td, isActive);
|
|
710
736
|
if (isActive) {
|
|
711
737
|
if (!wasTriggered) {
|
|
712
738
|
this.didTrigger[i] = true;
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
739
|
+
// If a signal doesnt have any explicit receivers it will invoke the signal globally
|
|
740
|
+
if (this.receivers?.length <= 0) {
|
|
741
|
+
SignalReceiver.invoke(model.asset);
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
for (const rec of this.receivers) {
|
|
745
|
+
if (!rec) continue;
|
|
746
|
+
rec.invoke(model.asset);
|
|
747
|
+
}
|
|
716
748
|
}
|
|
717
|
-
// console.log("TRIGGER " + model.asset);
|
|
718
|
-
// TimelineSignals.invoke(model.asset);
|
|
719
749
|
}
|
|
720
750
|
}
|
|
721
751
|
else {
|