@needle-tools/engine 4.14.0 → 4.15.0-next.cecd8e7

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 (81) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-NolzHLqO.min.js → needle-engine.bundle-CuAiLb-d.min.js} +137 -129
  4. package/dist/{needle-engine.bundle-COL2Bar3.umd.cjs → needle-engine.bundle-JQGIFVRm.umd.cjs} +128 -120
  5. package/dist/{needle-engine.bundle-Z_gAD7Kg.js → needle-engine.bundle-VZVrVbc3.js} +5497 -5252
  6. package/dist/needle-engine.d.ts +318 -42
  7. package/dist/needle-engine.js +570 -569
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/engine_accessibility.d.ts +77 -0
  11. package/lib/engine/engine_accessibility.js +162 -0
  12. package/lib/engine/engine_accessibility.js.map +1 -0
  13. package/lib/engine/engine_context.d.ts +2 -0
  14. package/lib/engine/engine_context.js +7 -0
  15. package/lib/engine/engine_context.js.map +1 -1
  16. package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
  17. package/lib/engine/engine_materialpropertyblock.js +97 -7
  18. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  19. package/lib/engine/engine_math.d.ts +34 -1
  20. package/lib/engine/engine_math.js +34 -1
  21. package/lib/engine/engine_math.js.map +1 -1
  22. package/lib/engine/engine_networking.js +1 -1
  23. package/lib/engine/engine_networking.js.map +1 -1
  24. package/lib/engine/engine_types.d.ts +2 -0
  25. package/lib/engine/engine_types.js +2 -0
  26. package/lib/engine/engine_types.js.map +1 -1
  27. package/lib/engine/webcomponents/icons.js +3 -0
  28. package/lib/engine/webcomponents/icons.js.map +1 -1
  29. package/lib/engine/webcomponents/logo-element.d.ts +1 -0
  30. package/lib/engine/webcomponents/logo-element.js +3 -1
  31. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  32. package/lib/engine/webcomponents/needle-button.d.ts +37 -11
  33. package/lib/engine/webcomponents/needle-button.js +42 -11
  34. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  35. package/lib/engine/webcomponents/needle-engine.d.ts +10 -2
  36. package/lib/engine/webcomponents/needle-engine.js +13 -3
  37. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  38. package/lib/engine-components/Component.d.ts +1 -2
  39. package/lib/engine-components/Component.js +1 -2
  40. package/lib/engine-components/Component.js.map +1 -1
  41. package/lib/engine-components/DragControls.d.ts +1 -0
  42. package/lib/engine-components/DragControls.js +21 -0
  43. package/lib/engine-components/DragControls.js.map +1 -1
  44. package/lib/engine-components/NeedleMenu.d.ts +2 -0
  45. package/lib/engine-components/NeedleMenu.js +2 -0
  46. package/lib/engine-components/NeedleMenu.js.map +1 -1
  47. package/lib/engine-components/Networking.d.ts +28 -3
  48. package/lib/engine-components/Networking.js +28 -3
  49. package/lib/engine-components/Networking.js.map +1 -1
  50. package/lib/engine-components/ReflectionProbe.d.ts +1 -0
  51. package/lib/engine-components/ReflectionProbe.js +20 -2
  52. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  53. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +15 -0
  54. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +77 -0
  55. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  56. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  57. package/lib/engine-components/ui/Button.d.ts +1 -0
  58. package/lib/engine-components/ui/Button.js +11 -0
  59. package/lib/engine-components/ui/Button.js.map +1 -1
  60. package/lib/engine-components/ui/Text.d.ts +1 -0
  61. package/lib/engine-components/ui/Text.js +11 -0
  62. package/lib/engine-components/ui/Text.js.map +1 -1
  63. package/package.json +2 -2
  64. package/src/engine/engine_accessibility.ts +198 -0
  65. package/src/engine/engine_context.ts +9 -0
  66. package/src/engine/engine_materialpropertyblock.ts +102 -11
  67. package/src/engine/engine_math.ts +34 -1
  68. package/src/engine/engine_networking.ts +1 -1
  69. package/src/engine/engine_types.ts +5 -0
  70. package/src/engine/webcomponents/icons.ts +3 -0
  71. package/src/engine/webcomponents/logo-element.ts +4 -1
  72. package/src/engine/webcomponents/needle-button.ts +44 -13
  73. package/src/engine/webcomponents/needle-engine.ts +18 -7
  74. package/src/engine-components/Component.ts +1 -3
  75. package/src/engine-components/DragControls.ts +29 -4
  76. package/src/engine-components/NeedleMenu.ts +5 -3
  77. package/src/engine-components/Networking.ts +29 -4
  78. package/src/engine-components/ReflectionProbe.ts +21 -2
  79. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +108 -32
  80. package/src/engine-components/ui/Button.ts +12 -0
  81. package/src/engine-components/ui/Text.ts +13 -0
