@needle-tools/engine 4.14.0-beta → 4.14.0-next.52fdb13

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 (97) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-BW2VusZV.min.js → needle-engine.bundle-BwfaInTa.min.js} +131 -123
  4. package/dist/{needle-engine.bundle-Cb5bKEqa.umd.cjs → needle-engine.bundle-DJE-Bjpa.umd.cjs} +124 -116
  5. package/dist/{needle-engine.bundle-D9VPvp5o.js → needle-engine.bundle-TmE5-_na.js} +3457 -3227
  6. package/dist/needle-engine.d.ts +430 -94
  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/dist/{postprocessing-CctM1XIO.min.js → postprocessing-06AXuvdv.min.js} +2 -2
  11. package/dist/{postprocessing-DGm6qJ-I.js → postprocessing-CI2x8Cln.js} +2 -2
  12. package/dist/{postprocessing-Dbl2PJpd.umd.cjs → postprocessing-CPDcA21P.umd.cjs} +2 -2
  13. package/lib/engine/api.d.ts +203 -18
  14. package/lib/engine/api.js +271 -18
  15. package/lib/engine/api.js.map +1 -1
  16. package/lib/engine/engine_accessibility.d.ts +58 -0
  17. package/lib/engine/engine_accessibility.js +143 -0
  18. package/lib/engine/engine_accessibility.js.map +1 -0
  19. package/lib/engine/engine_context.d.ts +2 -0
  20. package/lib/engine/engine_context.js +7 -0
  21. package/lib/engine/engine_context.js.map +1 -1
  22. package/lib/engine/engine_materialpropertyblock.d.ts +91 -5
  23. package/lib/engine/engine_materialpropertyblock.js +97 -7
  24. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  25. package/lib/engine/engine_math.d.ts +34 -1
  26. package/lib/engine/engine_math.js +34 -1
  27. package/lib/engine/engine_math.js.map +1 -1
  28. package/lib/engine/engine_networking.js +1 -1
  29. package/lib/engine/engine_networking.js.map +1 -1
  30. package/lib/engine/engine_types.d.ts +2 -0
  31. package/lib/engine/engine_types.js +2 -0
  32. package/lib/engine/engine_types.js.map +1 -1
  33. package/lib/engine/webcomponents/icons.js +3 -0
  34. package/lib/engine/webcomponents/icons.js.map +1 -1
  35. package/lib/engine/webcomponents/logo-element.d.ts +1 -0
  36. package/lib/engine/webcomponents/logo-element.js +3 -1
  37. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  38. package/lib/engine/webcomponents/needle-button.d.ts +37 -11
  39. package/lib/engine/webcomponents/needle-button.js +42 -11
  40. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  41. package/lib/engine/webcomponents/needle-engine.d.ts +10 -2
  42. package/lib/engine/webcomponents/needle-engine.js +13 -3
  43. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  44. package/lib/engine-components/Component.d.ts +1 -2
  45. package/lib/engine-components/Component.js +1 -2
  46. package/lib/engine-components/Component.js.map +1 -1
  47. package/lib/engine-components/DragControls.d.ts +1 -0
  48. package/lib/engine-components/DragControls.js +21 -0
  49. package/lib/engine-components/DragControls.js.map +1 -1
  50. package/lib/engine-components/GroundProjection.js.map +1 -1
  51. package/lib/engine-components/NeedleMenu.d.ts +2 -0
  52. package/lib/engine-components/NeedleMenu.js +2 -0
  53. package/lib/engine-components/NeedleMenu.js.map +1 -1
  54. package/lib/engine-components/Networking.d.ts +28 -3
  55. package/lib/engine-components/Networking.js +28 -3
  56. package/lib/engine-components/Networking.js.map +1 -1
  57. package/lib/engine-components/ReflectionProbe.d.ts +1 -0
  58. package/lib/engine-components/ReflectionProbe.js +21 -3
  59. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  60. package/lib/engine-components/RendererLightmap.js +1 -1
  61. package/lib/engine-components/RendererLightmap.js.map +1 -1
  62. package/lib/engine-components/SeeThrough.js +1 -1
  63. package/lib/engine-components/SeeThrough.js.map +1 -1
  64. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +107 -13
  65. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +167 -30
  66. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  67. package/lib/engine-components/ui/Button.d.ts +1 -0
  68. package/lib/engine-components/ui/Button.js +11 -0
  69. package/lib/engine-components/ui/Button.js.map +1 -1
  70. package/lib/engine-components/ui/Text.d.ts +1 -0
  71. package/lib/engine-components/ui/Text.js +11 -0
  72. package/lib/engine-components/ui/Text.js.map +1 -1
  73. package/lib/engine-components/web/ViewBox.js.map +1 -1
  74. package/package.json +2 -2
  75. package/src/engine/api.ts +371 -19
  76. package/src/engine/engine_accessibility.ts +178 -0
  77. package/src/engine/engine_context.ts +9 -0
  78. package/src/engine/engine_materialpropertyblock.ts +103 -12
  79. package/src/engine/engine_math.ts +34 -1
  80. package/src/engine/engine_networking.ts +1 -1
  81. package/src/engine/engine_types.ts +5 -0
  82. package/src/engine/webcomponents/icons.ts +3 -0
  83. package/src/engine/webcomponents/logo-element.ts +4 -1
  84. package/src/engine/webcomponents/needle-button.ts +44 -13
  85. package/src/engine/webcomponents/needle-engine.ts +18 -7
  86. package/src/engine-components/Component.ts +1 -3
  87. package/src/engine-components/DragControls.ts +29 -4
  88. package/src/engine-components/GroundProjection.ts +1 -1
  89. package/src/engine-components/NeedleMenu.ts +5 -3
  90. package/src/engine-components/Networking.ts +29 -4
  91. package/src/engine-components/ReflectionProbe.ts +22 -3
  92. package/src/engine-components/RendererLightmap.ts +1 -1
  93. package/src/engine-components/SeeThrough.ts +1 -1
  94. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +198 -65
  95. package/src/engine-components/ui/Button.ts +12 -0
  96. package/src/engine-components/ui/Text.ts +13 -0
  97. package/src/engine-components/web/ViewBox.ts +3 -3
