@playcanvas/web-components 0.17.0 → 0.19.0

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 (117) hide show
  1. package/README.md +5 -3
  2. package/dist/app.d.cts +28 -3
  3. package/dist/app.d.ts +28 -3
  4. package/dist/asset.d.cts +13 -3
  5. package/dist/asset.d.ts +13 -3
  6. package/dist/components/anim-clip.d.cts +3 -1
  7. package/dist/components/anim-clip.d.ts +3 -1
  8. package/dist/components/anim-component.d.cts +5 -0
  9. package/dist/components/anim-component.d.ts +5 -0
  10. package/dist/components/audio-listener-component.d.cts +4 -0
  11. package/dist/components/audio-listener-component.d.ts +4 -0
  12. package/dist/components/button-component.d.cts +5 -0
  13. package/dist/components/button-component.d.ts +5 -0
  14. package/dist/components/camera-component.d.cts +35 -1
  15. package/dist/components/camera-component.d.ts +35 -1
  16. package/dist/components/collision-component.d.cts +4 -0
  17. package/dist/components/collision-component.d.ts +4 -0
  18. package/dist/components/element-component.d.cts +4 -0
  19. package/dist/components/element-component.d.ts +4 -0
  20. package/dist/components/gsplat-component.d.cts +4 -0
  21. package/dist/components/gsplat-component.d.ts +4 -0
  22. package/dist/components/joint-component.d.cts +12 -2
  23. package/dist/components/joint-component.d.ts +12 -2
  24. package/dist/components/layout-child-component.d.cts +4 -0
  25. package/dist/components/layout-child-component.d.ts +4 -0
  26. package/dist/components/layout-group-component.d.cts +4 -0
  27. package/dist/components/layout-group-component.d.ts +4 -0
  28. package/dist/components/light-component.d.cts +50 -1
  29. package/dist/components/light-component.d.ts +50 -1
  30. package/dist/components/particle-system-component.d.cts +4 -0
  31. package/dist/components/particle-system-component.d.ts +4 -0
  32. package/dist/components/render-component.d.cts +5 -0
  33. package/dist/components/render-component.d.ts +5 -0
  34. package/dist/components/rigid-body-component.d.cts +5 -0
  35. package/dist/components/rigid-body-component.d.ts +5 -0
  36. package/dist/components/screen-component.d.cts +4 -0
  37. package/dist/components/screen-component.d.ts +4 -0
  38. package/dist/components/script-component.d.cts +3 -0
  39. package/dist/components/script-component.d.ts +3 -0
  40. package/dist/components/script-instance.d.cts +4 -0
  41. package/dist/components/script-instance.d.ts +4 -0
  42. package/dist/components/scroll-view-component.d.cts +4 -0
  43. package/dist/components/scroll-view-component.d.ts +4 -0
  44. package/dist/components/scrollbar-component.d.cts +4 -0
  45. package/dist/components/scrollbar-component.d.ts +4 -0
  46. package/dist/components/sound-component.d.cts +4 -0
  47. package/dist/components/sound-component.d.ts +4 -0
  48. package/dist/components/sound-slot.d.cts +3 -0
  49. package/dist/components/sound-slot.d.ts +3 -0
  50. package/dist/custom-elements.json +338 -7
  51. package/dist/entity-base.d.cts +5 -4
  52. package/dist/entity-base.d.ts +5 -4
  53. package/dist/entity-owner.d.cts +2 -2
  54. package/dist/entity-owner.d.ts +2 -2
  55. package/dist/entity.d.cts +13 -2
  56. package/dist/entity.d.ts +13 -2
  57. package/dist/index.d.cts +4 -1
  58. package/dist/index.d.ts +4 -1
  59. package/dist/material.d.cts +18 -7
  60. package/dist/material.d.ts +18 -7
  61. package/dist/model.d.cts +11 -0
  62. package/dist/model.d.ts +11 -0
  63. package/dist/node.d.cts +12 -1
  64. package/dist/node.d.ts +12 -1
  65. package/dist/pwc.cjs +601 -206
  66. package/dist/pwc.cjs.map +1 -1
  67. package/dist/pwc.js +601 -206
  68. package/dist/pwc.js.map +1 -1
  69. package/dist/pwc.min.js +1 -1
  70. package/dist/pwc.min.js.map +1 -1
  71. package/dist/pwc.min.mjs +1 -1
  72. package/dist/pwc.min.mjs.map +1 -1
  73. package/dist/pwc.mjs +602 -207
  74. package/dist/pwc.mjs.map +1 -1
  75. package/dist/scene.d.cts +19 -0
  76. package/dist/scene.d.ts +19 -0
  77. package/dist/sky.d.cts +4 -0
  78. package/dist/sky.d.ts +4 -0
  79. package/dist/vscode.html-custom-data.json +257 -62
  80. package/dist/wasm.d.cts +4 -0
  81. package/dist/wasm.d.ts +4 -0
  82. package/dist/web-types.json +169 -69
  83. package/package.json +3 -3
  84. package/src/app.ts +150 -71
  85. package/src/asset.ts +13 -4
  86. package/src/components/anim-clip.ts +3 -1
  87. package/src/components/anim-component.ts +5 -0
  88. package/src/components/audio-listener-component.ts +4 -0
  89. package/src/components/button-component.ts +5 -0
  90. package/src/components/camera-component.ts +63 -6
  91. package/src/components/collision-component.ts +4 -0
  92. package/src/components/element-component.ts +4 -0
  93. package/src/components/gsplat-component.ts +4 -0
  94. package/src/components/joint-component.ts +12 -2
  95. package/src/components/layout-child-component.ts +4 -0
  96. package/src/components/layout-group-component.ts +4 -0
  97. package/src/components/light-component.ts +108 -17
  98. package/src/components/particle-system-component.ts +4 -0
  99. package/src/components/render-component.ts +5 -0
  100. package/src/components/rigid-body-component.ts +5 -0
  101. package/src/components/screen-component.ts +4 -0
  102. package/src/components/script-component.ts +3 -0
  103. package/src/components/script-instance.ts +4 -0
  104. package/src/components/scroll-view-component.ts +4 -0
  105. package/src/components/scrollbar-component.ts +4 -0
  106. package/src/components/sound-component.ts +4 -0
  107. package/src/components/sound-slot.ts +3 -0
  108. package/src/entity-base.ts +28 -16
  109. package/src/entity-owner.ts +2 -2
  110. package/src/entity.ts +16 -4
  111. package/src/index.ts +12 -1
  112. package/src/material.ts +19 -8
  113. package/src/model.ts +14 -2
  114. package/src/node.ts +15 -3
  115. package/src/scene.ts +35 -1
  116. package/src/sky.ts +4 -0
  117. package/src/wasm.ts +4 -0
package/dist/pwc.js CHANGED
@@ -125,6 +125,10 @@
125
125
  * A `<pc-wasm>` without a `name` warns and never becomes ready; a containing `<pc-app>` still
126
126
  * boots.
127
127
  *
128
+ * @elementSummary The `<pc-wasm>` element loads a WebAssembly module the engine needs before the
129
+ * application starts — `Ammo` for physics, `Basis` or `DracoDecoderModule` for compressed assets.
130
+ * Must be a direct child of `<pc-app>`.
131
+ *
128
132
  * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
129
133
  * `Ammo`.
130
134
  * @attribute {string} glue - The URL of the module's glue script.
@@ -177,6 +181,136 @@
177
181
  }
178
182
  customElements.define('pc-wasm', WasmElement);
179
183
 
