Needle Engine

Changes between version 4.2.5 and 4.3.0-alpha
Files changed (5) hide show
  1. plugins/vite/alias.js +6 -3
  2. src/engine/engine_addressables.ts +21 -0
  3. src/engine/engine_input.ts +20 -1
  4. src/engine-components/ui/EventSystem.ts +9 -7
  5. src/engine-components/SceneSwitcher.ts +30 -8
plugins/vite/alias.js CHANGED
@@ -57,6 +57,8 @@
57
57
  }
58
58
  if (debug) {
59
59
  const outputFilePath = path.resolve(projectDir, 'node_modules/.vite/needle.alias.log');
60
+ const outputDirectory = path.dirname(outputFilePath);
61
+ if (!existsSync(outputDirectory)) mkdirSync(outputDirectory, { recursive: true });
60
62
  outputDebugFile = createWriteStream(outputFilePath, { flags: "a" });
61
63
  const timestamp = new Date().toISOString();
62
64
  outputDebugFile.write("\n\n\n--------------------------\n");
@@ -68,7 +70,7 @@
68
70
  const aliasPlugin = {
69
71
  name: "needle-alias",
70
72
  config(config) {
71
- if (debug) console.log('[needle-alias] ProjectDirectory: ' + projectDir);
73
+ if (debug) console.log('[needle-alias] ProjectDirectory: ' + projectDir);
72
74
  if (!config.resolve) config.resolve = {};
73
75
  if (!config.resolve.alias) config.resolve.alias = {};
74
76
  const aliasDict = config.resolve.alias;
@@ -97,6 +99,7 @@
97
99
  let lastImporter = "";
98
100
  /** This plugin logs all imports. This helps to find cases where incorrect folders are found/resolved. */
99
101
 
102
+ /** @type {import("vite").Plugin} */
100
103
  const debuggingPlugin = {
101
104
  name: "needle:alias-debug",
102
105
  // needs to run before regular resolver
@@ -115,9 +118,9 @@
115
118
  // verbose logging for all imports
116
119
  if (lastImporter !== importer) {
117
120
  lastImporter = importer;
118
- log('[needle-alias] Resolving: ', importer, "(file)");
121
+ log(`[needle-alias] Resolving: ${importer} (file${options?.ssr ? ", SSR" : ""})`);
119
122
  }
120
- log('[needle-alias] ' + id);
123
+ log(`[needle-alias] ${id}`);
121
124
  return;
122
125
  },
123
126
  }
src/engine/engine_addressables.ts CHANGED
@@ -164,6 +164,11 @@
164
164
  return this._url;
165
165
  }
166
166
 
167
+ /** The name of the assigned url. This name is deduced from the url and might not reflect the actual name of the asset */
168
+ get urlName(): string {
169
+ return this._urlName;
170
+ };
171
+
167
172
  /**
168
173
  * @returns true if the uri is a valid URL (http, https, blob)
169
174
  */
@@ -180,6 +185,7 @@
180
185
  private _asset: any;
181
186
  private _glbRoot?: Object3D | null;
182
187
  private _url: string;
188
+ private _urlName: string;
183
189
  private _progressListeners: ProgressCallback[] = [];
184
190
 
185
191
  private _hash?: string;
@@ -191,12 +197,27 @@
191
197
  /** @internal */
192
198
  constructor(uri: string, hash?: string, asset: any = null) {
193
199
  this._url = uri;
200
+
201
+ const lastUriPart = uri.lastIndexOf("/");
202
+ if (lastUriPart >= 0) {
203
+ this._urlName = uri.substring(lastUriPart + 1);
204
+ // remove file extension
205
+ const lastDot = this._urlName.lastIndexOf(".");
206
+ if (lastDot >= 0) {
207
+ this._urlName = this._urlName.substring(0, lastDot);
208
+ }
209
+ }
210
+ else {
211
+ this._urlName = uri;
212
+ }
213
+
194
214
  this._hash = hash;
195
215
  if (uri.includes("?v="))
196
216
  this._hashedUri = uri;
197
217
  else
198
218
  this._hashedUri = hash ? uri + "?v=" + hash : uri;
199
219
  if (asset !== null) this.asset = asset;
220
+
200
221
  registerPrefabProvider(this._url, this.onResolvePrefab.bind(this));
201
222
  }
202
223
 
src/engine/engine_input.ts CHANGED
@@ -255,7 +255,26 @@
255
255
  /** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
256
256
  * @param type The event type to listen for
257
257
  * @param callback The callback to call when the event is triggered
258
- * @param options The options for adding the event listener
258
+ * @param options The options for adding the event listener.
259
+ * @example Basic usage:
260
+ * ```ts
261
+ * input.addEventListener("pointerdown", (evt) => {
262
+ * console.log("Pointer down", evt.pointerId, evt.pointerType);
263
+ * });
264
+ * ```
265
+ * @example Adding a listener that is called after all other listeners
266
+ * By using a higher value for the queue the listener will be called after other listeners (default queue is 0).
267
+ * ```ts
268
+ * input.addEventListener("pointerdown", (evt) => {
269
+ * console.log("Pointer down", evt.pointerId, evt.pointerType);
270
+ * }, { queue: 10 });
271
+ * ```
272
+ * @example Adding a listener that is only called once
273
+ * ```ts
274
+ * input.addEventListener("pointerdown", (evt) => {
275
+ * console.log("Pointer down", evt.pointerId, evt.pointerType);
276
+ * }, { once: true });
277
+ * ```
259
278
  */
260
279
  addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions);
