Needle Engine

Changes between version 3.36.0-beta and 3.36.1-beta
Files changed (66) hide show
  1. src/engine/engine_fileloader.js +2 -1
  2. src/engine-components/Animation.ts +3 -0
  3. src/engine-components/AnimationCurve.ts +4 -2
  4. src/engine-components/avatar/Avatar_Brain_LookAt.ts +2 -0
  5. src/engine-components/avatar/Avatar_MouthShapes.ts +1 -0
  6. src/engine-components/avatar/Avatar_MustacheShake.ts +1 -0
  7. src/engine-components/avatar/AvatarBlink_Simple.ts +1 -0
  8. src/engine-components/avatar/AvatarEyeLook_Rotation.ts +1 -0
  9. src/engine-components/AxesHelper.ts +3 -0
  10. src/engine/webcomponents/buttons.ts +117 -1
  11. src/engine-components/Component.ts +31 -3
  12. src/engine-components/codegen/components.ts +1 -0
  13. src/engine-components/ContactShadows.ts +7 -1
  14. src/engine/engine_addressables.ts +17 -2
  15. src/engine/engine_application.ts +8 -0
  16. src/engine/engine_audio.ts +3 -1
  17. src/engine/engine_camera.ts +5 -0
  18. src/engine/engine_constants.ts +3 -1
  19. src/engine/engine_context_registry.ts +19 -0
  20. src/engine/engine_context.ts +31 -3
  21. src/engine/engine_create_objects.ts +8 -0
  22. src/engine/engine_editor-sync.ts +5 -6
  23. src/engine/engine_element_attributes.ts +3 -1
  24. src/engine/engine_element_loading.ts +4 -0
  25. src/engine/engine_element_overlay.ts +1 -0
  26. src/engine/engine_element.ts +2 -1
  27. src/engine/engine_gameobject.ts +1 -0
  28. src/engine/engine_gizmos.ts +8 -2
  29. src/engine/xr/events.ts +74 -0
  30. src/engine-components/EventType.ts +3 -68
  31. src/engine-components/js-extensions/ExtensionUtils.ts +3 -0
  32. src/engine-components/Fog.ts +2 -2
  33. src/engine-components/GridHelper.ts +5 -0
  34. src/engine-components/GroundProjection.ts +3 -0
  35. src/engine-components/Joints.ts +1 -1
  36. src/engine-components/LODGroup.ts +3 -0
  37. src/engine-components/utils/LookAt.ts +18 -0
  38. src/engine/extensions/NEEDLE_progressive.ts +6 -4
  39. src/engine-components/NeedleMenu.ts +4 -1
  40. src/engine/xr/NeedleXRSession.ts +1 -0
  41. src/engine-components/js-extensions/Object3D.ts +4 -1
  42. src/engine-components/utils/OpenURL.ts +26 -4
  43. src/engine-components/timeline/PlayableDirector.ts +52 -22
  44. src/engine-components/PlayerColor.ts +4 -1
  45. src/engine-components/postprocessing/PostProcessingEffect.ts +26 -0
  46. src/engine-components/postprocessing/PostProcessingHandler.ts +3 -0
  47. src/engine/codegen/register_types.ts +4 -2
  48. src/engine-components/RendererInstancing.ts +4 -4
  49. src/engine-components/js-extensions/RGBAColor.ts +3 -0
  50. src/engine-components/timeline/SignalAsset.ts +6 -0
  51. src/engine-components/SyncedCamera.ts +11 -0
  52. src/engine-components/SyncedRoom.ts +36 -1
  53. src/engine-components/SyncedTransform.ts +10 -0
  54. src/engine-components/TestRunner.ts +3 -0
  55. src/engine-components/timeline/TimelineTracks.ts +4 -0
  56. src/engine-components/TransformGizmo.ts +5 -0
  57. src/engine-components/js-extensions/Vector.ts +1 -0
  58. src/engine-components/postprocessing/Volume.ts +7 -0
  59. src/engine-components/postprocessing/VolumeProfile.ts +1 -0
  60. src/engine-components/webxr/WebARCameraBackground.ts +14 -0
  61. src/engine-components/webxr/WebARSessionRoot.ts +4 -0
  62. src/engine-components/webxr/WebXR.ts +6 -1
  63. src/engine-components/webxr/WebXRButtons.ts +15 -92
  64. src/engine-components/webxr/WebXRRig.ts +4 -0
  65. src/engine-components/webxr/controllers/XRControllerModel.ts +3 -0
  66. src/engine-components/webxr/controllers/XRControllerMovement.ts +3 -0
src/engine/engine_fileloader.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { FileLoader } from "three";
2
2
 
3
- export const loader = new FileLoader();
3
+ const loader = new FileLoader();
4
4
 