@@ -1,4 +1,4 @@
1
- import { Material, Object3D, Color, Texture, Vector2, Vector3, Vector4, WebGLRenderer, Scene, Camera, BufferGeometry, Group, Euler } from "three";
1
+ import { BufferGeometry, Camera, Color, Euler,Group, Material, Object3D, Scene, Texture, Vector2, Vector3, Vector4, WebGLRenderer } from "three";
2
2
 
3
3
 
4
4
  // @TODO: we need to detect objects with materials both transparent and NOT transparent. These need to be updated in scene.onBeforeRender to have correct renderlists
@@ -122,17 +122,26 @@ class PropertyBlockRegistry {
122
122
  const registry = new PropertyBlockRegistry();
123
123
 
124
124
  /**
125
- * MaterialPropertyBlock allows per-object material property overrides without creating new material instances.
126
- * This is useful for rendering multiple objects with the same base material but different properties
127
- * (e.g., different colors, textures, or shader parameters).
125
+ * MaterialPropertyBlock allows per-object material property overrides without creating new material instances.
126
+ * This is useful for rendering multiple objects with the same base material but different properties
127
+ * (e.g., different colors, textures, or shader parameters).
128
128
  *
129
- * The property block system works by:
129
+ * ## How Property Blocks Work
130
+ *
131
+ * **Important**: Overrides are registered on the **Object3D**, not on the material.
132
+ * This means:
133
+ * - If you change the object's material, the overrides will still be applied to the new material
134
+ * - Multiple objects can share the same material but have different property overrides
135
+ * - If you don't want overrides applied after changing a material, you must remove them using {@link removeOveride}, {@link clearAllOverrides}, or {@link dispose}
136
+ *
137
+ * The property block system works by:
130
138
  * - Temporarily applying overrides in onBeforeRender
131
139
  * - Restoring original values in onAfterRender
132
140
  * - Managing shader defines and program cache keys for correct shader compilation
133
141
  * - Supporting texture coordinate transforms per object
134
142
  *
135
- * Common use cases:
143
+ * ## Common Use Cases
144
+ *
136
145
  * - **Lightmaps**: Apply unique lightmap textures to individual objects sharing the same material
137
146
  * - **Reflection Probes**: Apply different environment maps per object for localized reflections
138
147
  * - **See-through effects**: Temporarily override transparency/transmission properties for X-ray effects
@@ -170,6 +179,26 @@ const registry = new PropertyBlockRegistry();
170
179
  * block.setDefine("USE_CUSTOM_FEATURE", 1);
171
180
  * ```
172
181
  *
182
+ * @example Material swapping behavior
183
+ * ```typescript
184
+ * const mesh = new Mesh(geometry, materialA);
185
+ * const block = MaterialPropertyBlock.get(mesh);
186
+ * block.setOverride("color", new Color(1, 0, 0));
187
+ *
188
+ * // The color override is red for materialA
189
+ *
190
+ * // Swap the material - overrides persist and apply to the new material!
191
+ * mesh.material = materialB;
192
+ * // The color override is now red for materialB too
193
+ *
194
+ * // If you don't want overrides on the new material, remove them:
195
+ * block.clearAllOverrides(); // Remove all overrides
196
+ * // or
197
+ * block.removeOveride("color"); // Remove specific override
198
+ * // or
199
+ * block.dispose(); // Remove the entire property block
200
+ * ```
201
+ *
173
202
  * @example Lightmap usage
174
203
  * ```typescript
175
204
  * const block = MaterialPropertyBlock.get(mesh);
@@ -317,8 +346,25 @@ export class MaterialPropertyBlock<T extends Material = Material> {
317
346
  }
318
347
 
319
348
  /**
320
- * Removes a specific property override
321
- * @param name The property name to clear
349
+ * Removes a specific property override.
350
+ * After removal, the material will use its original property value for this property.
351
+ *
352
+ * @param name The property name to remove the override for
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const block = MaterialPropertyBlock.get(mesh);
357
+ *
358
+ * // Set some overrides
359
+ * block.setOverride("color", new Color(1, 0, 0));
360
+ * block.setOverride("roughness", 0.5);
361
+ * block.setOverride("lightMap", lightmapTexture);
362
+ *
363
+ * // Remove a specific override - the material will now use its original color
364
+ * block.removeOveride("color");
365
+ *
366
+ * // Other overrides (roughness, lightMap) remain active
367
+ * ```
322
368
  */
323
369
  removeOveride<K extends NonFunctionPropertyNames<T>>(name: K | ({} & string)): void {
324
370
  const index = this._overrides.findIndex(o => o.name === name);
@@ -328,7 +374,47 @@ export class MaterialPropertyBlock<T extends Material = Material> {
328
374
  }
329
375
 
330
376
  /**
331
- * Removes all property overrides from this block
377
+ * Removes all property overrides from this block.
378
+ * After calling this, the material will use its original values for all properties.
379
+ *
380
+ * **Note**: This does NOT remove shader defines. Use {@link clearDefine} or {@link dispose} for that.
381
+ *
382
+ * @example Remove all overrides but keep the property block
383
+ * ```typescript
384
+ * const block = MaterialPropertyBlock.get(mesh);
385
+ *
386
+ * // Set multiple overrides
387
+ * block.setOverride("color", new Color(1, 0, 0));
388
+ * block.setOverride("roughness", 0.5);
389
+ * block.setOverride("lightMap", lightmapTexture);
390
+ *
391
+ * // Later, remove all overrides at once
392
+ * block.clearAllOverrides();
393
+ *
394
+ * // The material now uses its original values
395
+ * // The property block still exists and can be reused with new overrides
396
+ * ```
397
+ *
398
+ * @example Temporarily disable all overrides
399
+ * ```typescript
400
+ * const block = MaterialPropertyBlock.get(mesh);
401
+ *
402
+ * // Save current overrides if you want to restore them later
403
+ * const savedOverrides = [...block.overrides];
404
+ *
405
+ * // Clear all overrides temporarily
406
+ * block.clearAllOverrides();
407
+ *
408
+ * // Do some rendering without overrides...
409
+ *
410
+ * // Restore overrides
411
+ * savedOverrides.forEach(override => {
412
+ * block.setOverride(override.name, override.value, override.textureTransform);
413
+ * });
414
+ * ```
415
+ *
416
+ * @see {@link removeOveride} - To remove a single override
417
+ * @see {@link dispose} - To completely remove the property block and clean up resources
332
418
  */
333
419
  clearAllOverrides(): void {
334
420
  this._overrides = [];
@@ -797,11 +883,11 @@ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialP
797
883
  if (object.type === "Group") {
798
884
  object.children.forEach(child => {
799
885
  if (child.type === "Mesh" || child.type === "SkinnedMesh") {
800
- attachCallbacksToMesh(child, object);
886
+ attachCallbacksToMesh(child, object, _propertyBlock);
801
887
  }
802
888
  });
803
889
  } else if (object.type === "Mesh" || object.type === "SkinnedMesh") {
804
- attachCallbacksToMesh(object, object);
890
+ attachCallbacksToMesh(object, object, _propertyBlock);
805
891
  }
806
892
  }
807
893
  /**
@@ -811,7 +897,7 @@ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialP
811
897
  * @param propertyBlockOwner The object that owns the property block (may be the mesh itself or its parent Group)
812
898
  * @internal
813
899
  */
814
- function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): void {
900
+ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D, _propertyBlock: MaterialPropertyBlock): void {
815
901
  // Check if this specific mesh already has our callbacks attached for this property block owner
816
902
  if (registry.isHooked(mesh, propertyBlockOwner)) {
817
903
  // Already hooked for this property block owner
@@ -820,6 +906,11 @@ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): vo
820
906
 
821
907
  registry.addHook(mesh, propertyBlockOwner);
822
908
 
909
+ /**
910
+ * Expose the property block for e.g. Needle Inspector
911
+ */
912
+ mesh["needle:materialPropertyBlock"] = _propertyBlock;
913
+
823
914
  if (!mesh.onBeforeRender) {
824
915
  mesh.onBeforeRender = onBeforeRender_MaterialBlock;
825
916
  } else {
@@ -254,7 +254,40 @@ class LowPassFilter {
254
254
  }
255
255
 
256
256
  /**
257
- * OneEuroFilter is a simple low-pass filter for noisy signals. It uses a one-euro filter to smooth the signal.
257
+ * [OneEuroFilter](https://engine.needle.tools/docs/api/OneEuroFilter) is a low-pass filter designed to reduce jitter in noisy signals while maintaining low latency.
258
+ * It's particularly useful for smoothing tracking data from XR controllers, hand tracking, or other input devices where the signal contains noise but responsiveness is important.
259
+ *
260
+ * The filter automatically adapts its smoothing strength based on the signal's velocity:
261
+ * - When the signal moves slowly, it applies strong smoothing to reduce jitter
262
+ * - When the signal moves quickly, it reduces smoothing to maintain responsiveness
263
+ *
264
+ * Based on the research paper: [1€ Filter: A Simple Speed-based Low-pass Filter for Noisy Input](http://cristal.univ-lille.fr/~casiez/1euro/)
265
+ *
266
+ * @example Basic usage with timestamp
267
+ * ```ts
268
+ * const filter = new OneEuroFilter(120, 1.0, 0.0);
269
+ *
270
+ * // In your update loop:
271
+ * const smoothedValue = filter.filter(noisyValue, this.context.time.time);
272
+ * ```
273
+ *
274
+ * @example Without timestamps (using frequency estimate)
275
+ * ```ts
276
+ * // Assuming 60 FPS update rate
277
+ * const filter = new OneEuroFilter(60, 1.0, 0.5);
278
+ *
279
+ * // Call without timestamp - uses the frequency estimate
280
+ * const smoothedValue = filter.filter(noisyValue);
281
+ * ```
282
+ *
283
+ * @example Smoothing 3D positions
284
+ * ```ts
285
+ * const posFilter = new OneEuroFilterXYZ(90, 0.5, 0.0);
286
+ *
287
+ * posFilter.filter(trackedPosition, smoothedPosition, this.context.time.time);
288
+ * ```
289
+ *
290
+ * @see {@link OneEuroFilterXYZ} for filtering 3D vectors
258
291
  */
259
292
  export class OneEuroFilter {
260
293
  /**
@@ -1,5 +1,5 @@
1
1
  const defaultNetworkingBackendUrlProvider = "https://urls.needle.tools/default-networking-backend/index";
2
- let networkingServerUrl: string | undefined = "wss://networking.needle.tools/socket";
2
+ let networkingServerUrl: string | undefined = "wss://networking-2.needle.tools/socket";
3
3
 
4
4
  import * as flatbuffers from 'flatbuffers';
5
5
  import { type Websocket } from 'websocket-ts';
@@ -164,10 +164,15 @@ export interface IHasGuid {
164
164
  guid: string;
165
165
  }
166
166
 
167
+ // DO NOT CHANGE THE SYMBOL NAME
168
+ export const $componentName = Symbol("component-name");
169
+
167
170
  // TODO: we might want to separate the IComponent and IBehaviour where the Behaviour is the one with custom methods and the component only has e.g. the gameobject reference
168
171
  export interface IComponent extends IHasGuid {
169
172
  get isComponent(): boolean;
170
173
 
174
+ get [$componentName](): string | undefined;
175
+
171
176
  /** the object this component is attached to */
172
177
  gameObject: IGameObject;
173
178
  // guid: string;
@@ -17,6 +17,9 @@ export function getIconElement(str: string): HTMLElement {
17
17
  span.innerText = str;
18
18
  span.style.visibility = "hidden";
19
19
  span.style.userSelect = "none";
20
+ span.setAttribute("role", "img");
21
+ span.setAttribute("aria-label", str + " icon");
22
+ span.setAttribute("aria-hidden", "true");
20
23
  fontReady(fontname).then(res => {
21
24
  if (res) span.style.visibility = "";
22
25
  else {
@@ -84,8 +84,11 @@ export class NeedleLogoElement extends HTMLElement {
84
84
  globalThis.open("https://needle.tools", "_blank");
85
85
  });
86
86
 
87
- // set title
87
+ }
88
+
89
+ connectedCallback() {
88
90
  this.wrapper.setAttribute("title", "Made with Needle Engine");
91
+ this.setAttribute("aria-label", "Needle Engine logo. Click to open the Needle Engine website.");
89
92
  }
90
93
 
91
94
  private readonly _root: ShadowRoot;
@@ -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
 
@@ -5,9 +5,9 @@ import { Gizmos } from "../engine/engine_gizmos.js";
5
5
  import { serializable } from "../engine/engine_serialization_decorator.js";
6
6
  import { getBoundingBox, getTempVector, getWorldScale, Graphics, setVisibleInCustomShadowRendering, setWorldPosition } from "../engine/engine_three_utils.js";
7
7
  import { delayForFrames, getParam, Watch as Watch } from "../engine/engine_utils.js";
8
- import { Behaviour } from "./Component.js";
9
8
  // Type-only imports for TSDoc @see links
10
9
  import type { Camera } from "./Camera.js";
10
+ import { Behaviour } from "./Component.js";
11
11
  import type { ContactShadows } from "./ContactShadows.js";
12
12
 
13
13
  const debug = getParam("debuggroundprojection");
@@ -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;