@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,14 +1,15 @@
1
1
  import { Color, CubeReflectionMapping, CubeTexture, EquirectangularReflectionMapping, LinearSRGBColorSpace, Material, MeshBasicMaterial, MeshStandardMaterial, Object3D, SRGBColorSpace, Texture, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
4
+ import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
4
5
  import { serializable } from "../engine/engine_serialization.js";
5
6
  import { Context } from "../engine/engine_setup.js";
6
7
  import type { IRenderer } from "../engine/engine_types.js";
7
- import { getParam } from "../engine/engine_utils.js";
8
+ import { getParam, resolveUrl } from "../engine/engine_utils.js";
8
9
  import { BoxHelperComponent } from "./BoxHelperComponent.js";
9
10
  import { Behaviour } from "./Component.js";
10
- import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
11
11
  import { EventList } from "./EventList.js";
12
+ import { loadPMREM } from "../engine/engine_pmrem.js";
12
13
 
13
14
  export const debug = getParam("debugreflectionprobe");
14
15
  const disable = getParam("noreflectionprobe");
@@ -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)"));
@@ -1,9 +1,9 @@
1
1
  import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
2
2
  import { Group, Mesh, Object3D, ShaderMaterial, Texture, Vector2, Vector4 } from "three";
3
3
 
4
+ import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
4
5
  import type { Context } from "../engine/engine_setup.js";
5
6
  import { getParam } from "../engine/engine_utils.js";
6
- import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
7
7
  import { type Renderer } from "./Renderer.js";
8
8
 
9
9
  const debug = getParam("debuglightmaps");
@@ -1,6 +1,7 @@
1
1
  import { Material, Object3D, Object3DEventMap, Vector3 } from "three";
2
2
 
3
3
  import { Gizmos } from "../engine/engine_gizmos.js";
4
+ import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
4
5
  import { Mathf } from "../engine/engine_math.js";
5
6
  import { serializable } from "../engine/engine_serialization_decorator.js";
6
7
  import { getTempVector } from "../engine/engine_three_utils.js";
@@ -11,7 +12,6 @@ import { IUSDExporterExtension } from "./export/usdz/Extension.js";
11
12
  import { USDZExporter } from "./export/usdz/USDZExporter.js";
12
13
  import type { OrbitControls } from "./OrbitControls.js";
13
14
  import { Renderer } from "./Renderer.js";
14
- import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
15
15
 
16
16
  const debugSeeThrough = getParam("debugseethrough");
17
17
 
@@ -13,41 +13,40 @@ import { AudioSource } from "../../../../AudioSource.js";
13
13
  import { Behaviour, GameObject } from "../../../../Component.js";
14
14
  import { Rigidbody } from "../../../../RigidBody.js";
15
15
  import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
16
- import { ObjectRaycaster,Raycaster } from "../../../../ui/Raycaster.js";
17
- import { makeNameSafeForUSD,USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
16
+ import { makeNameSafeForUSD, USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
18
17
  import { AnimationExtension, RegisteredAnimationInfo, type UsdzAnimation } from "../Animation.js";
19
18
  import { AudioExtension } from "./AudioExtension.js";
20
19
  import type { BehaviorExtension, UsdzBehaviour } from "./Behaviour.js";
21
- import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType,GroupActionModel,type IBehaviorElement, Target, TriggerBuilder } from "./BehavioursBuilder.js";
20
+ import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType, GroupActionModel, type IBehaviorElement, Target, TriggerBuilder } from "./BehavioursBuilder.js";
22
21
 
23
22
  const debug = getParam("debugusdzbehaviours");
24
23
 
25
- function ensureRaycaster(obj: GameObject) {
26
- if (!obj) return;
27
- if (!obj.getComponentInParent(Raycaster)) {
28
- if (isDevEnvironment())
29
- console.debug("Raycaster on \"" + obj.name + "\" was automatically added, because no raycaster was found in the parent hierarchy.")
30
- obj.addComponent(ObjectRaycaster);
31
- }
32
- }
33
-
34
24
  /**
35
- * Make the object move to the target object's transform when clicked.
25
+ * Moves an object to the target object's transform when clicked.
26
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
27
+ *
28
+ * @see {@link SetActiveOnClick}to toggle visibility of objects when clicked
29
+ * @see {@link PlayAnimationOnClick} to play animations when clicked
30
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
36
31
  * @summary Moves an object to a target transform upon click
37
32
  * @category Everywhere Actions
38
33
  * @group Components
39
34
  */
