Needle Engine

Changes between version 3.37.4-alpha and 3.37.5-alpha
Files changed (3) hide show
  1. src/engine-components/OrbitControls.ts +29 -11
  2. src/engine-components/ParticleSystem.ts +44 -4
  3. src/engine-components/SceneSwitcher.ts +45 -5
src/engine-components/OrbitControls.ts CHANGED
@@ -185,12 +185,14 @@
185
185
 
186
186
  targetElement: HTMLElement | null = null;
187
187
 
188
+ /** @internal */
188
189
  awake(): void {
189
190
  this._didStart = false;
190
191
  this._didSetTarget = false;
191
192
  this._startedListeningToKeyEvents = false;
192
193
  }
193
194
 
195
+ /** @internal */
194
196
  start() {
195
197
  this._didStart = true;
196
198
  this._eventSystem = EventSystem.get(this.context) ?? undefined;
@@ -200,11 +202,13 @@
200
202
  }
201
203
  }
202
204
 
205
+ /** @internal */
203
206
  onDestroy() {
204
207
  this._controls?.dispose();
205
208
  this._eventSystem?.removeEventListener(EventSystemEvents.AfterHandleInput, this._afterHandleInputFn!);
206
209
  }
207
210
 
211
+ /** @internal */
208
212
  onEnable() {
209
213
  this._enableTime = this.context.time.time;
210
214
  const cameraComponent = GameObject.getComponent(this.gameObject, Camera);
@@ -266,6 +270,7 @@
266
270
  // }
267
271
  }
268
272
 