@@ -13,32 +13,58 @@ const htmlTagName = "needle-button";
13
13
  const isDev = isDevEnvironment();
14
14
 
15
15
  /**
16
- * A <needle-button> can be used to simply add VR, AR or Quicklook buttons to your website without having to write any code.
17
- * @example
16
+ * [&lt;needle-button&gt;](https://engine.needle.tools/docs/api/NeedleButtonElement) is a web component for easily adding AR, VR, Quicklook, or QR code buttons to your website without writing JavaScript code.
17
+ *
18
+ * The button automatically handles session management and displays appropriate UI based on device capabilities.
19
+ * It comes with default styling (glassmorphism design) but can be fully customized with CSS.
20
+ *
21
+ * **Supported button types:**
22
+ * - `ar` - WebXR AR session button
23
+ * - `vr` - WebXR VR session button
24
+ * - `quicklook` - Apple AR Quick Look button (iOS only)
25
+ * - `qrcode` - QR code sharing button
26
+ *
27
+ * @example Basic AR/VR buttons
18
28
  * ```html
29
+ * <needle-engine src="scene.glb"></needle-engine>
19
30
  * <needle-button ar></needle-button>
20
31
  * <needle-button vr></needle-button>
21
32
  * <needle-button quicklook></needle-button>
22
33
  * ```
23
- *
24
- * @example custom label
34
+ *
35
+ * @example Custom button labels
25
36
  * ```html
26
- * <needle-button ar>Start AR</needle-button>
27
- * <needle-button vr>Start VR</needle-button>
37
+ * <needle-button ar>Start AR Experience</needle-button>
38
+ * <needle-button vr>Enter VR Mode</needle-button>
28
39
  * <needle-button quicklook>View in AR</needle-button>
29
40
  * ```
30
- *
31
- * @example custom styling
41
+ *
42
+ * @example Custom styling
32
43
  * ```html
33
- * <!-- You can either style the element directly or use a CSS stylesheet -->
34
44
  * <style>
35
- * needle-button {
36
- * background-color: red;
37
- * color: white;
38
- * }
45
+ * needle-button {
46
+ * background-color: #ff6b6b;
47
+ * color: white;
48
+ * border-radius: 8px;
49
+ * padding: 1rem 2rem;
50
+ * }
51
+ * needle-button:hover {
52
+ * background-color: #ff5252;
53
+ * }
39
54
  * </style>
40
55
  * <needle-button ar>Start AR</needle-button>
41
56
  * ```
57
+ *
58
+ * @example Unstyled button (for complete custom styling)
59
+ * ```html
60
+ * <needle-button ar unstyled>
61
+ * <span class="my-icon">🥽</span>
62
+ * Launch AR
63
+ * </needle-button>
64
+ * ```
65
+ *
66
+ * @see {@link NeedleEngineWebComponent} for the main &lt;needle-engine&gt; element
67
+ * @see {@link NeedleMenu} for the built-in menu component that can display similar buttons
42
68
  */
