@needle-tools/engine 3.36.3-beta → 3.36.4-beta

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 (71) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/needle-engine.js +2824 -2711
  3. package/dist/needle-engine.light.js +1522 -1409
  4. package/dist/needle-engine.light.min.js +151 -146
  5. package/dist/needle-engine.light.umd.cjs +124 -119
  6. package/dist/needle-engine.min.js +110 -105
  7. package/dist/needle-engine.umd.cjs +124 -119
  8. package/lib/engine/engine_addressables.d.ts +22 -3
  9. package/lib/engine/engine_addressables.js +36 -4
  10. package/lib/engine/engine_addressables.js.map +1 -1
  11. package/lib/engine/engine_input.d.ts +4 -0
  12. package/lib/engine/engine_input.js +4 -0
  13. package/lib/engine/engine_input.js.map +1 -1
  14. package/lib/engine/engine_instancing.d.ts +3 -0
  15. package/lib/engine/engine_instancing.js +3 -0
  16. package/lib/engine/engine_instancing.js.map +1 -1
  17. package/lib/engine/engine_license.d.ts +4 -0
  18. package/lib/engine/engine_license.js +37 -13
  19. package/lib/engine/engine_license.js.map +1 -1
  20. package/lib/engine/engine_networking_instantiate.d.ts +1 -0
  21. package/lib/engine/engine_networking_instantiate.js +1 -0
  22. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  23. package/lib/engine/engine_scenetools.d.ts +17 -0
  24. package/lib/engine/engine_scenetools.js +17 -0
  25. package/lib/engine/engine_scenetools.js.map +1 -1
  26. package/lib/engine/engine_shaders.d.ts +22 -0
  27. package/lib/engine/engine_shaders.js +23 -0
  28. package/lib/engine/engine_shaders.js.map +1 -1
  29. package/lib/engine/engine_texture.d.ts +5 -1
  30. package/lib/engine/engine_texture.js +5 -1
  31. package/lib/engine/engine_texture.js.map +1 -1
  32. package/lib/engine/engine_three_utils.d.ts +46 -2
  33. package/lib/engine/engine_three_utils.js +47 -1
  34. package/lib/engine/engine_three_utils.js.map +1 -1
  35. package/lib/engine/engine_types.d.ts +8 -0
  36. package/lib/engine/engine_types.js +8 -1
  37. package/lib/engine/engine_types.js.map +1 -1
  38. package/lib/engine/engine_util_decorator.d.ts +2 -1
  39. package/lib/engine/engine_util_decorator.js +2 -1
  40. package/lib/engine/engine_util_decorator.js.map +1 -1
  41. package/lib/engine/engine_utils.js +4 -1
  42. package/lib/engine/engine_utils.js.map +1 -1
  43. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -1
  44. package/lib/engine/webcomponents/needle menu/needle-menu.js +13 -3
  45. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  46. package/lib/engine-components/Component.d.ts +1 -1
  47. package/lib/engine-components/ContactShadows.js +5 -0
  48. package/lib/engine-components/ContactShadows.js.map +1 -1
  49. package/lib/engine-components/OrbitControls.js +8 -0
  50. package/lib/engine-components/OrbitControls.js.map +1 -1
  51. package/lib/engine-components/SceneSwitcher.d.ts +50 -0
  52. package/lib/engine-components/SceneSwitcher.js +66 -0
  53. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/engine/engine_addressables.ts +36 -4
  56. package/src/engine/engine_input.ts +5 -1
  57. package/src/engine/engine_instancing.ts +3 -0
  58. package/src/engine/engine_license.ts +33 -12
  59. package/src/engine/engine_networking_instantiate.ts +2 -0
  60. package/src/engine/engine_scenetools.ts +17 -0
  61. package/src/engine/engine_shaders.ts +24 -0
  62. package/src/engine/engine_texture.ts +5 -1
  63. package/src/engine/engine_three_utils.ts +56 -7
  64. package/src/engine/engine_types.ts +8 -1
  65. package/src/engine/engine_util_decorator.ts +3 -2
  66. package/src/engine/engine_utils.ts +3 -1
  67. package/src/engine/webcomponents/needle menu/needle-menu.ts +15 -3
  68. package/src/engine-components/Component.ts +1 -1
  69. package/src/engine-components/ContactShadows.ts +9 -1
  70. package/src/engine-components/OrbitControls.ts +7 -0
  71. package/src/engine-components/SceneSwitcher.ts +70 -1