184
+ /**
185
+ * The event types the containing `<pc-app>` synthesizes on entity-fronting elements via picking:
186
+ * the `pointer*` events, plus `click` — which concludes a primary-button press and release, and
187
+ * is delivered as a `PointerEvent` exactly as modern browsers deliver native clicks.
188
+ * @internal
189
+ */
190
+ const SYNTHESIZED_EVENTS = [
191
+ 'pointerenter',
192
+ 'pointerleave',
193
+ 'pointerdown',
194
+ 'pointerup',
195
+ 'pointermove',
196
+ 'click'
197
+ ];
198
+ const SYNTHESIZED_EVENT_SET = new Set(SYNTHESIZED_EVENTS);
199
+ /**
200
+ * The attribute names of the inline event handlers (`onpointerdown`, `onclick`, ...), shared by
201
+ * every element that fronts an engine entity. Spread into `observedAttributes` by subclasses.
202
+ * @internal
203
+ */
204
+ const EVENT_ATTRIBUTES = SYNTHESIZED_EVENTS.map((type) => `on${type}`);
205
+ /**
206
+ * The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
207
+ * `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
208
+ * instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
209
+ * with the owning application (which joins picked scene nodes back to elements by identity,
210
+ * never by name), and the pointer listener bookkeeping that lets the application lazily attach
211
+ * its canvas handlers.
212
+ */
213
+ class EntityBaseElement extends AsyncElement {
214
+ _entity = null;
215
+ /**
216
+ * The application element this entity is registered with, cached at registration time so the
217
+ * entity can be unregistered even once this element has left the DOM.
218
+ */
219
+ _appElement = null;
220
+ /**
221
+ * The event listeners registered on the element, by type.
222
+ */
223
+ _listeners = {};
224
+ /**
225
+ * The event types for which an inline handler attribute (`onpointerdown`, `onclick`, ...)
226
+ * is currently present.
227
+ */
228
+ _inlineHandlerTypes = new Set();
229
+ /**
230
+ * The PlayCanvas entity instance. `null` until the element is ready, and again once the
231
+ * entity is gone — await {@link whenReady} or the element's `ready()` promise before
232
+ * accessing it.
233
+ * @returns The entity instance, or `null`.
234
+ */
235
+ get entity() {
236
+ return this._entity;
237
+ }
238
+ /**
239
+ * Registers `entity` as this element's backing entity with the owning application, which
240
+ * joins engine nodes back to elements by identity (never by name).
241
+ *
242
+ * @param entity - The entity to register.
243
+ */
244
+ _registerEntity(entity) {
245
+ this._appElement = this.closestApp;
246
+ this._appElement?._registerEntityElement(entity, this);
247
+ }
248
+ /**
249
+ * Removes the registration for `entity`.
250
+ *
251
+ * @param entity - The entity to unregister.
252
+ */
253
+ _unregisterEntity(entity) {
254
+ this._appElement?._unregisterEntityElement(entity);
255
+ this._appElement = null;
256
+ }
257
+ /**
258
+ * Tracks whether an inline handler attribute is present. The browser itself compiles and
259
+ * runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
260
+ * the previous handler and removing it removes the handler, exactly like `onclick` on any
261
+ * HTML element. But because they bypass {@link EventTarget.addEventListener}, the connect/disconnect
262
+ * bookkeeping that lets the application lazily attach its canvas pointer handlers must be
263
+ * kept in sync here.
264
+ *
265
+ * @param name - The attribute name (e.g. 'onpointerdown').
266
+ * @param value - The attribute value, or `null` when the attribute has been removed.
267
+ */
268
+ _updateInlineHandler(name, value) {
269
+ const type = name.substring(2);
270
+ const had = this._inlineHandlerTypes.has(type);
271
+ const has = value !== null;
272
+ if (has && !had) {
273
+ this._inlineHandlerTypes.add(type);
274
+ this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
275
+ }
276
+ else if (!has && had) {
277
+ this._inlineHandlerTypes.delete(type);
278
+ this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
279
+ }
280
+ }
281
+ addEventListener(type, listener, options) {
282
+ if (!this._listeners[type]) {
283
+ this._listeners[type] = [];
284
+ }
285
+ this._listeners[type].push(listener);
286
+ super.addEventListener(type, listener, options);
287
+ if (SYNTHESIZED_EVENT_SET.has(type)) {
288
+ this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
289
+ }
290
+ }
291
+ removeEventListener(type, listener, options) {
292
+ if (this._listeners[type]) {
293
+ this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
294
+ }
295
+ super.removeEventListener(type, listener, options);
296
+ if (SYNTHESIZED_EVENT_SET.has(type)) {
297
+ this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
298
+ }
299
+ }
300
+ /**
301
+ * Whether the element has a listener for an event type, registered either with
302
+ * {@link EventTarget.addEventListener} or with the matching inline handler attribute. Read by the
303
+ * containing `<pc-app>` element to gate event synthesis.
304
+ *
305
+ * @param type - The event type.
306
+ * @returns Whether a listener is registered.
307
+ * @internal
308
+ */
309
+ _hasListeners(type) {
310
+ return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
311
+ }
312
+ }
313
+
180
314
  /** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
181
315
  const REMOVAL_DELAY_MS = 250;
182
316
  /**
@@ -730,14 +864,51 @@
730
864
  return element?.entity ?? null;
731
865
  };
732
866
 
733
- /** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
734
- const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
735
867
  /**
736
868
  * The event types whose listeners make an element a hover target. Hover resolution walks past
737
869
  * elements listening for none of them, so a silent element never swallows an ancestor's
738
870
  * enter/leave pair.
739
871
  */
740
872
  const hoverEventTypes = ['pointerenter', 'pointerleave', 'pointermove'];
873
+ /**
874
+ * The canvas listeners each synthesized event type is driven by. Enter and leave are derived
875
+ * from move picks. A click is concluded from the down/up pair, with pointercancel discarding a
876
+ * press the browser takes back (for example a touch that becomes a scroll).
877
+ */
878
+ const canvasEventsFor = {
879
+ pointermove: ['pointermove'],
880
+ pointerenter: ['pointermove'],
881
+ pointerleave: ['pointermove'],
882
+ pointerdown: ['pointerdown'],
883
+ pointerup: ['pointerup'],
884
+ click: ['pointerdown', 'pointerup', 'pointercancel']
885
+ };
886
+ /**
887
+ * How long after a click a further click on the same target still raises the click count that
888
+ * `detail` carries, approximating the platform's double-click time.
889
+ */
890
+ const CLICK_CHAIN_MS = 500;
891
+ /**
892
+ * Finds the nearest common inclusive ancestor of two picked nodes - the node a click belongs to
893
+ * when the press and the release picked different geometry, exactly as the DOM assigns a click
894
+ * whose down and up have different targets.
895
+ *
896
+ * @param a - The node the press picked, or `null`.
897
+ * @param b - The node the release picked, or `null`.
898
+ * @returns The nearest common inclusive ancestor, or `null` when there is none.
899
+ */
900
+ const commonAncestor = (a, b) => {
901
+ const ancestors = new Set();
902
+ for (let node = a; node !== null; node = node.parent) {
903
+ ancestors.add(node);
904
+ }
905
+ for (let node = b; node !== null; node = node.parent) {
906
+ if (ancestors.has(node)) {
907
+ return node;
908
+ }
909
+ }
910
+ return null;
911
+ };
741
912
  /**
742
913
  * Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
743
914
  * box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
@@ -766,6 +937,10 @@
766
937
  * tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
767
938
  * its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
768
939
  *
940
+ * @elementSummary The `<pc-app>` element creates a PlayCanvas application and the canvas it renders
941
+ * into, and is the root of every scene. It holds the `<pc-asset>`, `<pc-material>`, `<pc-wasm>` and
942
+ * `<pc-scene>` elements, and the page's CSS sizes it, as it would a `<video>`.
943
+ *
769
944
  * @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
770
945
  * `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
771
946
  * Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
@@ -818,21 +993,30 @@
818
993
  */
819
994
  _entityElements = new Map();
820
995
  _picker = null;
821
- _hasPointerListeners = {
822
- pointerenter: false,
823
- pointerleave: false,
824
- pointerdown: false,
825
- pointerup: false,
826
- pointermove: false
827
- };
828
996
  _hoveredEntity = null;
829
997
  // Identifies the newest in-flight hover pick, so out-of-order results can be discarded
830
998
  _pickToken = 0;
831
999
  _pointerHandlers = {
832
1000
  pointermove: null,
833
1001
  pointerdown: null,
834
- pointerup: null
1002
+ pointerup: null,
1003
+ pointercancel: null
835
1004
  };
1005
+ /**
1006
+ * The pick of each pointer's primary-button press, keyed by pointerId and kept while a click
1007
+ * may still conclude it. The promise is stored rather than its result, so a release can
1008
+ * await a press pick that has not resolved yet. Entries are removed by the matching
1009
+ * pointerup or pointercancel, and only ever stored while some element listens for click -
1010
+ * which is also what keeps those two canvas listeners attached.
1011
+ */
1012
+ _downPicks = new Map();
1013
+ /** Whether any element in the tree listens for click. Maintained by _syncCanvasListeners. */
1014
+ _clickListened = false;
1015
+ /**
1016
+ * The previous click's target, time and count, for chaining successive clicks into the
1017
+ * click count that `detail` carries. `null` until a click has fired.
1018
+ */
1019
+ _lastClick = null;
836
1020
  _app = null;
837
1021
  _loadProgress = 0;
838
1022
  /**
@@ -867,12 +1051,12 @@
867
1051
  */