43
69
  export class NeedleButtonElement extends HTMLElement {
44
70
 
@@ -73,18 +99,22 @@ export class NeedleButtonElement extends HTMLElement {
73
99
  if (this.getAttribute("ar") != null) {
74
100
  this.#webxrfactory ??= new WebXRButtonFactory()
75
101
  this.#button = this.#webxrfactory.createARButton();
102
+ this.setAttribute("aria-label", "Enter augmented reality mode");
76
103
  }
77
104
  else if (this.getAttribute("vr") != null) {
78
105
  this.#webxrfactory ??= new WebXRButtonFactory()
79
106
  this.#button = this.#webxrfactory.createVRButton();
107
+ this.setAttribute("aria-label", "Enter virtual reality mode");
80
108
  }
81
109
  else if (this.getAttribute("quicklook") != null) {
82
110
  this.#webxrfactory ??= new WebXRButtonFactory()
83
111
  this.#button = this.#webxrfactory.createQuicklookButton();
112
+ this.setAttribute("aria-label", "View in AR with Apple Quick Look");
84
113
  }
85
114
  else if (this.getAttribute("qrcode") != null) {
86
115
  this.#buttonfactory ??= new ButtonsFactory();
87
116
  this.#button = this.#buttonfactory.createQRCode({ anchorElement: this });
117
+ this.setAttribute("aria-label", "Share application with QR code");
88
118
  }
89
119
  else {
90
120
  if (isDev) {
@@ -93,6 +123,7 @@ export class NeedleButtonElement extends HTMLElement {
93
123
  else {
94
124
  console.debug("No button type specified for <needle-button>. Use either ar, vr or quicklook attribute.")
95
125
  }
126
+ this.setAttribute("aria-label", "Needle Button with no specified type");
96
127
  return;
97
128
  }
98
129
 
@@ -87,17 +87,25 @@ const observedAttributes = [
87
87
 
88
88
  // https://developers.google.com/web/fundamentals/web-components/customelements
89
89
 
90
- /**
91
- * The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
92
- * The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
93
- * The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
90
+ /**
91
+ * The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
92
+ * The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
93
+ * The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
94
94
  * The context is accessible from the `<needle-engine>` element: `document.querySelector("needle-engine").context`.
95
95
  * See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
96
96
  *
97
- * @example
97
+ * @example Basic usage
98
+ * ```html
98
99
  * <needle-engine src="https://example.com/scene.glb"></needle-engine>
99
- * @example
100
+ * ```
101
+ *
102
+ * @example With camera controls disabled
103
+ * ```html
100
104
  * <needle-engine src="https://example.com/scene.glb" camera-controls="false"></needle-engine>
105
+ * ```
106
+ *
107
+ * @see {@link NeedleButtonElement} for adding AR/VR/Quicklook buttons via &lt;needle-button&gt;
108
+ * @see {@link NeedleMenu} for the built-in menu configuration component
101
109
  */
102
110
  export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
103
111
 
@@ -176,7 +184,9 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
176
184
 
177
185
  ensureFonts();
178
186
 
179
- this.attachShadow({ mode: 'open' });
187
+ this.attachShadow({ mode: 'open', delegatesFocus: true });
188
+ this.setAttribute("role", "application");
189
+ this.setAttribute("aria-label", "Needle Engine 3D scene");
180
190
  const template = document.createElement('template');
181
191
  // #region CSS
182
192
  template.innerHTML = `<style>
@@ -282,6 +292,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
282
292
  if (this.getAttribute("tabindex") === null || this.getAttribute("tabindex") === undefined)
283
293
  this.setAttribute("tabindex", "0");
284
294
 
295
+
285
296
  this.addEventListener("xr-session-started", this.onXRSessionStarted);
286
297
  this.onSetupDesktop();
287
298
 
@@ -10,7 +10,7 @@ import * as main from "../engine/engine_mainloop_utils.js";
10
10
  import { syncDestroy, syncInstantiate, SyncInstantiateOptions } from "../engine/engine_networking_instantiate.js";
11
11
  import { Context, FrameEvent } from "../engine/engine_setup.js";
12
12
  import * as threeutils from "../engine/engine_three_utils.js";
13
- import type { Collision, ComponentInit, Constructor, ConstructorConcrete, GuidsMap, ICollider, IComponent, IGameObject, SourceIdentifier } from "../engine/engine_types.js";
13
+ import { $componentName, type Collision, type ComponentInit, type Constructor, type ConstructorConcrete, type GuidsMap, type ICollider, type IComponent, type IGameObject, type SourceIdentifier } from "../engine/engine_types.js";
14
14
  import { TypeStore } from "../engine/engine_typestore.js";
15
15
  import type { INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs } from "../engine/engine_xr.js";
16
16
  import { type IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
@@ -542,8 +542,6 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
542
542
  }
543
543
  }
544
544
 
545
- // DO NOT CHANGE THE SYMBOL NAME
546
- const $componentName = Symbol("component-name");
547
545
 
548
546
  /**
549
547
  * Needle Engine component's are the main building blocks of the Needle Engine.
@@ -193,7 +193,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
193
193
  }
194
194
  }
195
195
  }
196
-
196
+
197
197
  private _rigidbody: Rigidbody | null = null;
198
198
 
199
199
  // future:
@@ -235,12 +235,22 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
235
235
  /** @internal */
236
236
  onEnable(): void {
237
237
  DragControls._instances.push(this);
238
+ this.context.accessibility.updateElement(this, {
239
+ role: "button",
240
+ label: "Drag " + (this.gameObject.name || "object"),
241
+ hidden: false,
242
+ });
238
243
  }
239
244
  /** @internal */
240
245
  onDisable(): void {
246
+ this.context.accessibility.updateElement(this, { hidden: true });
241
247
  DragControls._instances = DragControls._instances.filter(i => i !== this);
242
248
  }
243
249
 
250
+ onDestroy(): void {
251
+ this.context.accessibility.removeElement(this);
252
+ }
253
+
244
254
  /**
245
255
  * Checks if editing is allowed for the current networking connection.
246
256
  * @param _obj Optional object to check edit permissions for
@@ -268,6 +278,8 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
268
278
  if (!dc || dc !== this) return;
269
279
  DragControls.lastHovered = evt.object;
270
280
  this.context.domElement.style.cursor = 'pointer';
281
+
282
+ this.context.accessibility.hover(this, `Draggable ${evt.object?.name}`);
271
283
  }
272
284
 
273
285
  /**
@@ -339,6 +351,14 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
339
351
  }
340
352
 
341
353
  args.use();
354
+
355
+ this.context.accessibility.updateElement(this, {
356
+ role: "button",
357
+ label: "Dragging " + (this.gameObject.name || "object"),
358
+ hidden: false,
359
+ busy: true,
360
+ });
361
+ this.context.accessibility.focus(this);
342
362
  }
343
363
  }
344
364
 
@@ -375,6 +395,11 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
375
395
  }
376
396
  args.use();
377
397
  }
398
+
399
+ this.context.accessibility.unfocus(this);
400
+ this.context.accessibility.updateElement(this, {
401
+ busy: false,
402
+ });
378
403
  }
379
404
 
380
405
  /**
@@ -779,12 +804,12 @@ class DragPointerHandler implements IDragHandler {
779
804
  * Used for determining if enough motion has occurred to start a drag.
780
805
  */
781
806
  getTotalMovement(): Vector3 { return this._totalMovement; }
782
-
807
+
783
808
  /**
784
809
  * Returns the object that follows the pointer during dragging operations.
785
810
  */
786
811
  get followObject(): GameObject { return this._followObject; }
787
-
812
+
788
813
  /**
789
814
  * Returns the point where the pointer initially hit the object in local space.
790
815
  */
@@ -1377,7 +1402,7 @@ class LegacyDragVisualsHelper {
1377
1402
 
1378
1403
  /** Controls whether visual helpers like lines and markers are displayed */
1379
1404
  showGizmo: boolean = true;
1380
-
1405
+
1381
1406
  /** When true, drag plane alignment changes based on view angle */
1382
1407
  useViewAngle: boolean = true;
1383
1408
 
@@ -9,8 +9,8 @@ import type { Voip } from './Voip.js';
9
9
  /**
10
10
  * [NeedleMenu](https://engine.needle.tools/docs/api/NeedleMenu) provides configuration for the built-in UI menu.
11
11
  * The menu renders as HTML overlay in browser mode and automatically
12
- * switches to a 3D spatial menu in VR/AR.
13
- *
12
+ * switches to a 3D spatial menu in VR/AR.
13
+ *
14
14
  * ![](https://cloud.needle.tools/-/media/YKleg1oPy_I8Hv8sg_k40Q.png)
15
15
  *
16
16
  * **Features:**
@@ -18,7 +18,7 @@ import type { Voip } from './Voip.js';
18
18
  * - Audio mute/unmute button
19
19
  * - QR code sharing (desktop only)
20
20
  * - Spatial menu in XR (appears when looking up)
21
- * - Custom positioning (top/bottom)
21
+ * - Custom positioning (top/bottom)
22
22
  *
23
23
  * **Programmatic access:**
24
24
  * Access the menu API via `this.context.menu` to add custom buttons,
@@ -36,6 +36,8 @@ import type { Voip } from './Voip.js';
36
36
  * @category User Interface
37
37
  * @group Components
38
38
  * @see {@link Context.menu} for programmatic menu control
39
+ * @see {@link NeedleButtonElement} for standalone &lt;needle-button&gt; web component
40
+ * @see {@link NeedleEngineWebComponent} for the main &lt;needle-engine&gt; element
39
41
  * @see {@link Voip} adds a microphone button to the menu
40
42
  * @see {@link ScreenCapture} adds a screen sharing button
41
43
  **/
@@ -7,10 +7,34 @@ import { Behaviour } from "./Component.js";
7
7
  const debug = getParam("debugnet");
8
8
 
9
9
  /**
10
- * Provides configuration to the built-in networking system.
11
- * This component supplies websocket URLs for establishing connections.
12
- * It implements the {@link INetworkingWebsocketUrlProvider} interface.
13
- *
10
+ * Provides websocket URL configuration for the built-in networking system.
11
+ * Add this component to override the default networking backend URL used by {@link NetworkConnection} (`this.context.connection`).
12
+ *
13
+ * The component registers itself as a URL provider on `awake()`. When the networking system connects,
14
+ * it queries this provider for the websocket URL to use instead of the default Needle networking backend.
15
+ *
16
+ * **URL resolution order:**
17
+ * 1. If `urlParameterName` is set and the corresponding URL parameter exists in the browser URL, that value is used
18
+ * 2. If running on a local network and `localhost` is set, the `localhost` URL is used
19
+ * 3. Otherwise, the `url` field is used
20
+ *
21
+ * Without this component, the default backend URL `wss://networking-2.needle.tools/socket` is used.
22
+ *
23
+ * **Note:** This component only configures the websocket URL. To actually join a networked room,
24
+ * use a `SyncedRoom` component or call `this.context.connection.joinRoom("room-name")` directly.
25
+ *
26
+ * @example Overriding the URL via browser parameter
27
+ * ```ts
28
+ * // With urlParameterName="server", visiting:
29
+ * // https://myapp.com/?server=wss://my-server.com/socket
30
+ * // will connect to that server instead
31
+ * ```
32
+ *
33
+ * @see {@link NetworkConnection} for the main networking API (`this.context.connection`)
34
+ * @see {@link SyncedRoom} for automatic room joining
35
+ * @see {@link OwnershipModel} for networked object ownership
36
+ * @see {@link RoomEvents} for room lifecycle events
37
+ * @link https://engine.needle.tools/docs/how-to-guides/networking/
14
38
  * @summary Networking configuration
15
39
  * @category Networking
16
40
  * @group Components
@@ -20,6 +44,7 @@ export class Networking extends Behaviour implements INetworkingWebsocketUrlProv
20
44
  /**
21
45
  * The websocket URL to connect to for networking functionality.
22
46
  * Can be a complete URL or a relative path that will be resolved against the current origin.
47
+ * @default null
23
48
  */
24
49
  @serializable()
25
50
  url: string | null = null;
@@ -2,10 +2,11 @@ import { Color, CubeReflectionMapping, CubeTexture, EquirectangularReflectionMap
2
2
 
3
3
  import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
4
4
  import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
5
+ import { loadPMREM } from "../engine/engine_pmrem.js";
5
6
  import { serializable } from "../engine/engine_serialization.js";
6
7
  import { Context } from "../engine/engine_setup.js";
7
8
  import type { IRenderer } from "../engine/engine_types.js";
8
- import { getParam } from "../engine/engine_utils.js";
9
+ import { getParam, resolveUrl } from "../engine/engine_utils.js";
9
10
  import { BoxHelperComponent } from "./BoxHelperComponent.js";
10
11
  import { Behaviour } from "./Component.js";
11
12
  import { EventList } from "./EventList.js";
@@ -90,10 +91,28 @@ export class ReflectionProbe extends Behaviour {
90
91
  }
91
92
 
92
93
  private _texture!: Texture;
94
+ private _textureUrlInFlight?: string;
93
95
 
94
- @serializable(Texture)
96
+ @serializable([Texture, String])
95
97
  set texture(tex: Texture) {
96
98
  if (this._texture === tex) return;
99
+
100
+ if (typeof tex === "string") {
101
+ if(debug) console.debug(`[ReflectionProbe] Loading reflection probe texture from URL: ${tex}`);
102
+ this._textureUrlInFlight = tex;
103
+ const textureUrl = resolveUrl(this.sourceId, tex);
104
+ loadPMREM(textureUrl, this.context.renderer).then(loaded => {
105
+ if (this._textureUrlInFlight === tex && loaded) {
106
+ this._textureUrlInFlight = undefined;
107
+ if (debug) console.debug(`[ReflectionProbe] Successfully loaded reflection probe texture: ${tex}`);
108
+ this.texture = loaded;
109
+ }
110
+ });
111
+ return;
112
+ }
113
+
114
+ this._textureUrlInFlight = undefined;
115
+
97
116
  this._texture = tex;
98
117
 
99
118
  if (debug) console.debug("[ReflectionProbe] Set reflection probe texture " + (tex?.name || "(removed)"));