40
35
  export class ChangeTransformOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
41
36
 
37
+ /** The object to move. */
42
38
  @serializable(Object3D)
43
39
  object?: Object3D;
44
40
 
41
+ /** The target object whose transform to move to. */
45
42
  @serializable(Object3D)
46
43
  target?: Object3D;
47
44
 
45
+ /** The duration of the movement animation in seconds. */
48
46
  @serializable()
49
47
  duration: number = 1;
50
48
 
49
+ /** If true, the motion is relative to the object's current transform instead of moving to the target's absolute position. */
51
50
  @serializable()
52
51
  relativeMotion: boolean = false;
53
52
 
@@ -57,8 +56,20 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
57
56
  private targetRot = new Quaternion();
58
57
  private targetScale = new Vector3();
59
58
 
60
- start(): void {
61
- ensureRaycaster(this.gameObject);
59
+ onEnable(): void {
60
+ this.context.accessibility.updateElement(this, {
61
+ role: "button",
62
+ label: "Move " + (this.object?.name || "object") + " to " + (this.target?.name || "target") + " on click",
63
+ hidden: false,
64
+ })
65
+ }
66
+ onDisable(): void {
67
+ this.context.accessibility.updateElement(this, {
68
+ hidden: true,
69
+ });
70
+ }
71
+ onDestroy(): void {
72
+ this.context.accessibility.removeElement(this);
62
73
  }
63
74
 
64
75
  onPointerEnter() {
@@ -72,8 +83,8 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
72
83
 
73
84
  const rbs = this.object?.getComponentsInChildren(Rigidbody);
74
85
 
75
- if (rbs){
76
- for (const rb of rbs) {
86
+ if (rbs) {
87
+ for (const rb of rbs) {
77
88
  rb.resetVelocities();
78
89
  rb.resetForcesAndTorques();
79
90
  }
@@ -189,7 +200,15 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
189
200
  }
190
201
 
191
202
  /**
192
- * Change the material of objects when clicked.
203
+ * Switches the material of objects in the scene when clicked.
204
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
205
+ *
206
+ * Finds all objects in the scene that use `materialToSwitch` and replaces it with `variantMaterial`.
207
+ * Multiple `ChangeMaterialOnClick` components using the same `materialToSwitch` can be combined to create a material selection UI.
208
+ *
209
+ * @see {@link SetActiveOnClick} to toggle visibility of objects when clicked
210
+ * @see {@link PlayAnimationOnClick} to play animations when clicked
211
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
193
212
  * @summary Changes the material of objects when clicked
194
213
  * @category Everywhere Actions
195
214
  * @group Components
@@ -218,12 +237,27 @@ export class ChangeMaterialOnClick extends Behaviour implements IPointerClickHan
218
237
  start(): void {
219
238
  // initialize the object list
220
239
  this._objectsWithThisMaterial = this.objectsWithThisMaterial;
221
- ensureRaycaster(this.gameObject);
222
240
  if (isDevEnvironment() && this._objectsWithThisMaterial.length <= 0) {
223
241
  console.warn("ChangeMaterialOnClick: No objects found with material \"" + this.materialToSwitch?.name + "\"");
224
242
  }
225
243
  }
226
244
 
245
+ onEnable(): void {
246
+ this.context.accessibility.updateElement(this, {
247
+ role: "button",
248
+ label: "Change material to " + (this.variantMaterial?.name || "unknown material"),
249
+ hidden: false,
250
+ })
251
+ }
252
+ onDisable(): void {
253
+ this.context.accessibility.updateElement(this, {
254
+ hidden: true,
255
+ });
256
+ }
257
+ onDestroy(): void {
258
+ this.context.accessibility.removeElement(this);
259
+ }
260
+
227
261
  onPointerEnter(_args: PointerEventData) {
228
262
  this.context.input.setCursor("pointer");
229
263
  }
@@ -345,7 +379,7 @@ export class ChangeMaterialOnClick extends Behaviour implements IPointerClickHan
345
379
  );
346
380
  ChangeMaterialOnClick._parallelStartHiddenActions.push(...myVariants);
347
381
  if (!ChangeMaterialOnClick._startHiddenBehaviour) {
348
- ChangeMaterialOnClick._startHiddenBehaviour =
382
+ ChangeMaterialOnClick._startHiddenBehaviour =
349
383
  new BehaviorModel("StartHidden_" + this.selfModel.name,
350
384
  TriggerBuilder.sceneStartTrigger(),
351
385
  ActionBuilder.fadeAction(ChangeMaterialOnClick._parallelStartHiddenActions, fadeDuration, false));
@@ -381,29 +415,37 @@ export class ChangeMaterialOnClick extends Behaviour implements IPointerClickHan
381
415
  }
382
416
 
383
417
  /**
384
- * Set the active state of an object when clicked.
418
+ * Shows or hides a target object when this object is clicked.
419
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
420
+ *
421
+ * Optionally hides itself after being clicked (`hideSelf`), or toggles the target's visibility on each click (`toggleOnClick`).
422
+ *
423
+ * @see {@link HideOnStart}to hide an object when the scene starts
424
+ * @see {@link PlayAnimationOnClick} to play animations when clicked
425
+ * @see {@link ChangeMaterialOnClick} to change material when clicked
426
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
385
427
  * @summary Sets the active state of an object when clicked
386
428
  * @category Everywhere Actions
387
429
  * @group Components
388
430
  */
389
431
  export class SetActiveOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
390
432
 
433
+ /** The target object to show or hide. */
391
434
  @serializable(Object3D)
392
435
  target?: Object3D;
393
436
 
437
+ /** If true, the target's visibility will be toggled on each click. When enabled, `hideSelf` and `targetState` are ignored. */
394
438
  @serializable()
395
439
  toggleOnClick: boolean = false;
396
440
 
441
+ /** The visibility state to apply to the target when clicked. Only used when `toggleOnClick` is false. */
397
442
  @serializable()
398
443
  targetState: boolean = true;
399
444
 
445
+ /** If true, this object will hide itself after being clicked. Only used when `toggleOnClick` is false. */
400
446
  @serializable()
401
447
  hideSelf: boolean = true;
402
448
 
403
- start(): void {
404
- ensureRaycaster(this.gameObject);
405
- }
406
-
407
449
  onPointerEnter() {
408
450
  this.context.input.setCursor("pointer");
409
451
  }
@@ -416,7 +458,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
416
458
 
417
459
  if (!this.toggleOnClick && this.hideSelf)
418
460
  this.gameObject.visible = false;
419
-
461
+
420
462
  if (this.target)
421
463
  this.target.visible = this.toggleOnClick ? !this.target.visible : this.targetState;
422
464
  }
@@ -442,7 +484,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
442
484
 
443
485
  beforeCreateDocument() {
444
486
  if (!this.target) return;
445
-
487
+
446
488
  // need to cache on the object itself, because otherwise different actions would override each other's visibility state
447
489
  // TODO would probably be better to have this somewhere on the exporter, not on this component
448
490
  if (this.gameObject[SetActiveOnClick.wasVisible] === undefined)
@@ -503,7 +545,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
503
545
  // It's much easier to reason about nested actions when we're not duplicating tons of hierarchy...
504
546
  // We can probably only do a shallow clone when the tapped object has geometry of its own, otherwise
505
547
  // we end up with nothing to tap on.
506
-
548
+
507
549
  // Option A: we deep clone ourselves. This makes hierarchical cases and nested behaviours really complex.
508
550
  // We do this currently when the object doesn't have any geometry.
509
551
  if (!this.selfModelClone.geometry) {
@@ -522,11 +564,11 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
522
564
  clone.name += "_toggle" + (SetActiveOnClick.clonedToggleIndex++);
523
565
  originalModel.add(clone);
524
566
  this.gameObject[SetActiveOnClick.toggleClone] = clone;
525
-
567
+
526
568
  console.warn("USDZExport: Toggle " + this.gameObject.name + " doesn't have geometry. It will be deep cloned and nested behaviours will likely not work.");
527
569
  }
528
570
  const clonedSelfModel = this.gameObject[SetActiveOnClick.toggleClone];
529
-
571
+
530
572
  if (!this.gameObject[SetActiveOnClick.reverseToggleClone]) {
531
573
  const clone = this.selfModelClone.clone();
532
574
  clone.setMatrix(new Matrix4());
@@ -578,7 +620,7 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
578
620
  TriggerBuilder.tapTrigger(selfModel),
579
621
  ActionBuilder.parallel(...toggleSequence)
580
622
  ));
581
-
623
+
582
624
  const reverseSequence: ActionModel[] = [];
583
625
  reverseSequence.push(ActionBuilder.fadeAction(this.toggleModel, 0, false));
584
626
  reverseSequence.push(ActionBuilder.fadeAction(selfModel, 0, true));
@@ -621,7 +663,13 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
621
663
  }
622
664
 
623
665
  /**
624
- * Hides the object on scene start.
666
+ * Hides the object when the scene starts.
667
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
668
+ *
669
+ * Useful for setting up objects that should initially be hidden and shown later via a {@link SetActiveOnClick} component.
670
+ *
671
+ * @see {@link SetActiveOnClick} to show or hide objects on click
672
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
625
673
  * @summary Hides the object on scene start
626
674
  * @category Everywhere Actions
627
675
  * @group Components
@@ -663,29 +711,55 @@ export class HideOnStart extends Behaviour implements UsdzBehaviour {
663
711
  }
664
712
 
665
713
  private wasVisible: boolean = false;
666
-
714
+
667
715
  beforeCreateDocument() {
668
716
  this.wasVisible = GameObject.isActiveSelf(this.gameObject);
669
717
  }
670
718
  }
671
719
 
672
720
  /**
673
- * Emphasize the target object when clicked.
721
+ * Applies an emphasis animation to a target object when this object is clicked.
722
+ * Works in USDZ/QuickLook (Everywhere Actions).
723
+ *
724
+ * The emphasis effect can be a bounce, jiggle, or other motion type defined by `motionType`.
725
+ *
726
+ * @see {@link PlayAnimationOnClick} to play animations when clicked
727
+ * @see {@link SetActiveOnClick} to toggle visibility when clicked
728
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
674
729
  * @summary Emphasizes the target object when clicked
675
730
  * @category Everywhere Actions
676
731
  * @group Components
677
732
  */
678
733
  export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
679
734
 
735
+ /** The target object to emphasize. */
680
736
  @serializable()
681
737
  target?: Object3D;
682
738
 
739
+ /** The duration of the emphasis animation in seconds. */
683
740
  @serializable()
684
741
  duration: number = 0.5;
685
742
 
743
+ /** The type of motion to use for the emphasis effect (e.g. `"bounce"`, `"jiggle"`). */
686
744
  @serializable()
687
745
  motionType: EmphasizeActionMotionType = "bounce";
688
746
 
747
+ onEnable(): void {
748
+ this.context.accessibility.updateElement(this, {
749
+ role: "button",
750
+ label: "Emphasize " + this.target?.name + " on click",
751
+ hidden: false,
752
+ })
753
+ }
754
+ onDisable(): void {
755
+ this.context.accessibility.updateElement(this, {
756
+ hidden: true,
757
+ });
758
+ }
759
+ onDestroy(): void {
760
+ this.context.accessibility.removeElement(this);
761
+ }
762
+
689
763
  beforeCreateDocument() { }
690
764
 
691
765
  createBehaviours(ext, model, _context) {
@@ -704,29 +778,37 @@ export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
704
778
  }
705
779
 
706
780
  /**
707
- * Plays an audio clip when clicked.
781
+ * Plays an audio clip when this object is clicked.
782
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
783
+ *
784
+ * Assign a `target` {@link AudioSource} to use its spatial audio settings, or assign a `clip` URL directly.
785
+ * If no `target` is assigned, an {@link AudioSource} will be created automatically on this object.
786
+ *
787
+ * @see {@link AudioSource}for spatial audio settings
788
+ * @see {@link PlayAnimationOnClick} to play animations when clicked
789
+ * @see {@link SetActiveOnClick} to toggle visibility when clicked
790
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
708
791
  * @summary Plays an audio clip when clicked
709
792
  * @category Everywhere Actions
710
793
  * @group Components
711
794
  */
712
795
  export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
713
796
 
797
+ /** The {@link AudioSource} to use for playback. If not set, one will be created automatically on this object. */
714
798
  @serializable(AudioSource)
715
799
  target?: AudioSource;
716
800
 
801
+ /** URL of the audio clip to play. If not set, the clip assigned to `target` is used. */
717
802
  @serializable(URL)
718
803
  clip: string = "";
719
804
 
805
+ /** If true, clicking again while the audio is playing will stop it. */
720
806
  @serializable()
721
807
  toggleOnClick: boolean = false;
722
808
 
723
809
  // Not exposed, but used for implicit playback of PlayOnAwake audio sources
724
810
  trigger: "tap" | "start" = "tap";
725
811
 
726
- start(): void {
727
- ensureRaycaster(this.gameObject);
728
- }
729
-
730
812
  ensureAudioSource() {
731
813
  if (!this.target) {
732
814
  const newAudioSource = this.gameObject.addComponent(AudioSource);
@@ -740,6 +822,21 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
740
822
  }
741
823
  }
742
824
 
825
+ onEnable(): void {
826
+ this.context.accessibility.updateElement(this, {
827
+ role: "button",
828
+ label: "Play audio: " + (this.clip || this.target?.clip || "unknown clip"),
829
+ hidden: false,
830
+ })
831
+ }
832
+ onDisable(): void {
833
+ this.context.accessibility.updateElement(this, {
834
+ hidden: true,
835
+ });
836
+ }
837
+ onDestroy(): void {
838
+ this.context.accessibility.removeElement(this);
839
+ }
743
840
 
744
841
  onPointerEnter() {
745
842
  this.context.input.setCursor("pointer");
@@ -781,7 +878,7 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
781
878
  const clipName = AudioExtension.getName(clipUrl);
782
879
  const volume = this.target ? this.target.volume : 1;
783
880
  const auralMode = this.target && this.target.spatialBlend == 0 ? "nonSpatial" : "spatial";
784
-
881
+
785
882
  // This checks if any child is clickable – if yes, the tap trigger is added; if not, we omit it.
786
883
  let anyChildHasGeometry = false;
787
884
  this.gameObject.traverse(c => {
@@ -790,7 +887,7 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
790
887
  // Workaround: seems iOS often simply doesn't play audio on scene start when this is NOT present.
791
888
  // unclear why, but having a useless tap action (nothing to tap on) "fixes" it.
792
889
  anyChildHasGeometry = true;
793
-
890
+
794
891
  const audioClip = ext.addAudioClip(clipUrl);
795
892
  // const stopAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, audioClip, "stop", volume, auralMode);
796
893
  let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, audioClip, "play", volume, auralMode);
@@ -799,8 +896,7 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
799
896
 
800
897
  const behaviorName = (this.name ? "_" + this.name : "");
801
898
 
802
- if (anyChildHasGeometry && this.trigger === "tap")
803
- {
899
+ if (anyChildHasGeometry && this.trigger === "tap") {
804
900
  // does not seem to work in iOS / QuickLook...
805
901
  // TODO use play "type" which can be start/stop/pause
806
902
  if (this.toggleOnClick) (playAction as ActionModel).multiplePerformOperation = "stop";
@@ -831,16 +927,30 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
831
927
  }
832
928
 
833
929
  /**
834
- * Plays an animation when clicked.
930
+ * Plays an animation state when this object is clicked.
931
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
932
+ *
933
+ * Assign an {@link Animator} and a `stateName` to play a specific animation state,
934
+ * or assign an {@link Animation} component to play a legacy animation clip.
935
+ *
936
+ * For USDZ export, the component follows animator state transitions automatically, including looping states.
937
+ *
938
+ * @see {@link Animator}for playing animator state machine animations
939
+ * @see {@link Animation} for playing legacy animation clips
940
+ * @see {@link PlayAudioOnClick} to play audio when clicked
941
+ * @see {@link SetActiveOnClick} to toggle visibility when clicked
942
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
835
943
  * @summary Plays an animation when clicked
836
944
  * @category Everywhere Actions
837
945
  * @group Components
838
946
  */
839
947
  export class PlayAnimationOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour, UsdzAnimation {
840
948
 
949
+ /** The {@link Animator} component whose state to play when clicked. */
841
950
  @serializable(Animator)
842
951
  animator?: Animator;
843
952
 
953
+ /** The name of the animation state to play. Required when using an {@link Animator}. */
844
954
  @serializable()
845
955
  stateName?: string;
846
956
 
@@ -852,13 +962,25 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
852
962
 
853
963
  private get target() { return this.animator?.gameObject || this.animation?.gameObject }
854
964
 
855
- start(): void {
856
- ensureRaycaster(this.gameObject);
965
+ onEnable(): void {
966
+ this.context.accessibility.updateElement(this, {
967
+ role: "button",
968
+ label: "Plays animation " + (this.stateName || "") + " on " + (this.target ? this.target.name : ""),
969
+ hidden: false
970
+ });
971
+ }
972
+ onDisable(): void {
973
+ this.context.accessibility.updateElement(this, {
974
+ hidden: true,
975
+ });
976
+ }
977
+ onDestroy(): void {
978
+ this.context.accessibility.removeElement(this);
857
979
  }
858
-
859
980
 
860
981
  onPointerEnter() {
861
982
  this.context.input.setCursor("pointer");
983
+ this.context.accessibility.hover(this, "Click to play animation " + (this.stateName || "") + " on " + (this.target ? this.target.name : ""));
862
984
  }
863
985
  onPointerExit() {
864
986
  this.context.input.unsetCursor("pointer");
@@ -867,6 +989,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
867
989
  args.use();
868
990
  if (!this.target) return;
869
991
  if (this.stateName) {
992
+ this.context.accessibility.focus(this);
870
993
  // TODO this is currently quite annoying to use,
871
994
  // as for the web we use the Animator component and its states directly,
872
995
  // while in QuickLook we use explicit animations / states.
@@ -878,8 +1001,8 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
878
1001
 
879
1002
  private stateAnimationModel: any;
880
1003
 
881
- private animationSequence? = new Array<RegisteredAnimationInfo>();
882
- private animationLoopAfterSequence? = new Array<RegisteredAnimationInfo>();
1004
+ private animationSequence?= new Array<RegisteredAnimationInfo>();
1005
+ private animationLoopAfterSequence?= new Array<RegisteredAnimationInfo>();
883
1006
  private randomOffsetNormalized: number = 0;
884
1007
 
885
1008
  createBehaviours(_ext: BehaviorExtension, model: USDObject, _context: USDZExporterContext) {
@@ -905,9 +1028,9 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
905
1028
  afterCreateDocument(ext: BehaviorExtension, context: USDZExporterContext) {
906
1029
  if ((this.animationSequence === undefined && this.animationLoopAfterSequence === undefined) || !this.stateAnimationModel) return;
907
1030
  if (!this.target) return;
908
-
1031
+
909
1032
  const document = context.document;
910
-
1033
+
911
1034
  // check if the AnimationExtension has been attached and what data it has for the current object
912
1035
  const animationExt = context.extensions.find(ext => ext instanceof AnimationExtension) as AnimationExtension;
913
1036
  if (!animationExt) return;
@@ -921,7 +1044,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
921
1044
  if (requiresExclusivePlayback) {
922
1045
  if (isDevEnvironment())
923
1046
  console.warn("Setting exclusive playback for " + this.target.name + "@" + this.stateName + " because it has " + animationExt.getClipCount(this.target) + " animations. This works around QuickLook bug FB13410767.");
924
-
1047
+
925
1048
  PlayAnimationOnClick.rootsWithExclusivePlayback.add(this.target);
926
1049
  }
927
1050
 
@@ -941,7 +1064,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
941
1064
  this.trigger == "tap" ? TriggerBuilder.tapTrigger(this.selfModel) : TriggerBuilder.sceneStartTrigger(),
942
1065
  sequence
943
1066
  );
944
-
1067
+
945
1068
  // See comment above for why exclusive playback is currently required when playing multiple animations on the same root.
946
1069
  if (requiresExclusivePlayback)
947
1070
  playAnimationOnTap.makeExclusive(true);
@@ -963,8 +1086,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
963
1086
 
964
1087
  const sequence = ActionBuilder.sequence();
965
1088
 
966
- if (animationSequence && animationSequence.length > 0)
967
- {
1089
+ if (animationSequence && animationSequence.length > 0) {
968
1090
  for (const anim of animationSequence) {
969
1091
  sequence.addAction(getOrCacheAction(model, anim));
970
1092
  }
@@ -988,11 +1110,10 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
988
1110
  return sequence;
989
1111
  }
990
1112
 
991
- static getAndRegisterAnimationSequences(ext: AnimationExtension, target: GameObject, stateName?: string):
992
- {
993
- animationSequence: Array<RegisteredAnimationInfo>,
1113
+ static getAndRegisterAnimationSequences(ext: AnimationExtension, target: GameObject, stateName?: string): {
1114
+ animationSequence: Array<RegisteredAnimationInfo>,
994
1115
  animationLoopAfterSequence: Array<RegisteredAnimationInfo>,
995
- randomTimeOffset: number,
1116
+ randomTimeOffset: number,
996
1117
  } | undefined {
997
1118
 
998
1119
  if (!target) return undefined;
@@ -1010,14 +1131,14 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
1010
1131
  let animationLoopAfterSequence: Array<RegisteredAnimationInfo> = [];
1011
1132
 
1012
1133
  if (animation) {
1013
- const anim = ext.registerAnimation(target, animation.clip);
1014
- if (anim) {
1134
+ const anim = ext.registerAnimation(target, animation.clip);
1135
+ if (anim) {
1015
1136
  if (animation.loop)
1016
1137
  animationLoopAfterSequence.push(anim);
1017
1138
  else
1018
1139
  animationSequence.push(anim);
1019
1140
  }
1020
-
1141
+
1021
1142
  let randomTimeOffset = 0;
1022
1143
  if (animation.minMaxOffsetNormalized) {
1023
1144
  const from = animation.minMaxOffsetNormalized.x;
@@ -1111,7 +1232,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
1111
1232
  if (lastClip) {
1112
1233
  let clipCopy: AnimationClip | undefined;
1113
1234
  if (ext.holdClipMap.has(lastClip)) {
1114
- clipCopy = ext.holdClipMap.get(lastClip);
1235
+ clipCopy = ext.holdClipMap.get(lastClip);
1115
1236
  }
1116
1237
  else {
1117
1238
  // We're creating a "hold" clip here; exactly 1 second long, and inteprolates exactly on the duration of the clip
@@ -1241,17 +1362,24 @@ export class PreliminaryTrigger extends Behaviour {
1241
1362
  }
1242
1363
 
1243
1364
  /**
1244
- * Hides or shows the object when clicked.
1365
+ * Action to show or hide an object.
1366
+ * Use together with a {@link TapGestureTrigger} to show or hide objects when tapped or clicked.
1367
+ *
1368
+ * @see {@link TapGestureTrigger} to trigger actions on tap/click
1369
+ * @see {@link SetActiveOnClick} for a combined trigger and action component
1370
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
1245
1371
  * @summary Hides or shows the object when clicked
1246
1372
  * @category Everywhere Actions
1247
1373
  * @group Components
1248
1374
  */
1249
1375
  export class VisibilityAction extends PreliminaryAction {
1250
1376
 
1377
+ /** The type of visibility action to apply. */
1251
1378
  //@type int
1252
1379
  @serializable()
1253
1380
  type: VisibilityActionType = VisibilityActionType.Hide;
1254
1381
 
1382
+ /** The duration of the fade animation in seconds. */
1255
1383
  @serializable()
1256
1384
  duration: number = 1;
1257
1385
 
@@ -1268,7 +1396,12 @@ export class VisibilityAction extends PreliminaryAction {
1268
1396
  }
1269
1397
 
1270
1398
  /**
1271
- * Triggers an action when the object is tapped/clicked.
1399
+ * Triggers a {@link PreliminaryAction} (such as {@link VisibilityAction}) when the object is tapped or clicked.
1400
+ * Works in the browser and in USDZ/QuickLook (Everywhere Actions).
1401
+ *
1402
+ * @see {@link VisibilityAction} for controlling object visibility on tap
1403
+ * @see {@link SetActiveOnClick} for a combined trigger and action component
1404
+ * @see [Everywhere Actions](https://engine.needle.tools/docs/everywhere-actions)
1272
1405
  * @summary Triggers an action when the object is tapped/clicked
1273
1406
  * @category Everywhere Actions
1274
1407
  * @group Components