273
+ /** @internal */
269
274
  onDisable() {
270
275
  if (this._camera?.cam) {
271
276
  setCameraController(this._camera.cam, this, false);
@@ -299,10 +304,16 @@
299
304
  }
300
305
 
301
306
 
307
+ /** @internal */
302
308
  onBeforeRender() {
303
309
  if (!this._controls) return;
304
- if (this._cameraObject !== this.context.mainCamera) return;
305
-
310
+ if (this._cameraObject !== this.context.mainCamera) {
311
+ this._controls.enabled = false;
312
+ return;
313
+ }
314
+ this._controls.enabled = true;
315
+
316
+
306
317
  this.__handleSetTargetWhenBecomingActiveTheFirstTime();
307
318
 
308
319
  if (this.context.input.getPointerDown(1) || this.context.input.getPointerDown(2) || this.context.input.mouseWheelChanged || (this.context.input.getPointerPressed(0) && this.context.input.getPointerPositionDelta(0)?.length() || 0 > .1)) {
@@ -448,6 +459,7 @@
448
459
 
449
460
  /** Moves the camera to position smoothly.
450
461
  * @param position The position in local space of the controllerObject to move the camera to. If null the camera will stop lerping to the target.
462
+ * @param immediateOrDuration If true the camera will move immediately to the new position, otherwise it will lerp. If a number is passed in it will be used as the duration of the lerp.
451
463
  */
452
464
  public setCameraTargetPosition(position?: Object3D | Vector3 | null, immediateOrDuration: boolean | number = false) {
453
465
  if (!position) return;
@@ -477,8 +489,11 @@
477
489
  this._cameraLerpActive = false;
478
490
  }
479
491
 
480
- /** Moves the camera look-at target to a position smoothly. */
481
- public setLookTargetPosition(position: Object3D | Vector3 | null = null, immediateOrDuration: boolean = false) {
492
+ /** Moves the camera look-at target to a position smoothly.
493
+ * @param position The position in world space to move the camera target to. If null the camera will stop lerping to the target.
494
+ * @param immediateOrDuration If true the camera target will move immediately to the new position, otherwise it will lerp. If a number is passed in it will be used as the duration of the lerp.
495
+ */
496
+ public setLookTargetPosition(position: Object3D | Vector3 | null = null, immediateOrDuration: boolean | number = false) {
482
497
  if (!this._controls) return;
483
498
  if (!position) return
484
499
  if (position instanceof Object3D) {
@@ -506,7 +521,10 @@
506
521
  this._lookTargetLerpActive = false;
507
522
  }
508
523
 
509
- /** Sets the look at target from an assigned lookAtConstraint source by index */
524
+ /** Sets the look at target from an assigned lookAtConstraint source by index
525
+ * @param index The index of the source to use
526
+ * @param t The interpolation factor between the current look at target and the new target
527
+ */
510
528
  private setLookTargetFromConstraint(index: number = 0, t: number = 1): boolean {
511
529
  if (!this._controls) return false;
512
530
  const sources = this.lookAtConstraint?.sources;
@@ -546,12 +564,12 @@
546
564
 
547
565
  this.setLookTargetPosition(hit.point);
548
566
 
549
- if (this.context.mainCamera) {
550
- const pos = getWorldPosition(this.context.mainCamera);
551
- const cameraTarget = pos.clone().sub(this.controls.target).add(this._lookTargetEndPosition);
552
- this._cameraObject?.parent?.worldToLocal(cameraTarget);
553
- this.setCameraTargetPosition(cameraTarget);
554
- }
567
+ // if (this.context.mainCamera) {
568
+ // const pos = getWorldPosition(this.context.mainCamera);
569
+ // const cameraTarget = pos.clone().sub(this.controls.target).add(this._lookTargetEndPosition);
570
+ // this._cameraObject?.parent?.worldToLocal(cameraTarget);
571
+ // this.setCameraTargetPosition(cameraTarget);
572
+ // }
555
573
  break;
556
574
  }
557
575
  }
src/engine-components/ParticleSystem.ts CHANGED
@@ -35,6 +35,7 @@
35
35
  Manual = 4,
36
36
  }
37
37
 
38
+ /** @internal */
38
39
  export class ParticleSystemRenderer extends Behaviour {
39
40
 
40
41
  @serializable()
@@ -88,7 +89,7 @@
88
89
  // don't modify the assigned material
89
90
  material = material.clone();
90
91
  material.side = BackSide;
91
- if(trailEnabled) this.trailMaterial = material;
92
+ if (trailEnabled) this.trailMaterial = material;
92
93
  else this.particleMaterial = material;
93
94
  }
94
95
  }
@@ -642,6 +643,11 @@
642
643
  waitEmiting: number = 0;
643
644
  }
644
645
 
646
+ /**
647
+ * The ParticleSystem component efficiently handles the rendering of particles.
648
+ *
649
+ * You can add custom behaviours to the particle system to fully customize the behaviour of the particles. See {@link ParticleSystemBaseBehaviour} and {@link ParticleSystem.addBehaviour} for more information.
650
+ */
645
651
  export class ParticleSystem extends Behaviour implements IParticleSystem {
646
652
 
647
653
  play(includeChildren: boolean = false) {
@@ -838,8 +844,23 @@
838
844
  return this._isUsedAsSubsystem;
839
845
  }
840
846
 
841
- /** Add a custom quarks behaviour to the particle system. You can add a quarks.Behaviour type.
842
- * @link https://github.com/Alchemist0823/three.quarks
847
+ /** Add a custom quarks behaviour to the particle system.
848
+ * You can add a quarks.Behaviour type or derive from {@link ParticleSystemBaseBehaviour}
849
+ * @link https://github.com/Alchemist0823/three.quarks
850
+ * @example
851
+ * ```typescript
852
+ * class MyBehaviour extends ParticleSystemBaseBehaviour {
853
+ * initialize(particle: Particle) {
854
+ * // initialize the particle
855
+ * }
856
+ * update(particle: Particle, delta: number) {
857
+ * // do something with the particle
858
+ * }
859
+ * }
860
+ *
861
+ * const system = gameObject.getComponent(ParticleSystem);
862
+ * system.addBehaviour(new MyBehaviour());
863
+ * ```
843
864
  */
844
865
  addBehaviour(particleSystemBehaviour: Behavior | ParticleSystemBaseBehaviour): boolean {
845
866
  if (!this._particleSystem) {
@@ -852,6 +873,19 @@
852
873
  this._particleSystem.addBehavior(particleSystemBehaviour);
853
874
  return true;
854
875
  }
876
+ /** Remove a custom quarks behaviour from the particle system. **/
877
+ removeBehaviour(particleSystemBehaviour: Behavior | ParticleSystemBaseBehaviour): boolean {
878
+ if (!this._particleSystem) {
879
+ return false;
880
+ }
881
+ const behaviours = this._particleSystem.behaviors;
882
+ const index = behaviours.indexOf(particleSystemBehaviour);
883
+ if (index !== -1) {
884
+ behaviours.splice(index, 1);
885
+ return true;
886
+ }
887
+ return true;
888
+ }
855
889
  /** Removes all behaviours from the particle system
856
890
  * **Note:** this will also remove the default behaviours like SizeBehaviour, ColorBehaviour etc.
857
891
  */
@@ -919,6 +953,7 @@
919
953
  }
920
954
  private _subEmitterSystems?: SubEmitterSystem[];
921
955
 
956
+ /** @internal */
922
957
  onAfterDeserialize(_) {
923
958
  // doing this here to get a chance to resolve the subemitter guid
924
959
  if (this._subEmitterSystems && Array.isArray(this._subEmitterSystems)) {
@@ -928,6 +963,7 @@
928
963
  }
929
964
  }
930
965
 
966
+ /** @internal */
931
967
  awake(): void {
932
968
  this._worldPositionFrame = -1;
933
969
 
@@ -976,11 +1012,13 @@
976
1012
  }
977
1013
  }
978
1014
 
1015
+ /** @internal */
979
1016
  start() {
980
1017
  this.addSubParticleSystems();
981
1018
  this.updateLayers();
982
1019
  }
983
1020
 
1021
+ /** @internal */
984
1022
  onDestroy(): void {
985
1023
  this._container?.removeFromParent();
986
1024
  this._batchSystem?.removeFromParent();
@@ -988,6 +1026,7 @@
988
1026
  this._particleSystem?.dispose();
989
1027
  }
990
1028
 
1029
+ /** @internal */
991
1030
  onEnable() {
992
1031
  if (!this.main) return;
993
1032
  if (this.inheritVelocity)
@@ -1004,6 +1043,7 @@
1004
1043
  this._batchSystem.visible = false;
1005
1044
  }
1006
1045
 
1046
+ /** @internal */
1007
1047
  onBeforeRender() {
1008
1048
  if (!this.main) return;
1009
1049
  if (this._didPreWarm === false && this.main?.prewarm === true) {
@@ -1143,7 +1183,7 @@
1143
1183
  }
1144
1184
 
1145
1185
 
1146
-
1186
+ /** @internal */
1147
1187
  export class SubEmitterSystem {
1148
1188
 
1149
1189
  particleSystem?: ParticleSystem;
src/engine-components/SceneSwitcher.ts CHANGED
@@ -43,7 +43,23 @@
43
43
  index: number;
44
44
  }
45
45
 
46
- /** The ISceneEventListener is called by the SceneSwitcher when a scene is loaded or unloaded. It must be added to the root object of your scene */
46
+ /**
47
+ * The ISceneEventListener is called by the {@link SceneSwitcher} when a scene is loaded or unloaded.
48
+ * It must be added to the root object of your scene (that is being loaded) or on the same object as the SceneSwitcher
49
+ * It can be used to e.g. smooth the transition between scenes or to load additional content when a scene is loaded.
50
+ * @example
51
+ * ```ts
52
+ * import { ISceneEventListener } from "@needle-tools/engine";
53
+ *
54
+ * // Add this component to the root object of a scene loaded by a SceneSwitcher or to the same object as the SceneSwitcher
55
+ * export class MySceneListener implements ISceneEventListener {
56
+ * async sceneOpened(sceneSwitcher: SceneSwitcher) {
57
+ * console.log("Scene opened", sceneSwitcher.currentlyLoadedScene?.uri);
58
+ * }
59
+ * }
60
+ * ```
61
+ *
62
+ **/
47
63
  export interface ISceneEventListener {
48
64
  /** Called when the scene is loaded and added */
49
65
  sceneOpened?(sceneSwitcher: SceneSwitcher): Promise<void>
@@ -60,6 +76,9 @@
60
76
  * - [Needle Website](https://needle.tools)
61
77
  * - [Songs Of Cultures](https://app.songsofcultures.com)
62
78
  *
79
+ * ### Interfaces
80
+ * Use the {@link ISceneEventListener} interface to listen to scene open and closing events with the ability to modify transitions and stall the scene loading process.
81
+ *
63
82
  * ### Events
64
83
  * - `loadscene-start`: Called when a scene starts loading
65
84
  * - `loadscene-finished`: Called when a scene finished loading
@@ -438,7 +457,8 @@
438
457
  }
439
458
 
440
459
  private _currentlyLoadingScene?: AssetReference;
441
- async __internalSwitchScene(scene: AssetReference): Promise<boolean> {
460
+ /** @internal */
461
+ private async __internalSwitchScene(scene: AssetReference): Promise<boolean> {
442
462
 
443
463
  if (this._currentScene) {
444
464
  if (debug) console.log("UNLOAD", scene.uri)
@@ -591,15 +611,25 @@
591
611
  await this._loadingScenePromise;
592
612
  if (this._isCurrentlyLoading && this.loadingScene?.asset) {
593
613
  if (debug) console.log("Add loading scene", this.loadingScene.uri, this.loadingScene.asset)
594
- const obj = this.loadingScene.asset as any as Object3D;
595
- GameObject.add(obj, this.gameObject);
596
- const sceneListener = this.tryGetSceneEventListener(obj);
614
+ const loadingScene = this.loadingScene.asset as any as Object3D;
615
+ GameObject.add(loadingScene, this.gameObject);
616
+ const sceneListener = this.tryGetSceneEventListener(loadingScene);
597
617
  if (sceneListener?.sceneOpened) {
598
618
  const res = sceneListener.sceneOpened(this);
599
619
  if (res instanceof Promise) await res;
600
620
  }
601
621
  }
602
622
  }
623
+ // Invoke the event on a loading listener on the same object as the scene switcher
624
+ if (this._isCurrentlyLoading) {
625
+ const listener = this.tryGetSceneEventListener(this.gameObject);
626
+ if (listener) {
627
+ if (listener.sceneOpened) {
628
+ const res = listener.sceneOpened(this);
629
+ if (res instanceof Promise) await res;
630
+ }
631
+ }
632
+ }
603
633
  }
604
634
  private async onEndLoading() {
605
635
  this._isCurrentlyLoading = false;
@@ -614,6 +644,16 @@
614
644
  }
615
645
  GameObject.remove(obj);
616
646
  }
647
+ // Invoke the event on a loading listener on the same object as the scene switcher
648
+ if (!this._isCurrentlyLoading) {
649
+ const listener = this.tryGetSceneEventListener(this.gameObject);
650
+ if (listener) {
651
+ if (listener.sceneClosing) {
652
+ const res = listener.sceneClosing();
653
+ if (res instanceof Promise) await res;
654
+ }
655
+ }
656
+ }
617
657
  }
618
658
 
619
659
  private tryGetSceneEventListener(obj: Object3D, level: number = 0): ISceneEventListener | null {