@needle-tools/engine 3.2.0-alpha → 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 +5 -0
- package/dist/needle-engine.js +8024 -7968
- package/dist/needle-engine.min.js +262 -262
- package/dist/needle-engine.umd.cjs +263 -263
- package/lib/engine/engine_gltf.js +4 -0
- package/lib/engine/engine_gltf.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +1 -0
- package/lib/engine-components/SceneSwitcher.js +20 -4
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.js +6 -2
- 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 +31 -9
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/engine/engine_gltf.ts +6 -2
- package/src/engine-components/SceneSwitcher.ts +23 -7
- package/src/engine-components/timeline/PlayableDirector.ts +13 -10
- package/src/engine-components/timeline/SignalAsset.ts +35 -8
- package/src/engine-components/timeline/TimelineTracks.ts +36 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1-alpha",
|
|
4
4
|
"description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in",
|
|
5
5
|
"main": "dist/needle-engine.umd.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -8,8 +8,8 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
|
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 @@ export function getLoader(): INeedleGltfLoader {
|
|
|
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();
|
|
@@ -7,6 +7,8 @@ import { getParam, isMobileDevice, setParamWithoutReload } from "../engine/engin
|
|
|
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 @@ export class SceneSwitcher extends Behaviour {
|
|
|
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 @@ export class SceneSwitcher extends Behaviour {
|
|
|
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 @@ export class SceneSwitcher extends Behaviour {
|
|
|
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 @@ export class SceneSwitcher extends Behaviour {
|
|
|
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
|
|
@@ -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,15 +445,15 @@ 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
458
|
const animationClips = binding?.gameObject?.animations;
|
|
456
459
|
if (animationClips) {
|
|
@@ -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) {
|
|
@@ -225,8 +225,7 @@ 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
|
-
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 @@ export class AnimationTrackHandler extends TrackHandler {
|
|
|
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 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
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 @@ export class AudioTrackHandler extends TrackHandler {
|
|
|
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 @@ export class SignalTrackHandler extends TrackHandler {
|
|
|
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);
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
for (const rec of this.receivers) {
|
|
745
|
+
if (!rec) continue;
|
|
746
|
+
rec.invoke(model.asset);
|
|
747
|
+
}
|
|
722
748
|
}
|
|
723
|
-
// console.log("TRIGGER " + model.asset);
|
|
724
|
-
// TimelineSignals.invoke(model.asset);
|
|
725
749
|
}
|
|
726
750
|
}
|
|
727
751
|
else {
|