@needle-tools/engine 3.2.0-alpha → 3.2.2-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.
Files changed (37) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.js +10056 -9949
  3. package/dist/needle-engine.min.js +263 -263
  4. package/dist/needle-engine.umd.cjs +272 -272
  5. package/lib/engine/engine_gltf.js +4 -0
  6. package/lib/engine/engine_gltf.js.map +1 -1
  7. package/lib/engine/engine_rendererdata.d.ts +1 -1
  8. package/lib/engine/engine_rendererdata.js +3 -1
  9. package/lib/engine/engine_rendererdata.js.map +1 -1
  10. package/lib/engine/extensions/NEEDLE_lighting_settings.js +1 -1
  11. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  12. package/lib/engine-components/ParticleSystem.js +2 -1
  13. package/lib/engine-components/ParticleSystem.js.map +1 -1
  14. package/lib/engine-components/ParticleSystemModules.d.ts +17 -2
  15. package/lib/engine-components/ParticleSystemModules.js +105 -14
  16. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  17. package/lib/engine-components/SceneSwitcher.d.ts +2 -1
  18. package/lib/engine-components/SceneSwitcher.js +29 -11
  19. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  20. package/lib/engine-components/timeline/PlayableDirector.js +6 -2
  21. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  22. package/lib/engine-components/timeline/SignalAsset.d.ts +5 -7
  23. package/lib/engine-components/timeline/SignalAsset.js +38 -6
  24. package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
  25. package/lib/engine-components/timeline/TimelineTracks.js +31 -9
  26. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  27. package/lib/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +1 -1
  29. package/src/engine/engine_gltf.ts +6 -2
  30. package/src/engine/engine_rendererdata.ts +2 -1
  31. package/src/engine/extensions/NEEDLE_lighting_settings.ts +1 -1
  32. package/src/engine-components/ParticleSystem.ts +2 -1
  33. package/src/engine-components/ParticleSystemModules.ts +1483 -1400
  34. package/src/engine-components/SceneSwitcher.ts +32 -14
  35. package/src/engine-components/timeline/PlayableDirector.ts +13 -10
  36. package/src/engine-components/timeline/SignalAsset.ts +35 -8
  37. package/src/engine-components/timeline/TimelineTracks.ts +36 -12
@@ -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,18 +42,21 @@ 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;
43
49
 
44
- start() {
45
- if (!this.tryLoadFromQueryParam()) {
50
+ async start() {
51
+ if (this._currentIndex === -1 && !await this.tryLoadFromQueryParam()) {
46
52
  const value = this.context.domElement.getAttribute(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
47
53
  // let locked = this.lock;
48
54
  try {
49
55
  // this.lock = false;
50
- if (value === null || !this.trySelectSceneFromValue(value))
51
- this.select(0);
56
+ if (value === null || !await this.trySelectSceneFromValue(value)) {
57
+ if (this._currentIndex === -1)
58
+ this.select(0);
59
+ }
52
60
  }
53
61
  finally {
54
62
  // this.lock = locked;
@@ -146,18 +154,25 @@ export class SceneSwitcher extends Behaviour {
146
154
  }
147
155
  }
148
156
 
149
- selectNext() {
157
+ selectNext(): Promise<boolean> {
150
158
  return this.select(this._currentIndex + 1);
151
159
  }
152
160
 
153
- selectPrev() {
161
+ selectPrev(): Promise<boolean> {
154
162
  return this.select(this._currentIndex - 1);
155
163
  }
156
164
 
157
- select(index: number) {
165
+ select(index: number): Promise<boolean> {
166
+ if (debug) console.log("select", index)
158
167
  if (!this.scenes?.length) return couldNotLoadScenePromise;
159
- if (index < 0) index = this.scenes.length - 1;
160
- if (index >= this.scenes.length) index = 0;
168
+ if (index < 0) {
169
+ if (this.clamp) return couldNotLoadScenePromise;
170
+ index = this.scenes.length - 1;
171
+ }
172
+ else if (index >= this.scenes.length) {
173
+ if (this.clamp) return couldNotLoadScenePromise;
174
+ index = 0;
175
+ }
161
176
  const scene = this.scenes[index];
162
177
  return this.switchScene(scene);
163
178
  }
@@ -165,12 +180,15 @@ export class SceneSwitcher extends Behaviour {
165
180
  async switchScene(scene: AssetReference): Promise<boolean> {
166
181
  if (scene === this._currentScene) return true;
167
182
  if (this._currentScene)
168
- GameObject.remove(this._currentScene.asset);
183
+ this._currentScene.unload();
169
184
  const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
170
185
  this._currentScene = scene;
171
186
  try {
172
187
  await scene.loadAssetAsync();
173
- if (!scene.asset) return false;
188
+ if (!scene.asset) {
189
+ if (debug) console.warn("Failed loading scene:", scene);
190
+ return false;
191
+ }
174
192
  if (this._currentIndex === index) {
175
193
  GameObject.add(scene.asset, this.gameObject);
176
194
  // save the loaded scene as an url parameter
@@ -186,10 +204,10 @@ export class SceneSwitcher extends Behaviour {
186
204
  }
187
205
 
188
206
  private tryLoadFromQueryParam() {
189
- if (!this.queryParameterName?.length) return false;
207
+ if (!this.queryParameterName?.length) return couldNotLoadScenePromise;
190
208
  // try restore the scene from the url
191
209
  const value = getParam(this.queryParameterName);
192
- if (typeof value === "boolean") return false;
210
+ if (typeof value === "boolean") return couldNotLoadScenePromise;
193
211
  return this.trySelectSceneFromValue(value);
194
212
  }
195
213
 
@@ -217,7 +235,7 @@ export class SceneSwitcher extends Behaviour {
217
235
  }
218
236
  }
219
237
 
220
- if (isLocalNetwork()) console.warn("Unknown scene value or index: \"" + value + "\"", this)
238
+ if (isLocalNetwork()) console.warn("Can not find scene: \"" + value + "\"", this)
221
239
 
222
240
  return couldNotLoadScenePromise;
223
241
  }
@@ -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() : Tracks.AudioTrackHandler[] {
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 implements ISerializable {
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
- start() {
31
- console.log(this);
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 = model.retroActive ? td < 0 : (td < 0 && Math.abs(td) < .1);
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
- for (const rec of this.receivers) {
720
- if (!rec) continue;
721
- rec.invoke(model.asset);
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 {