@@ -1,5 +1,5 @@
1
- import { AnimationAction, Euler, Mesh,Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, Texture, Uniform, Vector3 } from "three";
2
- import { ShaderMaterial,WebGLRenderer } from "three";
1
+ import { AnimationAction, Euler, Mesh, Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, Texture, Uniform, Vector3 } from "three";
2
+ import { ShaderMaterial, WebGLRenderer } from "three";
3
3
 
4
4
  import { Mathf } from "./engine_math.js"
5
5
  import { CircularBuffer } from "./engine_utils.js";
@@ -13,6 +13,7 @@ export function slerp(vec: Vector3, end: Vector3, t: number) {
13
13
  }
14
14
 
15
15
  const flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
16
+
16
17
  export function lookAtInverse(obj: Object3D, target: Vector3) {
17
18
 
18
19
  obj.lookAt(target);
@@ -50,6 +51,14 @@ export function lookAtObject(object: Object3D, target: Object3D, keepUpDirection
50
51
 
51
52
 
52
53
  const _tempVecs = new CircularBuffer(() => new Vector3(), 100);
54
+
55
+ /** Gets a temporary vector. If a vector is passed in it will be copied to the temporary vector
56
+ * Temporary vectors are cached and reused internally. Don't store them!
57
+ * @param vecOrX the vector to copy or the x value
58
+ * @param y the y value
59
+ * @param z the z value
60
+ * @returns a temporary vector
61
+ */
53
62
  export function getTempVector(vecOrX?: Vector3 | number | DOMPointReadOnly, y?: number, z?: number) {
54
63
  const vec = _tempVecs.get();
55
64
  if (vecOrX instanceof Vector3) vec.copy(vecOrX);
@@ -62,6 +71,13 @@ export function getTempVector(vecOrX?: Vector3 | number | DOMPointReadOnly, y?:
62
71
  return vec;
63
72
  }
64
73
  const _tempQuats = new CircularBuffer(() => new Quaternion(), 100);
74
+
75
+ /**
76
+ * Gets a temporary quaternion. If a quaternion is passed in it will be copied to the temporary quaternion
77
+ * Temporary quaternions are cached and reused internally. Don't store them!
78
+ * @param value the quaternion to copy
79
+ * @returns a temporary quaternion
80
+ */
65
81
  export function getTempQuaternion(value?: Quaternion | DOMPointReadOnly) {
66
82
  const val = _tempQuats.get();
67
83
  if (value instanceof Quaternion) val.copy(value);
@@ -73,6 +89,13 @@ export function getTempQuaternion(value?: Quaternion | DOMPointReadOnly) {
73
89
  const _worldPositions = new CircularBuffer(() => new Vector3(), 100);
74
90
  const _lastMatrixWorldUpdateKey = Symbol("lastMatrixWorldUpdateKey");
75
91
 
92
+ /**
93
+ * Get the world position of an object
94
+ * @param obj the object to get the world position from
95
+ * @param vec a vector to store the result in. If not passed in a temporary vector will be used
96
+ * @param updateParents if true the parents will be updated before getting the world position
97
+ * @returns the world position
98
+ */
76
99
  export function getWorldPosition(obj: Object3D, vec: Vector3 | null = null, updateParents: boolean = true): Vector3 {
77
100
  const wp = vec ?? _worldPositions.get();
78
101
  if (!obj) return wp.set(0, 0, 0);
@@ -87,20 +110,34 @@ export function getWorldPosition(obj: Object3D, vec: Vector3 | null = null, upda
87
110
  return wp;
88
111
  }
89
112
 
90
- export function setWorldPosition(obj: Object3D, val: Vector3) {
91
- if (!obj) return;
113
+ /**
114
+ * Set the world position of an object
115
+ * @param obj the object to set the world position of
116
+ * @param val the world position to set
117
+ */
118
+ export function setWorldPosition(obj: Object3D, val: Vector3): Object3D {
119
+ if (!obj) return obj;
92
120
  const wp = _worldPositions.get();
93
121
  if (val !== wp)
94
122
  wp.copy(val);
95
123
  const obj2 = obj?.parent ?? obj;
96
124
  obj2.worldToLocal(wp);
97
125
  obj.position.set(wp.x, wp.y, wp.z);
126
+ return obj;
98
127
  }
99
128
 
100
- export function setWorldPositionXYZ(obj: Object3D, x: number, y: number, z: number) {
129
+ /**
130
+ * Set the world position of an object
131
+ * @param obj the object to set the world position of
132
+ * @param x the x position
133
+ * @param y the y position
134
+ * @param z the z position
135
+ */
136
+ export function setWorldPositionXYZ(obj: Object3D, x: number, y: number, z: number): Object3D {
101
137
  const wp = _worldPositions.get();
102
138
  wp.set(x, y, z);
103
139
  setWorldPosition(obj, wp);
140
+ return obj;
104
141
  }
105
142
 
106
143
 
@@ -254,7 +291,7 @@ export function logHierarchy(root: Object3D | null | undefined, collapsible: boo
254
291
 
255
292
  export function getParentHierarchyPath(obj: Object3D): string {
256
293
  let path = obj?.name || "";
257
- if(!obj) return path;
294
+ if (!obj) return path;
258
295
  let parent = obj.parent;
259
296
  while (parent) {
260
297
  path = parent.name + "/" + path;
@@ -278,6 +315,9 @@ export function isAnimationAction(obj: object) {
278
315
 
279
316
 
280
317
 
318
+ /**
319
+ * Utility class to perform various graphics operations like copying textures to canvas
320
+ */
281
321
  export class Graphics {
282
322
  private static planeGeometry = new PlaneGeometry(2, 2, 1, 1);
283
323
  private static renderer: WebGLRenderer | undefined = undefined;
@@ -300,6 +340,9 @@ export class Graphics {
300
340
  }`;
301
341
  private static blipMaterial: ShaderMaterial | undefined = undefined;
302
342
 
343
+ /**
344
+ * Create a blit material for copying textures
345
+ */
303
346
  static createBlitMaterial(fragment: string): ShaderMaterial {
304
347
  return new ShaderMaterial({
305
348
  uniforms: { map: new Uniform(null) },
@@ -309,7 +352,13 @@ export class Graphics {
309
352
  }
310
353
  private static mesh: Mesh | undefined = undefined;
311
354
 
312
- static copyTexture(texture: Texture, blitMaterial?: ShaderMaterial) {
355
+ /**
356
+ * Copy a texture to a new texture
357
+ * @param texture the texture to copy
358
+ * @param blitMaterial the material to use for copying (optional)
359
+ * @returns the newly created, copied texture
360
+ */
361
+ static copyTexture(texture: Texture, blitMaterial?: ShaderMaterial): Texture {
313
362
  const material = blitMaterial ?? this.blipMaterial!;
314
363
  material.uniforms.map.value = texture;
315
364
  material.needsUpdate = true;
@@ -320,6 +320,9 @@ export type Vec4 = {
320
320
 
321
321
  const contactsVectorBuffer = new CircularBuffer(() => new Vector3(), 20);
322
322
 
323
+ /**
324
+ * Holds information about physics contacts
325
+ */
323
326
  export class ContactPoint {
324
327
 
325
328
  private readonly _point: Vec3;
@@ -350,6 +353,7 @@ export class ContactPoint {
350
353
  return target.set(this._tangentVelocity.x, this._tangentVelocity.y, this._tangentVelocity.z);
351
354
  }
352
355
 
356
+ /** @internal */
353
357
  constructor(point: Vec3, dist: number, normal: Vec3, impulse: number, friction: number, tangentVelocity: Vec3) {
354
358
  this._point = point;
355
359
  this.distance = dist;
@@ -360,12 +364,15 @@ export class ContactPoint {
360
364
  }
361
365
  }
362
366
 
363
- /// all info in here must be readonly because the object is only created once per started collision
367
+ /**
368
+ * Holds information about a collision event. Includes a list of contact points and the colliders involved
369
+ */
364
370
  export class Collision {
365
371
 
366
372
  /** The contact points of this collision. Contains information about positions, normals, distance, friction, impulse... */
367
373
  readonly contacts: ContactPoint[];
368
374
 
375
+ /** @internal */
369
376
  constructor(obj: IGameObject, otherCollider: ICollider, contacts: ContactPoint[]) {
370
377
  this.me = obj;
371
378
  this._collider = otherCollider;
@@ -96,8 +96,9 @@ function createPropertyWrapper(target: IComponent | any, _propertyKey: string |
96
96
 
97
97
 
98
98
 
99
- /** experimental attribute - use to hook into another type's methods and run before the other methods run (similar to Harmony prefixes).
100
- * Return false to prevent the original method from running.
99
+ /** Experimental attribute
100
+ * Use to hook into another type's methods and run before the other methods run (similar to Harmony prefixes).
101
+ * Return false to prevent the original method from running.
101
102
  */
102
103
  export const prefix = function <T>(type: Constructor<T>) {
103
104
  return function (target: IComponent | any, _propertyKey: string | { name: string }, _PropertyDescriptor: PropertyDescriptor) {
@@ -319,10 +319,12 @@ export function resolveUrl(source: SourceIdentifier | undefined, uri: string): s
319
319
  if (pathIndex >= 0) {
320
320
  // Take the source uri as the base path
321
321
  const basePath = source.substring(0, pathIndex + 1);
322
+ // make sure we don't have double slashes
323
+ while (basePath.endsWith("/") && uri.startsWith("/")) uri = uri.substring(1);
322
324
  // Append the relative uri
323
325
  const newUri = basePath + uri;
324
326
  // newUri = new URL(newUri, globalThis.location.href).href;
325
- if (debugGetPath) console.log("source:", source, "- changed uri \nfrom", uri, "\n→ ", newUri, "\n" + basePath);
327
+ if (debugGetPath) console.log("source:", source, "changed uri \nfrom", uri, "\nto ", newUri, "\nbasePath: " + basePath);
326
328
  return newUri;
327
329
  }
328
330
  return uri;
@@ -34,7 +34,7 @@ export class NeedleMenu {
34
34
  private readonly _spatialMenu: NeedleSpatialMenu;
35
35
 
36
36
  constructor(context: Context) {
37
- this._menu = NeedleMenuElement.getOrCreate(context.domElement);
37
+ this._menu = NeedleMenuElement.getOrCreate(context.domElement, context);
38
38
  this._context = context;
39
39
  this._spatialMenu = new NeedleSpatialMenu(context, this._menu);
40
40
  window.addEventListener("message", this.onPostMessage);
@@ -160,7 +160,7 @@ export class NeedleMenuElement extends HTMLElement {
160
160
  return document.createElement(elementName, { is: elementName });
161
161
  }
162
162
 
163
- static getOrCreate(domElement: HTMLElement) {
163
+ static getOrCreate(domElement: HTMLElement, context: Context) {
164
164
  let element = domElement.querySelector(elementName) as NeedleMenuElement | null;
165
165
  if (!element && domElement.shadowRoot) {
166
166
  element = domElement.shadowRoot.querySelector(elementName);
@@ -168,6 +168,7 @@ export class NeedleMenuElement extends HTMLElement {
168
168
  if (!element) {
169
169
  element = NeedleMenuElement.create() as NeedleMenuElement;
170
170
  element._domElement = domElement;
171
+ element._context = context;
171
172
  if (domElement.shadowRoot)
172
173
  domElement.shadowRoot.appendChild(element);
173
174
  else
@@ -177,6 +178,7 @@ export class NeedleMenuElement extends HTMLElement {
177
178
  }
178
179
 
179
180
  private _domElement: HTMLElement | null = null;
181
+ private _context: Context | null = null;
180
182
 
181
183
  constructor() {
182
184
  super();
@@ -414,6 +416,11 @@ export class NeedleMenuElement extends HTMLElement {
414
416
  });
415
417
 
416
418
 
419
+
420
+ let context = this._context;
421
+ // we need to assign it in the timeout because the reference is set *after* the constructor did run
422
+ setTimeout(() => context = this._context);
423
+
417
424
  // watch changes
418
425
  let showInterval = -1;
419
426
  const rootObserver = new MutationObserver(mutations => {
@@ -425,7 +432,12 @@ export class NeedleMenuElement extends HTMLElement {
425
432
  if (!hasProLicense()) {
426
433
  clearInterval(showInterval);
427
434
  showInterval = setInterval(() => {
428
- if (parent != this._domElement?.shadowRoot)
435
+ if (context?.isInAR && context.arOverlayElement) {
436
+ if (parent != context.arOverlayElement) {
437
+ context.arOverlayElement.appendChild(this);
438
+ }
439
+ }
440
+ else if (parent != this._domElement?.shadowRoot)
429
441
  this._domElement?.shadowRoot?.appendChild(this);
430
442
  this.style.display = "flex";
431
443
  this.style.visibility = "visible";
@@ -447,7 +447,7 @@ export abstract class Component implements IComponent, EventTarget,
447
447
  this.__destroyed = true;
448
448
  }
449
449
  /** called when you decorate fields with the @validate() decorator
450
- * @param field the name of the field that was changed
450
+ * @param prop the name of the field that was changed
451
451
  */
452
452
  onValidate?(prop?: string): void;
453
453
 
@@ -1,4 +1,4 @@
1
- import { CustomBlending, DoubleSide, Group, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MinEquation, OrthographicCamera, PlaneGeometry, ShaderMaterial, WebGLRenderTarget } from "three";
1
+ import { CustomBlending, DoubleSide, FrontSide, Group, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, WebGLRenderTarget } from "three";
2
2
  import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
3
3
  import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
4
4
 
@@ -214,11 +214,19 @@ export class ContactShadows extends Behaviour {
214
214
 
215
215
  const prevXRState = renderer.xr.enabled;
216
216
  renderer.xr.enabled = false;
217
+
218
+ const list = renderer.renderLists.get(scene, 0);
219
+ const prev = list.transparent;
220
+ list.transparent = [];
217
221
 
218
222
  // render to the render target to get the depths
219
223
  renderer.setRenderTarget(this.renderTarget);
224
+ renderer.clear();
225
+
220
226
  renderer.render(scene, this.shadowCamera);
221
227
 
228
+ list.transparent = prev;
229
+
222
230
  // for the shearing idea
223
231
  // this.shadowCamera.projectionMatrix.copy(mat);
224
232
 
@@ -639,10 +639,17 @@ export class OrbitControls extends Behaviour implements ICameraController {
639
639
  expandByObjectRecursive(child);
640
640
  }
641
641
  }
642
+ let hasAnyObject = false;
642
643
  for (const object of objects) {
644
+ if (!object) continue;
645
+ hasAnyObject = true;
643
646
  object.updateMatrixWorld();
644
647
  expandByObjectRecursive(object);
645
648
  }
649
+ if (!hasAnyObject) {
650
+ console.warn("No objects to fit camera to...");
651
+ return;
652
+ }
646
653
 
647
654
  camera.updateMatrixWorld();
648
655
  camera.updateProjectionMatrix();
@@ -1,6 +1,6 @@
1
1
  import { Object3D } from "three";
2
2
 
3
- import { AssetReference } from "../engine/engine_addressables.js";
3
+ import { Addressables, AssetReference } from "../engine/engine_addressables.js";
4
4
  import { registerObservableAttribute } from "../engine/engine_element_extras.js";
5
5
  import { InputEvents } from "../engine/engine_input.js";
6
6
  import { isLocalNetwork } from "../engine/engine_networking_utils.js";
@@ -64,6 +64,7 @@ export interface ISceneEventListener {
64
64
  * - `loadscene-start`: Called when a scene starts loading
65
65
  * - `loadscene-finished`: Called when a scene finished loading
66
66
  * - `progress`: Called when a scene is loading and the progress changes
67
+ * - `scene-opened`: Called when a scene is loaded and added to the SceneSwitcher's GameObject
67
68
  * @example
68
69
  * ```ts
69
70
  * sceneSwitcher.addEventListener("loadscene-start", (e) => {
@@ -75,6 +76,9 @@ export interface ISceneEventListener {
75
76
  * sceneSwitcher.addEventListener("progress", (e) => {
76
77
  * console.log("Loading progress", e.loaded, e.total);
77
78
  * });
79
+ * sceneSwitcher.addEventListener("scene-opened", (e) => {
80
+ * console.log("Scene opened", e.detail.scene.uri);
81
+ * });
78
82
  * ```
79
83
  *
80
84
  */
@@ -159,10 +163,14 @@ export class SceneSwitcher extends Behaviour {
159
163
 
160
164
  private _preloadScheduler?: PreLoadScheduler;
161
165
 
166
+ /** @internal */
162
167
  awake(): void {
168
+ if (this.scenes === undefined) this.scenes = [];
169
+
163
170
  if (debug) console.log("SceneSwitcher", this);
164
171
  }
165
172
 
173
+ /** @internal */
166
174
  async onEnable() {
167
175
  globalThis.addEventListener("popstate", this.onPopState);
168
176
  this.context.input.addEventListener(InputEvents.KeyDown, this.onInputKeyDown);
@@ -210,6 +218,7 @@ export class SceneSwitcher extends Behaviour {
210
218
  }
211
219
  }
212
220
 
221
+ /** @internal */
213
222
  onDisable(): void {
214
223
  globalThis.removeEventListener("popstate", this.onPopState);
215
224
  this.context.input.removeEventListener(InputEvents.KeyDown, this.onInputKeyDown);
@@ -288,14 +297,58 @@ export class SceneSwitcher extends Behaviour {
288
297
  }
289
298
  }
290
299
 
300
+ /**
301
+ * Add a scene to the SceneSwitcher.
302
+ * If the scene is already added it will be added again.
303
+ * @param urlOrAssetReference The url of the scene or an AssetReference to the scene
304
+ * @returns The AssetReference of the scene that was added
305
+ * @example
306
+ * ```ts
307
+ * // adding a scene:
308
+ * sceneSwitcher.addScene("scene1.glb");
309
+ * // add another scene and load it:
310
+ * const scene2 = sceneSwitcher.addScene("scene2.glb");
311
+ * sceneSwitcher.switchScene(scene2).then(res => { console.log("Scene loaded", res); });
312
+ * ```
313
+ */
314
+ addScene(urlOrAssetReference: string | AssetReference): AssetReference {
315
+ if (typeof urlOrAssetReference === "string") {
316
+ let assetReference = this.context.addressables.findAssetReference(urlOrAssetReference);
317
+ if (!assetReference) {
318
+ assetReference = new AssetReference(urlOrAssetReference);
319
+ this.context.addressables.registerAssetReference(assetReference);
320
+ }
321
+ this.scenes.push(assetReference);
322
+ return assetReference;
323
+ }
324
+
325
+ this.scenes.push(urlOrAssetReference);
326
+ return urlOrAssetReference;
327
+ }
328
+
329
+ /**
330
+ * Load the next scene in the scenes array ({@link this.currentIndex} + 1)
331
+ * If the current scene is the last scene in the array and {@link this.clamp} is disabled then the first scene will be loaded.
332
+ * @returns a promise that resolves to true if the scene was loaded successfully
333
+ */
291
334
  selectNext(): Promise<boolean> {
292
335
  return this.select(this._currentIndex + 1);
293
336
  }
294
337
 
338
+ /**
339
+ * Load the previous scene in the scenes array ({@link this.currentIndex} - 1)
340
+ * If the current scene is the first scene in the array and {@link this.clamp} is disabled then the last scene will be loaded.
341
+ * @returns a promise that resolves to true if the scene was loaded successfully
342
+ */
295
343
  selectPrev(): Promise<boolean> {
296
344
  return this.select(this._currentIndex - 1);
297
345
  }
298
346
 
347
+ /**
348
+ * Load a scene by its index in the scenes array.
349
+ * @param index The index of the scene or a string that represents the scene uri (if the url is not known to the SceneSwitcher it will try to load the scene by its uri but it won't be added to the current scenes array. Use {@link addScene} to add a scene to the SceneSwitcher)
350
+ * @returns a promise that resolves to true if the scene was loaded successfully
351
+ */
299
352
  select(index: number | string): Promise<boolean> {
300
353
  if (debug) console.log("select", index);
301
354
 
@@ -341,6 +394,19 @@ export class SceneSwitcher extends Behaviour {
341
394
  private __lastSwitchScene?: AssetReference;
342
395
  private __lastSwitchScenePromise?: Promise<boolean>;
343
396
 
397
+ /**
398
+ * Switch to a scene by its AssetReference.
399
+ * If the scene is already loaded it will be unloaded and the new scene will be loaded.
400
+ * If the scene is already loading it will wait for the scene to be loaded.
401
+ * If the scene is already loaded and the same scene is requested again it will return the same promise that was returned the first time the scene was requested.
402
+ * @param scene The AssetReference of the scene to switch to
403
+ * @returns a promise that resolves to true if the scene was loaded successfully
404
+ * @example
405
+ * ```ts
406
+ * const myAssetReference = new AssetReference("scene1.glb");
407
+ * sceneSwitcher.switchScene(myAssetReference).then(res => { console.log("Scene loaded", res); });
408
+ * ```
409
+ */
344
410
  async switchScene(scene: AssetReference): Promise<boolean> {
345
411
  if (!(scene instanceof AssetReference)) {
346
412
  const type = typeof scene;
@@ -444,6 +510,9 @@ export class SceneSwitcher extends Behaviour {
444
510
  const res = sceneListener.sceneOpened(this);
445
511
  if (res instanceof Promise) await res;
446
512
  }
513
+
514
+ const openedEvt = new CustomEvent<LoadSceneEvent>("scene-opened", { detail: { scene: scene, switcher: this, index: index } });
515
+ this.dispatchEvent(openedEvt);
447
516
  return true;
448
517
  }
449
518
  }