868
1052
  constructor() {
869
1053
  super();
870
- // Track pointer listeners being added to and removed from descendant entities.
871
- // Registered once here rather than on every boot - the handlers no-op while there is no
872
- // canvas, and a re-booted element must not stack a second set.
873
- pointerEventTypes.forEach((type) => {
874
- this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
875
- this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
1054
+ // Track listeners for the synthesized events being added to and removed from descendant
1055
+ // entities. Registered once here rather than on every boot - the sync no-ops while there
1056
+ // is no canvas, and a re-booted element must not stack a second set.
1057
+ SYNTHESIZED_EVENTS.forEach((type) => {
1058
+ this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
1059
+ this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
876
1060
  });
877
1061
  }
878
1062
  async connectedCallback() {
@@ -1190,15 +1374,13 @@
1190
1374
  this._pointerHandlers.pointermove = listener(this._onPointerMove);
1191
1375
  this._pointerHandlers.pointerdown = listener(this._onPointerDown);
1192
1376
  this._pointerHandlers.pointerup = listener(this._onPointerUp);
1193
- // Attach canvas handlers for listeners registered before this boot (e.g. handlers
1194
- // created from onpointer* attributes when their elements were first upgraded, or
1377
+ this._pointerHandlers.pointercancel = (event) => {
1378
+ this._downPicks.delete(event.pointerId);
1379
+ };
1380
+ // Attach canvas listeners for element listeners registered before this boot (e.g.
1381
+ // handlers created from inline attributes when their elements were first upgraded, or
1195
1382
  // listeners carried over from before a re-boot)
1196
- pointerEventTypes.forEach((type) => {
1197
- const anyListeners = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node')).some((entity) => entity._hasListeners(type));
1198
- if (anyListeners) {
1199
- this._onPointerListenerAdded(type);
1200
- }
1201
- });
1383
+ this._syncCanvasListeners();
1202
1384
  }
1203
1385
  _pickerDestroy() {
1204
1386
  if (this._canvas) {
@@ -1213,15 +1395,12 @@
1213
1395
  this._pointerHandlers = {
1214
1396
  pointermove: null,
1215
1397
  pointerdown: null,
1216
- pointerup: null
1217
- };
1218
- this._hasPointerListeners = {
1219
- pointerenter: false,
1220
- pointerleave: false,
1221
- pointerdown: false,
1222
- pointerup: false,
1223
- pointermove: false
1398
+ pointerup: null,
1399
+ pointercancel: null
1224
1400
  };
1401
+ this._downPicks.clear();
1402
+ this._clickListened = false;
1403
+ this._lastClick = null;
1225
1404
  }
1226
1405
  /**
1227
1406
  * Registers the element that fronts an entity. Called by EntityElement when it creates its
@@ -1427,7 +1606,15 @@
1427
1606
  async _onPointerDown(event) {
1428
1607
  if (!this._picker || !this.app)
1429
1608
  return;
1430
- const node = await this._pickNode(event);
1609
+ const pick = this._pickNode(event);
1610
+ // A click concludes on the matching pointerup, which needs to know what the press
1611
+ // picked. Primary button only - the only button a click can conclude from - and only
1612
+ // while click is listened for, since it is the click mapping that keeps the pointerup
1613
+ // and pointercancel listeners attached to clean the entry up again.
1614
+ if (this._clickListened && event.button === 0) {
1615
+ this._downPicks.set(event.pointerId, pick);
1616
+ }
1617
+ const node = await pick;
1431
1618
  if (!this._picker)
1432
1619
  return; // the element disconnected while the pick was in flight
1433
1620
  const entityElement = this._elementWithListener(node, 'pointerdown');
@@ -1438,6 +1625,10 @@
1438
1625
  async _onPointerUp(event) {
1439
1626
  if (!this._picker || !this.app)
1440
1627
  return;
1628
+ // The press pick this release may conclude as a click. Claimed synchronously, so the
1629
+ // entry is gone before any other event for this pointer can be handled.
1630
+ const downPick = this._downPicks.get(event.pointerId);
1631
+ this._downPicks.delete(event.pointerId);
1441
1632
  const node = await this._pickNode(event);
1442
1633
  if (!this._picker)
1443
1634
  return; // the element disconnected while the pick was in flight
@@ -1445,30 +1636,60 @@
1445
1636
  if (entityElement) {
1446
1637
  entityElement.dispatchEvent(new PointerEvent('pointerup', event));
1447
1638
  }
1448
- }
1449
- _onPointerListenerAdded(type) {
1450
- if (!this._hasPointerListeners[type] && this._canvas) {
1451
- this._hasPointerListeners[type] = true;
1452
- // For enter/leave events, we need the move handler
1453
- const handler = type === 'pointerenter' || type === 'pointerleave'
1454
- ? this._pointerHandlers.pointermove
1455
- : this._pointerHandlers[type];
1456
- if (handler) {
1457
- this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
1639
+ // A click fires where the DOM fires it: at the nearest common inclusive ancestor of
1640
+ // what the press and the release picked, for the primary button only. The press pick
1641
+ // may still be in flight - a quick tap resolves in pick order, not event order.
1642
+ if (!downPick || event.button !== 0)
1643
+ return;
1644
+ const downNode = await downPick;
1645
+ if (!this._picker)
1646
+ return;
1647
+ const clickElement = this._elementWithListener(commonAncestor(downNode, node), 'click');
1648
+ if (clickElement) {
1649
+ const click = new PointerEvent('click', event);
1650
+ // The init above copied pointerup's `detail`, which the Pointer Events spec fixes
1651
+ // at 0 - but click is exempt: its detail is the click count, chained here as the
1652
+ // platform chains it (same target, within the double-click window). Overridden
1653
+ // with defineProperty because an event instance used as an init dict cannot have
1654
+ // single fields replaced.
1655
+ const time = performance.now();
1656
+ const last = this._lastClick;
1657
+ const count = last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
1658
+ this._lastClick = { element: clickElement, time, count };
1659
+ Object.defineProperty(click, 'detail', { value: count });
1660
+ clickElement.dispatchEvent(click);
1661
+ }
1662
+ }
1663
+ /**
1664
+ * Attaches exactly the canvas listeners the tree's current element listeners need, and
1665
+ * detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
1666
+ * this element: several synthesized types can need the same canvas listener (enter, leave
1667
+ * and move all ride the move pick; click rides the down/up pair), so one type's removal
1668
+ * must not detach a listener another type still uses. Re-attaching an attached listener is
1669
+ * a no-op by EventTarget semantics, so no attach state is kept.
1670
+ */
1671
+ _syncCanvasListeners() {
1672
+ const canvas = this._canvas;
1673
+ if (!canvas)
1674
+ return; // not booted yet: _pickerCreate syncs once the handlers exist
1675
+ const elements = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node'));
1676
+ const needed = new Set();
1677
+ for (const type of SYNTHESIZED_EVENTS) {
1678
+ if (elements.some((element) => element._hasListeners(type))) {
1679
+ canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
1458
1680
  }
1459
1681
  }
1460
- }
1461
- _onPointerListenerRemoved(type) {
1462
- const hasListeners = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node')).some((entity) => entity._hasListeners(type));
1463
- if (!hasListeners && this._canvas) {
1464
- this._hasPointerListeners[type] = false;
1465
- const handler = type === 'pointerenter' || type === 'pointerleave'
1466
- ? this._pointerHandlers.pointermove
1467
- : this._pointerHandlers[type];
1468
- if (handler) {
1469
- this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
1682
+ this._clickListened = elements.some((element) => element._hasListeners('click'));
1683
+ Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
1684
+ if (!handler)
1685
+ return;
1686
+ if (needed.has(canvasType)) {
1687
+ canvas.addEventListener(canvasType, handler);
1470
1688
  }
1471
- }
1689
+ else {
1690
+ canvas.removeEventListener(canvasType, handler);
1691
+ }
1692
+ });
1472
1693
  }
1473
1694
  /**
1474
1695
  * Warns that a graphics option was written too late to have any effect. These options are read
@@ -1637,126 +1858,6 @@
1637
1858
  }
1638
1859
  customElements.define('pc-app', AppElement);
1639
1860
 
1640
- /**
1641
- * The attribute names of the inline `onpointer*` event handlers, shared by every element that
1642
- * fronts an engine entity. Spread into `observedAttributes` by subclasses.
1643
- * @internal
1644
- */
1645
- const POINTER_ATTRIBUTES = [
1646
- 'onpointerenter',
1647
- 'onpointerleave',
1648
- 'onpointerdown',
1649
- 'onpointerup',
1650
- 'onpointermove'
1651
- ];
1652
- /**
1653
- * The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
1654
- * `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
1655
- * instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
1656
- * with the owning application (which joins picked scene nodes back to elements by identity,
1657
- * never by name), and the pointer listener bookkeeping that lets the application lazily attach
1658
- * its canvas handlers.
1659
- */
1660
- class EntityBaseElement extends AsyncElement {
1661
- _entity = null;
1662
- /**
1663
- * The application element this entity is registered with, cached at registration time so the
1664
- * entity can be unregistered even once this element has left the DOM.
1665
- */
1666
- _appElement = null;
1667
- /**
1668
- * The pointer event listeners for the entity.
1669
- */
1670
- _listeners = {};
1671
- /**
1672
- * The event types for which an inline `onpointer*` attribute is currently present.
1673
- */
1674
- _inlineHandlerTypes = new Set();
1675
- /**
1676
- * The PlayCanvas entity instance. `null` until the element is ready, and again once the
1677
- * entity is gone — await {@link whenReady} or the element's `ready()` promise before
1678
- * accessing it.
1679
- * @returns The entity instance, or `null`.
1680
- */
1681
- get entity() {
1682
- return this._entity;
1683
- }
1684
- /**
1685
- * Registers `entity` as this element's backing entity with the owning application, which
1686
- * joins engine nodes back to elements by identity (never by name).
1687
- *
1688
- * @param entity - The entity to register.
1689
- */
1690
- _registerEntity(entity) {
1691
- this._appElement = this.closestApp;
1692
- this._appElement?._registerEntityElement(entity, this);
1693
- }
1694
- /**
1695
- * Removes the registration for `entity`.
1696
- *
1697
- * @param entity - The entity to unregister.
1698
- */
1699
- _unregisterEntity(entity) {
1700
- this._appElement?._unregisterEntityElement(entity);
1701
- this._appElement = null;
1702
- }
1703
- /**
1704
- * Tracks whether an inline `onpointer*` attribute is present. The browser itself compiles and
1705
- * runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
1706
- * the previous handler and removing it removes the handler, exactly like `onclick` on any
1707
- * HTML element. But because they bypass {@link addEventListener}, the connect/disconnect
1708
- * bookkeeping that lets the application lazily attach its canvas pointer handlers must be
1709
- * kept in sync here.
1710
- *
1711
- * @param name - The attribute name (e.g. 'onpointerdown').
1712
- * @param value - The attribute value, or `null` when the attribute has been removed.
1713
- */
1714
- _updateInlineHandler(name, value) {
1715
- const type = name.substring(2);
1716
- const had = this._inlineHandlerTypes.has(type);
1717
- const has = value !== null;
1718
- if (has && !had) {
1719
- this._inlineHandlerTypes.add(type);
1720
- this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
1721
- }
1722
- else if (!has && had) {
1723
- this._inlineHandlerTypes.delete(type);
1724
- this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
1725
- }
1726
- }
1727
- addEventListener(type, listener, options) {
1728
- if (!this._listeners[type]) {
1729
- this._listeners[type] = [];
1730
- }
1731
- this._listeners[type].push(listener);
1732
- super.addEventListener(type, listener, options);
1733
- if (type.startsWith('pointer')) {
1734
- this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
1735
- }
1736
- }
1737
- removeEventListener(type, listener, options) {
1738
- if (this._listeners[type]) {
1739
- this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
1740
- }
1741
- super.removeEventListener(type, listener, options);
1742
- if (type.startsWith('pointer')) {
1743
- this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
1744
- }
1745
- }
1746
- /**
1747
- * Whether the element has a listener for an event type, registered either with
1748
- * {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
1749
- * containing `<pc-app>` element to gate pointer event synthesis.
1750
- *
1751
- * @param type - The event type.
1752
- * @returns Whether a listener is registered.
1753
- * @internal
1754
- */
1755
- _hasListeners(type) {
1756
- return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
1757
- }
1758
- }
1759
-
1760
1861
  /**
1761
1862
  * Creates and parents the entities of every descendant entity-owning element of `root`, in two
1762
1863
  * passes so that no parent's existence depends on document order. Called wherever a subtree could
@@ -1814,7 +1915,7 @@
1814
1915
  */
1815
1916
  _tags = [];
1816
1917
  /**
1817
- * Whether the hierarchy has been built for this entity — set once {@link _buildHierarchy} has
1918
+ * Whether the hierarchy has been built for this entity — set once `_buildHierarchy` has
1818
1919
  * parented it. Read by subclasses that gate work on the entity being in the scene graph.
1819
1920
  */
1820
1921
  _built = false;
@@ -1903,7 +2004,7 @@
1903
2004
  this._onBuilt();
1904
2005
  }
1905
2006
  /**
1906
- * Called by {@link _buildHierarchy} once the backing entity has been parented — exactly once
2007
+ * Called by `_buildHierarchy` once the backing entity has been parented — exactly once
1907
2008
  * per build cycle. The default announces readiness, which is what a parented `<pc-entity>`
1908
2009
  * means; `<pc-model>` overrides it to start loading content instead, because its readiness
1909
2010
  * tracks the content settling rather than the host entering the scene graph.
@@ -2024,8 +2125,13 @@
2024
2125
  *
2025
2126
  * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
2026
2127
  * intersects this entity's geometry. They are only generated while the entity has a listener for
2027
- * them, registered either with {@link addEventListener} or with the matching inline `onpointer*`
2028
- * attribute.
2128
+ * them, registered either with {@link EventTarget.addEventListener} or with the matching inline
2129
+ * attribute (`onpointerdown`, `onclick`, ...).
2130
+ *
2131
+ * @elementSummary The `<pc-entity>` element creates an entity: a named, transformable node of the
2132
+ * scene hierarchy, and the host for component elements such as `<pc-camera>`, `<pc-light>` and
2133
+ * `<pc-render>`. Place it in the `<pc-scene>`, or nest it under another `<pc-entity>`, a
2134
+ * `<pc-model>` or a `<pc-node>` to parent it there.
2029
2135
  *
2030
2136
  * @attribute {boolean} enabled - The enabled state of the entity.
2031
2137
  * @attribute {string} name - The name of the entity.
@@ -2040,11 +2146,17 @@
2040
2146
  * entity.
2041
2147
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
2042
2148
  * entity.
2149
+ * @attribute {string} onclick - Script to run when the entity is clicked: a primary pointer
2150
+ * button pressed and then released over it.
2043
2151
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
2044
2152
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
2045
2153
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
2046
2154
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
2047
2155
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
2156
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
2157
+ * over the entity. A press and release that picked different entities fires on their nearest
2158
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
2159
+ * arrives as a click whose `detail` is 2.
2048
2160
  */
2049
2161
  class EntityElement extends EntityOwnerElement {
2050
2162
  connectedCallback() {
@@ -2079,7 +2191,7 @@
2079
2191
  this._entity?.destroy();
2080
2192
  }
2081
2193
  static get observedAttributes() {
2082
- return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...POINTER_ATTRIBUTES];
2194
+ return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
2083
2195
  }
2084
2196
  attributeChangedCallback(name, _oldValue, newValue) {
2085
2197
  switch (name) {
@@ -2106,6 +2218,7 @@
2106
2218
  case 'onpointerdown':
2107
2219
  case 'onpointerup':
2108
2220
  case 'onpointermove':
2221
+ case 'onclick':
2109
2222
  this._updateInlineHandler(name, newValue);
2110
2223
  break;
2111
2224
  }
@@ -2407,6 +2520,10 @@
2407
2520
  * Apart from `lazy` and the texture options, these attributes are read once when the asset is
2408
2521
  * created, so changing them later has no effect.
2409
2522
  *
2523
+ * @elementSummary The `<pc-asset>` element declares an asset for the application to load — a model,
2524
+ * texture, font, sound, script or JSON file — under an `id` that other elements reference. Must be
2525
+ * a direct child of `<pc-app>`.
2526
+ *
2410
2527
  * @attribute {string} id - The identifier used to reference the asset from other elements.
2411
2528
  * @attribute {string} src - The URL of the asset to load.
2412
2529
  * @attribute {string} type - The asset type. Inferred from the `src` file extension when omitted.
@@ -3033,6 +3150,11 @@
3033
3150
  * intersects the model's geometry, exactly as for `<pc-entity>` — a hit on a content node that no
3034
3151
  * `pc-node` fronts resolves to this element.
3035
3152
  *
3153
+ * @elementSummary The `<pc-model>` element instantiates a 3D model from a container asset
3154
+ * (typically a GLB) beneath an entity of its own, so the element's transform and tags place the
3155
+ * instance in the scene. Its `<pc-node>` children override what the asset authored. Place it in the
3156
+ * `<pc-scene>`, or nest it under a `<pc-entity>`, another `<pc-model>` or a `<pc-node>`.
3157
+ *
3036
3158
  * @attribute {boolean} enabled - The enabled state of the model.
3037
3159
  * @attribute {string} name - The name of the model.
3038
3160
  * @attribute {string} position - The position of the model.
@@ -3046,11 +3168,17 @@
3046
3168
  * model.
3047
3169
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
3048
3170
  * model.
3171
+ * @attribute {string} onclick - Script to run when the model is clicked: a primary pointer
3172
+ * button pressed and then released over it.
3049
3173
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the model.
3050
3174
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the model.
3051
3175
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the model.
3052
3176
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the model.
3053
3177
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the model.
3178
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
3179
+ * over the model. A press and release that picked different entities fires on their nearest
3180
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
3181
+ * arrives as a click whose `detail` is 2.
3054
3182
  * @fires {Event} load - Fired each time a container asset finishes instantiating, including
3055
3183
  * re-instantiation after `asset` changes. Does not bubble — listen on this element, or use a
3056
3184
  * capture-phase listener on an ancestor.
@@ -3299,7 +3427,7 @@
3299
3427
  return this._asset;
3300
3428
  }
3301
3429
  static get observedAttributes() {
3302
- return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...POINTER_ATTRIBUTES];
3430
+ return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
3303
3431
  }