5
+ /** @internal */
5
6
  export async function loadFileAsync(url) {
6
7
  return new Promise((resolve, reject) => {
7
8
  loader.load(url, resolve, undefined, reject);
src/engine-components/Animation.ts CHANGED
@@ -21,6 +21,9 @@
21
21
 
22
22
  class Vec2 { x!: number; y!: number }
23
23
 
24
+ /**
25
+ * Animation component to play animations on a GameObject
26
+ */
24
27
  export class Animation extends Behaviour {
25
28
 
26
29
  @serializable()
src/engine-components/AnimationCurve.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { Mathf } from "../engine/engine_math.js";
2
1
  import { serializable } from "../engine/engine_serialization_decorator.js";
3
2
 
4
- class Keyframe {
3
+ /**
4
+ * Keyframe is a representation of a keyframe in an AnimationCurve.
5
+ */
6
+ export class Keyframe {
5
7
  @serializable()
6
8
  time!: number;
7
9
  @serializable()
src/engine-components/avatar/Avatar_Brain_LookAt.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { Behaviour, GameObject } from "../Component.js";
9
9
  import { AvatarMarker } from "../webxr/WebXRAvatar.js";
10
10
 
11
+ /** @internal */
11
12
  export class Avatar_POI {
12
13
 
13
14
  public static Pois: { obj: Object3D, avatar: AvatarMarker | null }[] = [];
@@ -46,6 +47,7 @@
46
47
  public position: Vector3 = new Vector3();
47
48
  }
48
49
 
50
+ /** @internal */
49
51
  export class Avatar_Brain_LookAt extends Behaviour {
50
52
 
51
53
  public set controlledTarget(target: Object3D) {
src/engine-components/avatar/Avatar_MouthShapes.ts CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  const debug = utils.getParam("debugmouth");
10
10
 
11
+ /** @internal */
11
12
  export class Avatar_MouthShapes extends Behaviour {
12
13
  @serializable(Object3D)
13
14
  public idle: Object3D[] = [];
src/engine-components/avatar/Avatar_MustacheShake.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  import { Voip } from "../Voip.js";
5
5
  import { AvatarMarker } from "../webxr/WebXRAvatar.js";
6
6
 
7
+ /** @internal */
7
8
  export class Avatar_MustacheShake extends Behaviour {
8
9
  private voip: Voip | null = null;
9
10
  private marker: AvatarMarker | null = null;
src/engine-components/avatar/AvatarBlink_Simple.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import { XRFlag } from "../webxr/XRFlag.js";
6
6
 
7
7
 
8
+ /** @internal */
8
9
  export class AvatarBlink_Simple extends Behaviour {
9
10
 
10
11
  @serializable(Object3D)
src/engine-components/avatar/AvatarEyeLook_Rotation.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import { Behaviour, GameObject } from "../Component.js";
6
6
  import { Avatar_Brain_LookAt } from "./Avatar_Brain_LookAt.js";
7
7
 
8
+ /** @internal */
8
9
  export class AvatarEyeLook_Rotation extends Behaviour {
9
10
 
10
11
  @serializable(Object3D)
src/engine-components/AxesHelper.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
5
5
  import { Behaviour } from "./Component.js";
6
6
 
7
+ /**
8
+ * AxesHelper is a component that displays the axes of the object in the scene.
9
+ */
7
10
  export class AxesHelper extends Behaviour {
8
11
  @serializable()
9
12
  public length: number = 1;
src/engine/webcomponents/buttons.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { IContext } from "../engine_types.js";
2
+ import { generateQRCode } from "../engine_utils.js";
3
+ import { onXRSessionEnd, onXRSessionStart } from "../xr/events.js";
2
4
  import { getIconElement } from "./icons.js";
3
5
 
4
6
  /** Use the ButtonsFactory to create buttons with icons and functionality
@@ -8,7 +10,10 @@
8
10
  export class ButtonsFactory {
9
11
 
10
12
  private static _instance?: ButtonsFactory;
11
- /** get access to the default factory */
13
+ /** Get access to the default HTML button factory.
14
+ * Use this to get or create default Needle Engine buttons that can be added to your HTML UI
15
+ * If you want to create a new factory and create new button instances instead of shared buttons, use `ButtonsFactory.create()` instead
16
+ */
12
17
  static getOrCreate() {
13
18
  if (!this._instance) {
14
19
  this._instance = new ButtonsFactory();
@@ -104,4 +109,115 @@
104
109
  };
105
110
  return button;
106
111
  }
112
+
113
+
114
+ private _qrButton?: HTMLButtonElement;
115
+ /** Create a QR code button (or return the existing one if it already exists)
116
+ * The QR code button will show a QR code that can be scanned to open the current page on a phone
117
+ * The QR code will be generated with the current URL when the button is clicked
118
+ * @returns the QR code button element
119
+ */
120
+ createQRCode(): HTMLButtonElement {
121
+
122
+ if (this._qrButton) return this._qrButton;
123
+
124
+ const qrCodeButton = document.createElement("button");
125
+ this._qrButton = qrCodeButton;
126
+ qrCodeButton.innerText = "QR Code";
127
+ qrCodeButton.prepend(getIconElement("qr_code"));
128
+ qrCodeButton.title = "Scan this QR code with your phone to open this page";
129
+ this.hideElementDuringXRSession(qrCodeButton);
130
+
131
+
132
+ const qrCodeContainer = document.createElement("div");
133
+ qrCodeContainer.style.position = "fixed";
134
+ qrCodeContainer.style.display = "inline-block";
135
+ qrCodeContainer.style.padding = "1rem";
136
+ qrCodeContainer.style.backgroundColor = "white";
137
+ qrCodeContainer.style.borderRadius = "0.4rem";
138
+ qrCodeContainer.style.cursor = "pointer";
139
+ qrCodeContainer.style.zIndex = "1000";
140
+ qrCodeContainer.style.boxShadow = "0 0 12px rgba(0, 0, 0, 0.2)";
141
+
142
+ const qrCodeElement = document.createElement("div");
143
+ qrCodeElement.classList.add("qr-code-container");
144
+ qrCodeContainer.appendChild(qrCodeElement);
145
+
146
+ qrCodeButton.addEventListener("click", () => {
147
+ if (qrCodeContainer.parentNode) return hideQRCode();
148
+ showQRCode();
149
+ });
150
+
151
+ /** shows the QRCode near the button */
152
+ async function showQRCode() {
153
+ // generate the qr code when the button is clicked
154
+ // this ensures that we get the QRcode with the latest URL
155
+ await generateAndInsertQRCode();
156
+ // TODO: make sure it doesnt overflow the screen
157
+ // we need to add the qrCodeContainer to the body to get the correct size
158
+ document.body.appendChild(qrCodeContainer);
159
+ const containerRect = qrCodeElement.getBoundingClientRect();
160
+ const buttonRect = qrCodeButton.getBoundingClientRect();
161
+ qrCodeContainer.style.left = (buttonRect.left + buttonRect.width * .5 - containerRect.width * .5) + "px";
162
+ const isButtonInTopHalf = buttonRect.top < containerRect.height;
163
+ if (isButtonInTopHalf)
164
+ qrCodeContainer.style.top = `calc(${buttonRect.bottom}px + ${qrCodeContainer.style.padding} * .6)`;
165
+ else
166
+ qrCodeContainer.style.top = `calc(${buttonRect.top - containerRect.height}px - ${qrCodeContainer.style.padding} * 2.5)`;
167
+ qrCodeContainer.style.opacity = "0";
168
+ qrCodeContainer.style.pointerEvents = "all";
169
+ qrCodeContainer.style.transition = "opacity 0.2s ease-in-out";
170
+
171
+ // context click to hide the QR code again, if we dont timeout the event will be triggered immediately
172
+ setTimeout(() => {
173
+ qrCodeContainer.style.opacity = "1";
174
+ window.addEventListener("click", hideQRCode, { once: true })
175
+ });
176
+ window.addEventListener("resize", hideQRCode);
177
+ window.addEventListener("scroll", hideQRCode);
178
+
179
+ document.body.appendChild(qrCodeContainer);
180
+ }
181
+
182
+ /** hides to QRCode overlay and unsubscribes from events */
183
+ function hideQRCode() {
184
+ qrCodeContainer.style.pointerEvents = "none";
185
+ qrCodeContainer.style.transition = "opacity 0.2s";
186
+ qrCodeContainer.style.opacity = "0";
187
+ setTimeout(() => qrCodeContainer.parentNode?.removeChild(qrCodeContainer), 500);
188
+ window.removeEventListener("click", hideQRCode);
189
+ window.removeEventListener("resize", hideQRCode);
190
+ window.removeEventListener("scroll", hideQRCode);
191
+ };
192
+
193
+ /** generates a QR code and inserts it into the qrCodeElement */
194
+ async function generateAndInsertQRCode() {
195
+ const size = 200;
196
+ const code = await generateQRCode({
197
+ text: window.location.href,
198
+ width: size,
199
+ height: size,
200
+ });
201
+ qrCodeElement.innerHTML = "";
202
+ qrCodeElement.appendChild(code);
203
+ }
204
+
205
+ // lazily create the qr button
206
+ qrCodeButton.addEventListener("pointerenter", () => {
207
+ generateAndInsertQRCode();
208
+ }, { once: true });
209
+
210
+ return qrCodeButton;
211
+ }
212
+
213
+ private hideElementDuringXRSession(element: HTMLElement) {
214
+ onXRSessionStart(_ => {
215
+ element["previous-display"] = element.style.display;
216
+ element.style.display = "none";
217
+ });
218
+ onXRSessionEnd(_ => {
219
+ if (element["previous-display"] != undefined)
220
+ element.style.display = element["previous-display"];
221
+ });
222
+ }
107
223
  }
src/engine-components/Component.ts CHANGED
@@ -21,6 +21,13 @@
21
21
  // onDeserialize?(key: string, value: any): any | void;
22
22
  // }
23
23
 
24
+ /**
25
+ * All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like `addComponent` etc.
26
+ * Many of the GameObject methods can be imported directly via `@needle-tools/engine` as well:
27
+ * ```typescript
28
+ * import { addComponent } from "@needle-tools/engine";
29
+ * ```
30
+ */
24
31
  export abstract class GameObject extends Object3D implements Object3D, IGameObject {
25
32
 
26
33
  // these are implemented via threejs object extensions
@@ -303,17 +310,33 @@
303
310
 
304
311
 
305
312
 
306
- /** Needle Engine component base class. Derive from this component to implement your own using the provided lifecycle methods. Components can be added to threejs objects using `GameObject.addComponent`.
313
+ /**
314
+ * Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
315
+ * Derive from {@link Behaviour} to implement your own using the provided lifecycle methods.
316
+ * Components can be added to threejs objects using `{@link addComponent}` or `{@link GameObject.addComponent}`
307
317
  *
308
318
  * The most common lifecycle methods are `awake`, `start`, `onEnable`, `onDisable` `update` and `onDestroy`.
309
319
  * XR specific callbacks include `onEnterXR`, `onLeaveXR`, `onUpdateXR`, `onControllerAdded` and `onControllerRemoved`.
310
320
  * To receive pointer events implement `onPointerDown`, `onPointerUp`, `onPointerEnter`, `onPointerExit` and `onPointerMove`.
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * import { Behaviour } from "@engine/engine/engine";
325
+ * export class MyComponent extends Behaviour {
326
+ * start() {
327
+ * console.log("Hello World");
328
+ * }
329
+ * update() {
330
+ * console.log("Frame", this.context.time.frame);
331
+ * }
332
+ * }
333
+ * ```
311
334
  */
312
335
  export abstract class Component implements IComponent, EventTarget,
313
336
  Partial<INeedleXRSessionEventReceiver>,
314
337
  Partial<IPointerEventHandler>
315
338
  {
316
-
339
+ /** @internal */
317
340
  get isComponent(): boolean { return true; }
318
341
 
319
342
  private __context: Context | undefined;
@@ -324,13 +347,16 @@
324
347
  set context(context: Context) {
325
348
  this.__context = context;
326
349
  }
327
- /** shorthand for `this.context.scene` */
350
+ /** shorthand for `this.context.scene`
351
+ * @returns the scene of the context */
328
352
  get scene(): Scene { return this.context.scene; }
329
353
 
354
+ /** @returns the layer of the gameObject this component is attached to */
330
355
  get layer(): number {
331
356
  return this.gameObject?.userData?.layer;
332
357
  }
333
358
 
359
+ /** @returns the name of the gameObject this component is attached to */
334
360
  get name(): string {
335
361
  if (this.gameObject?.name) {
336
362
  return this.gameObject.name;
@@ -348,6 +374,7 @@
348
374
  this.__name = str;
349
375
  }
350
376
  }
377
+ /** @returns the tag of the gameObject this component is attached to */
351
378
  get tag(): string {
352
379
  return this.gameObject?.userData.tag;
353
380
  }
@@ -357,6 +384,7 @@
357
384
  this.gameObject.userData.tag = str;
358
385
  }
359
386
  }
387
+ /** Is the gameObject marked as static */
360
388
  get static() {
361
389
  return this.gameObject?.userData.static;
362
390
  }
src/engine-components/codegen/components.ts CHANGED
@@ -90,6 +90,7 @@
90
90
  export { InstanceHandle } from "../RendererInstancing.js";
91
91
  export { InstancingHandler } from "../RendererInstancing.js";
92
92
  export { Interactable } from "../Interactable.js";
93
+ export { Keyframe } from "../AnimationCurve.js";
93
94
  export { Light } from "../Light.js";
94
95
  export { LimitVelocityOverLifetimeModule } from "../ParticleSystemModules.js";
95
96
  export { LODGroup } from "../LODGroup.js";
src/engine-components/ContactShadows.ts CHANGED
@@ -16,6 +16,9 @@
16
16
  // - backface shadowing (slightly less than front faces)
17
17
  // - node can simply be scaled in Y to adjust max. ground height
18
18
 
19
+ /**
20
+ * ContactShadows is a component that allows to display contact shadows in the scene.
21
+ */
19
22
  export class ContactShadows extends Behaviour {
20
23
 
21
24
  @serializable()
@@ -43,7 +46,8 @@
43
46
  private horizontalBlurMaterial?: ShaderMaterial;
44
47
  private verticalBlurMaterial?: ShaderMaterial;
45
48
 
46
- awake(): void {
49
+ /** @internal */
50
+ start(): void {
47
51
  if(debug) console.log("Create ContactShadows on " + this.gameObject.name, this)
48
52
  const textureSize = 512;
49
53
 
@@ -148,6 +152,7 @@
148
152
  this.shadowGroup.visible = false;
149
153
  }
150
154
 
155
+ /** @internal */
151
156
  onDestroy(): void {
152
157
  // dispose the render targets
153
158
  this.renderTarget?.dispose();
@@ -164,6 +169,7 @@
164
169
  this.occluderMesh?.geometry.dispose();
165
170
  }
166
171
 
172
+ /** @internal */
167
173
  onBeforeRender(_frame: XRFrame | null): void {
168
174
  const scene = this.context.scene;
169
175
  const renderer = this.context.renderer;
src/engine/engine_addressables.ts CHANGED
@@ -12,16 +12,22 @@
12
12
 
13
13
  const debug = getParam("debugaddressables");
14
14
 
15
+ /**
16
+ * The Addressables class is used to register and manage {@link AssetReference} types
17
+ * It can be accessed via {@link Context.Current} or `{@link Context.addressables}` (e.g. `this.context.addressables` in a component)
18
+ */
15
19
  export class Addressables {
16
20
 
17
21
  private _context: Context;
18
22
  private _assetReferences: { [key: string]: AssetReference } = {};
19
23
 
24
+ /** @internal */
20
25
  constructor(context: Context) {
21
26
  this._context = context;
22
27
  this._context.pre_update_callbacks.push(this.preUpdate);
23
28
  }
24
29
 
30
+ /** @internal */
25
31
  dispose() {
26
32
  const preUpdateIndex = this._context.pre_update_callbacks.indexOf(this.preUpdate);
27
33
  if (preUpdateIndex >= 0) {
@@ -33,7 +39,7 @@
33
39
  }
34
40
  this._assetReferences = {};
35
41
  }
36
-
42
+
37
43
  private preUpdate = () => {
38
44
 
39
45
  }
@@ -43,6 +49,7 @@
43
49
  return this._assetReferences[key] || null;
44
50
  }
45
51
 
52
+ /** @internal */
46
53
  registerAssetReference(ref: AssetReference): AssetReference {
47
54
  if (!ref.uri) return ref;
48
55
  if (!this._assetReferences[ref.uri]) {
@@ -55,6 +62,7 @@
55
62
  return ref;
56
63
  }
57
64
 
65
+ /** @internal */
58
66
  unregisterAssetReference(ref: AssetReference) {
59
67
  if (!ref.uri) return;
60
68
  delete this._assetReferences[ref.uri];
@@ -104,6 +112,7 @@
104
112
 
105
113
  private static currentlyInstantiating: Map<string, number> = new Map<string, number>();
106
114
 
115
+ /** The loaded asset */
107
116
  get asset(): any {
108
117
  return this._glbRoot ?? this._asset;
109
118
  }
@@ -115,10 +124,14 @@
115
124
  private _loading?: PromiseLike<any>;
116
125
 
117
126
  // TODO: rename to url
127
+ /** The url of the loaded asset (or the asset to be loaded) */
118
128
  get uri(): string {
119
129
  return this._url;
120
130
  }
121
131
 
132
+ /**
133
+ * This is the loaded asset root object. If the asset is a glb/gltf file this will be the {@link three#Scene} object.
134
+ */
122
135
  get rawAsset(): any { return this._asset; }
123
136
 
124
137
  private _asset: any;
@@ -132,6 +145,7 @@
132
145
  private _isLoadingRawBinary: boolean = false;
133
146
  private _rawBinary?: ArrayBuffer | null;
134
147
 
148
+ /** @internal */
135
149
  constructor(uri: string, hash?: string, asset: any = null) {
136
150
  this._url = uri;
137
151
  this._hash = hash;
@@ -157,7 +171,8 @@
157
171
  return !this.asset || this.asset.__destroyed === true || isDestroyed(this.asset) === true;
158
172
  }
159
173
 
160
- /** has the asset been loaded (via preload) or does it exist already (assigned to `asset`) */
174
+ /**
175
+ * @returns `true` if the asset has been loaded (via preload) or if it exists already (assigned to `asset`) */
161
176
  isLoaded() { return this._rawBinary || this.asset !== undefined }
162
177
 
163
178
  /** frees previously allocated memory and destroys the current `asset` instance (if any) */
src/engine/engine_application.ts CHANGED
@@ -27,6 +27,9 @@
27
27
  onUserInteraction();
28
28
  })
29
29
 
30
+ /**
31
+ * The Application class can be used to mute audio globally, and to check if the application (canvas) is currently visible (it's tab is active and not minimized).
32
+ */
30
33
  export class Application extends EventTarget {
31
34
 
32
35
  public static get userInteractionRegistered(): boolean {
@@ -56,16 +59,21 @@
56
59
 
57
60
  private context: Context;
58
61
 
62
+ /** @returns true if the document is focused */
59
63
  public get hasFocus(): boolean {
60
64
  return document.hasFocus();
61
65
  }
62
66
 
67
+ /**
68
+ * @returns true if the application is currently visible (it's tab is active and not minimized)
69
+ */
63
70
  public get isVisible(): boolean {
64
71
  return this._isVisible;
65
72
  }
66
73
 
67
74
  private _isVisible: boolean = true;
68
75
 
76
+ /** @internal */
69
77
  constructor(context: Context) {
70
78
  super();
71
79
  this.context = context;
src/engine/engine_audio.ts CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  import { Application } from "./engine_application.js";
4
4
 
5
- /** Ensure the audio context is resumed if it gets suspended or interrupted */
5
+ /**
6
+ * @internal
7
+ * Ensure the audio context is resumed if it gets suspended or interrupted */
6
8
  export function ensureAudioContextIsResumed() {
7
9
  Application.registerWaitForAllowAudio(() => {
8
10
  // this is a fix for https://github.com/mrdoob/three.js/issues/27779 & https://linear.app/needle/issue/NE-4257
src/engine/engine_camera.ts CHANGED
@@ -5,10 +5,13 @@
5
5
 
6
6
  const $cameraController = Symbol("cameraController");
7
7
 
8
+ /** Get the camera controller for the given camera (if any)
9
+ */
8
10
  export function getCameraController(cam: Camera): ICameraController | null {
9
11
  return cam[$cameraController];
10
12
  }
11
13
 
14
+ /** Set the camera controller for the given camera */
12
15
  export function setCameraController(cam: Camera, cameraController: ICameraController, active: boolean) {
13
16
  if (active)
14
17
  cam[$cameraController] = cameraController;
@@ -21,6 +24,7 @@
21
24
 
22
25
  const $autofit = Symbol("camera autofit");
23
26
 
27
+ /** @internal */
24
28
  export function useForAutoFit(obj: Object3D): boolean {
25
29
  // if autofit is not defined we assume it may be included
26
30
  if (obj[$autofit] === undefined) return true;
@@ -28,6 +32,7 @@
28
32
  return obj[$autofit] !== false;
29
33
  }
30
34
 
35
+ /** @internal */
31
36
  export function setAutoFitEnabled(obj: Object3D, enabled: boolean): void {
32
37
  obj[$autofit] = enabled;
33
38
 
src/engine/engine_constants.ts CHANGED
@@ -19,9 +19,11 @@
19
19
  tryEval(`globalThis["__NEEDLE_ENGINE_GENERATOR__"] = "` + NEEDLE_ENGINE_GENERATOR + `";`)
20
20
  tryEval(`globalThis["__NEEDLE_PROJECT_BUILD_TIME__"] = "` + NEEDLE_PROJECT_BUILD_TIME + `";`)
21
21
 
22
-
22
+ /** The version of the Needle engine */
23
23
  export const VERSION = NEEDLE_ENGINE_VERSION;
24
+ /** The generator used to export the scene / build the web project */
24
25
  export const GENERATOR = NEEDLE_ENGINE_GENERATOR;
26
+ /** The build time of the project */
25
27
  export const BUILD_TIME = NEEDLE_PROJECT_BUILD_TIME;
26
28
  if (debug) console.log(`Engine version: ${VERSION} (generator: ${GENERATOR})\nProject built at ${BUILD_TIME}`);
27
29
 
src/engine/engine_context_registry.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { type IComponent, type IContext, type LoadedGLTF } from "./engine_types.js";
2
2
 
3
+ /** The various events that can be dispatched by a Needle Engine {@link IContext} instance
4
+ */
3
5
  export enum ContextEvent {
4
6
  /** called when the context is registered to the registry, the context is not fully initialized at this point */
5
7
  ContextRegistered = "ContextRegistered",
@@ -29,6 +31,13 @@
29
31
 
30
32
  /** Use to register to various Needle Engine context events and to get access to all current instances
31
33
  * e.g. when being created in the DOM
34
+ * @example
35
+ * ```typescript
36
+ * import { NeedleEngine } from "./engine/engine_context_registry.js";
37
+ * NeedleEngine.addContextCreatedCallback((evt) => {
38
+ * console.log("Context created", evt.context);
39
+ * });
40
+ * ```
32
41
  * */
33
42
  export class ContextRegistry {
34
43
 
@@ -64,11 +73,15 @@
64
73
 
65
74
  private static _callbacks: { [evt: string]: Array<ContextCallback> } = {};
66
75
 
76
+ /**
77
+ * Register a callback to be called when the given event occurs
78
+ */
67
79
  static registerCallback(evt: ContextEvent, callback: ContextCallback) {
68
80
  if (!this._callbacks[evt]) this._callbacks[evt] = [];
69
81
  this._callbacks[evt].push(callback);
70
82
  }
71
83
 
84
+ /** Unregister a callback */
72
85
  static unregisterCallback(evt: ContextEvent, callback: ContextCallback) {
73
86
  if (!this._callbacks[evt]) return;
74
87
  const index = this._callbacks[evt].indexOf(callback);
@@ -93,9 +106,15 @@
93
106
  return Promise.all(promises);
94
107
  }
95
108
 
109
+ /**
110
+ * Register a callback to be called when a context is created
111
+ */
96
112
  static addContextCreatedCallback(callback: ContextCallback) {
97
113
  this.registerCallback(ContextEvent.ContextCreated, callback);
98
114
  }
115
+ /**
116
+ * Register a callback to be called when a context is registered
117
+ */
99
118
  static addContextDestroyedCallback(callback: ContextCallback) {
100
119
  this.registerCallback(ContextEvent.ContextDestroyed, callback);
101
120
  }
src/engine/engine_context.ts CHANGED
@@ -114,6 +114,20 @@
114
114
  }
115
115
  }
116
116
 
117
+ /**
118
+ * The context is the main object that holds all the data and state of the Needle Engine.
119
+ * It can be used to access the scene, renderer, camera, input, physics, networking, and more.
120
+ * @example
121
+ * ```typescript
122
+ * import { Behaviour } from "@needle-tools/engine";
123
+ * export class MyScript extends Behaviour {
124
+ * start() {
125
+ * console.log("Hello from MyScript");
126
+ * this.context.scene.add(new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial()));
127
+ * }
128
+ * }
129
+ * ```
130
+ */
117
131
  export class Context implements IContext {
118
132
 
119
133
  private static _defaultTargetFramerate: { value?: number } = { value: 60 }
@@ -131,11 +145,19 @@
131
145
  alpha: false,
132
146
  powerPreference: "high-performance",
133
147
  };
148
+ /** The default parameters that will be used when creating a new WebGLRenderer.
149
+ * Modify in global context to change the default parameters for all new contexts.
150
+ * @example
151
+ * ```typescript
152
+ * import { Context } from "@needle-tools/engine";
153
+ * Context.DefaultWebGLRendererParameters.antialias = false;
154
+ * ```
155
+ */
134
156
  static get DefaultWebGLRendererParameters(): WebGLRendererParameters {
135
157
  return Context._defaultWebglRendererParameters;
136
158
  }
137
159
 
138
- /** the needle engine version */
160
+ /** The needle engine version */
139
161
  get version() {
140
162
  return VERSION;
141
163
  }
@@ -150,7 +172,9 @@
150
172
  ContextRegistry.Current = context;
151
173
  }
152
174
 
175
+ /** The name of the context */
153
176
  name: string;
177
+ /** An alias for the context */
154
178
  alias: string | undefined | null;
155
179
  /** When the renderer or camera are managed by an external process (e.g. when running in r3f context).
156
180
  * When this is false you are responsible to call update(timestamp, xframe.
@@ -178,7 +202,7 @@
178
202
  /** used to append to loaded assets */
179
203
  hash?: string;
180
204
 
181
- /** the <needle-engine> HTML element */
205
+ /** The `<needle-engine>` web component */
182
206
  domElement: HTMLElement;
183
207
 
184
208
  appendHTMLElement(element: HTMLElement) {
@@ -310,9 +334,11 @@
310
334
  readonly new_scripts_post_setup_callbacks: Function[] = [];
311
335
  readonly new_scripts_xr: INeedleXRSessionEventReceiver[] = [];
312
336
 
337
+ /** The main camera component of the scene - this camera is used for rendering */
313
338
  mainCameraComponent: ICamera | undefined;
314
339
 
315
- private _camera: Camera | null = null;
340
+
341
+ /** The main camera of the scene - this camera is used for rendering */
316
342
  get mainCamera(): Camera | null {
317
343
  if (this._camera) {
318
344
  return this._camera;
@@ -325,9 +351,11 @@
325
351
  }
326
352
  return null;
327
353
  }
354
+ /** Set the main camera of the scene. If set to null the camera of the {@link mainCameraComponent} will be used - this camera is used for rendering */
328
355
  set mainCamera(cam: Camera | null) {
329
356
  this._camera = cam;
330
357
  }
358
+ private _camera: Camera | null = null;
331
359
 
332
360
  application: Application;
333
361
  /** access timings (current frame number, deltaTime, timeScale, ...) */
src/engine/engine_create_objects.ts CHANGED
@@ -18,8 +18,16 @@
18
18
  scale?: Vec3,
19
19
  }
20
20
 
21
+ /**
22
+ * Utility class to create primitive objects
23
+ * @example
24
+ * ```typescript
25
+ * const cube = ObjectUtils.createPrimitive("Cube", { name: "Cube", position: { x: 0, y: 0, z: 0 } });
26
+ * ```
27
+ */
21
28
  export class ObjectUtils {
22
29
 
30
+ /** Creates a primitive object like a Cube or Sphere */
23
31
  static createPrimitive(type: PrimitiveType | PrimitiveTypeNames, opts?: ObjectOptions): Mesh {
24
32
  let obj: Mesh;
25
33
  const color = 0xffffff;
src/engine/engine_editor-sync.ts CHANGED
@@ -1,14 +1,16 @@
1
1
 
2
2
 
3
3
 
4
-
4
+ /**
5
+ * @internal
6
+ */
5
7
  export declare type EditorModification = {
6
8
  guid: string,
7
9
  propertyName: string,
8
10
  value: any
9
11
  }
10
12
 
11
- /** Implement to receive callbacks from @needle-tools/editor-sync package */
13
+ /** Implement to receive callbacks from {@type @needle-tools/editor-sync} package */
12
14
  export interface IEditorModification {
13
15
  /**
14
16
  * Called when a modification is made through the external editor (called from @needle-tools/editor-sync)
@@ -21,10 +23,7 @@
21
23
  onAfterEditorModification?(mod: EditorModification): void;
22
24
  }
23
25
 
24
- // let editorCache = new Map<string, EditorModification>();
25
- // export function setEditorModificationCache(cache: Map<string, EditorModification>) {
26
- // cache = editorCache;
27
- // }
26
+ /** @internal */
28
27
  export function getEditorModificationCache() {
29
28
  return globalThis["NeedleEditorSync.ModificationCache"] as null | undefined | Map<string, EditorModification>;
30
29
  }
src/engine/engine_element_attributes.ts CHANGED
@@ -53,7 +53,9 @@
53
53
  "environment-image"?: string,
54
54
  }
55
55
 
56
-
56
+ /**
57
+ * Available attributes for the `<needle-engine>` web component
58
+ */
57
59
  export type NeedleEngineAttributes =
58
60
  MainAttributes
59
61
  & Partial<Omit<HTMLElement, "style">>
src/engine/engine_element_loading.ts CHANGED
@@ -11,11 +11,13 @@
11
11
 
12
12
  declare type LoadingStyleOption = "dark" | "light" | "auto";
13
13
 
14
+ /** @internal */
14
15
  export class LoadingElementOptions {
15
16
  className?: string;
16
17
  additionalClasses?: string[];
17
18
  }
18
19
 
20
+ /** @internal */
19
21
  export interface ILoadingViewHandler {
20
22
  onLoadingBegin(message?: string)
21
23
  onLoadingUpdate(progress: LoadingProgressArgs | number, message?: string);
@@ -25,6 +27,7 @@
25
27
 
26
28
  let currentFileProgress = 0;
27
29
  let currentFileName: string;
30
+ /** @internal */
28
31
  export function calculateProgress01(progress: LoadingProgressArgs) {
29
32
  if (debug) console.log(progress.progress.loaded.toFixed(0) + "/" + progress.progress.total.toFixed(0), progress);
30
33
 
@@ -48,6 +51,7 @@
48
51
  return Mathf.clamp01(prog);
49
52
  }
50
53
 
54
+ /** @internal */
51
55
  export class EngineLoadingView implements ILoadingViewHandler {
52
56
 
53
57
  static LoadingContainerClassName = "loading";
src/engine/engine_element_overlay.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  export const quitARClassName = "quit-ar";
7
7
 
8
8
  // https://developers.google.com/web/fundamentals/web-components/customelements
9
+ /** @internal */
9
10
  export class AROverlayHandler {
10
11
 
11
12
  get ARContainer(): HTMLElement | null { return this.arContainer; }
src/engine/engine_element.ts CHANGED
@@ -38,7 +38,8 @@
38
38
  ]
39
39
 
40
40
  // https://developers.google.com/web/fundamentals/web-components/customelements
41
- /** <needle-engine> web component. See @type {NeedleEngineAttributes} attributes for supported attributes
41
+
42
+ /** <needle-engine> web component. See {@link NeedleEngineAttributes} attributes for supported attributes
42
43
  * @type {import ("./engine_element_attributes.js").NeedleEngineAttributes}
43
44
  */
44
45
  export class NeedleEngineHTMLElement extends HTMLElement implements INeedleEngineComponent {
src/engine/engine_gameobject.ts CHANGED
@@ -17,6 +17,7 @@
17
17
  const debug = getParam("debuggetcomponent");
18
18
  const debugInstantiate = getParam("debuginstantiate");
19
19
 
20
+ /** @internal */
20
21
  export enum HideFlags {
21
22
  None = 0,
22
23
  HideInHierarchy = 1,
src/engine/engine_gizmos.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AxesHelper,Box3, BoxGeometry, BufferAttribute, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Mesh, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
1
+ import { AxesHelper, Box3, BoxGeometry, BufferAttribute, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Mesh, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
2
2
  import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
3
3
  import { type Options } from 'three-mesh-ui/build/types/core/elements/MeshUIBaseElement.js';
4
4
 
@@ -21,8 +21,14 @@
21
21
  }
22
22
  declare type ColorWithAlpha = Color & { a: number };
23
23
 
24
+ /** Gizmos are temporary objects that are drawn in the scene for debugging or visualization purposes
25
+ * They are automatically removed after a given duration and cached internally to reduce overhead.
26
+ * Use the static methods of this class to draw gizmos in the scene.
27
+ */
24
28
  export class Gizmos {
25
29
 
30
+ private constructor() { }
31
+
26
32
  /**
27
33
  * Allow creating gizmos
28
34
  * If disabled then no gizmos will be added to the scene anymore
@@ -51,7 +57,7 @@
51
57
  element.position.z = position.z;
52
58
  return element as LabelHandle;
53
59
  }
54
-
60
+
55
61
  static DrawRay(origin: Vec3, dir: Vec3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true) {
56
62
  if (!Gizmos.enabled) return;
57
63
  const obj = Internal.getLine(duration);
src/engine/xr/events.ts CHANGED
@@ -4,11 +4,34 @@
4
4
 
5
5
  const onXRSessionStartListeners: ((evt: XRSessionEventArgs) => void)[] = [];
6
6
 
7
+ /**
8
+ * Add a listener for when an XR session starts
9
+ * This event is triggered when the XR session is started, either by the user or by the application
10
+ * @param fn The function to call when the XR session starts
11
+ * @example
12
+ * ```js
13
+ * onXRSessionStart((evt) => {
14
+ * console.log("XR session started", evt);
15
+ * });
16
+ * ```
17
+ */
7
18
  export function onXRSessionStart(fn: (evt: XRSessionEventArgs) => void) {
8
19
  if (onXRSessionStartListeners.indexOf(fn) === -1) {
9
20
  onXRSessionStartListeners.push(fn);
10
21
  }
11
22
  }
23
+ /**
24
+ * Remove a listener for when an XR session starts
25
+ * @param fn The function to remove from the listeners
26
+ * @example
27
+ * ```js
28
+ * const myFunction = (evt) => {
29
+ * console.log("XR session started", evt);
30
+ * };
31
+ * onXRSessionStart(myFunction);
32
+ * offXRSessionStart(myFunction);
33
+ * ```
34
+ */
12
35
  export function offXRSessionStart(fn: (evt: XRSessionEventArgs) => void) {
13
36
  const index = onXRSessionStartListeners.indexOf(fn);
14
37
  if (index !== -1) {
@@ -16,7 +39,50 @@
16
39
  }
17
40
  }
18
41
 
42
+ const onXRSessionEndListeners: ((evt: XRSessionEventArgs) => void)[] = [];
19
43
 
44
+ /**
45
+ * Add a listener for when an XR session ends
46
+ * This event is triggered when the XR session is ended, either by the user or by the application
47
+ * @param fn The function to call when the XR session ends
48
+ * @example
49
+ * ```js
50
+ * onXRSessionEnd((evt) => {
51
+ * console.log("XR session ended", evt);
52
+ * });
53
+ * ```
54
+ */
55
+ export function onXRSessionEnd(fn: (evt: XRSessionEventArgs) => void) {
56
+ if (onXRSessionEndListeners.indexOf(fn) === -1) {
57
+ onXRSessionEndListeners.push(fn);
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Remove a listener for when an XR session ends
63
+ * @param fn The function to remove from the listeners
64
+ * @example
65
+ * ```js
66
+ * const myFunction = (evt) => {
67
+ * console.log("XR session ended", evt);
68
+ * };
69
+ * onXRSessionEnd(myFunction);
70
+ * offXRSessionEnd(myFunction);
71
+ * ```
72
+ */
73
+ export function offXRSessionEnd(fn: (evt: XRSessionEventArgs) => void) {
74
+ const index = onXRSessionEndListeners.indexOf(fn);
75
+ if (index !== -1) {
76
+ onXRSessionEndListeners.splice(index, 1);
77
+ }
78
+ }
79
+
80
+
81
+ /**
82
+ * @internal
83
+ * Invoke the XRSessionStart event
84
+ * @param evt The XRSession event arguments
85
+ */
20
86
  export function invokeXRSessionStart(evt: XRSessionEventArgs) {
21
87
  globalThis.dispatchEvent(new CustomEvent("needle-xrsession-start", { detail: evt }));
22
88
  for (let i = 0; i < onXRSessionStartListeners.length; i++) {
@@ -24,6 +90,14 @@
24
90
  }
25
91
  }
26
92
 
93
+ /**
94
+ * @internal
95
+ * Invoke the XRSessionEnd event
96
+ * @param evt The XRSession event arguments
97
+ */
27
98
  export function invokeXRSessionEnd(evt: XRSessionEventArgs) {
28
99
  globalThis.dispatchEvent(new CustomEvent("needle-xrsession-end", { detail: evt }));
100
+ for (let i = 0; i < onXRSessionEndListeners.length; i++) {
101
+ onXRSessionEndListeners[i](evt);
102
+ }
29
103
  }
src/engine-components/EventType.ts CHANGED
@@ -1,88 +1,23 @@
1
1
 
2
+ /**
3
+ * @internal
4
+ */
2
5
  export enum EventType {
3
-
4
- /// <summary>
5
- /// Intercepts a IPointerEnterHandler.OnPointerEnter.
6
- /// </summary>
7
6
  PointerEnter = 0,
8
-
9
- /// <summary>
10
- /// Intercepts a IPointerExitHandler.OnPointerExit.
11
- /// </summary>
12
7
  PointerExit = 1,
13
-
14
- /// <summary>
15
- /// Intercepts a IPointerDownHandler.OnPointerDown.
16
- /// </summary>
17
8
  PointerDown = 2,
18
-
19
- /// <summary>
20
- /// Intercepts a IPointerUpHandler.OnPointerUp.
21
- /// </summary>
22
9
  PointerUp = 3,
23
-
24
- /// <summary>
25
- /// Intercepts a IPointerClickHandler.OnPointerClick.
26
- /// </summary>
27
10
  PointerClick = 4,
28
-
29
- /// <summary>
30
- /// Intercepts a IDragHandler.OnDrag.
31
- /// </summary>
32
11
  Drag = 5,
33
-
34
- /// <summary>
35
- /// Intercepts a IDropHandler.OnDrop.
36
- /// </summary>
37
12
  Drop = 6,
38
-
39
- /// <summary>
40
- /// Intercepts a IScrollHandler.OnScroll.
41
- /// </summary>
42
13
  Scroll = 7,
43
-
44
- /// <summary>
45
- /// Intercepts a IUpdateSelectedHandler.OnUpdateSelected.
46
- /// </summary>
47
14
  UpdateSelected = 8,
48
-
49
- /// <summary>
50
- /// Intercepts a ISelectHandler.OnSelect.
51
- /// </summary>
52
15
  Select = 9,
53
-
54
- /// <summary>
55
- /// Intercepts a IDeselectHandler.OnDeselect.
56
- /// </summary>
57
16
  Deselect = 10,
58
-
59
- /// <summary>
60
- /// Intercepts a IMoveHandler.OnMove.
61
- /// </summary>
62
17
  Move = 11,
63
-
64
- /// <summary>
65
- /// Intercepts IInitializePotentialDrag.InitializePotentialDrag.
66
- /// </summary>
67
18
  InitializePotentialDrag = 12,
68
-
69
- /// <summary>
70
- /// Intercepts IBeginDragHandler.OnBeginDrag.</
71
- /// </summary>
72
19
  BeginDrag = 13,
73
-
74
- /// <summary>
75
- /// Intercepts IEndDragHandler.OnEndDrag.
76
- /// </summary>
77
20
  EndDrag = 14,
78
-
79
- /// <summary>
80
- /// Intercepts ISubmitHandler.Submit.
81
- /// </summary>
82
21
  Submit = 15,
83
-
84
- /// <summary>
85
- /// Intercepts ICancelHandler.OnCancel.
86
- /// </summary>
87
22
  Cancel = 16
88
23
  }
src/engine-components/js-extensions/ExtensionUtils.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  const handlers: Map<any, ApplyPrototypeExtension> = new Map();
6
6
 
7
+ /** @internal */
7
8
  export function applyPrototypeExtensions<T>(obj: any, prototype : Constructor<T>) {
8
9
  if (!obj) return;
9
10
  // const prototype = Object.getPrototypeOf(obj);
@@ -20,6 +21,7 @@
20
21
  // applyPrototypeExtensions(prototype);
21
22
  }
22
23
 
24
+ /** @internal */
23
25
  export function registerPrototypeExtensions<T>(type: Constructor<T>) {
24
26
  // console.log("Register", type.prototype.constructor.name);
25
27
  const handler = createPrototypeExtensionHandler(type.prototype);
@@ -31,6 +33,7 @@
31
33
  return new ApplyPrototypeExtension(prototype);
32
34
  }
33
35
 
36
+ /** @internal */
34
37
  export interface IApplyPrototypeExtension {
35
38
  apply(object: object): void;
36
39
  }
src/engine-components/Fog.ts CHANGED
@@ -4,13 +4,13 @@
4
4
  import { Behaviour } from "./Component.js";
5
5
 
6
6
 
7
- export enum FogMode {
8
-
7
+ enum FogMode {
9
8
  Linear = 1,
10
9
  Exponential = 2,
11
10
  ExponentialSquared = 3,
12
11
  }
13
12
 
13
+ /** @internal */
14
14
  export class Fog extends Behaviour {
15
15
 
16
16
  get fog() {
src/engine-components/GridHelper.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
5
5
  import { Behaviour } from "./Component.js";
6
6
 
7
+ /**
8
+ * GridHelper is a component that allows to display a grid in the scene.
9
+ */
7
10
  export class GridHelper extends Behaviour {
8
11
 
9
12
  @serializable()
@@ -18,6 +21,7 @@
18
21
  private divisions!: number;
19
22
  private offset!: number;
20
23
 
24
+ /** @internal */
21
25
  onEnable() {
22
26
  if (this.isGizmo && !params.showGizmos) return;
23
27
 
@@ -32,6 +36,7 @@
32
36
  this.gameObject.add(this.gridHelper);
33
37
  }
34
38
 
39
+ /** @internal */
35
40
  onDisable(): void {
36
41
  if (this.gridHelper) {
37
42
  this.gameObject.remove(this.gridHelper);
src/engine-components/GroundProjection.ts CHANGED
@@ -7,6 +7,9 @@
7
7
 
8
8
  const debug = getParam("debuggroundprojection");
9
9
 
10
+ /**
11
+ * GroundProjectedEnv creates a ground projection of the current environment map.
12
+ */
10
13
  export class GroundProjectedEnv extends Behaviour {
11
14
 
12
15
  @serializable()
src/engine-components/Joints.ts CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  private *create() {
24
24
  yield;
25
- if (this.rigidBody && this.connectedBody) {
25
+ if (this.rigidBody && this.connectedBody && this.activeAndEnabled) {
26
26
  this.createJoint(this.rigidBody, this.connectedBody)
27
27
  }
28
28
  }
src/engine-components/LODGroup.ts CHANGED
@@ -38,6 +38,9 @@
38
38
  distance: number;
39
39
  }
40
40
 
41
+ /**
42
+ * LODGroup allows to create a group of LOD levels for an object.
43
+ */
41
44
  export class LODGroup extends Behaviour {
42
45
 
43
46
  fadeMode: LODFadeMode = LODFadeMode.None;
src/engine-components/utils/LookAt.ts CHANGED
@@ -7,22 +7,39 @@
7
7
  import { USDObject } from "../../engine-components/export/usdz/ThreeUSDZExporter.js";
8
8
  import { Behaviour } from "../Component.js";
9
9
 
10
+ /**
11
+ * LookAt behaviour makes the object look at a target object or the camera.
12
+ * It can also invert the forward direction and keep the up direction.
13
+ */
10
14
  export class LookAt extends Behaviour implements UsdzBehaviour {
11
15
 
16
+ /**
17
+ * The target object to look at. If not set, the main camera will be used.
18
+ */
12
19
  @serializable(Object3D)
13
20
  target?: Object3D;
14
21
 
22
+ /**
23
+ * Inverts the forward direction.
24
+ */
15
25
  @serializable()
16
26
  invertForward: boolean = false;
17
27
 
28
+ /**
29
+ * Keep the up direction.
30
+ */
18
31
  @serializable()
19
32
  keepUpDirection: boolean = true;
20
33
 
34
+ /**
35
+ * Copy the target rotation.
36
+ */
21
37
  @serializable()
22
38
  copyTargetRotation: boolean = false;
23
39
 
24
40
  private static flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
25
41
 
42
+ /** @internal */
26
43
  onBeforeRender(): void {
27
44
  let target: Object3D | null | undefined = this.target;
28
45
  if (!target) target = this.context.mainCamera;
@@ -34,6 +51,7 @@
34
51
  this.gameObject.quaternion.multiply(LookAt.flipYQuat);
35
52
  }
36
53
 
54
+ /** @internal */
37
55
  createBehaviours(ext, model: USDObject, _context) {
38
56
  if (model.uuid === this.gameObject.uuid) {
39
57
  let alignmentTarget = model;
src/engine/extensions/NEEDLE_progressive.ts CHANGED
@@ -34,13 +34,14 @@
34
34
  const cur = obj[key];
35
35
  if (cur instanceof BufferGeometry) {
36
36
  const info = NEEDLE_progressive.getMeshLODInformation(cur);
37
- const level = !info ? 0 : Math.min(currentDebugLodLevel, info.lods.length - 1);
37
+ const level = !info ? 0 : Math.min(currentDebugLodLevel, info.lods.length);
38
38
  obj["DEBUG:LOD"] = level;
39
39
  NEEDLE_progressive.assignMeshLOD(context, arr.sourceId, obj as Mesh, level);
40
- if(info) maxLevel = Math.max(maxLevel, info.lods.length);
40
+ if (info) maxLevel = Math.max(maxLevel, info.lods.length - 1);
41
41
  }
42
- else if (cur instanceof Texture) {
43
- NEEDLE_progressive.assignTextureLOD(context, arr.sourceId, cur, currentDebugLodLevel);
42
+ else if (obj instanceof Material) {
43
+ NEEDLE_progressive.assignTextureLOD(context, arr.sourceId, obj, currentDebugLodLevel);
44
+ break;
44
45
  }
45
46
  }
46
47
  });
@@ -388,6 +389,7 @@
388
389
  const LODLEVEL = ext.lods.length;
389
390
  if (obj instanceof Mesh) {
390
391
  applyMeshLOD(LODKEY, obj, LODLEVEL, undefined, ext);
392
+ NEEDLE_progressive.lowresCache.set(LODKEY, obj.geometry);
391
393
  }
392
394
  else {
393
395
  const geometries = new Array<BufferGeometry>();
src/engine-components/NeedleMenu.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { serializable } from '../engine/engine_serialization.js';
2
2
  import { Behaviour } from './Component.js';
3
3
 
4
- /** Exposes options to editors to customize the Needle menu. */
4
+ /**
5
+ * Exposes options to editors to customize the Needle menu.
6
+ * From code you can access the menu via `{@link Context.menu}`
7
+ **/
5
8
  export class NeedleMenu extends Behaviour {
6
9
 
7
10
  @serializable()
src/engine/xr/NeedleXRSession.ts CHANGED
@@ -526,6 +526,7 @@
526
526
 
527
527
  /**
528
528
  * @link https://developer.mozilla.org/en-US/docs/Web/API/XRSession/visibilityState
529
+ * @returns {XRVisibilityState} The visibility state of the XRSession
529
530
  */
530
531
  get visibilityState() { return this.session.visibilityState; }
531
532
 
src/engine-components/js-extensions/Object3D.ts CHANGED
@@ -19,7 +19,10 @@
19
19
  import { applyPrototypeExtensions, registerPrototypeExtensions } from "./ExtensionUtils.js";
20
20
 
21
21
 
22
- /** used to decorate cloned object3D objects with the same added components defined above */
22
+ /**
23
+ * @internal
24
+ * used to decorate cloned object3D objects with the same added components defined above
25
+ **/
23
26
  export function apply(object: Object3D) {
24
27
  if (object && object.isObject3D === true) {
25
28
  applyPrototypeExtensions(object, Object3D);
src/engine-components/utils/OpenURL.ts CHANGED
@@ -6,23 +6,41 @@
6
6
  import { type IPointerClickHandler, PointerEventData } from "../ui/index.js";
7
7
  import { ObjectRaycaster, Raycaster } from "../ui/Raycaster.js";
8
8
 
9
+ /**
10
+ * OpenURLMode defines how a URL should be opened.
11
+ */
9
12
  export enum OpenURLMode {
10
13
  NewTab = 0,
11
14
  SameTab = 1,
12
15
  NewWindow = 2
13
16
  }
14
17
 
18
+ /**
19
+ * OpenURL behaviour opens a URL in a new tab or window.
20
+ */
15
21
  export class OpenURL extends Behaviour implements IPointerClickHandler {
16
22
 
23
+ /**
24
+ * The URL to open.
25
+ */
17
26
  @serializable()
18
- clickable: boolean = true;
19
-
20
- @serializable()
21
27
  url?: string;
22
28
 
29
+ /**
30
+ * The mode in which the URL should be opened: NewTab, SameTab, NewWindow.
31
+ */
23
32
  @serializable()
24
33
  mode: OpenURLMode = OpenURLMode.NewTab;
25
34
 
35
+ /**
36
+ * If true, the URL will be opened when the object with this component is clicked.
37
+ */
38
+ @serializable()
39
+ clickable: boolean = true;
40
+
41
+ /**
42
+ * Opens the URL in a new tab or window.
43
+ */
26
44
  async open() {
27
45
  if (!this.url) {
28
46
  console.warn("OpenURL: URL is not set, can't open.", this);
@@ -63,18 +81,22 @@
63
81
 
64
82
  }
65
83
  }
84
+ /** @internal */
66
85
  start(): void {
67
86
  const raycaster = this.gameObject.getComponentInParent(ObjectRaycaster);
68
- if (!raycaster) this.gameObject.addNewComponent(ObjectRaycaster);
87
+ if (!raycaster) this.gameObject.addComponent(ObjectRaycaster);
69
88
  }
89
+ /** @internal */
70
90
  onPointerEnter(args) {
71
91
  if (!args.used && this.clickable)
72
92
  this.context.input.setCursorPointer();
73
93
  }
94
+ /** @internal */
74
95
  onPointerExit() {
75
96
  if (this.clickable)
76
97
  this.context.input.setCursorNormal();
77
98
  }
99
+ /** @internal */
78
100
  onPointerClick(args: PointerEventData) {
79
101
  if (this.clickable && !args.used && this.url?.length)
80
102
  this.open();
src/engine-components/timeline/PlayableDirector.ts CHANGED
@@ -14,9 +14,9 @@
14
14
 
15
15
  const debug = getParam("debugtimeline");
16
16
 
17
- /// <summary>
18
- /// <para>Wrap mode for Playables.</para>
19
- /// </summary>
17
+ /**
18
+ * The wrap mode of the {@link PlayableDirector}.
19
+ */
20
20
  export enum DirectorWrapMode {
21
21
  /// <summary>
22
22
  /// <para>Hold the last frame when the playable time reaches it's duration.</para>
@@ -32,34 +32,29 @@
32
32
  None = 2,
33
33
  }
34
34
 
35
- /// <summary>
36
- /// How the clip handles time outside its start and end range.
37
- /// </summary>
35
+
36
+ /** How the clip handles time outside its start and end range. */
38
37
  export enum ClipExtrapolation {
39
- /// <summary>
40
- /// No extrapolation is applied.
41
- /// </summary>
38
+ /** No extrapolation is applied. */
42
39
  None = 0,
43
- /// <summary>
44
- /// Hold the time at the end value of the clip.
45
- /// </summary>
40
+ /** Hold the time at the end value of the clip. */
46
41
  Hold = 1,
47
- /// <summary>
48
- /// Repeat time values outside the start/end range.
49
- /// </summary>
42
+ /** Repeat time values outside the start/end range. */
50
43
  Loop = 2,
51
- /// <summary>
52
- /// Repeat time values outside the start/end range, reversing direction at each loop
53
- /// </summary>
44
+ /** Repeat time values outside the start/end range, reversing direction at each loop */
54
45
  PingPong = 3,
55
- /// <summary>
56
- /// Time values are passed in without modification, extending beyond the clips range
57
- /// </summary>
46
+ /** Time values are passed in without modification, extending beyond the clips range */
58
47
  Continue = 4
59
48
  };
60
49
 
50
+ /** @internal */
61
51
  export type CreateTrackFunction = (director: PlayableDirector, track: Models.TrackModel) => Tracks.TrackHandler | undefined | null;
62
52
 
53
+ /**
54
+ * The PlayableDirector component is the main component to control timelines in needle engine.
55
+ * It is used to play, pause, stop and evaluate timelines.
56
+ * Assign a TimelineAsset to the `playableAsset` property to start playing a timeline.
57
+ */
63
58
  export class PlayableDirector extends Behaviour {
64
59
 
65
60
  private static createTrackFunctions: { [key: string]: CreateTrackFunction } = {};
@@ -68,11 +63,15 @@
68
63
  }
69
64
 
70
65
  playableAsset?: Models.TimelineAssetModel;
66
+ /** Set to true to start playing the timeline when the scene starts */
71
67
  playOnAwake?: boolean;
72
68
  extrapolationMode: DirectorWrapMode = DirectorWrapMode.Loop;
73
69
 
70
+ /** @returns true if the timeline is currently playing */
74
71
  get isPlaying(): boolean { return this._isPlaying; }
72
+ /** @returns true if the timeline is currently paused */
75
73
  get isPaused(): boolean { return this._isPaused; }
74
+ /** the current time of the timeline */
76
75
  get time(): number { return this._time; }
77
76
  set time(value: number) {
78
77
  if (typeof value === "number" && !Number.isNaN(value))
@@ -81,10 +80,13 @@
81
80
  console.error("INVALID TIMELINE.TIME VALUE", value, this.name)
82
81
  };
83
82
  }
83
+ /** the duration of the timeline */
84
84
  get duration(): number { return this._duration; }
85
85
  set duration(value: number) { this._duration = value; }
86
+ /** the weight of the timeline. Set to a value below 1 to blend with other timelines */
86
87
  get weight(): number { return this._weight; };
87
88
  set weight(value: number) { this._weight = value; }
89
+ /** the playback speed of the timeline */
88
90
  get speed(): number { return this._speed; }
89
91
  set speed(value: number) { this._speed = value; }
90
92
 
@@ -95,6 +97,7 @@
95
97
  private _clonedPlayableAsset: boolean = false;
96
98
  private _speed: number = 1;
97
99
 
100
+ /** @internal */
98
101
  awake(): void {
99
102
  if (debug)
100
103
  console.log(this, this.playableAsset?.tracks);
@@ -104,6 +107,7 @@
104
107
  if (!this.isValid()) console.warn("PlayableDirector is not valid", this.playableAsset, this.playableAsset?.tracks, Array.isArray(this.playableAsset?.tracks), this);
105
108
  }
106
109
 
110
+ /** @internal */
107
111
  onEnable() {
108
112
  for (const track of this._audioTracks) {
109
113
  track.onEnable?.();
@@ -131,6 +135,7 @@
131
135
  window.addEventListener('visibilitychange', this._visibilityChangeEvt);
132
136
  }
133
137
 
138
+ /** @internal */
134
139
  onDisable(): void {
135
140
  this.stop();
136
141
  for (const track of this._audioTracks) {
@@ -146,6 +151,7 @@
146
151
  window.removeEventListener('visibilitychange', this._visibilityChangeEvt);
147
152
  }
148
153
 
154
+ /** @internal */
149
155
  onDestroy(): void {
150
156
  for (const tracks of this._allTracks) {
151
157
  for (const track of tracks)
@@ -153,6 +159,7 @@
153
159
  }
154
160
  }
155
161
 
162
+ /** @internal */
156
163
  rebuildGraph() {
157
164
  if (!this.isValid()) return;
158
165
  this.resolveBindings();
@@ -160,6 +167,10 @@
160
167
  this.setupAndCreateTrackHandlers();
161
168
  }
162
169
 
170
+ /**
171
+ * Play the timeline from the current time.
172
+ * If the timeline is already playing this method does nothing.
173
+ */
163
174
  async play() {
164
175
  if (!this.isValid()) return;
165
176
  const pauseChanged = this._isPaused == true;
@@ -190,6 +201,9 @@
190
201
  this._internalUpdateRoutine = this.startCoroutine(this.internalUpdate(), FrameEvent.LateUpdate);
191
202
  }
192
203
 
204
+ /**
205
+ * Pause the timeline.
206
+ */
193
207
  pause() {
194
208
  if (!this.isValid()) return;
195
209
  this._isPlaying = false;
@@ -200,6 +214,9 @@
200
214
  this.invokeStateChangedMethodsOnTracks();
201
215
  }
202
216
 
217
+ /**
218
+ * Stop the timeline.
219
+ */
203
220
  stop() {
204
221
  this._isStopping = true;
205
222
  for (const track of this._audioTracks) track.stop();
@@ -222,14 +239,23 @@
222
239
  this._isStopping = false;
223
240
  }
224
241
 
242
+ /**
243
+ * Evaluate the timeline at the current time. This is useful when you want to manually update the timeline e.g. when the timeline is paused and you set `time` to a new value.
244
+ */
225
245
  evaluate() {
226
246
  this.internalEvaluate(true);
227
247
  }
228
248
 
249
+ /**
250
+ * @returns true if the timeline is valid and has tracks
251
+ */
229
252
  isValid() {
230
253
  return this.playableAsset && this.playableAsset.tracks && Array.isArray(this.playableAsset.tracks);
231
254
  }
232
255
 
256
+ /** Iterates over all tracks of the timeline
257
+ * @returns all tracks of the timeline
258
+ */
233
259
  *forEachTrack() {
234
260
  for (const tracks of this._allTracks) {
235
261
  for (const track of tracks)
@@ -237,11 +263,15 @@
237
263
  }
238
264
  }
239
265
 
266
+ /**
267
+ * @returns all audio tracks of the timeline
268
+ */
240
269
  get audioTracks(): Tracks.AudioTrackHandler[] {
241
270
  return this._audioTracks;
242
271
  }
243
272
 
244
273
  private _guidsMap?: GuidsMap;
274
+ /** @internal */
245
275
  resolveGuids(map: GuidsMap) {
246
276
  this._guidsMap = map;
247
277
  }
@@ -561,7 +591,7 @@
561
591
  audio.director = this;
562
592
  audio.track = track;
563
593
  audio.audioSource = track.outputs.find(o => o instanceof AudioSource) as AudioSource;
564
-
594
+
565
595
  this._audioTracks.push(audio);
566
596
  if (!audioListener) {
567
597
  // If the scene doesnt have an AudioListener we add one to the main camera
src/engine-components/PlayerColor.ts CHANGED
@@ -6,7 +6,10 @@
6
6
  import { Behaviour, GameObject } from "./Component.js";
7
7
  import { AvatarMarker } from "./webxr/WebXRAvatar.js";
8
8
 
9
-
9
+ /**
10
+ * PlayerColor assigns a unique color for each user in the room to the object it is attached to.
11
+ * The color is generated based on the user's ID.
12
+ */
10
13
  export class PlayerColor extends Behaviour {
11
14
 
12
15
  private _didAssignPlayerColor: boolean = false;
src/engine-components/postprocessing/PostProcessingEffect.ts CHANGED
@@ -16,6 +16,32 @@
16
16
  unapply(): void;
17
17
  }
18
18
 
19
+ /**
20
+ * PostProcessingEffect is a base class for post processing effects that can be applied to the scene.
21
+ * To create a custom post processing effect, extend this class and override the `onCreateEffect` method and call `registerCustomEffectType` to make it available in the editor.
22
+ * @example
23
+ * ```typescript
24
+ * import { EdgeDetectionMode, SMAAEffect, SMAAPreset } from "postprocessing";
25
+ * export class Antialiasing extends PostProcessingEffect {
26
+ * get typeName(): string {
27
+ * return "Antialiasing";
28
+ * }
29
+ * @serializable(VolumeParameter)
30
+ * preset!: VolumeParameter = new VolumeParameter();
31
+ * onCreateEffect(): EffectProviderResult {
32
+ * const effect = new SMAAEffect({
33
+ * preset: SMAAPreset.HIGH,
34
+ * edgeDetectionMode: EdgeDetectionMode.DEPTH
35
+ * });
36
+ * this.preset.onValueChanged = (newValue) => {
37
+ * effect.applyPreset(newValue);
38
+ * };
39
+ * return effect;
40
+ * }
41
+ * }
42
+ * registerCustomEffectType("Antialiasing", Antialiasing)
43
+ * ```
44
+ */
19
45
  export abstract class PostProcessingEffect extends Component implements IEffectProvider, ISerializable, IEditorModification {
20
46
 
21
47
  constructor(params: any = undefined) {
src/engine-components/postprocessing/PostProcessingHandler.ts CHANGED
@@ -14,6 +14,9 @@
14
14
  const activeKey = Symbol("needle:postprocessing-handler");
15
15
  const autoclearSetting = Symbol("needle:previous-autoclear-state")
16
16
 
17
+ /**
18
+ * PostProcessingHandler is responsible for applying post processing effects to the scene. It is internally used by the {@link Volume} component
19
+ */
17
20
  export class PostProcessingHandler {
18
21
 
19
22
  private _composer: EffectComposer | null = null;
src/engine/codegen/register_types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable */
2
2
  import { TypeStore } from "./../engine_typestore.js"
3
-
3
+
4
4
  // Import types
5
5
  import { __Ignore } from "../../engine-components/codegen/components.js";
6
6
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -92,6 +92,7 @@
92
92
  import { InstanceHandle } from "../../engine-components/RendererInstancing.js";
93
93
  import { InstancingHandler } from "../../engine-components/RendererInstancing.js";
94
94
  import { Interactable } from "../../engine-components/Interactable.js";
95
+ import { Keyframe } from "../../engine-components/AnimationCurve.js";
95
96
  import { Light } from "../../engine-components/Light.js";
96
97
  import { LimitVelocityOverLifetimeModule } from "../../engine-components/ParticleSystemModules.js";
97
98
  import { LODGroup } from "../../engine-components/LODGroup.js";
@@ -218,7 +219,7 @@
218
219
  import { XRFlag } from "../../engine-components/webxr/XRFlag.js";
219
220
  import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
220
221
  import { XRState } from "../../engine-components/webxr/XRFlag.js";
221
-
222
+
222
223
  // Register types
223
224
  TypeStore.add("__Ignore", __Ignore);
224
225
  TypeStore.add("ActionBuilder", ActionBuilder);
@@ -310,6 +311,7 @@
310
311
  TypeStore.add("InstanceHandle", InstanceHandle);
311
312
  TypeStore.add("InstancingHandler", InstancingHandler);
312
313
  TypeStore.add("Interactable", Interactable);
314
+ TypeStore.add("Keyframe", Keyframe);
313
315
  TypeStore.add("Light", Light);
314
316
  TypeStore.add("LimitVelocityOverLifetimeModule", LimitVelocityOverLifetimeModule);
315
317
  TypeStore.add("LODGroup", LODGroup);
src/engine-components/RendererInstancing.ts CHANGED
@@ -456,7 +456,7 @@
456
456
  if (this.mustGrow()) {
457
457
  this.grow(geo);
458
458
  }
459
- if (debugInstancing) console.log("UPDATE MESH", index, geo.name, getMeshInformation(geo), geo.attributes.position.count, geo.index ? geo.index.count : 0);
459
+ if (debugInstancing) console.debug("UPDATE MESH", index, geo.name, getMeshInformation(geo), geo.attributes.position.count, geo.index ? geo.index.count : 0);
460
460
  this.inst.setGeometryAt(index, geo);
461
461
  this.markNeedsUpdate();
462
462
  return true;
@@ -633,7 +633,7 @@
633
633
  if (smallestBucket != null) {
634
634
  const bucket = smallestBucket;
635
635
  if (debugInstancing)
636
- console.log(`RE-USE SPACE #${bucket.index}, ${handle.maxVertexCount} vertices, ${handle.maxIndexCount} indices, ${handle.name}`);
636
+ console.debug(`RE-USE SPACE #${bucket.index}, ${handle.maxVertexCount} vertices, ${handle.maxIndexCount} indices, ${handle.name}`);
637
637
  this.inst.setGeometryAt(bucket.index, handle.object.geometry as BufferGeometry);
638
638
  this.inst.setMatrixAt(bucket.index, handle.object.matrixWorld);
639
639
  this.inst.setVisibleAt(bucket.index, true);
@@ -647,7 +647,7 @@
647
647
  const geo = handle.object.geometry as BufferGeometry;
648
648
 
649
649
 
650
- if (debugInstancing) console.log("ADD GEOMETRY", geo.name, "\nvertex:", `${this._currentVertexCount} + ${handle.maxVertexCount} < ${this._maxVertexCount}?`, "\nindex:", handle.maxIndexCount, this._currentIndexCount, this._maxIndexCount);
650
+ if (debugInstancing) console.debug("ADD GEOMETRY", geo.name, "\nvertex:", `${this._currentVertexCount} + ${handle.maxVertexCount} < ${this._maxVertexCount}?`, "\nindex:", handle.maxIndexCount, this._currentIndexCount, this._maxIndexCount);
651
651
 
652
652
  const i = this.inst.addGeometry(geo, handle.maxVertexCount, handle.maxIndexCount);
653
653
  handle.__instanceIndex = i;
@@ -658,7 +658,7 @@
658
658
  this._usedBuckets[i] = { index: i, vertexCount: handle.maxVertexCount, indexCount: handle.maxIndexCount };
659
659
  this.inst.setMatrixAt(i, handle.object.matrixWorld);
660
660
  if (debugInstancing)
661
- console.log(`ADD MESH & RESERVE SPACE #${i}, ${handle.maxVertexCount} vertices, ${handle.maxIndexCount} indices, ${handle.name} ${handle.object.uuid}`);
661
+ console.debug(`ADD MESH & RESERVE SPACE #${i}, ${handle.maxVertexCount} vertices, ${handle.maxIndexCount} indices, ${handle.name} ${handle.object.uuid}`);
662
662
 
663
663
  }
664
664
 
src/engine-components/js-extensions/RGBAColor.ts CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { Mathf } from "../../engine/engine_math.js";
4
4
 
5
+ /**
6
+ * RGBAColor is a class that represents a color with red, green, blue and alpha components.
7
+ */
5
8
  export class RGBAColor extends Color {
6
9
  alpha: number = 1;
7
10
 
src/engine-components/timeline/SignalAsset.ts CHANGED
@@ -18,6 +18,9 @@
18
18
  reaction!: EventList;
19
19
  }
20
20
 
21
+ /** SignalReceiver is a component that listens for signals and invokes a reaction when a signal is received.
22
+ * Signals can be added to a signal track on a timeline
23
+ */
21
24
  export class SignalReceiver extends Behaviour {
22
25
 
23
26
  private static receivers: { [key: string]: SignalReceiver[] } = {};
@@ -36,10 +39,12 @@
36
39
  @serializable(SignalReceiverEvent)
37
40
  events?: SignalReceiverEvent[];
38
41
 
42
+ /** @internal */
39
43
  awake(): void {
40
44
  if(debug) console.log("SignalReceiver awake", this);
41
45
  }
42
46
 
47
+ /** @internal */
43
48
  onEnable(): void {
44
49
  if (this.events) {
45
50
  for (const evt of this.events) {
@@ -50,6 +55,7 @@
50
55
  }
51
56
  }
52
57
 
58
+ /** @internal */
53
59
  onDisable(): void {
54
60
  if (this.events) {
55
61
  for (const evt of this.events) {
src/engine-components/SyncedCamera.ts CHANGED
@@ -60,6 +60,10 @@
60
60
  userId: string;
61
61
  };
62
62
 
63
+ /**
64
+ * SyncedCamera is a component that syncs the camera position and rotation of all users in the room.
65
+ * A prefab can be set to represent the remote cameras visually in the scene.
66
+ */
63
67
  export class SyncedCamera extends Behaviour {
64
68
 
65
69
  static instances: UserCamInfo[] = [];
@@ -70,6 +74,9 @@
70
74
  return this.remoteCams[guid].obj;
71
75
  }
72
76
 
77
+ /**
78
+ * The prefab to visually represent the remote cameras in the scene.
79
+ */
73
80
  @serializable([Object3D, AssetReference])
74
81
  public cameraPrefab: Object3D | null | AssetReference = null;
75
82
 
@@ -84,6 +91,7 @@
84
91
  private _camTimeoutInSeconds = 10;
85
92
  private _receiveCallback: Function | null = null;
86
93
 
94
+ /** @internal */
87
95
  async awake() {
88
96
  this._lastWorldPosition = this.worldPosition.clone();
89
97
  this._lastWorldQuaternion = this.worldQuaternion.clone();
@@ -101,14 +109,17 @@
101
109
 
102
110
  }
103
111
 
112
+ /** @internal */
104
113
  onEnable(): void {
105
114
  this._receiveCallback = this.context.connection.beginListenBinary(SyncedCameraModelIdentifier, this.onReceivedRemoteCameraInfoBin.bind(this));
106
115
  }
107
116
 
117
+ /** @internal */
108
118
  onDisable(): void {
109
119
  this.context.connection.stopListenBinary(SyncedCameraModelIdentifier, this._receiveCallback);
110
120
  }
111
121
 
122
+ /** @internal */
112
123
  update(): void {
113
124
 
114
125
  for (const guid in this.remoteCams) {
src/engine-components/SyncedRoom.ts CHANGED
@@ -10,28 +10,59 @@
10
10
  const viewParamName = "view";
11
11
  const debug = utils.getParam("debugsyncedroom");
12
12
 
13
+ /**
14
+ * SyncedRoom is a behaviour that will attempt to join a networked room based on the URL parameters or a random room.
15
+ * It will also create a button in the menu to join or leave the room.
16
+ * You can also join a networked room by calling the core methods like `this.context.connection.joinRoom("roomName")`.
17
+ */
13
18
  export class SyncedRoom extends Behaviour {
14
19
 
20
+ /**
21
+ * The name of the room to join.
22
+ */
15
23
  @serializable()
16
24
  public roomName!: string;
25
+ /**
26
+ * The URL parameter name to use for the room name. E.g. if set to "room" the URL will look like `?room=roomName`.
27
+ */
17
28
  @serializable()
18
29
  public urlParameterName: string = "room";
30
+ /**
31
+ * If true, the room will be joined automatically when this component becomes active
32
+ */
19
33
  @serializable()
20
34
  public joinRandomRoom: boolean = true;
35
+ /**
36
+ * If true and no room parameter is found in the URL then no room will be joined.
37
+ */
21
38
  @serializable()
22
39
  public requireRoomParameter: boolean = false;
40
+ /**
41
+ * If true, the room will be rejoined automatically when disconnected.
42
+ */
23
43
  @serializable()
24
44
  public autoRejoin: boolean = true;
25
45
 
46
+ /**
47
+ * If true, a join/leave room button will be created in the menu.
48
+ */
26
49
  @serializable()
27
50
  public createJoinButton: boolean = true;
28
51
 
29
52
  private _roomPrefix?: string;
30
53
 
54
+ /**
55
+ * The room prefix to use for the room name. E.g. if set to "room_" and the room name is "name" the final room name will be "room_name".
56
+ */
57
+ public get roomPrefix(): string | undefined {
58
+ return this._roomPrefix;
59
+ }
60
+ /** @deprecated use roomPrefix */
31
61
  public get RoomPrefix(): string | undefined {
32
62
  return this._roomPrefix;
33
63
  }
34
64
 
65
+ /** @internal */
35
66
  awake() {
36
67
  if (debug) console.log("Room", this.roomName, this.urlParameterName, this.joinRandomRoom, this.requireRoomParameter, this.autoRejoin);
37
68
  if (this._roomPrefix === undefined) {
@@ -40,6 +71,7 @@
40
71
  }
41
72
  }
42
73
 
74
+ /** @internal */
43
75
  onEnable() {
44
76
  // if the url contains a view parameter override room and join in view mode
45
77
  const viewId = utils.getParam(viewParamName);
@@ -58,12 +90,14 @@
58
90
  }
59
91
  }
60
92
 
93
+ /** @internal */
61
94
  onDisable(): void {
62
95
  this._roomButton?.remove();
63
96
  if (this.roomName && this.roomName.length > 0)
64
97
  this.context.connection.leaveRoom(this.roomName);
65
98
  }
66
99
 
100
+ /** @internal */
67
101
  onDestroy(): void {
68
102
  this.destroyRoomButton();
69
103
  }
@@ -125,6 +159,7 @@
125
159
  private _lastRoomTime: number = -1;
126
160
  private _userWantsToBeInARoom = false;
127
161
 
162
+ /** @internal */
128
163
  update(): void {
129
164
  if (this.context.connection.isConnected) {
130
165
  if (this.context.time.time - this._lastPingTime > 3) {
@@ -211,7 +246,7 @@
211
246
  else {
212
247
  if (this.urlParameterName) {
213
248
  if (!getParam(this.urlParameterName)) {
214
- if(this._lastJoinedRoom)
249
+ if (this._lastJoinedRoom)
215
250
  utils.setParamWithoutReload(this.urlParameterName, this._lastJoinedRoom);
216
251
  else
217
252
  this.setRandomRoomUrlParameter();
src/engine-components/SyncedTransform.ts CHANGED
@@ -19,6 +19,8 @@
19
19
 
20
20
  const builder = new flatbuffers.Builder();
21
21
 
22
+ /** Creates a flatbuffer model containing the transform data of a game object. Used by {@link SyncedTransform}
23
+ */
22
24
  export function createTransformModel(guid: string, b: Behaviour, fast: boolean = true): Uint8Array {
23
25
  builder.clear();
24
26
  const guidObj = builder.createString(guid);
@@ -47,6 +49,9 @@
47
49
  if(debug && FAST_INTERVAL > 0) console.log("Sync Transform Fast Interval", FAST_INTERVAL);
48
50
  })
49
51
 
52
+ /**
53
+ * SyncedTransform is a behaviour that syncs the transform of a game object over the network.
54
+ */
50
55
  export class SyncedTransform extends Behaviour {
51
56
 
52
57
 
@@ -93,6 +98,7 @@
93
98
  private joinedRoomCallback: any = null;
94
99
  private receivedDataCallback: any = null;
95
100
 
101
+ /** @internal */
96
102
  awake() {
97
103
  if (debug)
98
104
  console.log("new instance", this.guid, this);
@@ -122,6 +128,7 @@
122
128
  this.context.connection.beginListenBinary(SyncedTransformIdentifier, this.receivedDataCallback);
123
129
  }
124
130
 
131
+ /** @internal */
125
132
  onDestroy(): void {
126
133
  // TODO: can we add a new component for this?! do we really need this?!
127
134
  if (this.syncDestroy)
@@ -174,6 +181,7 @@
174
181
  }
175
182
  }
176
183
 
184
+ /** @internal */
177
185
  onEnable(): void {
178
186
  this.lastWorldPos.copy(this.worldPosition);
179
187
  this.lastWorldRotation.copy(this.worldQuaternion);
@@ -184,6 +192,7 @@
184
192
  }
185
193
  }
186
194
 
195
+ /** @internal */
187
196
  onDisable(): void {
188
197
  if (this._model)
189
198
  this._model.freeOwnership();
@@ -194,6 +203,7 @@
194
203
  private lastWorldPos!: Vector3;
195
204
  private lastWorldRotation!: Quaternion;
196
205
 
206
+ /** @internal */
197
207
  onBeforeRender() {
198
208
  if (!this.activeAndEnabled || !this.context.connection.isConnected) return;
199
209
  // console.log("BEFORE RENDER", this.destroyed, this.guid, this._model?.isOwned, this.name, this.gameObject);
src/engine-components/TestRunner.ts CHANGED
@@ -8,12 +8,15 @@
8
8
  import { Rigidbody } from "./RigidBody.js";
9
9
  import { createTransformModel, SyncedTransformIdentifier } from "./SyncedTransform.js";
10
10
 
11
+ /** The TestRunner component is used to run tests when the scene starts
12
+ * @internal */
11
13
  export class TestRunner extends Behaviour {
12
14
  awake(): void {
13
15
  tests.detect_run_tests();
14
16
  }
15
17
  }
16
18
 
19
+ /** @internal */
17
20
  export class TestSimulateUserData extends Behaviour {
18
21
 
19
22
  transformsPerFrame: number = 10;
src/engine-components/timeline/TimelineTracks.ts CHANGED
@@ -13,6 +13,10 @@
13
13
 
14
14
  const debug = getParam("debugtimeline");
15
15
 
16
+ /**
17
+ * A TrackHandler is responsible for evaluating a specific type of timeline track.
18
+ * A timeline track can be an animation track, audio track, signal track, control track etc and is controlled by a {@link PlayableDirector}.
19
+ */
16
20
  export abstract class TrackHandler {
17
21
  director!: PlayableDirector;
18
22
  track!: Models.TrackModel;
src/engine-components/TransformGizmo.ts CHANGED
@@ -7,6 +7,9 @@
7
7
  import { OrbitControls } from "./OrbitControls.js";
8
8
  import { SyncedTransform } from "./SyncedTransform.js";
9
9
 
10
+ /**
11
+ * TransformGizmo is a component that displays a gizmo for transforming the object in the scene.
12
+ */
10
13
  export class TransformGizmo extends Behaviour {
11
14
 
12
15
  @serializable()
@@ -24,6 +27,7 @@
24
27
  private control?: TransformControls;
25
28
  private orbit?: OrbitControls;
26
29
 
30
+ /** @internal */
27
31
  onEnable() {
28
32
  if (this.isGizmo && !params.showGizmos) return;
29
33
 
@@ -58,6 +62,7 @@
58
62
  }
59
63
  }
60
64
 
65
+ /** @internal */
61
66
  onDisable() {
62
67
  this.control?.removeFromParent();
63
68
  this.control?.removeEventListener('dragging-changed', this.onControlChangedEvent);
src/engine-components/js-extensions/Vector.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { slerp } from "../../engine/engine_three_utils.js";
4
4
  import { applyPrototypeExtensions, registerPrototypeExtensions } from "./ExtensionUtils.js";
5
5
 
6
+ /** @internal */
6
7
  export function apply(object: Vector3) {
7
8
  if (object && object.isVector3 === true) {
8
9
  applyPrototypeExtensions(object, Vector3);
src/engine-components/postprocessing/Volume.ts CHANGED
@@ -26,10 +26,14 @@
26
26
  private _postprocessing?: PostProcessingHandler;
27
27
  private _effects: PostProcessingEffect[] = [];
28
28
 
29
+ /**
30
+ * When dirty the post processing effects will be re-applied
31
+ */
29
32
  markDirty() {
30
33
  this._isDirty = true;
31
34
  }
32
35
 
36
+ /** @internal */
33
37
  awake() {
34
38
  if (debug) {
35
39
  console.log(this);
@@ -47,10 +51,12 @@
47
51
  this.sharedProfile?.init();
48
52
  }
49
53
 
54
+ /** @internal */
50
55
  onDisable() {
51
56
  this._postprocessing?.unapply();
52
57
  }
53
58
 
59
+ /** @internal */
54
60
  onBeforeRender(): void {
55
61
  if (!this.context.isInXR) {
56
62
 
@@ -75,6 +81,7 @@
75
81
  }
76
82
  }
77
83
 
84
+ /** @internal */
78
85
  onDestroy(): void {
79
86
  this._postprocessing?.dispose();
80
87
  }
src/engine-components/postprocessing/VolumeProfile.ts CHANGED
@@ -27,6 +27,7 @@
27
27
  return PostProcessingEffect;
28
28
  }
29
29
 
30
+ /** @internal */
30
31
  export class VolumeProfile {
31
32
 
32
33
  @serializeable([d => resolveComponentType(d), PostProcessingEffect])
src/engine-components/webxr/WebARCameraBackground.ts CHANGED
@@ -18,8 +18,12 @@
18
18
 
19
19
  const debug = getParam("debugarcamera");
20
20
 
21
+ /**
22
+ * WebARCameraBackground is a component that allows to display the camera feed as a background in an AR session to more easily blend the real world with the virtual world or applying effects to the camera feed.
23
+ */
21
24
  export class WebARCameraBackground extends Behaviour {
22
25
 
26
+ /** @internal */
23
27
  onBeforeXR(_mode: XRSessionMode, args: XRSessionInit): void {
24
28
  args.optionalFeatures = args.optionalFeatures || [];
25
29
  args.optionalFeatures.push('camera-access');
@@ -27,6 +31,7 @@
27
31
  if (debug) console.warn("Requesting camera-access");
28
32
  }
29
33
 
34
+ /** @internal */
30
35
  onEnterXR(_args: NeedleXREventArgs): void {
31
36
  if (this.backgroundPlane) {
32
37
  this.context.scene.add(this.backgroundPlane);
@@ -37,6 +42,7 @@
37
42
  this.context.pre_render_callbacks.push(this.preRender);
38
43
  }
39
44
 
45
+ /** @internal */
40
46
  onLeaveXR(_args: NeedleXREventArgs): void {
41
47
  if (this.backgroundPlane) this.backgroundPlane.removeFromParent();
42
48
  const i = this.context.pre_render_callbacks.indexOf(this.preRender);
@@ -44,6 +50,9 @@
44
50
  this.context.pre_render_callbacks.splice(i, 1);
45
51
  }
46
52
 
53
+ /**
54
+ * The tint color of the camera feed
55
+ */
47
56
  @serializable(RGBAColor)
48
57
  public backgroundTint: RGBAColor = new RGBAColor(1,1,1,1);
49
58
 
@@ -69,6 +78,7 @@
69
78
 
70
79
 
71
80
 
81
+ /** @internal */
72
82
  private preRender = () => {
73
83
  if (!this || !this.gameObject) return;
74
84
 
@@ -98,6 +108,7 @@
98
108
  }
99
109
  }
100
110
 
111
+ /** @internal */
101
112
  onBeforeRender(frame: XRFrame | null) {
102
113
  this.updateFromFrame(frame);
103
114
  }
@@ -174,6 +185,9 @@
174
185
  `;
175
186
 
176
187
  // not sure where we want to move this and in which form is best (extends Object3D?)
188
+ /**
189
+ * Creates a fullscreen plane with a shader that can be used to display a camera feed or other background.
190
+ */
177
191
  export function makeFullscreenPlane(tint: RGBAColor ) {
178
192
  const replacementTint = "vec4(" + tint.r.toFixed(3) + "," + tint.g.toFixed(3) + "," + tint.b.toFixed(3) + "," + tint.a.toFixed(3) + ")";
179
193
  if (debug) console.log(replacementTint);
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -20,6 +20,10 @@
20
20
 
21
21
  // TODO: webarsessionroot needs to place the rig (and not itself)
22
22
 
23
+ /**
24
+ * The WebARSessionRoot is the root object for a WebAR session and used to place the scene in AR.
25
+ * It is also responsible for scaling the user in AR and optionally creating a XR anchor for the scene placement.
26
+ */
23
27
  export class WebARSessionRoot extends Behaviour {
24
28
 
25
29
  /** The scale of a user in AR:
src/engine-components/webxr/WebXR.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import { serializable } from "../../engine/engine_serialization.js";
6
6
  import { getParam, isDesktop, isiOS, isMobileDevice, isQuest, isSafari } from "../../engine/engine_utils.js";
7
7
  import { type NeedleXREventArgs, NeedleXRSession } from "../../engine/engine_xr.js";
8
+ import { ButtonsFactory } from "../../engine/webcomponents/buttons.js";
8
9
  import { getIconElement } from "../../engine/webcomponents/icons.js";
9
10
  import { PlayerSync } from "../../engine-components-experimental/networking/PlayerSync.js";
10
11
  import { Behaviour, GameObject } from "../Component.js";
@@ -20,6 +21,10 @@
20
21
  const debug = getParam("debugwebxr");
21
22
  const debugQuicklook = getParam("debugusdz");
22
23
 
24
+ /**
25
+ * WebXR component to enable VR, AR and Quicklook on iOS in your scene.
26
+ * It provides a simple wrapper around the {@link NeedleXRSession} API and adds some additional features like creating buttons or enabling default movement behaviour.
27
+ */
23
28
  export class WebXR extends Behaviour {
24
29
 
25
30
  // UI
@@ -305,7 +310,7 @@
305
310
  if (this.createQRCode && !isMobileDevice()) {
306
311
  NeedleXRSession.isXRSupported().then(supported => {
307
312
  if (isDesktop() || !supported) {
308
- const qrCode = this.getButtonsFactory().createQRCode();
313
+ const qrCode = ButtonsFactory.getOrCreate().createQRCode();
309
314
  this.addButton(qrCode, xrButtonsPriority);
310
315
  }
311
316
  });
src/engine-components/webxr/WebXRButtons.ts CHANGED
@@ -2,12 +2,19 @@
2
2
  import { generateQRCode } from "../../engine/engine_utils.js";
3
3
  import { isMozillaXR } from "../../engine/engine_utils.js";
4
4
  import { NeedleXRSession } from "../../engine/engine_xr.js";
5
+ import { ButtonsFactory } from "../../engine/webcomponents/buttons.js";
5
6
  import { getIconElement } from "../../engine/webcomponents/icons.js";
7
+ import { onXRSessionStart } from "../../engine/xr/events.js";
6
8
  import { GameObject } from "../Component.js";
7
9
  import { USDZExporter } from "../export/usdz/USDZExporter.js";
8
10
 
9
11
  // TODO: move these buttons into their own web components so their logic is encapsulated (e.g. the CSS animation when a xr session is requested)
10
12
 
13
+ /**
14
+ * Factory to create WebXR buttons for AR, VR, Quicklook and Send to Quest
15
+ * The buttons are created as HTMLButtonElements and can be added to the DOM.
16
+ * The buttons will automatically hide when a XR session is started and show again when the session ends.
17
+ */
11
18
  export class WebXRButtonFactory {
12
19
 
13
20
  private static _instance: WebXRButtonFactory;
@@ -37,10 +44,8 @@
37
44
  get sendToQuestButton() { return this._sendToQuestButton; }
38
45
  private _sendToQuestButton?: HTMLButtonElement;
39
46
 
40
- get qrButton() { return this._qrButton; }
41
- private _qrButton?: HTMLButtonElement;
47
+ get qrButton() { return ButtonsFactory.getOrCreate().createQRCode(); }
42
48
 
43
-
44
49
  /** get or create the quicklook button
45
50
  * Behaviour of the button:
46
51
  * - if the button is clicked a USDZExporter component will be searched for in the scene and if found, it will be used to export the scene to USDZ / Quicklook
@@ -173,96 +178,11 @@
173
178
  return button;
174
179
  }
175
180
 
181
+ /**
182
+ * @deprecated please use ButtonsFactory.getOrCreate().createQRCode(). This method will be removed in a future update
183
+ */
176
184
  createQRCode(): HTMLButtonElement {
177
- if (this._qrButton) return this._qrButton;
178
-
179
- const qrCodeButton = document.createElement("button");
180
- this._qrButton = qrCodeButton;
181
- qrCodeButton.innerText = "QR Code";
182
- qrCodeButton.prepend(getIconElement("qr_code"));
183
- qrCodeButton.title = "Scan this QR code with your phone to open this page";
184
- this.hideElementDuringXRSession(qrCodeButton);
185
-
186
-
187
- const qrCodeContainer = document.createElement("div");
188
- qrCodeContainer.style.position = "fixed";
189
- qrCodeContainer.style.display = "inline-block";
190
- qrCodeContainer.style.padding = "1rem";
191
- qrCodeContainer.style.backgroundColor = "white";
192
- qrCodeContainer.style.borderRadius = "0.4rem";
193
- qrCodeContainer.style.cursor = "pointer";
194
- qrCodeContainer.style.zIndex = "1000";
195
- qrCodeContainer.style.boxShadow = "0 0 12px rgba(0, 0, 0, 0.2)";
196
-
197
- const qrCodeElement = document.createElement("div");
198
- qrCodeElement.classList.add("qr-code-container");
199
- qrCodeContainer.appendChild(qrCodeElement);
200
-
201
- qrCodeButton.addEventListener("click", () => {
202
- if (qrCodeContainer.parentNode) return hideQRCode();
203
- showQRCode();
204
- });
205
-
206
- /** shows the QRCode near the button */
207
- async function showQRCode() {
208
- // generate the qr code when the button is clicked
209
- // this ensures that we get the QRcode with the latest URL
210
- await generateAndInsertQRCode();
211
- // TODO: make sure it doesnt overflow the screen
212
- // we need to add the qrCodeContainer to the body to get the correct size
213
- document.body.appendChild(qrCodeContainer);
214
- const containerRect = qrCodeElement.getBoundingClientRect();
215
- const buttonRect = qrCodeButton.getBoundingClientRect();
216
- qrCodeContainer.style.left = (buttonRect.left + buttonRect.width * .5 - containerRect.width * .5) + "px";
217
- const isButtonInTopHalf = buttonRect.top < containerRect.height;
218
- if (isButtonInTopHalf)
219
- qrCodeContainer.style.top = `calc(${buttonRect.bottom}px + ${qrCodeContainer.style.padding} * .6)`;
220
- else
221
- qrCodeContainer.style.top = `calc(${buttonRect.top - containerRect.height}px - ${qrCodeContainer.style.padding} * 2.5)`;
222
- qrCodeContainer.style.opacity = "0";
223
- qrCodeContainer.style.pointerEvents = "all";
224
- qrCodeContainer.style.transition = "opacity 0.2s ease-in-out";
225
-
226
- // context click to hide the QR code again, if we dont timeout the event will be triggered immediately
227
- setTimeout(() => {
228
- qrCodeContainer.style.opacity = "1";
229
- window.addEventListener("click", hideQRCode, { once: true })
230
- });
231
- window.addEventListener("resize", hideQRCode);
232
- window.addEventListener("scroll", hideQRCode);
233
-
234
- document.body.appendChild(qrCodeContainer);
235
- }
236
-
237
- /** hides to QRCode overlay and unsubscribes from events */
238
- function hideQRCode() {
239
- qrCodeContainer.style.pointerEvents = "none";
240
- qrCodeContainer.style.transition = "opacity 0.2s";
241
- qrCodeContainer.style.opacity = "0";
242
- setTimeout(() => qrCodeContainer.parentNode?.removeChild(qrCodeContainer), 500);
243
- window.removeEventListener("click", hideQRCode);
244
- window.removeEventListener("resize", hideQRCode);
245
- window.removeEventListener("scroll", hideQRCode);
246
- };
247
-
248
- /** generates a QR code and inserts it into the qrCodeElement */
249
- async function generateAndInsertQRCode() {
250
- const size = 200;
251
- const code = await generateQRCode({
252
- text: window.location.href,
253
- width: size,
254
- height: size,
255
- });
256
- qrCodeElement.innerHTML = "";
257
- qrCodeElement.appendChild(code);
258
- }
259
-
260
- // lazily create the qr button
261
- qrCodeButton.addEventListener("pointerenter", () => {
262
- generateAndInsertQRCode();
263
- }, { once: true });
264
-
265
- return qrCodeButton;
185
+ return ButtonsFactory.getOrCreate().createQRCode();
266
186
  }
267
187
 
268
188
  private updateSessionSupported(button: HTMLButtonElement, mode: XRSessionMode) {
@@ -277,6 +197,9 @@
277
197
  }
278
198
 
279
199
  private hideElementDuringXRSession(element: HTMLElement) {
200
+ onXRSessionStart(_ => {
201
+ })
202
+ onXRSessionStart
280
203
  NeedleXRSession.onXRSessionStart(_ => {
281
204
  element["previous-display"] = element.style.display;
282
205
  element.style.display = "none";
src/engine-components/webxr/WebXRRig.ts CHANGED
@@ -10,6 +10,10 @@
10
10
 
11
11
  const debug = getParam("debugwebxr");
12
12
 
13
+ /**
14
+ * A user in XR (VR or AR) is parented to an XR rig during the session.
15
+ * When moving through the scene the rig is moved instead of the user.
16
+ */
13
17
  export class XRRig extends Behaviour implements IXRRig {
14
18
 
15
19
  @serializable()
src/engine-components/webxr/controllers/XRControllerModel.ts CHANGED
@@ -21,6 +21,9 @@
21
21
  const handsJointBuffer = new Float32Array(16 * 25);
22
22
  const renderingUpdateTimings = new Array<number>();
23
23
 
24
+ /**
25
+ * XRControllerModel is a component that allows to display controller models or hand models in an XR session.
26
+ */
24
27
  export class XRControllerModel extends Behaviour {
25
28
 
26
29
  @serializable()
src/engine-components/webxr/controllers/XRControllerMovement.ts CHANGED
@@ -17,6 +17,9 @@
17
17
 
18
18
  const debug = getParam("debugwebxr");
19
19
 
20
+ /**
21
+ * XRControllerMovement is a component that allows to move the XR rig using the XR controller input.
22
+ */
20
23
  export class XRControllerMovement extends Behaviour implements XRMovementBehaviour {
21
24
 
22
25
  /** Movement speed in meters per second */