261
280
  addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions);
src/engine-components/ui/EventSystem.ts CHANGED
@@ -59,6 +59,7 @@
59
59
  return ctx.scene.getComponent(EventSystem);
60
60
  }
61
61
 
62
+ /** Get the currently active event system */
62
63
  static get instance(): EventSystem | null {
63
64
  return this.get(Context.Current);
64
65
  }
@@ -75,7 +76,10 @@
75
76
  }
76
77
  }
77
78
 
78
- get hasActiveUI() { return this.currentActiveMeshUIComponents.length > 0; }
79
+ get hasActiveUI() {
80
+ return this.currentActiveMeshUIComponents.length > 0;
81
+ }
82
+
79
83
  get isHoveringObjects() { return this.hoveredByID.size > 0; }
80
84
 
81
85
  awake(): void {
@@ -424,7 +428,6 @@
424
428
  const res = UIRaycastUtils.isInteractable(actualGo, this.out);
425
429
  if (!res) return false;
426
430
  canvasGroup = this.out.canvasGroup ?? null;
427
-
428
431
  const handled = this.handleMeshUIIntersection(object, pressedOrClicked);
429
432
  if (!clicked && handled) {
430
433
  // return true;
@@ -753,7 +756,6 @@
753
756
  }
754
757
 
755
758
  private resetMeshUIStates() {
756
-
757
759
  if (this.context.input.getPointerPressedCount() > 0) {
758
760
  MeshUIHelper.resetLastSelected();
759
761
  }
@@ -816,7 +818,7 @@
816
818
  let foundBlock: Object3D | null = null;
817
819
 
818
820
  if (intersect) {
819
- foundBlock = this.findBlockInParent(intersect);
821
+ foundBlock = this.findBlockOrTextInParent(intersect);
820
822
  // console.log(intersect, "-- found block:", foundBlock)
821
823
  if (foundBlock && foundBlock !== this.lastSelected) {
822
824
  const interactable = foundBlock["interactable"];
@@ -840,13 +842,13 @@
840
842
  this.needsUpdate = true;
841
843
  }
842
844
 
843
- static findBlockInParent(elem: any): Object3D | null {
845
+ static findBlockOrTextInParent(elem: any): Object3D | null {
844
846
  if (!elem) return null;
845
- if (elem.isBlock) {
847
+ if (elem.isBlock || (elem.isText)) {
846
848
  // @TODO : Replace states managements
847
849
  // if (Object.keys(elem.states).length > 0)
848
850
  return elem;
849
851
  }
850
- return this.findBlockInParent(elem.parent);
852
+ return this.findBlockOrTextInParent(elem.parent);
851
853
  }
852
854
  }
src/engine-components/SceneSwitcher.ts CHANGED
@@ -46,6 +46,10 @@
46
46
  index: number;
47
47
  }
48
48
 
49
+ export type LoadSceneProgressEvent = LoadSceneEvent & {
50
+ progress: number,
51
+ }
52
+
49
53
  /**
50
54
  * The ISceneEventListener is called by the {@link SceneSwitcher} when a scene is loaded or unloaded.
51
55
  * It must be added to the root object of your scene (that is being loaded) or on the same object as the SceneSwitcher
@@ -226,6 +230,15 @@
226
230
  get currentlyLoadedScene() { return this._currentScene; }
227
231
 
228
232
  /**
233
+ * Called when a scene starts loading
234
+ */
235
+ @serializable(EventList)
236
+ sceneLoadingStart: EventList<LoadSceneEvent> = new EventList();
237
+
238
+ @serializable(EventList)
239
+ sceneLoadingProgress: EventList<ProgressEvent> = new EventList();
240
+
241
+ /**
229
242
  * The sceneLoaded event is called when a scene/glTF is loaded and added to the scene
230
243
  */
231
244
  @serializable(EventList)
@@ -281,7 +294,7 @@
281
294
  this._preloadScheduler.maxLoadAhead = this.preloadNext;
282
295
  this._preloadScheduler.maxLoadBehind = this.preloadPrevious;
283
296
  this._preloadScheduler.maxConcurrent = this.preloadConcurrent;
284
- this._preloadScheduler.begin();
297
+ this._preloadScheduler.begin(2000);
285
298
 
286
299
  // Begin loading the loading scene
287
300
  if (this.autoLoadFirstScene && this._currentIndex === -1 && !await this.tryLoadFromQueryParam()) {
@@ -602,11 +615,13 @@
602
615
 
603
616
  const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
604
617
  this.dispatchEvent(loadStartEvt);
618
+ this.sceneLoadingStart?.invoke(loadStartEvt.detail);
605
619
  await this.onStartLoading();
606
620
  // start loading and wait for the scene to be loaded
607
621
  await scene.loadAssetAsync((_, prog) => {
608
622
  this._currentLoadingProgress = prog;
609
623
  this.dispatchEvent(prog);
624
+ this.sceneLoadingProgress?.invoke(prog);
610
625
  }).catch(console.error);
611
626
  await this.onEndLoading();
612
627
  const finishedEvt = new CustomEvent<LoadSceneEvent>("loadscene-finished", { detail: { scene: scene, switcher: this, index: index } });
@@ -859,26 +874,30 @@
859
874
  this.maxConcurrent = maxConcurrent;
860
875
  }
861
876
 
862
- begin() {
877
+ begin(delay: number) {
863
878
  if (this._isRunning) return;
864
- if (debug) console.log("Preload begin")
879
+ if (debug) console.log("Preload begin", { delay })
865
880
  this._isRunning = true;
866
- let lastRoom: number = -1;
881
+ let lastRoom: number = -10;
867
882
  let searchDistance: number;
868
883
  let searchCall: number;
869
884
  const array = this._switcher.scenes;
885
+ const startTime = Date.now() + delay;
870
886
  const interval = setInterval(() => {
871
887
  if (this.allLoaded()) {
872
888
  if (debug)
873
- console.log("All scenes loaded");
889
+ console.log("All scenes (pre-)loaded");
874
890
  this.stop();
875
891
  }
876
892
  if (!this._isRunning) {
877
893
  clearInterval(interval);
878
894
  return;
879
895
  }
896
+
897
+ if (Date.now() < startTime) return;
898
+
880
899
  if (this.canLoadNewScene() === false) return;
881
- if (lastRoom !== this._switcher.currentIndex) {
900
+ if (lastRoom === -10 || lastRoom !== this._switcher.currentIndex) {
882
901
  lastRoom = this._switcher.currentIndex;
883
902
  searchCall = 0;
884
903
  searchDistance = 0;
@@ -892,8 +911,11 @@
892
911
  if (roomIndex < 0) return;
893
912
  // if (roomIndex < 0) roomIndex = array.length + roomIndex;
894
913
  if (roomIndex < 0 || roomIndex >= array.length) return;
895
- const scene = array[roomIndex];
896
- new LoadTask(roomIndex, scene, this._loadTasks);
914
+ if (!this._loadTasks.some(t => t.index === roomIndex)) {
915
+ const scene = array[roomIndex];
916
+ if (debug) console.log("Preload scene", { roomIndex, searchForward, lastRoom, currentIndex: this._switcher.currentIndex, tasks: this._loadTasks.length }, scene?.url);
917
+ new LoadTask(roomIndex, scene, this._loadTasks);
918
+ }
897
919
  }, 200);
898
920
  }