3304
3432
  attributeChangedCallback(name, _oldValue, newValue) {
3305
3433
  switch (name) {
@@ -3329,6 +3457,7 @@
3329
3457
  case 'onpointerdown':
3330
3458
  case 'onpointerup':
3331
3459
  case 'onpointermove':
3460
+ case 'onclick':
3332
3461
  this._updateInlineHandler(name, newValue);
3333
3462
  break;
3334
3463
  }
@@ -3566,6 +3695,11 @@
3566
3695
  *
3567
3696
  * Engine component: {@link AnimComponent} (`anim`).
3568
3697
  *
3698
+ * @elementSummary The `<pc-anim>` element plays animation clips over its entity's hierarchy, taken
3699
+ * from `<pc-anim-clip>` children or from the enclosing `<pc-model>`'s own animations. The first
3700
+ * clip plays automatically, and the `clip` attribute switches between them. Must be a child of a
3701
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
3702
+ *
3569
3703
  * @category Components
3570
3704
  */
3571
3705
  class AnimComponentElement extends ComponentElement {
@@ -4184,7 +4318,9 @@
4184
4318
  * named; in a multi-track source the track named `name` is chosen, falling back to the first
4185
4319
  * with a warning. The element becomes ready once its resolved track is assigned.
4186
4320
  *
4187
- * @category Components
4321
+ * @elementSummary The `<pc-anim-clip>` element declares one named animation clip on its parent
4322
+ * `<pc-anim>`, taken from the `asset` it names or, without one, from the enclosing `<pc-model>`'s
4323
+ * own animations. Must be a direct child of `<pc-anim>`.
4188
4324
  */
4189
4325
  class AnimClipElement extends AsyncElement {
4190
4326
  /**
@@ -4512,6 +4648,10 @@
4512
4648
  *
4513
4649
  * Engine component: {@link AudioListenerComponent} (`audiolistener`).
4514
4650
  *
4651
+ * @elementSummary The `<pc-audio-listener>` element makes its entity the point from which
4652
+ * positional sounds are heard, typically the entity holding the `<pc-camera>`. Must be a child of a
4653
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
4654
+ *
4515
4655
  * @category Components
4516
4656
  */
4517
4657
  class AudioListenerComponentElement extends ComponentElement {
@@ -4541,6 +4681,11 @@
4541
4681
  *
4542
4682
  * Engine component: {@link ButtonComponent} (`button`).
4543
4683
  *
4684
+ * @elementSummary The `<pc-button>` element makes its entity respond to pointer input, tinting or
4685
+ * swapping its image as the pointer hovers, presses and releases it. The entity also needs a
4686
+ * `<pc-element>` with `use-input` set. Must be a child of a `<pc-entity>`, `<pc-model>` or
4687
+ * `<pc-node>`.
4688
+ *
4544
4689
  * @category Components
4545
4690
  */
4546
4691
  class ButtonComponentElement extends ComponentElement {
@@ -4944,13 +5089,18 @@
4944
5089
  *
4945
5090
  * Engine component: {@link CameraComponent} (`camera`).
4946
5091
  *
5092
+ * @elementSummary The `<pc-camera>` element renders the scene from its entity's transform, with
5093
+ * attributes for the projection, field of view, clip planes, clear color and tonemapping. Must be a
5094
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5095
+ *
4947
5096
  * @category Components
4948
5097
  */
4949
5098
  class CameraComponentElement extends ComponentElement {
4950
5099
  _clearColor = new playcanvas.Color(0.75, 0.75, 0.75, 1);
4951
5100
  _clearColorBuffer = true;
5101
+ _clearDepth = 1;
4952
5102
  _clearDepthBuffer = true;
4953
- _clearStencilBuffer = false;
5103
+ _clearStencilBuffer = true;
4954
5104
  _cullFaces = true;
4955
5105
  _farClip = 1000;
4956
5106
  _flipFaces = false;
@@ -4973,6 +5123,7 @@
4973
5123
  return {
4974
5124
  clearColor: this._clearColor,
4975
5125
  clearColorBuffer: this._clearColorBuffer,
5126
+ clearDepth: this._clearDepth,
4976
5127
  clearDepthBuffer: this._clearDepthBuffer,
4977
5128
  clearStencilBuffer: this._clearStencilBuffer,
4978
5129
  cullFaces: this._cullFaces,
@@ -4991,9 +5142,31 @@
4991
5142
  toneMapping: tonemaps.get(this._tonemap) ?? playcanvas.TONEMAP_NONE
4992
5143
  };
4993
5144
  }
4994
- get xrAvailable() {
5145
+ /**
5146
+ * Whether immersive AR is available. Independent of {@link vrAvailable}: a device can offer
5147
+ * either mode without the other.
5148
+ * @returns Whether immersive AR is available.
5149
+ */
5150
+ get arAvailable() {
5151
+ return this._available(playcanvas.XRTYPE_AR);
5152
+ }
5153
+ /**
5154
+ * Whether immersive VR is available. Independent of {@link arAvailable}: a device can offer
5155
+ * either mode without the other.
5156
+ * @returns Whether immersive VR is available.
5157
+ */
5158
+ get vrAvailable() {
5159
+ return this._available(playcanvas.XRTYPE_VR);
5160
+ }
5161
+ /**
5162
+ * Whether one XR session type is available on this device.
5163
+ *
5164
+ * @param type - The XR session type to test.
5165
+ * @returns Whether that type is available.
5166
+ */
5167
+ _available(type) {
4995
5168
  const xrManager = this.component?.system.app.xr;
4996
- return xrManager && xrManager.supported && xrManager.isAvailable(playcanvas.XRTYPE_VR);
5169
+ return Boolean(xrManager?.supported && xrManager.isAvailable(type));
4997
5170
  }
4998
5171
  /**
4999
5172
  * Starts the camera in XR mode.
@@ -5001,11 +5174,13 @@
5001
5174
  * @param space - The space to start the camera in.
5002
5175
  */
5003
5176
  startXr(type, space) {
5004
- if (this.component && this.xrAvailable) {
5177
+ // Gated on the mode being started, not on XR in general: a device that offers only
5178
+ // one of the two would otherwise accept a session it cannot serve
5179
+ if (this.component && this._available(type)) {
5005
5180
  this.component.startXr(type, space, {
5006
5181
  callback: (err) => {
5007
5182
  if (err)
5008
- console.error(`WebXR Immersive VR failed to start: ${err.message}`);
5183
+ console.error(`WebXR ${type} failed to start: ${err.message}`);
5009
5184
  }
5010
5185
  });
5011
5186
  }
@@ -5059,6 +5234,23 @@
5059
5234
  get clearColorBuffer() {
5060
5235
  return this._clearColorBuffer;
5061
5236
  }
5237
+ /**
5238
+ * Sets the depth value the depth buffer is cleared to. Defaults to 1.
5239
+ * @param value - The clear depth value.
5240
+ */
5241
+ set clearDepth(value) {
5242
+ this._clearDepth = value;
5243
+ if (this.component) {
5244
+ this.component.clearDepth = value;
5245
+ }
5246
+ }
5247
+ /**
5248
+ * Gets the depth value the depth buffer is cleared to.
5249
+ * @returns The clear depth value.
5250
+ */
5251
+ get clearDepth() {
5252
+ return this._clearDepth;
5253
+ }
5062
5254
  /**
5063
5255
  * Sets the clear depth buffer of the camera.
5064
5256
  * @param value - The clear depth buffer.
@@ -5337,6 +5529,7 @@
5337
5529
  ...super.observedAttributes,
5338
5530
  'clear-color',
5339
5531
  'clear-color-buffer',
5532
+ 'clear-depth',
5340
5533
  'clear-depth-buffer',
5341
5534
  'clear-stencil-buffer',
5342
5535
  'cull-faces',
@@ -5364,11 +5557,14 @@
5364
5557
  case 'clear-color-buffer':
5365
5558
  this.clearColorBuffer = parseBool(newValue, true);
5366
5559
  break;
5560
+ case 'clear-depth':
5561
+ this.clearDepth = parseNumber(newValue, 1, name);
5562
+ break;
5367
5563
  case 'clear-depth-buffer':
5368
5564
  this.clearDepthBuffer = parseBool(newValue, true);
5369
5565
  break;
5370
5566
  case 'clear-stencil-buffer':
5371
- this.clearStencilBuffer = parseBool(newValue, false);
5567
+ this.clearStencilBuffer = parseBool(newValue, true);
5372
5568
  break;
5373
5569
  case 'cull-faces':
5374
5570
  this.cullFaces = parseBool(newValue, true);
@@ -5431,6 +5627,10 @@
5431
5627
  *
5432
5628
  * Engine component: {@link CollisionComponent} (`collision`).
5433
5629
  *
5630
+ * @elementSummary The `<pc-collision>` element gives its entity a collision shape — a box, sphere,
5631
+ * capsule, cone, cylinder or mesh — for the physics simulation to collide against. Pair it with a
5632
+ * `<pc-rigid-body>`. Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5633
+ *
5434
5634
  * @category Components
5435
5635
  */
5436
5636
  class CollisionComponentElement extends ComponentElement {
@@ -5621,6 +5821,10 @@
5621
5821
  *
5622
5822
  * Engine component: {@link ElementComponent} (`element`).
5623
5823
  *
5824
+ * @elementSummary The `<pc-element>` element gives its entity a 2D UI rectangle inside a
5825
+ * `<pc-screen>` hierarchy, drawing an image, a line of text or nothing (`type="image"`, `"text"` or
5826
+ * `"group"`). Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5827
+ *
5624
5828
  * @category Components
5625
5829
  */
5626
5830
  class ElementComponentElement extends ComponentElement {
@@ -6305,6 +6509,11 @@
6305
6509
  * a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
6306
6510
  * space. The underlying engine component is in alpha, so its API may change.
6307
6511
  *
6512
+ * @elementSummary The `<pc-joint>` element constrains two rigid bodies to each other — a hinged
6513
+ * door, a swinging chain, a sliding drawer. Its entity's transform is the joint frame, and
6514
+ * `entity-a` and `entity-b` name the bodies. Must be a child of a `<pc-entity>`, `<pc-model>` or
6515
+ * `<pc-node>`.
6516
+ *
6308
6517
  * @fires {CustomEvent} break - Fired when the impulse on the joint exceeds `break-impulse` and the
6309
6518
  * constraint breaks. A broken joint no longer constrains its bodies; calling `refreshFrames()` on
6310
6519
  * the underlying component re-attaches it. Bubbles and is composed.
@@ -7175,6 +7384,10 @@
7175
7384
  *
7176
7385
  * Engine component: {@link LayoutChildComponent} (`layoutchild`).
7177
7386
  *
7387
+ * @elementSummary The `<pc-layout-child>` element controls how its entity is sized by the
7388
+ * `<pc-layout-group>` above it, through minimum and maximum sizes and fit proportions. Must be a
7389
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
7390
+ *
7178
7391
  * @category Components
7179
7392
  */
7180
7393
  class LayoutChildComponentElement extends ComponentElement {
@@ -7387,6 +7600,10 @@
7387
7600
  *
7388
7601
  * Engine component: {@link LayoutGroupComponent} (`layoutgroup`).
7389
7602
  *
7603
+ * @elementSummary The `<pc-layout-group>` element arranges its entity's children in a row or
7604
+ * column, with spacing, padding, alignment and fitting. Must be a child of a `<pc-entity>`,
7605
+ * `<pc-model>` or `<pc-node>`.
7606
+ *
7390
7607
  * @category Components
7391
7608
  */
7392
7609
  class LayoutGroupComponentElement extends ComponentElement {
@@ -7647,23 +7864,30 @@
7647
7864
  *
7648
7865
  * Engine component: {@link LightComponent} (`light`).
7649
7866
  *
7867
+ * @elementSummary The `<pc-light>` element lights the scene from its entity — as a directional,
7868
+ * omni or spot light — with attributes for color, intensity, range and shadows. Must be a child of
7869
+ * a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
7870
+ *
7650
7871
  * @category Components
7651
7872
  */
7652
7873
  class LightComponentElement extends ComponentElement {
7874
+ _cascadeBlend = 0;
7875
+ _cascadeDistribution = 0.5;
7653
7876
  _castShadows = false;
7654
7877
  _color = new playcanvas.Color(1, 1, 1);
7655
7878
  _innerConeAngle = 40;
7656
7879
  _intensity = 1;
7657
- _normalOffsetBias = 0.05;
7880
+ _normalOffsetBias = 0;
7881
+ _numCascades = 1;
7658
7882
  _outerConeAngle = 45;
7659
7883
  _range = 10;
7660
- _shadowBias = 0.2;
7661
- _shadowDistance = 16;
7884
+ _shadowBias = 0.05;
7885
+ _shadowDistance = 40;
7662
7886
  _shadowIntensity = 1;
7663
7887
  _shadowResolution = 1024;
7664
7888
  _shadowType = 'pcf3-32f';
7665
7889
  _type = 'directional';
7666
- _vsmBias = 0.01;
7890
+ _vsmBias = 0.0025;
7667
7891
  _vsmBlurSize = 11;
7668
7892
  _penumbraSize = 1;
7669
7893
  _penumbraFalloff = 1;
@@ -7675,11 +7899,14 @@
7675
7899
  }
7676
7900
  getInitialComponentData() {
7677
7901
  return {
7902
+ cascadeBlend: this._cascadeBlend,
7903
+ cascadeDistribution: this._cascadeDistribution,
7678
7904
  castShadows: this._castShadows,
7679
7905
  color: this._color,
7680
7906
  innerConeAngle: this._innerConeAngle,
7681
7907
  intensity: this._intensity,
7682
7908
  normalOffsetBias: this._normalOffsetBias,
7909
+ numCascades: this._numCascades,
7683
7910
  outerConeAngle: this._outerConeAngle,
7684
7911
  penumbraFalloff: this._penumbraFalloff,
7685
7912
  penumbraSize: this._penumbraSize,
@@ -7690,7 +7917,7 @@
7690
7917
  shadowIntensity: this._shadowIntensity,
7691
7918
  shadowResolution: this._shadowResolution,
7692
7919
  shadowSamples: this._shadowSamples,
7693
- shadowType: shadowTypes.get(this._shadowType),
7920
+ shadowType: shadowTypes.get(this._shadowType) ?? playcanvas.SHADOW_PCF3_32F,
7694
7921
  type: this._type,
7695
7922
  vsmBias: this._vsmBias,
7696
7923
  vsmBlurSize: this._vsmBlurSize
@@ -7703,6 +7930,47 @@
7703
7930
  get component() {
7704
7931
  return super.component;
7705
7932
  }
7933
+ /**
7934
+ * Sets the fraction of each shadow cascade that is blended into the next one, from 0 (no
7935
+ * blending) to 1, which applies only to `directional` lights with `num-cascades` greater than
7936
+ * 1. Defaults to 0.
7937
+ * @param value - The cascade blend factor.
7938
+ */
7939
+ set cascadeBlend(value) {
7940
+ this._cascadeBlend = value;
7941
+ if (this.component) {
7942
+ this.component.cascadeBlend = value;
7943
+ }
7944
+ }
7945
+ /**
7946
+ * Gets the cascade blend factor of the light, from 0 (no blending) to 1, which applies only to
7947
+ * `directional` lights with `num-cascades` greater than 1.
7948
+ * @returns The cascade blend factor.
7949
+ */
7950
+ get cascadeBlend() {
7951
+ return this._cascadeBlend;
7952
+ }
7953
+ /**
7954
+ * Sets the distribution of the camera frustum split between shadow cascades, from 0 (linear
7955
+ * split) to 1 (logarithmic split, concentrating shadow resolution near the camera), which
7956
+ * applies only to `directional` lights with `num-cascades` greater than 1. Defaults to 0.5.
7957
+ * @param value - The cascade distribution.
7958
+ */
7959
+ set cascadeDistribution(value) {
7960
+ this._cascadeDistribution = value;
7961
+ if (this.component) {
7962
+ this.component.cascadeDistribution = value;
7963
+ }
7964
+ }
7965
+ /**
7966
+ * Gets the cascade distribution of the light, from 0 (linear split) to 1 (logarithmic split,
7967
+ * concentrating shadow resolution near the camera), which applies only to `directional` lights
7968
+ * with `num-cascades` greater than 1.
7969
+ * @returns The cascade distribution.
7970
+ */
7971
+ get cascadeDistribution() {
7972
+ return this._cascadeDistribution;
7973
+ }
7706
7974
  /**
7707
7975
  * Sets the cast shadows flag of the light.
7708
7976
  * @param value - The cast shadows flag.
@@ -7788,6 +8056,25 @@
7788
8056
  get normalOffsetBias() {
7789
8057
  return this._normalOffsetBias;
7790
8058
  }
8059
+ /**
8060
+ * Sets the number of shadow cascades of the light, an integer from 1 (no cascades) to 4, which
8061
+ * applies only to `directional` lights. Defaults to 1.
8062
+ * @param value - The number of shadow cascades.
8063
+ */
8064
+ set numCascades(value) {
8065
+ this._numCascades = value;
8066
+ if (this.component) {
8067
+ this.component.numCascades = value;
8068
+ }
8069
+ }
8070
+ /**
8071
+ * Gets the number of shadow cascades of the light, an integer from 1 (no cascades) to 4, which
8072
+ * applies only to `directional` lights.
8073
+ * @returns The number of shadow cascades.
8074
+ */
8075
+ get numCascades() {
8076
+ return this._numCascades;
8077
+ }
7791
8078
  /**
7792
8079
  * Sets the outer cone angle of the light.
7793
8080
  * @param value - The outer cone angle.
@@ -8040,11 +8327,14 @@
8040
8327
  static get observedAttributes() {
8041
8328
  return [
8042
8329
  ...super.observedAttributes,
8043
- 'color',
8330
+ 'cascade-blend',
8331
+ 'cascade-distribution',
8044
8332
  'cast-shadows',
8045
- 'intensity',
8333
+ 'color',
8046
8334
  'inner-cone-angle',
8335
+ 'intensity',
8047
8336
  'normal-offset-bias',
8337
+ 'num-cascades',
8048
8338
  'outer-cone-angle',
8049
8339
  'penumbra-falloff',
8050
8340
  'penumbra-size',
@@ -8064,12 +8354,18 @@
8064
8354
  attributeChangedCallback(name, _oldValue, newValue) {
8065
8355
  super.attributeChangedCallback(name, _oldValue, newValue);
8066
8356
  switch (name) {
8067
- case 'color':
8068
- this.color = parseColor(newValue, playcanvas.Color.WHITE, name);
8357
+ case 'cascade-blend':
8358
+ this.cascadeBlend = parseNumber(newValue, 0, name);
8359
+ break;
8360
+ case 'cascade-distribution':
8361
+ this.cascadeDistribution = parseNumber(newValue, 0.5, name);
8069
8362
  break;
8070
8363
  case 'cast-shadows':
8071
8364
  this.castShadows = parseBool(newValue, false);
8072
8365
  break;
8366
+ case 'color':
8367
+ this.color = parseColor(newValue, playcanvas.Color.WHITE, name);
8368
+ break;
8073
8369
  case 'inner-cone-angle':
8074
8370
  this.innerConeAngle = parseNumber(newValue, 40, name);
8075
8371
  break;
@@ -8077,7 +8373,10 @@
8077
8373
  this.intensity = parseNumber(newValue, 1, name);
8078
8374
  break;
8079
8375
  case 'normal-offset-bias':
8080
- this.normalOffsetBias = parseNumber(newValue, 0.05, name);
8376
+ this.normalOffsetBias = parseNumber(newValue, 0, name);
8377
+ break;
8378
+ case 'num-cascades':
8379
+ this.numCascades = parseNumber(newValue, 1, name);
8081
8380
  break;
8082
8381
  case 'outer-cone-angle':
8083
8382
  this.outerConeAngle = parseNumber(newValue, 45, name);
@@ -8092,20 +8391,20 @@
8092
8391
  this.range = parseNumber(newValue, 10, name);
8093
8392
  break;
8094
8393
  case 'shadow-bias':
8095
- this.shadowBias = parseNumber(newValue, 0.2, name);
8096
- break;
8097
- case 'shadow-distance':
8098
- this.shadowDistance = parseNumber(newValue, 16, name);
8394
+ this.shadowBias = parseNumber(newValue, 0.05, name);
8099
8395
  break;
8100
8396
  case 'shadow-blocker-samples':
8101
8397
  this.shadowBlockerSamples = parseNumber(newValue, 16, name);
8102
8398
  break;
8103
- case 'shadow-resolution':
8104
- this.shadowResolution = parseNumber(newValue, 1024, name);
8399
+ case 'shadow-distance':
8400
+ this.shadowDistance = parseNumber(newValue, 40, name);
8105
8401
  break;
8106
8402
  case 'shadow-intensity':
8107
8403
  this.shadowIntensity = parseNumber(newValue, 1, name);
8108
8404
  break;
8405
+ case 'shadow-resolution':
8406
+ this.shadowResolution = parseNumber(newValue, 1024, name);
8407
+ break;
8109
8408
  case 'shadow-samples':
8110
8409
  this.shadowSamples = parseNumber(newValue, 16, name);
8111
8410
  break;
@@ -8116,7 +8415,7 @@
8116
8415
  this.type = parseEnum(newValue, ['directional', 'omni', 'spot'], 'directional', name);
8117
8416
  break;
8118
8417
  case 'vsm-bias':
8119
- this.vsmBias = parseNumber(newValue, 0.01, name);
8418
+ this.vsmBias = parseNumber(newValue, 0.0025, name);
8120
8419
  break;
8121
8420
  case 'vsm-blur-size':
8122
8421
  this.vsmBlurSize = parseNumber(newValue, 11, name);
@@ -8134,6 +8433,10 @@
8134
8433
  *
8135
8434
  * Engine component: {@link ParticleSystemComponent} (`particlesystem`).
8136
8435
  *
8436
+ * @elementSummary The `<pc-particle-system>` element emits particles from its entity, with
8437
+ * attributes for the emitter's shape, rate, lifetime, textures and blending. Must be a child of a
8438
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
8439
+ *
8137
8440
  * @category Components
8138
8441
  */
8139
8442
  class ParticleSystemComponentElement extends ComponentElement {
@@ -8282,6 +8585,8 @@
8282
8585
  ['ao', playcanvas.SPECOCC_AO],
8283
8586
  ['gloss-dependent', playcanvas.SPECOCC_GLOSSDEPENDENT]
8284
8587
  ]);
8588
+ // The DITHER_* constants are strings whose values are exactly these names, so a parsed value is
8589
+ // assigned to the material unchanged rather than mapped through a table.
8285
8590
  const opacityDithers = ['none', 'bayer8', 'bluenoise', 'ignnoise'];
8286
8591
  const colorChannels = ['r', 'g', 'b', 'a', 'rgb'];
8287
8592
  const scalarChannels = ['r', 'g', 'b', 'a'];
@@ -8318,6 +8623,10 @@
8318
8623
  * The two aliases are documented here rather than on an accessor, because they resolve to the
8319
8624
  * `gloss` properties and would otherwise inherit gloss's description - which reads inverted.
8320
8625
  *
8626
+ * @elementSummary The `<pc-material>` element defines a physically based material, which
8627
+ * `<pc-render>` elements apply by naming its `id`. It is metal/rough by default, with `metalness`
8628
+ * starting at 0. Must be a direct child of `<pc-app>`.
8629
+ *
8321
8630
  * @attribute {number} roughness - The roughness of the material, from 0 (shiny) to 1 (rough). An
8322
8631
  * alias for `gloss` that also inverts it, so do not combine it with the `gloss` attributes.
8323
8632
  * @attribute {string} roughness-map - The id of the `pc-asset` to use as the roughness map. An
@@ -10486,6 +10795,11 @@
10486
10795
  *
10487
10796
  * Engine component: {@link RenderComponent} (`render`).
10488
10797
  *
10798
+ * @elementSummary The `<pc-render>` element renders one of the engine's built-in primitives at its
10799
+ * entity — box, sphere, capsule, cone, cylinder or plane — shaded by the `<pc-material>` its
10800
+ * `material` attribute names. For glTF content, use `<pc-model>` instead. Must be a child of a
10801
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
10802
+ *
10489
10803
  * @category Components
10490
10804
  */
10491
10805
  class RenderComponentElement extends ComponentElement {
@@ -10616,6 +10930,11 @@
10616
10930
  *
10617
10931
  * Engine component: {@link RigidBodyComponent} (`rigidbody`).
10618
10932
  *
10933
+ * @elementSummary The `<pc-rigid-body>` element hands its entity to the physics simulation, with
10934
+ * attributes for its type, mass, friction and restitution. It needs a sibling `<pc-collision>` for
10935
+ * its shape, and `Ammo` loaded through `<pc-wasm>`. Must be a child of a `<pc-entity>`,
10936
+ * `<pc-model>` or `<pc-node>`.
10937
+ *
10619
10938
  * @category Components
10620
10939
  */
10621
10940
  class RigidBodyComponentElement extends ComponentElement {
@@ -10825,6 +11144,10 @@
10825
11144
  *
10826
11145
  * Engine component: {@link ScreenComponent} (`screen`).
10827
11146
  *
11147
+ * @elementSummary The `<pc-screen>` element gives its entity a 2D space — in screen space or in the
11148
+ * world — that a hierarchy of `<pc-element>` descendants lays out inside. Must be a child of a
11149
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11150
+ *
10828
11151
  * @category Components
10829
11152
  */
10830
11153
  class ScreenComponentElement extends ComponentElement {
@@ -10979,6 +11302,10 @@
10979
11302
  *
10980
11303
  * Engine component: {@link ScrollbarComponent} (`scrollbar`).
10981
11304
  *
11305
+ * @elementSummary The `<pc-scrollbar>` element gives its entity a draggable handle reporting a
11306
+ * position from 0 to 1, which a `<pc-scroll-view>` references to scroll its content. Must be a
11307
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11308
+ *
10982
11309
  * @category Components
10983
11310
  */
10984
11311
  class ScrollbarComponentElement extends ComponentElement {
@@ -11120,6 +11447,10 @@
11120
11447
  *
11121
11448
  * Engine component: {@link ScrollViewComponent} (`scrollview`).
11122
11449
  *
11450
+ * @elementSummary The `<pc-scroll-view>` element scrolls a larger content entity within a clipped
11451
+ * viewport at its entity, optionally driven by the `<pc-scrollbar>` elements it references. Must be
11452
+ * a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11453
+ *
11123
11454
  * @category Components
11124
11455
  */
11125
11456
  class ScrollViewComponentElement extends ComponentElement {
@@ -11506,6 +11837,10 @@
11506
11837
  * The element becomes ready once its script instance has been created by the parent
11507
11838
  * `<pc-script>` element.
11508
11839
  *
11840
+ * @elementSummary The `<pc-script-instance>` element attaches one script class, named by `name`, to
11841
+ * the entity of its parent `<pc-script>`. Its other attributes set script attributes of the same
11842
+ * name, and `attributes` takes a JSON object instead. Must be a direct child of `<pc-script>`.
11843
+ *
11509
11844
  * @fires {CustomEvent} scriptattributeschange - Fired when the script's attributes change. The
11510
11845
  * `detail` carries the new `attributes` object. Bubbles.
11511
11846
  * @fires {CustomEvent} scriptenablechange - Fired when the script's enabled state changes. The
@@ -11850,6 +12185,9 @@
11850
12185
  *
11851
12186
  * Engine component: {@link ScriptComponent} (`script`).
11852
12187
  *
12188
+ * @elementSummary The `<pc-script>` element holds the `<pc-script-instance>` children that attach
12189
+ * scripts to its entity. Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
12190
+ *
11853
12191
  * @category Components
11854
12192
  */
11855
12193
  class ScriptComponentElement extends ComponentElement {
@@ -12299,6 +12637,10 @@
12299
12637
  *
12300
12638
  * Engine component: {@link SoundComponent} (`sound`).
12301
12639
  *
12640
+ * @elementSummary The `<pc-sound>` element holds the `<pc-sound-slot>` children that play sounds at
12641
+ * its entity, along with the positional audio settings they share. Must be a child of a
12642
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
12643
+ *
12302
12644
  * @category Components
12303
12645
  */
12304
12646
  class SoundComponentElement extends ComponentElement {
@@ -12495,6 +12837,9 @@
12495
12837
  * The SoundSlotElement interface provides properties and methods for manipulating
12496
12838
  * `<pc-sound-slot>` elements. The SoundSlotElement interface also inherits the properties and
12497
12839
  * methods of the {@link AsyncElement} interface.
12840
+ *
12841
+ * @elementSummary The `<pc-sound-slot>` element declares one named sound on its parent `<pc-sound>`
12842
+ * — its asset, volume, pitch, looping and autoplay. Must be a direct child of `<pc-sound>`.
12498
12843
  */
12499
12844
  class SoundSlotElement extends AsyncElement {
12500
12845
  _asset = '';
@@ -12776,6 +13121,10 @@
12776
13121
  *
12777
13122
  * Engine component: {@link GSplatComponent} (`gsplat`).
12778
13123
  *
13124
+ * @elementSummary The `<pc-gsplat>` element renders the 3D Gaussian splats of a `gsplat` asset at
13125
+ * its entity, with attributes for shadow casting and level of detail. Must be a child of a
13126
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
13127
+ *
12779
13128
  * @category Components
12780
13129
  */
12781
13130
  class GSplatComponentElement extends ComponentElement {
@@ -13071,6 +13420,11 @@
13071
13420
  * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
13072
13421
  * intersects the bound node's geometry, exactly as for `<pc-entity>`.
13073
13422
  *
13423
+ * @elementSummary The `<pc-node>` element binds to a node inside the hierarchy a `<pc-model>`
13424
+ * instantiated and declares overrides against it: a transform, an enabled state, tags, components
13425
+ * to add, or content to attach. Its `name` is a lookup, never a rename. Must be a descendant of
13426
+ * `<pc-model>`.
13427
+ *
13074
13428
  * @attribute {string} name - The name of the node to bind, resolved within the nearest ancestor
13075
13429
  * `pc-model` (or `pc-node`) once it has instantiated.
13076
13430
  * @attribute {number} index - Which match to bind when `name` matches more than one node,
@@ -13095,11 +13449,17 @@
13095
13449
  * node.
13096
13450
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
13097
13451
  * node.
13452
+ * @attribute {string} onclick - Script to run when the node is clicked: a primary pointer
13453
+ * button pressed and then released over it.
13098
13454
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the node.
13099
13455
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the node.
13100
13456
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the node.
13101
13457
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the node.
13102
13458
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the node.
13459
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
13460
+ * over the node. A press and release that picked different entities fires on their nearest
13461
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
13462
+ * arrives as a click whose `detail` is 2.
13103
13463
  */
13104
13464
  class NodeElement extends EntityBaseElement {
13105
13465
  _name = '';
@@ -13749,7 +14109,7 @@
13749
14109
  'rotation',
13750
14110
  'scale',
13751
14111
  'tags',
13752
- ...POINTER_ATTRIBUTES
14112
+ ...EVENT_ATTRIBUTES
13753
14113
  ];
13754
14114
  }
13755
14115
  attributeChangedCallback(name, _oldValue, newValue) {
@@ -13799,6 +14159,7 @@
13799
14159
  case 'onpointerdown':
13800
14160
  case 'onpointerup':
13801
14161
  case 'onpointermove':
14162
+ case 'onclick':
13802
14163
  this._updateInlineHandler(name, newValue);
13803
14164
  break;
13804
14165
  }
@@ -13811,8 +14172,16 @@
13811
14172
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-scene/ | `<pc-scene>`} elements.
13812
14173
  * The SceneElement interface also inherits the properties and methods of the
13813
14174
  * {@link HTMLElement} interface.
14175
+ *
14176
+ * @elementSummary The `<pc-scene>` element holds the entity hierarchy the application renders,
14177
+ * along with the scene-wide fog, exposure and gravity settings. Must be a direct child of
14178
+ * `<pc-app>`.
13814
14179
  */
13815
14180
  class SceneElement extends AsyncElement {
14181
+ /**
14182
+ * The exposure of the scene.
14183
+ */
14184
+ _exposure = 1;
13816
14185
  /**
13817
14186
  * The fog type of the scene.
13818
14187
  */
@@ -13880,6 +14249,7 @@
13880
14249
  }
13881
14250
  _updateSceneSettings() {
13882
14251
  if (this._scene) {
14252
+ this._scene.exposure = this._exposure;
13883
14253
  this._scene.fog.type = this._fog;
13884
14254
  this._scene.fog.color = this._fogColor;
13885
14255
  this._scene.fog.density = this._fogDensity;
@@ -13898,6 +14268,24 @@
13898
14268
  _applyGravity(value) {
13899
14269
  this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
13900
14270
  }
14271
+ /**
14272
+ * Sets the exposure of the scene, which tweaks the overall brightness of the rendered image.
14273
+ * Ignored if the scene is using physical units. Defaults to 1.
14274
+ * @param value - The exposure.
14275
+ */
14276
+ set exposure(value) {
14277
+ this._exposure = value;
14278
+ if (this.scene) {
14279
+ this.scene.exposure = value;
14280
+ }
14281
+ }
14282
+ /**
14283
+ * Gets the exposure of the scene.
14284
+ * @returns The exposure.
14285
+ */
14286
+ get exposure() {
14287
+ return this._exposure;
14288
+ }
13901
14289
  /**
13902
14290
  * Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
13903
14291
  * `none`.
@@ -14002,10 +14390,13 @@
14002
14390
  return this._gravity;
14003
14391
  }
14004
14392
  static get observedAttributes() {
14005
- return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
14393
+ return ['exposure', 'fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
14006
14394
  }
14007
14395
  attributeChangedCallback(name, _oldValue, newValue) {
14008
14396
  switch (name) {
14397
+ case 'exposure':
14398
+ this.exposure = parseNumber(newValue, 1, name);
14399
+ break;
14009
14400
  case 'fog':
14010
14401
  this.fog = parseEnum(newValue, ['none', 'linear', 'exp', 'exp2'], 'none', name);
14011
14402
  break;
@@ -14034,6 +14425,10 @@
14034
14425
  * The SkyElement interface provides properties and methods for manipulating
14035
14426
  * `<pc-sky>` elements. The SkyElement interface also inherits the properties and
14036
14427
  * methods of the {@link HTMLElement} interface.
14428
+ *
14429
+ * @elementSummary The `<pc-sky>` element renders a skybox from a texture asset, projected as an
14430
+ * infinite background, a box or a dome, and optionally lights the scene from it. Must be a direct
14431
+ * child of `<pc-scene>`.
14037
14432
  */
14038
14433
  class SkyElement extends AsyncElement {
14039
14434
  _asset = '';