@@ -8,8 +8,8 @@
|
|
8
8
|
export interface INeedleGltfLoader {
|
9
9
|
createBuiltinComponents(context: Context, gltfId: SourceIdentifier, gltf, seed: number | null | UIDProvider, extension?: NEEDLE_components): Promise<void>
|
10
10
|
writeBuiltinComponentData(comp: object, context: SerializationContext);
|
11
|
-
parseSync(context: Context, data
|
12
|
-
loadSync(context: Context, url: string, sourceId:string, seed: number | UIDProvider | null, prog?: (prog
|
11
|
+
parseSync(context: Context, data: string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined>;
|
12
|
+
loadSync(context: Context, url: string, sourceId: string, seed: number | UIDProvider | null, prog?: (prog: ProgressEvent) => void): Promise<GLTF | undefined>
|
13
13
|
}
|
14
14
|
|
15
15
|
let gltfLoader: INeedleGltfLoader;
|
@@ -20,6 +20,10 @@
|
|
20
20
|
}
|
21
21
|
|
22
22
|
export function registerLoader<T extends INeedleGltfLoader>(loader: ConstructorConcrete<T>) {
|
23
|
+
if (loader === null || loader === undefined) {
|
24
|
+
console.warn("Oh no: someone tried registering a non-existend gltf-loader. When you see this log it might mean that needle-engine is being imported multiple times. Please check your project setup.");
|
25
|
+
return;
|
26
|
+
}
|
23
27
|
if (gltfLoaderType !== loader) {
|
24
28
|
gltfLoaderType = loader;
|
25
29
|
gltfLoader = new loader();
|
@@ -172,7 +172,7 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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,15 +445,15 @@
|
|
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
458
|
const animationClips = binding?.gameObject?.animations;
|
456
459
|
if (animationClips) {
|
@@ -7,6 +7,8 @@
|
|
7
7
|
import { serializable } from "../engine/engine_serialization";
|
8
8
|
import { Behaviour, GameObject } from "./Component";
|
9
9
|
|
10
|
+
const debug = getParam("debugsceneswitcher");
|
11
|
+
|
10
12
|
const ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME = "scene";
|
11
13
|
|
12
14
|
ContextRegistry.registerCallback(ContextEvent.ContextRegistered, _ => {
|
@@ -25,6 +27,9 @@
|
|
25
27
|
@serializable()
|
26
28
|
queryParameterName: string = "scene";
|
27
29
|
|
30
|
+
@serializable()
|
31
|
+
clamp: boolean = true;
|
32
|
+
|
28
33
|
/** when enabled the new scene is pushed to the browser navigation history, only works with a valid query parameter set */
|
29
34
|
@serializable()
|
30
35
|
useHistory: boolean = true;
|
@@ -37,6 +42,7 @@
|
|
37
42
|
@serializable()
|
38
43
|
useSwipe: boolean = true;
|
39
44
|
|
45
|
+
|
40
46
|
private _currentIndex: number = -1;
|
41
47
|
private _currentScene: AssetReference | undefined = undefined;
|
42
48
|
private _engineElementOverserver: MutationObserver | undefined = undefined;
|
@@ -146,18 +152,25 @@
|
|
146
152
|
}
|
147
153
|
}
|
148
154
|
|
149
|
-
selectNext() {
|
155
|
+
selectNext(): Promise<boolean> {
|
150
156
|
return this.select(this._currentIndex + 1);
|
151
157
|
}
|
152
158
|
|
153
|
-
selectPrev() {
|
159
|
+
selectPrev(): Promise<boolean> {
|
154
160
|
return this.select(this._currentIndex - 1);
|
155
161
|
}
|
156
162
|
|
157
|
-
select(index: number) {
|
163
|
+
select(index: number): Promise<boolean> {
|
164
|
+
if (debug) console.log("select", index)
|
158
165
|
if (!this.scenes?.length) return couldNotLoadScenePromise;
|
159
|
-
if (index < 0)
|
160
|
-
|
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
|
+
}
|
161
174
|
const scene = this.scenes[index];
|
162
175
|
return this.switchScene(scene);
|
163
176
|
}
|
@@ -165,12 +178,15 @@
|
|
165
178
|
async switchScene(scene: AssetReference): Promise<boolean> {
|
166
179
|
if (scene === this._currentScene) return true;
|
167
180
|
if (this._currentScene)
|
168
|
-
|
181
|
+
this._currentScene.unload();
|
169
182
|
const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
|
170
183
|
this._currentScene = scene;
|
171
184
|
try {
|
172
185
|
await scene.loadAssetAsync();
|
173
|
-
if (!scene.asset)
|
186
|
+
if (!scene.asset) {
|
187
|
+
if (debug) console.warn("Failed loading scene:", scene);
|
188
|
+
return false;
|
189
|
+
}
|
174
190
|
if (this._currentIndex === index) {
|
175
191
|
GameObject.add(scene.asset, this.gameObject);
|
176
192
|
// save the loaded scene as an url parameter
|
@@ -7,28 +7,55 @@
|
|
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[] } = {};
|
25
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
|
+
|
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) {
|
@@ -225,8 +225,7 @@
|
|
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
|
-
if (this._animator)
|
229
|
-
{
|
228
|
+
if (this._animator) {
|
230
229
|
this._animatorWasEnabled = this._animator.enabled;
|
231
230
|
this._animator.enabled = false;
|
232
231
|
}
|
@@ -331,7 +330,7 @@
|
|
331
330
|
weight *= this.director.weight;
|
332
331
|
|
333
332
|
let handleLoop = isInTimeRange;
|
334
|
-
if(doPreExtrapolate){
|
333
|
+
if (doPreExtrapolate) {
|
335
334
|
if (preExtrapolation !== Models.ClipExtrapolation.Hold) {
|
336
335
|
time += model.start;
|
337
336
|
handleLoop = true;
|
@@ -666,7 +665,7 @@
|
|
666
665
|
public static dispose() {
|
667
666
|
AudioTrackHandler._currentlyLoading.clear();
|
668
667
|
}
|
669
|
-
|
668
|
+
|
670
669
|
private handleAudioLoading(model: Models.ClipModel, audio: THREE.Audio): Promise<AudioBuffer | null> | null {
|
671
670
|
if (!this._audioLoader) {
|
672
671
|
this._audioLoader = new THREE.AudioLoader();
|
@@ -681,7 +680,7 @@
|
|
681
680
|
});
|
682
681
|
return promise;
|
683
682
|
}
|
684
|
-
|
683
|
+
|
685
684
|
if (debug) console.warn("LOAD audio track", path, this.director.sourceId);
|
686
685
|
const loadingPromise = new Promise<AudioBuffer | null>((resolve, _reject) => {
|
687
686
|
this._audioLoader!.load(path,
|
@@ -705,23 +704,48 @@
|
|
705
704
|
didTrigger: boolean[] = [];
|
706
705
|
receivers: Array<SignalReceiver | null> = [];
|
707
706
|
|
707
|
+
// private _lastTime: number = -1;
|
708
|
+
|
708
709
|
evaluate(time: number) {
|
709
|
-
if (this.receivers.length <= 0) return;
|
710
710
|
if (this.track.muted) return;
|
711
|
+
|
712
|
+
// let lastTime = this._lastTime;
|
713
|
+
// if (lastTime === -1) lastTime = time;
|
714
|
+
// this._lastTime = time;
|
715
|
+
|
711
716
|
for (let i = 0; i < this.models.length; i++) {
|
712
717
|
const model = this.models[i];
|
713
718
|
const wasTriggered = this.didTrigger[i];
|
714
719
|
const td = model.time - time;
|
715
|
-
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);
|
716
736
|
if (isActive) {
|
717
737
|
if (!wasTriggered) {
|
718
738
|
this.didTrigger[i] = true;
|
719
|
-
|
720
|
-
|
721
|
-
|
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);
|
722
742
|
}
|
723
|
-
|
724
|
-
|
743
|
+
else {
|
744
|
+
for (const rec of this.receivers) {
|
745
|
+
if (!rec) continue;
|
746
|
+
rec.invoke(model.asset);
|
747
|
+
}
|
748
|
+
}
|
725
749
|
}
|
726
750
|
}
|
727
751
|
else {
|