@@ -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)
|
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
|
-
|
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
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
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
|
}
|
@@ -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.
|
842
|
-
* @link
|
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;
|
@@ -43,7 +43,23 @@
|
|
43
43
|
index: number;
|
44
44
|
}
|
45
45
|
|
46
|
-
/**
|
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
|
-
|
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
|
595
|
-
GameObject.add(
|
596
|
-
const sceneListener = this.tryGetSceneEventListener(
|
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 {
|