@playcanvas/web-components 0.9.0 → 0.10.1

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 (74) hide show
  1. package/README.md +18 -0
  2. package/dist/app.d.ts +55 -9
  3. package/dist/asset.d.ts +25 -1
  4. package/dist/async-element.d.ts +15 -2
  5. package/dist/components/button-component.d.ts +1 -1
  6. package/dist/components/camera-component.d.ts +1 -1
  7. package/dist/components/collision-component.d.ts +1 -1
  8. package/dist/components/component.d.ts +6 -5
  9. package/dist/components/element-component.d.ts +1 -1
  10. package/dist/components/gsplat-component.d.ts +1 -1
  11. package/dist/components/layoutchild-component.d.ts +1 -1
  12. package/dist/components/layoutgroup-component.d.ts +1 -1
  13. package/dist/components/light-component.d.ts +1 -1
  14. package/dist/components/particlesystem-component.d.ts +1 -1
  15. package/dist/components/render-component.d.ts +1 -1
  16. package/dist/components/rigidbody-component.d.ts +1 -1
  17. package/dist/components/screen-component.d.ts +1 -1
  18. package/dist/components/script.d.ts +8 -1
  19. package/dist/components/scrollbar-component.d.ts +1 -1
  20. package/dist/components/scrollview-component.d.ts +1 -1
  21. package/dist/components/sound-component.d.ts +1 -1
  22. package/dist/components/sound-slot.d.ts +9 -1
  23. package/dist/custom-elements.json +16704 -0
  24. package/dist/entity.d.ts +24 -5
  25. package/dist/loading-bar.d.ts +35 -0
  26. package/dist/material.d.ts +972 -4
  27. package/dist/model.d.ts +1 -1
  28. package/dist/module.d.ts +10 -0
  29. package/dist/{utils.d.ts → parse.d.ts} +63 -33
  30. package/dist/pwc.cjs +3070 -699
  31. package/dist/pwc.cjs.map +1 -1
  32. package/dist/pwc.js +3070 -699
  33. package/dist/pwc.js.map +1 -1
  34. package/dist/pwc.min.js +1 -1
  35. package/dist/pwc.min.js.map +1 -1
  36. package/dist/pwc.min.mjs +2 -0
  37. package/dist/pwc.min.mjs.map +1 -0
  38. package/dist/pwc.mjs +3071 -700
  39. package/dist/pwc.mjs.map +1 -1
  40. package/dist/scene.d.ts +12 -4
  41. package/dist/sky.d.ts +1 -1
  42. package/dist/vscode.html-custom-data.json +1800 -0
  43. package/dist/web-types.json +3836 -0
  44. package/package.json +29 -11
  45. package/src/app.ts +178 -78
  46. package/src/asset.ts +44 -2
  47. package/src/async-element.ts +17 -4
  48. package/src/components/button-component.ts +6 -6
  49. package/src/components/camera-component.ts +2 -2
  50. package/src/components/collision-component.ts +2 -2
  51. package/src/components/component.ts +8 -7
  52. package/src/components/element-component.ts +6 -6
  53. package/src/components/gsplat-component.ts +3 -3
  54. package/src/components/layoutchild-component.ts +2 -2
  55. package/src/components/layoutgroup-component.ts +2 -2
  56. package/src/components/light-component.ts +2 -2
  57. package/src/components/particlesystem-component.ts +2 -2
  58. package/src/components/render-component.ts +10 -5
  59. package/src/components/rigidbody-component.ts +2 -2
  60. package/src/components/screen-component.ts +2 -2
  61. package/src/components/script-component.ts +4 -4
  62. package/src/components/script.ts +9 -2
  63. package/src/components/scrollbar-component.ts +3 -3
  64. package/src/components/scrollview-component.ts +6 -6
  65. package/src/components/sound-component.ts +2 -2
  66. package/src/components/sound-slot.ts +31 -9
  67. package/src/entity.ts +56 -22
  68. package/src/loading-bar.ts +122 -0
  69. package/src/material.ts +2402 -59
  70. package/src/model.ts +2 -2
  71. package/src/module.ts +10 -0
  72. package/src/{utils.ts → parse.ts} +104 -65
  73. package/src/scene.ts +51 -21
  74. package/src/sky.ts +3 -3
package/dist/pwc.cjs CHANGED
@@ -4,8 +4,13 @@ var playcanvas = require('playcanvas');
4
4
 
5
5
  /**
6
6
  * Base class for all PlayCanvas Web Components that initialize asynchronously.
7
+ *
8
+ * @fires {CustomEvent} ready - Fired once the element is fully initialized. Bubbles and is
9
+ * composed.
7
10
  */
8
11
  class AsyncElement extends HTMLElement {
12
+ _readyPromise;
13
+ _readyResolve;
9
14
  /** @ignore */
10
15
  constructor() {
11
16
  super();
@@ -13,13 +18,21 @@ class AsyncElement extends HTMLElement {
13
18
  this._readyResolve = resolve;
14
19
  });
15
20
  }
21
+ /**
22
+ * The nearest ancestor `<pc-app>` element, or `null` if this element has no `<pc-app>`
23
+ * ancestor. The search starts at the parent, so an element never resolves to itself.
24
+ * @returns The closest app element, or `null`.
25
+ */
16
26
  get closestApp() {
17
- var _a;
18
- return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-app');
27
+ return this.parentElement?.closest('pc-app') ?? null;
19
28
  }
29
+ /**
30
+ * The nearest ancestor `<pc-entity>` element, or `null` if this element has no `<pc-entity>`
31
+ * ancestor. The search starts at the parent, so an element never resolves to itself.
32
+ * @returns The closest entity element, or `null`.
33
+ */
20
34
  get closestEntity() {
21
- var _a;
22
- return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-entity');
35
+ return this.parentElement?.closest('pc-entity') ?? null;
23
36
  }
24
37
  /**
25
38
  * Called when the element is fully initialized and ready. Subclasses should call this when
@@ -50,7 +63,7 @@ async function whenReady(target) {
50
63
  try {
51
64
  element = document.querySelector(target);
52
65
  }
53
- catch (_a) {
66
+ catch {
54
67
  throw new Error(`whenReady: '${target}' is not a valid CSS selector`);
55
68
  }
56
69
  if (!element) {
@@ -73,8 +86,19 @@ async function whenReady(target) {
73
86
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements.
74
87
  * The ModuleElement interface also inherits the properties and methods of the
75
88
  * {@link HTMLElement} interface.
89
+ *
90
+ * Note that these attributes are read once when the element is created, so changing them later
91
+ * has no effect.
92
+ *
93
+ * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
94
+ * `Ammo`.
95
+ * @attribute {string} glue - The URL of the module's glue script.
96
+ * @attribute {string} wasm - The URL of the module's WebAssembly binary.
97
+ * @attribute {string} fallback - The URL of the module's asm.js fallback script, used when
98
+ * WebAssembly is unavailable.
76
99
  */
77
100
  class ModuleElement extends HTMLElement {
101
+ loadPromise;
78
102
  /** @ignore */
79
103
  constructor() {
80
104
  super();
@@ -102,6 +126,116 @@ class ModuleElement extends HTMLElement {
102
126
  }
103
127
  customElements.define('pc-module', ModuleElement);
104
128
 
129
+ /** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
130
+ const REMOVAL_DELAY_MS = 250;
131
+ /**
132
+ * The slim progress bar `<pc-app>` shows while it boots and preloads. An implementation detail of
133
+ * AppElement rather than a custom element, so its shape can change without a breaking change.
134
+ *
135
+ * All styling is inline, so the library injects no stylesheet. The colors and height resolve CSS
136
+ * custom properties — `--pc-loading-bar-color`, `--pc-loading-bar-background` and
137
+ * `--pc-loading-bar-height` — so a page can theme the bar from `pc-app` or `:root`.
138
+ */
139
+ class LoadingBar {
140
+ _track;
141
+ _fill;
142
+ _sweep = null;
143
+ _removal = null;
144
+ /**
145
+ * Creates the bar and appends it to `parent`, starting in the indeterminate state.
146
+ * @param parent - The element to append the bar to.
147
+ */
148
+ constructor(parent) {
149
+ this._track = document.createElement('div');
150
+ this._track.setAttribute('role', 'progressbar');
151
+ this._track.setAttribute('aria-label', 'Loading');
152
+ this._track.setAttribute('aria-valuemin', '0');
153
+ this._track.setAttribute('aria-valuemax', '100');
154
+ // Fixed positioning matches the canvas, which always fills the window (FILLMODE_FILL_WINDOW)
155
+ this._track.style.cssText = [
156
+ 'position: fixed',
157
+ 'top: 0',
158
+ 'left: 0',
159
+ 'width: 100%',
160
+ 'height: var(--pc-loading-bar-height, 3px)',
161
+ 'background: var(--pc-loading-bar-background, rgba(0, 0, 0, 0.1))',
162
+ 'z-index: 10000',
163
+ 'pointer-events: none',
164
+ 'opacity: 1',
165
+ 'transition: opacity 0.2s ease'
166
+ ].join('; ');
167
+ this._fill = document.createElement('div');
168
+ this._fill.style.cssText = [
169
+ 'width: 100%',
170
+ 'height: 100%',
171
+ 'transform-origin: left center',
172
+ 'transform: scaleX(0)',
173
+ 'background: var(--pc-loading-bar-color, #f60)',
174
+ 'transition: transform 0.2s ease'
175
+ ].join('; ');
176
+ this._track.appendChild(this._fill);
177
+ parent.appendChild(this._track);
178
+ // Indeterminate sweep until the first progress() call reports a real total. No
179
+ // aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
180
+ // Animations API, so the guard degrades to a static bar there rather than crashing boot.
181
+ if (typeof this._fill.animate === 'function') {
182
+ this._sweep = this._fill.animate([
183
+ { transform: 'scaleX(0.25) translateX(-100%)' },
184
+ { transform: 'scaleX(0.25) translateX(500%)' }
185
+ ], {
186
+ duration: 1000,
187
+ iterations: Infinity,
188
+ easing: 'ease-in-out'
189
+ });
190
+ }
191
+ }
192
+ /**
193
+ * Reflects preload progress, switching the bar from indeterminate to determinate on the first
194
+ * call.
195
+ * @param loaded - The number of assets that have finished loading.
196
+ * @param total - The number of assets being preloaded.
197
+ */
198
+ progress(loaded, total) {
199
+ if (this._sweep) {
200
+ this._sweep.cancel();
201
+ this._sweep = null;
202
+ }
203
+ const fraction = total === 0 ? 1 : loaded / total;
204
+ this._track.setAttribute('aria-valuenow', String(Math.round(fraction * 100)));
205
+ this._fill.style.transform = `scaleX(${fraction})`;
206
+ }
207
+ /**
208
+ * Fills the bar, fades it out and removes it. Idempotent.
209
+ */
210
+ complete() {
211
+ if (this._removal !== null) {
212
+ return;
213
+ }
214
+ if (this._sweep) {
215
+ this._sweep.cancel();
216
+ this._sweep = null;
217
+ }
218
+ this._track.setAttribute('aria-valuenow', '100');
219
+ this._fill.style.transform = 'scaleX(1)';
220
+ this._track.style.opacity = '0';
221
+ this._removal = setTimeout(() => this._track.remove(), REMOVAL_DELAY_MS);
222
+ }
223
+ /**
224
+ * Removes the bar immediately, cancelling any pending fade. Idempotent.
225
+ */
226
+ destroy() {
227
+ if (this._sweep) {
228
+ this._sweep.cancel();
229
+ this._sweep = null;
230
+ }
231
+ if (this._removal !== null) {
232
+ clearTimeout(this._removal);
233
+ this._removal = null;
234
+ }
235
+ this._track.remove();
236
+ }
237
+ }
238
+
105
239
  const CSS_COLORS = {
106
240
  aliceblue: '#f0f8ff',
107
241
  antiquewhite: '#faebd7',
@@ -254,20 +388,22 @@ const CSS_COLORS = {
254
388
  };
255
389
 
256
390
  /**
257
- * Parse a boolean attribute value. The same rules apply to every boolean attribute:
391
+ * Converts HTML attribute values into the values the engine expects. Every element's
392
+ * `attributeChangedCallback` funnels through this module.
258
393
  *
259
- * - Attribute absent (or removed): the supplied default is used.
260
- * - Attribute set to the string 'false': `false`.
261
- * - Attribute present with any other value, including the empty string of a bare boolean
262
- * attribute (e.g. `<pc-light cast-shadows>`): `true`.
394
+ * The parsers share one contract:
263
395
  *
264
- * @param value - The attribute value to parse (`null` when the attribute is absent).
265
- * @param defaultValue - The value to use when the attribute is absent or removed.
266
- * @returns The parsed boolean.
396
+ * - A `null` value means the attribute is absent or was removed, and yields the supplied default.
397
+ * - A malformed value yields the same default and logs exactly one `console.warn` naming the
398
+ * attribute, so misuse is reported rather than thrown — nothing here throws or rejects.
399
+ * - A math-type default is cloned on the way out, which is what makes it safe to pass the engine's
400
+ * shared frozen constants (`Vec3.ZERO`, `Color.WHITE`) as defaults.
401
+ * - `parseBool` and `parseTags` take no attribute name, because every value is valid for them and
402
+ * so they never warn.
403
+ *
404
+ * `getEntity` is the exception: it resolves a reference to a live entity rather than parsing a
405
+ * literal, and returns `null` instead of falling back to a default.
267
406
  */
268
- const parseBool = (value, defaultValue) => {
269
- return value === null ? defaultValue : value !== 'false';
270
- };
271
407
  /**
272
408
  * Splits an attribute value into exactly `count` numeric components. Returns `null` when the
273
409
  * value does not consist of exactly `count` whitespace-separated finite numbers.
@@ -295,6 +431,21 @@ const parseComponents = (value, count) => {
295
431
  const cloneDefault = (value) => {
296
432
  return (value === null ? null : value.clone());
297
433
  };
434
+ /**
435
+ * Parse a boolean attribute value. The same rules apply to every boolean attribute:
436
+ *
437
+ * - Attribute absent (or removed): the supplied default is used.
438
+ * - Attribute set to the string 'false': `false`.
439
+ * - Attribute present with any other value, including the empty string of a bare boolean
440
+ * attribute (e.g. `<pc-light cast-shadows>`): `true`.
441
+ *
442
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
443
+ * @param defaultValue - The value to use when the attribute is absent or removed.
444
+ * @returns The parsed boolean.
445
+ */
446
+ const parseBool = (value, defaultValue) => {
447
+ return value === null ? defaultValue : value !== 'false';
448
+ };
298
449
  /**
299
450
  * Parse a color attribute value. The expected format is a CSS color name (e.g. 'rebeccapurple'),
300
451
  * a hex color (e.g. '#ff0000' or '#f00'), or 3 or 4 space-separated numbers in the range 0 to 1
@@ -308,7 +459,6 @@ const cloneDefault = (value) => {
308
459
  * @returns The parsed Color object.
309
460
  */
310
461
  const parseColor = (value, defaultValue, attribute) => {
311
- var _a;
312
462
  if (value === null) {
313
463
  return cloneDefault(defaultValue);
314
464
  }
@@ -326,13 +476,56 @@ const parseColor = (value, defaultValue, attribute) => {
326
476
  return new playcanvas.Color().fromString(`#${hex}`);
327
477
  }
328
478
  // 3 or 4 space-separated components (e.g. '1 0.5 0.5')
329
- const components = (_a = parseComponents(value, 4)) !== null && _a !== void 0 ? _a : parseComponents(value, 3);
479
+ const components = parseComponents(value, 4) ?? parseComponents(value, 3);
330
480
  if (components) {
331
481
  return new playcanvas.Color(components);
332
482
  }
333
483
  console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a CSS color name, a hex color or 3 or 4 space-separated numbers. Using '${defaultValue}'.`);
334
484
  return cloneDefault(defaultValue);
335
485
  };
486
+ /**
487
+ * Resolves an enum attribute value against its set of valid names. Returns the value when it is
488
+ * one of the valid names. Returns `defaultValue` when the attribute is absent (`null`), or when
489
+ * the value is invalid — the latter also logs a warning listing the valid names.
490
+ *
491
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
492
+ * @param valid - The valid names: an array, or a map whose keys are the valid names.
493
+ * @param defaultValue - The value to use when the attribute is absent or invalid.
494
+ * @param attribute - The attribute name, used in the warning message.
495
+ * @returns The resolved enum name.
496
+ */
497
+ const parseEnum = (value, valid, defaultValue, attribute) => {
498
+ if (value === null) {
499
+ return defaultValue;
500
+ }
501
+ const names = Array.isArray(valid) ? valid : [...valid.keys()];
502
+ if (names.includes(value)) {
503
+ return value;
504
+ }
505
+ console.warn(`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`);
506
+ return defaultValue;
507
+ };
508
+ /**
509
+ * Parses a number attribute value. Returns the parsed number when the value is a finite number.
510
+ * Returns `defaultValue` when the attribute is absent (`null`), or when the value is not a
511
+ * finite number — the latter also logs a warning.
512
+ *
513
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
514
+ * @param defaultValue - The value to use when the attribute is absent or invalid.
515
+ * @param attribute - The attribute name, used in the warning message.
516
+ * @returns The parsed number.
517
+ */
518
+ const parseNumber = (value, defaultValue, attribute) => {
519
+ if (value === null) {
520
+ return defaultValue;
521
+ }
522
+ const number = value.trim() === '' ? NaN : Number(value);
523
+ if (!Number.isFinite(number)) {
524
+ console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`);
525
+ return defaultValue;
526
+ }
527
+ return number;
528
+ };
336
529
  /**
337
530
  * Parse an Euler-angles attribute value into a quaternion. The expected format is 3
338
531
  * space-separated angles in degrees (e.g. '0 90 0'). Returns `defaultValue` (cloned, when it is
@@ -355,6 +548,26 @@ const parseQuat = (value, defaultValue, attribute) => {
355
548
  }
356
549
  return new playcanvas.Quat().setFromEulerAngles(components[0], components[1], components[2]);
357
550
  };
551
+ /**
552
+ * Parse a tags attribute value. The expected format is a comma-separated list of tag names
553
+ * (e.g. 'enemy, flying'). Surrounding whitespace is trimmed from each name and empty names are
554
+ * discarded, so a trailing comma or a doubled separator does not produce a blank tag. Returns a
555
+ * copy of `defaultValue` when the attribute is absent or removed (`null`).
556
+ *
557
+ * Every value is valid, so this never warns.
558
+ *
559
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
560
+ * @param defaultValue - The value to use when the attribute is absent or removed.
561
+ * @returns The parsed tag names.
562
+ */
563
+ const parseTags = (value, defaultValue = []) => {
564
+ if (value === null) {
565
+ // Copied for the same reason cloneDefault exists: a parsed result must never alias the
566
+ // caller's default, or a later mutation would write back through it.
567
+ return [...defaultValue];
568
+ }
569
+ return value.split(',').map(tag => tag.trim()).filter(tag => tag !== '');
570
+ };
358
571
  /**
359
572
  * Parse a Vec2 attribute value. The expected format is 2 space-separated numbers (e.g. '1 2').
360
573
  * Returns `defaultValue` (cloned, when it is a vector) when the attribute is absent (`null`),
@@ -418,49 +631,6 @@ const parseVec4 = (value, defaultValue, attribute) => {
418
631
  }
419
632
  return new playcanvas.Vec4(components);
420
633
  };
421
- /**
422
- * Resolves an enum attribute value against its set of valid names. Returns the value when it is
423
- * one of the valid names. Returns `defaultValue` when the attribute is absent (`null`), or when
424
- * the value is invalid — the latter also logs a warning listing the valid names.
425
- *
426
- * @param value - The attribute value to parse (`null` when the attribute is absent).
427
- * @param valid - The valid names: an array, or a map whose keys are the valid names.
428
- * @param defaultValue - The value to use when the attribute is absent or invalid.
429
- * @param attribute - The attribute name, used in the warning message.
430
- * @returns The resolved enum name.
431
- */
432
- const parseEnum = (value, valid, defaultValue, attribute) => {
433
- if (value === null) {
434
- return defaultValue;
435
- }
436
- const names = Array.isArray(valid) ? valid : [...valid.keys()];
437
- if (names.includes(value)) {
438
- return value;
439
- }
440
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`);
441
- return defaultValue;
442
- };
443
- /**
444
- * Parses a number attribute value. Returns the parsed number when the value is a finite number.
445
- * Returns `defaultValue` when the attribute is absent (`null`), or when the value is not a
446
- * finite number — the latter also logs a warning.
447
- *
448
- * @param value - The attribute value to parse (`null` when the attribute is absent).
449
- * @param defaultValue - The value to use when the attribute is absent or invalid.
450
- * @param attribute - The attribute name, used in the warning message.
451
- * @returns The parsed number.
452
- */
453
- const parseNumber = (value, defaultValue, attribute) => {
454
- if (value === null) {
455
- return defaultValue;
456
- }
457
- const number = value.trim() === '' ? NaN : Number(value);
458
- if (!Number.isFinite(number)) {
459
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`);
460
- return defaultValue;
461
- }
462
- return number;
463
- };
464
634
  /**
465
635
  * Resolves a reference string to the {@link Entity} backing a `<pc-entity>` element. The reference
466
636
  * can be a CSS selector (e.g. `#my-id`, `pc-entity[name="Foo"]`), a bare element id, or a bare
@@ -470,7 +640,6 @@ const parseNumber = (value, defaultValue, attribute) => {
470
640
  * @returns The resolved entity, or `null`.
471
641
  */
472
642
  const getEntity = (ref) => {
473
- var _a, _b;
474
643
  if (!ref) {
475
644
  return null;
476
645
  }
@@ -480,13 +649,13 @@ const getEntity = (ref) => {
480
649
  try {
481
650
  element = document.querySelector(ref);
482
651
  }
483
- catch (_c) {
652
+ catch {
484
653
  element = null;
485
654
  }
486
655
  if (!element) {
487
- element = (_a = document.getElementById(ref)) !== null && _a !== void 0 ? _a : document.querySelector(`pc-entity[name="${ref}"]`);
656
+ element = document.getElementById(ref) ?? document.querySelector(`pc-entity[name="${ref}"]`);
488
657
  }
489
- return (_b = element === null || element === void 0 ? void 0 : element.entity) !== null && _b !== void 0 ? _b : null;
658
+ return element?.entity ?? null;
490
659
  };
491
660
 
492
661
  /**
@@ -494,16 +663,63 @@ const getEntity = (ref) => {
494
663
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
495
664
  * The AppElement interface also inherits the properties and methods of the
496
665
  * {@link HTMLElement} interface.
666
+ *
667
+ * @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
668
+ * `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
669
+ * Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
670
+ * not bubble.
497
671
  */
498
672
  class AppElement extends AsyncElement {
499
673
  /**
500
- * The PlayCanvas application instance. Available once the element is ready — await
501
- * {@link whenReady} or the element's `ready()` promise before accessing it.
502
- * @returns The application instance.
674
+ * The canvas element.
675
+ */
676
+ _canvas = null;
677
+ _alpha = true;
678
+ _backend = 'webgpu';
679
+ _antialias = true;
680
+ _depth = true;
681
+ _stencil = true;
682
+ _highResolution = true;
683
+ _loadingBar = true;
684
+ _bar = null;
685
+ _hierarchyReady = false;
686
+ _picker = null;
687
+ _hasPointerListeners = {
688
+ pointerenter: false,
689
+ pointerleave: false,
690
+ pointerdown: false,
691
+ pointerup: false,
692
+ pointermove: false
693
+ };
694
+ _hoveredEntity = null;
695
+ // Identifies the newest in-flight hover pick, so out-of-order results can be discarded
696
+ _pickToken = 0;
697
+ _pointerHandlers = {
698
+ pointermove: null,
699
+ pointerdown: null,
700
+ pointerup: null
701
+ };
702
+ _app = null;
703
+ _loadProgress = 0;
704
+ /**
705
+ * The PlayCanvas application instance. `null` until the element is ready, and again once it
706
+ * has been removed from the document — await {@link whenReady} or the element's `ready()`
707
+ * promise before accessing it.
708
+ * @returns The application instance, or `null`.
503
709
  */
504
710
  get app() {
505
711
  return this._app;
506
712
  }
713
+ /**
714
+ * The asset preload progress of the application, as a fraction from 0 to 1. It is 0 until
715
+ * preloading begins (and again once the element has been removed from the document), and 1
716
+ * once preloading has finished — including when there was nothing to preload. Read this to
717
+ * initialize a loading UI; subsequent updates arrive via the `progress` event.
718
+ * @returns The preload progress.
719
+ */
720
+ get loadProgress() {
721
+ return this._loadProgress;
722
+ }
507
723
  /**
508
724
  * Creates a new AppElement instance.
509
725
  *
@@ -511,36 +727,15 @@ class AppElement extends AsyncElement {
511
727
  */
512
728
  constructor() {
513
729
  super();
514
- /**
515
- * The canvas element.
516
- */
517
- this._canvas = null;
518
- this._alpha = true;
519
- this._backend = 'webgl2';
520
- this._antialias = true;
521
- this._depth = true;
522
- this._stencil = true;
523
- this._highResolution = true;
524
- this._hierarchyReady = false;
525
- this._picker = null;
526
- this._hasPointerListeners = {
527
- pointerenter: false,
528
- pointerleave: false,
529
- pointerdown: false,
530
- pointerup: false,
531
- pointermove: false
532
- };
533
- this._hoveredEntity = null;
534
- this._pointerHandlers = {
535
- pointermove: null,
536
- pointerdown: null,
537
- pointerup: null
538
- };
539
- this._app = null;
540
730
  // Bind methods to maintain 'this' context
541
731
  this._onWindowResize = this._onWindowResize.bind(this);
542
732
  }
543
733
  async connectedCallback() {
734
+ // Created before the first await, so the bar is visible while modules and the graphics
735
+ // device are created, and exists before any disconnect could need to clean it up
736
+ if (this._loadingBar && !this._bar) {
737
+ this._bar = new LoadingBar(this);
738
+ }
544
739
  // Get all pc-module elements that are direct children of the pc-app element
545
740
  const moduleElements = this.querySelectorAll(':scope > pc-module');
546
741
  // Wait for all modules to load
@@ -628,10 +823,11 @@ class AppElement extends AsyncElement {
628
823
  createOptions.lightmapper = playcanvas.Lightmapper;
629
824
  createOptions.batchManager = playcanvas.BatchManager;
630
825
  createOptions.xr = playcanvas.XrManager;
631
- this._app = new playcanvas.AppBase(this._canvas);
632
- this.app.init(createOptions);
633
- this.app.setCanvasFillMode(playcanvas.FILLMODE_FILL_WINDOW);
634
- this.app.setCanvasResolution(playcanvas.RESOLUTION_AUTO);
826
+ const app = new playcanvas.AppBase(this._canvas);
827
+ this._app = app;
828
+ app.init(createOptions);
829
+ app.setCanvasFillMode(playcanvas.FILLMODE_FILL_WINDOW);
830
+ app.setCanvasResolution(playcanvas.RESOLUTION_AUTO);
635
831
  this._pickerCreate();
636
832
  // Get all pc-asset elements that are direct children of the pc-app element
637
833
  const assetElements = this.querySelectorAll(':scope > pc-asset');
@@ -639,7 +835,7 @@ class AppElement extends AsyncElement {
639
835
  assetElement.createAsset();
640
836
  const asset = assetElement.asset;
641
837
  if (asset) {
642
- this.app.assets.add(asset);
838
+ app.assets.add(asset);
643
839
  }
644
840
  });
645
841
  // Get all pc-material elements that are direct children of the pc-app element
@@ -650,17 +846,39 @@ class AppElement extends AsyncElement {
650
846
  // Create all entities
651
847
  const entityElements = this.querySelectorAll('pc-entity');
652
848
  Array.from(entityElements).forEach((entityElement) => {
653
- entityElement.createEntity(this.app);
849
+ entityElement.createEntity(app);
654
850
  });
655
851
  // Build hierarchy
656
852
  entityElements.forEach((entityElement) => {
657
- entityElement.buildHierarchy(this.app);
853
+ entityElement.buildHierarchy(app);
658
854
  });
659
855
  this._hierarchyReady = true;
856
+ // Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
857
+ // listener must be attached before preload() is called: an asset that is already loaded
858
+ // ticks synchronously inside it.
859
+ const total = app.assets.list({ preload: true }).length;
860
+ let loaded = 0;
861
+ const onPreloadProgress = () => {
862
+ loaded += 1;
863
+ this._loadProgress = loaded / total;
864
+ this._bar?.progress(loaded, total);
865
+ this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded, total }));
866
+ };
867
+ app.on('preload:progress', onPreloadProgress);
868
+ this._loadProgress = total === 0 ? 1 : 0;
869
+ this._bar?.progress(0, total);
870
+ this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
660
871
  // Load assets before starting the application
661
- this.app.preload(() => {
872
+ app.preload(() => {
873
+ // Scope the counter to this preload pass, so a later app.preload() call by user code
874
+ // cannot push `loaded` past `total`
875
+ app.off('preload:progress', onPreloadProgress);
876
+ this._loadProgress = 1;
662
877
  // Start the application
663
- this.app.start();
878
+ app.start();
879
+ // Dismiss the bar only once a frame has actually rendered; ready fires before the
880
+ // first rAF tick
881
+ app.once('frameend', () => this._bar?.complete());
664
882
  // Handle window resize to keep the canvas responsive
665
883
  window.addEventListener('resize', this._onWindowResize);
666
884
  this._onReady();
@@ -669,10 +887,13 @@ class AppElement extends AsyncElement {
669
887
  disconnectedCallback() {
670
888
  this._pickerDestroy();
671
889
  // Clean up the application
672
- if (this.app) {
673
- this.app.destroy();
890
+ if (this._app) {
891
+ this._app.destroy();
674
892
  this._app = null;
675
893
  }
894
+ this._loadProgress = 0;
895
+ this._bar?.destroy();
896
+ this._bar = null;
676
897
  // Remove event listeners
677
898
  window.removeEventListener('resize', this._onWindowResize);
678
899
  // Remove the canvas
@@ -689,10 +910,17 @@ class AppElement extends AsyncElement {
689
910
  _pickerCreate() {
690
911
  const { width, height } = this.app.graphicsDevice;
691
912
  this._picker = new playcanvas.Picker(this.app, width, height);
692
- // Create bound handlers but don't attach them yet
693
- this._pointerHandlers.pointermove = this._onPointerMove.bind(this);
694
- this._pointerHandlers.pointerdown = this._onPointerDown.bind(this);
695
- this._pointerHandlers.pointerup = this._onPointerUp.bind(this);
913
+ // Create bound handlers but don't attach them yet. The handlers pick asynchronously, so
914
+ // each is wrapped to discard the promise - a listener must not return one, and nothing
915
+ // awaits the result.
916
+ const listener = (handler) => {
917
+ return (event) => {
918
+ handler.call(this, event);
919
+ };
920
+ };
921
+ this._pointerHandlers.pointermove = listener(this._onPointerMove);
922
+ this._pointerHandlers.pointerdown = listener(this._onPointerDown);
923
+ this._pointerHandlers.pointerup = listener(this._onPointerUp);
696
924
  // Listen for pointer listeners being added/removed
697
925
  ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'].forEach((type) => {
698
926
  this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
@@ -740,29 +968,49 @@ class AppElement extends AsyncElement {
740
968
  const y = (event.clientY - canvasRect.top) * scaleY;
741
969
  return { x, y };
742
970
  }
743
- _onPointerMove(event) {
744
- if (!this._picker || !this.app)
745
- return;
971
+ /**
972
+ * Picks the scene under the pointer and returns the graph node that was hit, or `null`.
973
+ *
974
+ * The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
975
+ * supported on WebGPU, where it returns an empty selection rather than failing - which
976
+ * silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
977
+ * async variant works on both backends and does not block the main thread on a GPU read.
978
+ *
979
+ * @param event - The pointer event to pick under.
980
+ * @returns The graph node under the pointer, or `null` if nothing was hit.
981
+ */
982
+ async _pickNode(event) {
746
983
  const camera = this.app.root.findComponent('camera');
747
984
  if (!camera)
748
- return;
749
- // Use the helper to convert event coordinates into canvas/picker coordinates.
985
+ return null;
750
986
  const { x, y } = this._getPickerCoordinates(event);
751
987
  this._picker.prepare(camera, this.app.scene);
752
- const selection = this._picker.getSelection(x, y);
988
+ const selection = await this._picker.getSelectionAsync(x, y);
989
+ if (selection.length === 0)
990
+ return null;
991
+ const item = selection[0];
992
+ return item instanceof playcanvas.MeshInstance ? item.node : item.entity;
993
+ }
994
+ async _onPointerMove(event) {
995
+ if (!this._picker || !this.app)
996
+ return;
997
+ // Moves arrive faster than a pick resolves, so results can land out of order. Only the
998
+ // newest pick may update the hover state - an older one describes a pointer position the
999
+ // user has already left.
1000
+ const token = ++this._pickToken;
1001
+ const node = await this._pickNode(event);
1002
+ if (token !== this._pickToken || !this._picker)
1003
+ return;
753
1004
  // Get the currently hovered entity by walking up the hierarchy
754
1005
  let newHoverEntity = null;
755
- if (selection.length > 0) {
756
- const item = selection[0];
757
- let currentNode = item instanceof playcanvas.MeshInstance ? item.node : item.entity;
758
- while (currentNode !== null) {
759
- const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
760
- if (entityElement) {
761
- newHoverEntity = entityElement;
762
- break;
763
- }
764
- currentNode = currentNode.parent;
1006
+ let currentNode = node;
1007
+ while (currentNode !== null) {
1008
+ const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
1009
+ if (entityElement) {
1010
+ newHoverEntity = entityElement;
1011
+ break;
765
1012
  }
1013
+ currentNode = currentNode.parent;
766
1014
  }
767
1015
  // Handle enter/leave events
768
1016
  if (this._hoveredEntity !== newHoverEntity) {
@@ -780,46 +1028,30 @@ class AppElement extends AsyncElement {
780
1028
  newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
781
1029
  }
782
1030
  }
783
- _onPointerDown(event) {
1031
+ async _onPointerDown(event) {
784
1032
  if (!this._picker || !this.app)
785
1033
  return;
786
- const camera = this.app.root.findComponent('camera');
787
- if (!camera)
788
- return;
789
- // Convert the event's pointer coordinates
790
- const { x, y } = this._getPickerCoordinates(event);
791
- this._picker.prepare(camera, this.app.scene);
792
- const selection = this._picker.getSelection(x, y);
793
- if (selection.length > 0) {
794
- const item = selection[0];
795
- let currentNode = item instanceof playcanvas.MeshInstance ? item.node : item.entity;
796
- while (currentNode !== null) {
797
- const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
798
- if (entityElement && entityElement.hasListeners('pointerdown')) {
799
- entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
800
- break;
801
- }
802
- currentNode = currentNode.parent;
1034
+ let currentNode = await this._pickNode(event);
1035
+ if (!this._picker)
1036
+ return; // the element disconnected while the pick was in flight
1037
+ while (currentNode !== null) {
1038
+ const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`);
1039
+ if (entityElement && entityElement.hasListeners('pointerdown')) {
1040
+ entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
1041
+ break;
803
1042
  }
1043
+ currentNode = currentNode.parent;
804
1044
  }
805
1045
  }
806
- _onPointerUp(event) {
1046
+ async _onPointerUp(event) {
807
1047
  if (!this._picker || !this.app)
808
1048
  return;
809
- const camera = this.app.root.findComponent('camera');
810
- if (!camera)
1049
+ const node = await this._pickNode(event);
1050
+ if (!node || !this._picker)
811
1051
  return;
812
- // Convert CSS coordinates to picker coordinates
813
- const { x, y } = this._getPickerCoordinates(event);
814
- this._picker.prepare(camera, this.app.scene);
815
- const selection = this._picker.getSelection(x, y);
816
- if (selection.length > 0) {
817
- const item = selection[0];
818
- const node = item instanceof playcanvas.MeshInstance ? item.node : item.entity;
819
- const entityElement = this.querySelector(`pc-entity[name="${node.name}"]`);
820
- if (entityElement && entityElement.hasListeners('pointerup')) {
821
- entityElement.dispatchEvent(new PointerEvent('pointerup', event));
822
- }
1052
+ const entityElement = this.querySelector(`pc-entity[name="${node.name}"]`);
1053
+ if (entityElement && entityElement.hasListeners('pointerup')) {
1054
+ entityElement.dispatchEvent(new PointerEvent('pointerup', event));
823
1055
  }
824
1056
  }
825
1057
  _onPointerListenerAdded(type) {
@@ -876,7 +1108,8 @@ class AppElement extends AsyncElement {
876
1108
  return this._antialias;
877
1109
  }
878
1110
  /**
879
- * Sets the graphics backend.
1111
+ * Sets the graphics backend. Defaults to 'webgpu', which falls back to 'webgl2' if WebGPU
1112
+ * is not supported by the browser.
880
1113
  * @param value - The graphics backend ('webgpu', 'webgl2', or 'null').
881
1114
  */
882
1115
  set backend(value) {
@@ -929,6 +1162,29 @@ class AppElement extends AsyncElement {
929
1162
  get highResolution() {
930
1163
  return this._highResolution;
931
1164
  }
1165
+ /**
1166
+ * Sets whether the application shows its built-in loading bar while it boots and preloads its
1167
+ * assets. Enabled by default; setting `false` removes the bar immediately, while setting
1168
+ * `true` has no effect until the element is next connected. The bar can be themed with the
1169
+ * CSS custom properties `--pc-loading-bar-color`, `--pc-loading-bar-background` and
1170
+ * `--pc-loading-bar-height`.
1171
+ * @param value - The loading bar flag.
1172
+ */
1173
+ set loadingBar(value) {
1174
+ this._loadingBar = value;
1175
+ if (!value && this._bar) {
1176
+ this._bar.destroy();
1177
+ this._bar = null;
1178
+ }
1179
+ }
1180
+ /**
1181
+ * Gets whether the application shows its built-in loading bar while it boots and preloads
1182
+ * its assets.
1183
+ * @returns The loading bar flag.
1184
+ */
1185
+ get loadingBar() {
1186
+ return this._loadingBar;
1187
+ }
932
1188
  /**
933
1189
  * Sets the stencil flag.
934
1190
  * @param value - The stencil flag.
@@ -944,7 +1200,7 @@ class AppElement extends AsyncElement {
944
1200
  return this._stencil;
945
1201
  }
946
1202
  static get observedAttributes() {
947
- return ['alpha', 'antialias', 'backend', 'depth', 'stencil', 'high-resolution'];
1203
+ return ['alpha', 'antialias', 'backend', 'depth', 'stencil', 'high-resolution', 'loading-bar'];
948
1204
  }
949
1205
  attributeChangedCallback(name, _oldValue, newValue) {
950
1206
  switch (name) {
@@ -955,7 +1211,7 @@ class AppElement extends AsyncElement {
955
1211
  this.antialias = parseBool(newValue, true);
956
1212
  break;
957
1213
  case 'backend':
958
- this.backend = parseEnum(newValue, ['webgpu', 'webgl2', 'null'], 'webgl2', name);
1214
+ this.backend = parseEnum(newValue, ['webgpu', 'webgl2', 'null'], 'webgpu', name);
959
1215
  break;
960
1216
  case 'depth':
961
1217
  this.depth = parseBool(newValue, true);
@@ -963,6 +1219,9 @@ class AppElement extends AsyncElement {
963
1219
  case 'high-resolution':
964
1220
  this.highResolution = parseBool(newValue, true);
965
1221
  break;
1222
+ case 'loading-bar':
1223
+ this.loadingBar = parseBool(newValue, true);
1224
+ break;
966
1225
  case 'stencil':
967
1226
  this.stencil = parseBool(newValue, true);
968
1227
  break;
@@ -976,52 +1235,68 @@ customElements.define('pc-app', AppElement);
976
1235
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-entity/ | `<pc-entity>`} elements.
977
1236
  * The EntityElement interface also inherits the properties and methods of the
978
1237
  * {@link HTMLElement} interface.
1238
+ *
1239
+ * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
1240
+ * intersects this entity's geometry. They are only generated while the entity has a listener for
1241
+ * them, registered either with {@link addEventListener} or with the matching inline `onpointer*`
1242
+ * attribute.
1243
+ *
1244
+ * @attribute {string} onpointerenter - Script to run when the pointer moves onto the entity.
1245
+ * @attribute {string} onpointerleave - Script to run when the pointer moves off the entity.
1246
+ * @attribute {string} onpointermove - Script to run when the pointer moves over the entity.
1247
+ * @attribute {string} onpointerdown - Script to run when a pointer button is pressed over the
1248
+ * entity.
1249
+ * @attribute {string} onpointerup - Script to run when a pointer button is released over the
1250
+ * entity.
1251
+ * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
1252
+ * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
1253
+ * @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
1254
+ * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
1255
+ * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
979
1256
  */
980
1257
  class EntityElement extends AsyncElement {
981
- constructor() {
982
- super(...arguments);
983
- /**
984
- * Whether the entity is enabled.
985
- */
986
- this._enabled = true;
987
- /**
988
- * The name of the entity.
989
- */
990
- this._name = 'Untitled';
991
- /**
992
- * The position of the entity.
993
- */
994
- this._position = new playcanvas.Vec3();
995
- /**
996
- * The rotation of the entity.
997
- */
998
- this._rotation = new playcanvas.Vec3();
999
- /**
1000
- * The scale of the entity.
1001
- */
1002
- this._scale = new playcanvas.Vec3(1, 1, 1);
1003
- /**
1004
- * The tags of the entity.
1005
- */
1006
- this._tags = [];
1007
- /**
1008
- * The pointer event listeners for the entity.
1009
- */
1010
- this._listeners = {};
1011
- /**
1012
- * The event types for which an inline `onpointer*` attribute is currently present.
1013
- */
1014
- this._inlineHandlerTypes = new Set();
1015
- /**
1016
- * Whether the hierarchy has been built for this entity.
1017
- */
1018
- this._built = false;
1019
- this._entity = null;
1020
- }
1021
1258
  /**
1022
- * The PlayCanvas entity instance. Available once the element is ready — await
1023
- * {@link whenReady} or the element's `ready()` promise before accessing it.
1024
- * @returns The entity instance.
1259
+ * Whether the entity is enabled.
1260
+ */
1261
+ _enabled = true;
1262
+ /**
1263
+ * The name of the entity.
1264
+ */
1265
+ _name = 'Untitled';
1266
+ /**
1267
+ * The position of the entity.
1268
+ */
1269
+ _position = new playcanvas.Vec3();
1270
+ /**
1271
+ * The rotation of the entity.
1272
+ */
1273
+ _rotation = new playcanvas.Vec3();
1274
+ /**
1275
+ * The scale of the entity.
1276
+ */
1277
+ _scale = new playcanvas.Vec3(1, 1, 1);
1278
+ /**
1279
+ * The tags of the entity.
1280
+ */
1281
+ _tags = [];
1282
+ /**
1283
+ * The pointer event listeners for the entity.
1284
+ */
1285
+ _listeners = {};
1286
+ /**
1287
+ * The event types for which an inline `onpointer*` attribute is currently present.
1288
+ */
1289
+ _inlineHandlerTypes = new Set();
1290
+ /**
1291
+ * Whether the hierarchy has been built for this entity.
1292
+ */
1293
+ _built = false;
1294
+ _entity = null;
1295
+ /**
1296
+ * The PlayCanvas entity instance. `null` until the element is ready, and again once it has
1297
+ * been removed from the document — await {@link whenReady} or the element's `ready()`
1298
+ * promise before accessing it.
1299
+ * @returns The entity instance, or `null`.
1025
1300
  */
1026
1301
  get entity() {
1027
1302
  return this._entity;
@@ -1033,16 +1308,18 @@ class EntityElement extends AsyncElement {
1033
1308
  if (this._entity) {
1034
1309
  return;
1035
1310
  }
1036
- // Create a new entity
1037
- const entity = new playcanvas.Entity(this.getAttribute('name') || this._name, app);
1311
+ // Seed from the cached fields rather than re-reading the attributes. Every observed
1312
+ // attribute is routed through its property setter by attributeChangedCallback, so the field
1313
+ // already holds the parsed attribute value - and it also holds anything assigned through the
1314
+ // property API before the app booted, which reading the attribute back would discard.
1315
+ const entity = new playcanvas.Entity(this._name, app);
1038
1316
  this._entity = entity;
1039
- entity.enabled = parseBool(this.getAttribute('enabled'), true);
1040
- entity.setLocalPosition(parseVec3(this.getAttribute('position'), playcanvas.Vec3.ZERO, 'position'));
1041
- entity.setLocalEulerAngles(parseVec3(this.getAttribute('rotation'), playcanvas.Vec3.ZERO, 'rotation'));
1042
- entity.setLocalScale(parseVec3(this.getAttribute('scale'), playcanvas.Vec3.ONE, 'scale'));
1043
- const tags = this.getAttribute('tags');
1044
- if (tags) {
1045
- entity.tags.add(tags.split(',').map(tag => tag.trim()));
1317
+ entity.enabled = this._enabled;
1318
+ entity.setLocalPosition(this._position);
1319
+ entity.setLocalEulerAngles(this._rotation);
1320
+ entity.setLocalScale(this._scale);
1321
+ if (this._tags.length > 0) {
1322
+ entity.tags.add(this._tags);
1046
1323
  }
1047
1324
  }
1048
1325
  buildHierarchy(app) {
@@ -1050,7 +1327,7 @@ class EntityElement extends AsyncElement {
1050
1327
  return;
1051
1328
  this._built = true;
1052
1329
  const closestEntity = this.closestEntity;
1053
- if (closestEntity === null || closestEntity === void 0 ? void 0 : closestEntity.entity) {
1330
+ if (closestEntity?.entity) {
1054
1331
  closestEntity.entity.addChild(this.entity);
1055
1332
  }
1056
1333
  else {
@@ -1061,8 +1338,15 @@ class EntityElement extends AsyncElement {
1061
1338
  connectedCallback() {
1062
1339
  // Wait for app to be ready
1063
1340
  const closestApp = this.closestApp;
1064
- if (!closestApp)
1341
+ if (!closestApp) {
1342
+ // An entity outside an application is inert and never becomes ready, so awaiting it
1343
+ // hangs. Warn rather than fail silently, naming the parent it requires, as every other
1344
+ // misplaced element does.
1345
+ const name = this.getAttribute('name');
1346
+ const label = name ? ` '${name}'` : '';
1347
+ console.warn(`pc-entity${label} must be a descendant of pc-app - entity not created`);
1065
1348
  return;
1349
+ }
1066
1350
  // If app is already running, create entity immediately
1067
1351
  if (closestApp.hierarchyReady) {
1068
1352
  const app = closestApp.app;
@@ -1080,10 +1364,15 @@ class EntityElement extends AsyncElement {
1080
1364
  }
1081
1365
  disconnectedCallback() {
1082
1366
  if (this.entity) {
1083
- // Notify all children that their entities are about to become invalid
1367
+ // Notify all children that their entities are about to become invalid. Both fields have
1368
+ // to be reset here, not just _entity: a descendant's own disconnectedCallback runs after
1369
+ // this one and skips its reset behind the `if (this.entity)` guard, because we have
1370
+ // already nulled the entity it tests. Leaving _built set would make buildHierarchy bail
1371
+ // on re-insertion, so the descendant would get a fresh entity that is never parented.
1084
1372
  const children = this.querySelectorAll('pc-entity');
1085
1373
  children.forEach((child) => {
1086
1374
  child._entity = null;
1375
+ child._built = false;
1087
1376
  });
1088
1377
  // Destroy the entity
1089
1378
  this.entity.destroy();
@@ -1239,7 +1528,7 @@ class EntityElement extends AsyncElement {
1239
1528
  this.enabled = parseBool(newValue, true);
1240
1529
  break;
1241
1530
  case 'name':
1242
- this.name = newValue;
1531
+ this.name = newValue ?? 'Untitled';
1243
1532
  break;
1244
1533
  case 'position':
1245
1534
  this.position = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
@@ -1251,7 +1540,7 @@ class EntityElement extends AsyncElement {
1251
1540
  this.scale = parseVec3(newValue, playcanvas.Vec3.ONE, name);
1252
1541
  break;
1253
1542
  case 'tags':
1254
- this.tags = newValue.split(',').map(tag => tag.trim());
1543
+ this.tags = parseTags(newValue);
1255
1544
  break;
1256
1545
  case 'onpointerenter':
1257
1546
  case 'onpointerleave':
@@ -1282,8 +1571,7 @@ class EntityElement extends AsyncElement {
1282
1571
  }
1283
1572
  }
1284
1573
  hasListeners(type) {
1285
- var _a;
1286
- return Boolean((_a = this._listeners[type]) === null || _a === void 0 ? void 0 : _a.length) || this._inlineHandlerTypes.has(type);
1574
+ return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
1287
1575
  }
1288
1576
  }
1289
1577
  customElements.define('pc-entity', EntityElement);
@@ -1475,11 +1763,13 @@ const extToType = new Map([
1475
1763
  ['frag', 'shader'],
1476
1764
  ['glb', 'container'],
1477
1765
  ['glsl', 'shader'],
1766
+ ['gltf', 'container'],
1478
1767
  ['hdr', 'texture'],
1479
1768
  ['html', 'html'],
1480
1769
  ['jpg', 'texture'],
1481
1770
  ['js', 'script'],
1482
1771
  ['json', 'json'],
1772
+ ['ktx2', 'texture'],
1483
1773
  ['mp3', 'audio'],
1484
1774
  ['mjs', 'script'],
1485
1775
  ['ply', 'gsplat'],
@@ -1522,25 +1812,43 @@ const processBufferView = (gltfBuffer, buffers, continuation) => {
1522
1812
  * while the application is running are created and registered on insertion, and begin loading
1523
1813
  * immediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed
1524
1814
  * elsewhere, or with an unsupported asset type, never become ready.
1815
+ *
1816
+ * Apart from `lazy`, these attributes are read once when the asset is created, so changing them
1817
+ * later has no effect.
1818
+ *
1819
+ * @attribute {string} id - The identifier used to reference the asset from other elements.
1820
+ * @attribute {string} src - The URL of the asset to load.
1821
+ * @attribute {string} type - The asset type. Inferred from the `src` file extension when omitted.
1822
+ * @attribute {string} data - Additional asset data, as a JSON object.
1823
+ * @attribute {string} atlas - For a `sprite` asset, the `id` of the texture atlas asset it uses.
1824
+ * The atlas must be declared before the sprite.
1825
+ * @attribute {string} frame-keys - For a `sprite` asset, the atlas frame keys it uses, separated
1826
+ * by spaces or commas.
1827
+ * @attribute {number} pixels-per-unit - For a `sprite` asset, the number of pixels per world unit.
1828
+ * @attribute {'simple' | 'sliced' | 'tiled'} render-mode - For a `sprite` asset, how the sprite is
1829
+ * rendered when resized.
1830
+ *
1831
+ * @fires {Event} load - Fired each time the asset finishes loading, including a `lazy` asset
1832
+ * loaded later and any subsequent reloads. Does not bubble — listen on this element, or use a
1833
+ * capture-phase listener on an ancestor to observe every asset.
1834
+ * @fires {ErrorEvent} error - Fired when the asset fails to load, with the engine's error in
1835
+ * `message`. Does not bubble. The element still becomes ready — readiness means the load settled,
1836
+ * not that it succeeded.
1525
1837
  */
1526
1838
  class AssetElement extends AsyncElement {
1527
- constructor() {
1528
- super(...arguments);
1529
- this._lazy = false;
1530
- /**
1531
- * The asset that is loaded. Available once the element is ready — await
1532
- * {@link whenReady} or the element's `ready()` promise before accessing it.
1533
- */
1534
- this.asset = null;
1535
- }
1839
+ _lazy = false;
1840
+ /**
1841
+ * The asset that is loaded. Available once the element is ready — await
1842
+ * {@link whenReady} or the element's `ready()` promise before accessing it.
1843
+ */
1844
+ asset = null;
1536
1845
  async connectedCallback() {
1537
- var _a;
1538
1846
  const appElement = this.closestApp;
1539
1847
  if (!appElement)
1540
1848
  return;
1541
1849
  // Assets must be direct children of pc-app (matches the boot query ':scope > pc-asset')
1542
1850
  if (this.parentElement !== appElement) {
1543
- console.warn(`pc-asset '${(_a = this.getAttribute('id')) !== null && _a !== void 0 ? _a : this.getAttribute('src')}' must be a direct child of pc-app - asset not created`);
1851
+ console.warn(`pc-asset '${this.getAttribute('id') ?? this.getAttribute('src')}' must be a direct child of pc-app - asset not created`);
1544
1852
  return;
1545
1853
  }
1546
1854
  await appElement.ready();
@@ -1569,15 +1877,22 @@ class AssetElement extends AsyncElement {
1569
1877
  disconnectedCallback() {
1570
1878
  this.destroyAsset();
1571
1879
  }
1880
+ _onAssetLoad() {
1881
+ this.dispatchEvent(new Event('load'));
1882
+ }
1883
+ _onAssetError(err) {
1884
+ this.dispatchEvent(new ErrorEvent('error', {
1885
+ message: err instanceof Error ? err.message : String(err)
1886
+ }));
1887
+ }
1572
1888
  createAsset() {
1573
- var _a;
1574
1889
  const id = this.getAttribute('id') || '';
1575
1890
  const src = this.getAttribute('src') || '';
1576
1891
  let type = this.getAttribute('type');
1577
1892
  // If no type is specified, try to infer it from the file extension.
1578
1893
  if (!type) {
1579
1894
  const ext = src.split('.').pop();
1580
- type = (_a = extToType.get(ext || '')) !== null && _a !== void 0 ? _a : null;
1895
+ type = extToType.get(ext || '') ?? null;
1581
1896
  }
1582
1897
  if (!type) {
1583
1898
  console.warn(`Unsupported asset type: ${src}`);
@@ -1604,6 +1919,10 @@ class AssetElement extends AsyncElement {
1604
1919
  this.asset = new playcanvas.Asset(id, type, src ? { url: src } : null, data);
1605
1920
  }
1606
1921
  this.asset.preload = !this._lazy;
1922
+ // Forward the engine asset's load outcome as DOM events on this element, like <img>.
1923
+ // Attached before the asset joins the registry, which is what starts a preloaded load.
1924
+ this.asset.on('load', this._onAssetLoad, this);
1925
+ this.asset.on('error', this._onAssetError, this);
1607
1926
  }
1608
1927
  /**
1609
1928
  * Builds the `data` object for the asset from an optional inline `data` attribute (JSON) and,
@@ -1613,7 +1932,6 @@ class AssetElement extends AsyncElement {
1613
1932
  * @returns The asset data, or `undefined`.
1614
1933
  */
1615
1934
  _buildData(type) {
1616
- var _a, _b, _c, _d;
1617
1935
  let data;
1618
1936
  const dataAttr = this.getAttribute('data');
1619
1937
  if (dataAttr) {
@@ -1625,10 +1943,10 @@ class AssetElement extends AsyncElement {
1625
1943
  }
1626
1944
  }
1627
1945
  if (type === 'sprite') {
1628
- data = data !== null && data !== void 0 ? data : {};
1946
+ data = data ?? {};
1629
1947
  // Resolve the referenced texture atlas to its (numeric) asset id. The atlas must be
1630
1948
  // declared before the sprite so its asset already exists in the registry.
1631
- const atlas = (_a = this.getAttribute('atlas')) !== null && _a !== void 0 ? _a : data.textureAtlasAsset;
1949
+ const atlas = this.getAttribute('atlas') ?? data.textureAtlasAsset;
1632
1950
  if (typeof atlas === 'string') {
1633
1951
  const atlasAsset = AssetElement.get(atlas);
1634
1952
  if (atlasAsset) {
@@ -1651,17 +1969,19 @@ class AssetElement extends AsyncElement {
1651
1969
  data.renderMode = renderModes.get(parseEnum(renderMode, renderModes, 'simple', 'render-mode'));
1652
1970
  }
1653
1971
  // Apply engine defaults for any values not supplied.
1654
- data.renderMode = (_b = data.renderMode) !== null && _b !== void 0 ? _b : playcanvas.SPRITE_RENDERMODE_SIMPLE;
1655
- data.pixelsPerUnit = (_c = data.pixelsPerUnit) !== null && _c !== void 0 ? _c : 1;
1656
- data.frameKeys = (_d = data.frameKeys) !== null && _d !== void 0 ? _d : [];
1972
+ data.renderMode = data.renderMode ?? playcanvas.SPRITE_RENDERMODE_SIMPLE;
1973
+ data.pixelsPerUnit = data.pixelsPerUnit ?? 1;
1974
+ data.frameKeys = data.frameKeys ?? [];
1657
1975
  }
1658
1976
  return data;
1659
1977
  }
1660
1978
  destroyAsset() {
1661
- var _a;
1662
1979
  if (this.asset) {
1980
+ // A caller that keeps the Asset alive must not dispatch on a removed element
1981
+ this.asset.off('load', this._onAssetLoad, this);
1982
+ this.asset.off('error', this._onAssetError, this);
1663
1983
  // Deregister first so unload() can still notify the registry
1664
- (_a = this.asset.registry) === null || _a === void 0 ? void 0 : _a.remove(this.asset);
1984
+ this.asset.registry?.remove(this.asset);
1665
1985
  this.asset.unload();
1666
1986
  this.asset = null;
1667
1987
  }
@@ -1685,7 +2005,7 @@ class AssetElement extends AsyncElement {
1685
2005
  }
1686
2006
  static get(id) {
1687
2007
  const assetElement = document.querySelector(`pc-asset[id="${id}"]`);
1688
- return assetElement === null || assetElement === void 0 ? void 0 : assetElement.asset;
2008
+ return assetElement?.asset;
1689
2009
  }
1690
2010
  static get observedAttributes() {
1691
2011
  return ['lazy'];
@@ -1704,6 +2024,10 @@ customElements.define('pc-asset', AssetElement);
1704
2024
  * @category Components
1705
2025
  */
1706
2026
  class ComponentElement extends AsyncElement {
2027
+ _componentName;
2028
+ _enabled = true;
2029
+ _component = null;
2030
+ _appElement = null;
1707
2031
  /**
1708
2032
  * Creates a new ComponentElement instance.
1709
2033
  *
@@ -1712,9 +2036,6 @@ class ComponentElement extends AsyncElement {
1712
2036
  */
1713
2037
  constructor(componentName) {
1714
2038
  super();
1715
- this._enabled = true;
1716
- this._component = null;
1717
- this._appElement = null;
1718
2039
  this._componentName = componentName;
1719
2040
  }
1720
2041
  // Method to be overridden by subclasses to provide initial component data
@@ -1737,28 +2058,27 @@ class ComponentElement extends AsyncElement {
1737
2058
  }
1738
2059
  initComponent() { }
1739
2060
  async connectedCallback() {
1740
- var _a, _b;
1741
- this._appElement = (_a = this.closestApp) !== null && _a !== void 0 ? _a : null;
1742
- await ((_b = this._appElement) === null || _b === void 0 ? void 0 : _b.ready());
2061
+ this._appElement = this.closestApp ?? null;
2062
+ await this._appElement?.ready();
1743
2063
  await this.addComponent();
1744
2064
  this.initComponent();
1745
2065
  this._onReady();
1746
2066
  }
1747
2067
  disconnectedCallback() {
1748
- var _a, _b;
1749
2068
  // Remove the component when the element is disconnected. Skip this when the owning
1750
2069
  // application has already been destroyed — removing a <pc-app> disconnects it before
1751
2070
  // its children, taking the component systems with it.
1752
- if (((_a = this._appElement) === null || _a === void 0 ? void 0 : _a.app) && ((_b = this._component) === null || _b === void 0 ? void 0 : _b.entity)) {
2071
+ if (this._appElement?.app && this._component?.entity) {
1753
2072
  this._component.entity.removeComponent(this._componentName);
1754
2073
  }
1755
2074
  this._component = null;
1756
2075
  this._appElement = null;
1757
2076
  }
1758
2077
  /**
1759
- * The PlayCanvas component instance. Available once the element is ready await
1760
- * {@link whenReady} or the element's `ready()` promise before accessing it.
1761
- * @returns The component instance.
2078
+ * The PlayCanvas component instance. `null` until the element is ready, and also for an
2079
+ * element that is not a descendant of a `<pc-entity>` — await {@link whenReady} or the
2080
+ * element's `ready()` promise before accessing it.
2081
+ * @returns The component instance, or `null`.
1762
2082
  */
1763
2083
  get component() {
1764
2084
  return this._component;
@@ -1828,26 +2148,25 @@ const transitionModes = new Map([
1828
2148
  * @category Components
1829
2149
  */
1830
2150
  class ButtonComponentElement extends ComponentElement {
2151
+ _active = true;
2152
+ _image = '';
2153
+ _hitPadding = new playcanvas.Vec4(0, 0, 0, 0);
2154
+ _transitionMode = 'tint';
2155
+ _hoverTint = new playcanvas.Color(1, 1, 1, 1);
2156
+ _pressedTint = new playcanvas.Color(1, 1, 1, 1);
2157
+ _inactiveTint = new playcanvas.Color(1, 1, 1, 1);
2158
+ _fadeDuration = 0;
2159
+ _hoverSpriteAsset = '';
2160
+ _hoverSpriteFrame = 0;
2161
+ _pressedSpriteAsset = '';
2162
+ _pressedSpriteFrame = 0;
2163
+ _inactiveSpriteAsset = '';
2164
+ _inactiveSpriteFrame = 0;
1831
2165
  /** @ignore */
1832
2166
  constructor() {
1833
2167
  super('button');
1834
- this._active = true;
1835
- this._image = '';
1836
- this._hitPadding = new playcanvas.Vec4(0, 0, 0, 0);
1837
- this._transitionMode = 'tint';
1838
- this._hoverTint = new playcanvas.Color(1, 1, 1, 1);
1839
- this._pressedTint = new playcanvas.Color(1, 1, 1, 1);
1840
- this._inactiveTint = new playcanvas.Color(1, 1, 1, 1);
1841
- this._fadeDuration = 0;
1842
- this._hoverSpriteAsset = '';
1843
- this._hoverSpriteFrame = 0;
1844
- this._pressedSpriteAsset = '';
1845
- this._pressedSpriteFrame = 0;
1846
- this._inactiveSpriteAsset = '';
1847
- this._inactiveSpriteFrame = 0;
1848
2168
  }
1849
2169
  getInitialComponentData() {
1850
- var _a;
1851
2170
  const data = {
1852
2171
  active: this._active,
1853
2172
  hitPadding: this._hitPadding,
@@ -1862,7 +2181,7 @@ class ButtonComponentElement extends ComponentElement {
1862
2181
  };
1863
2182
  // The image entity defaults to the button's own entity (which carries the image element)
1864
2183
  // when no explicit reference is provided.
1865
- const imageEntity = this._image ? getEntity(this._image) : (_a = this.closestEntity) === null || _a === void 0 ? void 0 : _a.entity;
2184
+ const imageEntity = this._image ? getEntity(this._image) : this.closestEntity?.entity;
1866
2185
  if (imageEntity) {
1867
2186
  data.imageEntity = imageEntity;
1868
2187
  }
@@ -1946,10 +2265,9 @@ class ButtonComponentElement extends ComponentElement {
1946
2265
  * @param value - The transition mode.
1947
2266
  */
1948
2267
  set transitionMode(value) {
1949
- var _a;
1950
2268
  this._transitionMode = value;
1951
2269
  if (this.component) {
1952
- this.component.transitionMode = (_a = transitionModes.get(value)) !== null && _a !== void 0 ? _a : playcanvas.BUTTON_TRANSITION_MODE_TINT;
2270
+ this.component.transitionMode = transitionModes.get(value) ?? playcanvas.BUTTON_TRANSITION_MODE_TINT;
1953
2271
  }
1954
2272
  }
1955
2273
  /**
@@ -2164,7 +2482,7 @@ class ButtonComponentElement extends ComponentElement {
2164
2482
  this.active = parseBool(newValue, true);
2165
2483
  break;
2166
2484
  case 'image':
2167
- this.image = newValue;
2485
+ this.image = newValue ?? '';
2168
2486
  break;
2169
2487
  case 'hit-padding':
2170
2488
  this.hitPadding = parseVec4(newValue, playcanvas.Vec4.ZERO, name);
@@ -2185,19 +2503,19 @@ class ButtonComponentElement extends ComponentElement {
2185
2503
  this.fadeDuration = parseNumber(newValue, 0, name);
2186
2504
  break;
2187
2505
  case 'hover-sprite-asset':
2188
- this.hoverSpriteAsset = newValue;
2506
+ this.hoverSpriteAsset = newValue ?? '';
2189
2507
  break;
2190
2508
  case 'hover-sprite-frame':
2191
2509
  this.hoverSpriteFrame = parseNumber(newValue, 0, name);
2192
2510
  break;
2193
2511
  case 'pressed-sprite-asset':
2194
- this.pressedSpriteAsset = newValue;
2512
+ this.pressedSpriteAsset = newValue ?? '';
2195
2513
  break;
2196
2514
  case 'pressed-sprite-frame':
2197
2515
  this.pressedSpriteFrame = parseNumber(newValue, 0, name);
2198
2516
  break;
2199
2517
  case 'inactive-sprite-asset':
2200
- this.inactiveSpriteAsset = newValue;
2518
+ this.inactiveSpriteAsset = newValue ?? '';
2201
2519
  break;
2202
2520
  case 'inactive-sprite-frame':
2203
2521
  this.inactiveSpriteFrame = parseNumber(newValue, 0, name);
@@ -2225,27 +2543,27 @@ const tonemaps = new Map([
2225
2543
  * @category Components
2226
2544
  */
2227
2545
  class CameraComponentElement extends ComponentElement {
2546
+ _clearColor = new playcanvas.Color(0.75, 0.75, 0.75, 1);
2547
+ _clearColorBuffer = true;
2548
+ _clearDepthBuffer = true;
2549
+ _clearStencilBuffer = false;
2550
+ _cullFaces = true;
2551
+ _farClip = 1000;
2552
+ _flipFaces = false;
2553
+ _fov = 45;
2554
+ _frustumCulling = true;
2555
+ _gamma = 'srgb';
2556
+ _horizontalFov = false;
2557
+ _nearClip = 0.1;
2558
+ _orthographic = false;
2559
+ _orthoHeight = 10;
2560
+ _priority = 0;
2561
+ _rect = new playcanvas.Vec4(0, 0, 1, 1);
2562
+ _scissorRect = new playcanvas.Vec4(0, 0, 1, 1);
2563
+ _tonemap = 'none';
2228
2564
  /** @ignore */
2229
2565
  constructor() {
2230
2566
  super('camera');
2231
- this._clearColor = new playcanvas.Color(0.75, 0.75, 0.75, 1);
2232
- this._clearColorBuffer = true;
2233
- this._clearDepthBuffer = true;
2234
- this._clearStencilBuffer = false;
2235
- this._cullFaces = true;
2236
- this._farClip = 1000;
2237
- this._flipFaces = false;
2238
- this._fov = 45;
2239
- this._frustumCulling = true;
2240
- this._gamma = 'srgb';
2241
- this._horizontalFov = false;
2242
- this._nearClip = 0.1;
2243
- this._orthographic = false;
2244
- this._orthoHeight = 10;
2245
- this._priority = 0;
2246
- this._rect = new playcanvas.Vec4(0, 0, 1, 1);
2247
- this._scissorRect = new playcanvas.Vec4(0, 0, 1, 1);
2248
- this._tonemap = 'none';
2249
2567
  }
2250
2568
  getInitialComponentData() {
2251
2569
  return {
@@ -2270,8 +2588,7 @@ class CameraComponentElement extends ComponentElement {
2270
2588
  };
2271
2589
  }
2272
2590
  get xrAvailable() {
2273
- var _a;
2274
- const xrManager = (_a = this.component) === null || _a === void 0 ? void 0 : _a.system.app.xr;
2591
+ const xrManager = this.component?.system.app.xr;
2275
2592
  return xrManager && xrManager.supported && xrManager.isAvailable(playcanvas.XRTYPE_VR);
2276
2593
  }
2277
2594
  /**
@@ -2599,10 +2916,9 @@ class CameraComponentElement extends ComponentElement {
2599
2916
  * @param value - The tone mapping.
2600
2917
  */
2601
2918
  set tonemap(value) {
2602
- var _a;
2603
2919
  this._tonemap = value;
2604
2920
  if (this.component) {
2605
- this.component.toneMapping = (_a = tonemaps.get(value)) !== null && _a !== void 0 ? _a : playcanvas.TONEMAP_NONE;
2921
+ this.component.toneMapping = tonemaps.get(value) ?? playcanvas.TONEMAP_NONE;
2606
2922
  }
2607
2923
  }
2608
2924
  /**
@@ -2706,17 +3022,17 @@ customElements.define('pc-camera', CameraComponentElement);
2706
3022
  * @category Components
2707
3023
  */
2708
3024
  class CollisionComponentElement extends ComponentElement {
3025
+ _angularOffset = new playcanvas.Quat();
3026
+ _axis = 1;
3027
+ _convexHull = false;
3028
+ _halfExtents = new playcanvas.Vec3(0.5, 0.5, 0.5);
3029
+ _height = 2;
3030
+ _linearOffset = new playcanvas.Vec3();
3031
+ _radius = 0.5;
3032
+ _type = 'box';
2709
3033
  /** @ignore */
2710
3034
  constructor() {
2711
3035
  super('collision');
2712
- this._angularOffset = new playcanvas.Quat();
2713
- this._axis = 1;
2714
- this._convexHull = false;
2715
- this._halfExtents = new playcanvas.Vec3(0.5, 0.5, 0.5);
2716
- this._height = 2;
2717
- this._linearOffset = new playcanvas.Vec3();
2718
- this._radius = 0.5;
2719
- this._type = 'box';
2720
3036
  }
2721
3037
  getInitialComponentData() {
2722
3038
  return {
@@ -2853,52 +3169,51 @@ customElements.define('pc-collision', CollisionComponentElement);
2853
3169
  * @category Components
2854
3170
  */
2855
3171
  class ElementComponentElement extends ComponentElement {
3172
+ _anchor = new playcanvas.Vec4(0.5, 0.5, 0.5, 0.5);
3173
+ _autoWidth = true;
3174
+ _autoHeight = true;
3175
+ _autoFitWidth = false;
3176
+ _autoFitHeight = false;
3177
+ _color = new playcanvas.Color(1, 1, 1, 1);
3178
+ _enableMarkup = false;
3179
+ _fontAsset = '';
3180
+ _fontSize = 32;
3181
+ _maxFontSize = 32;
3182
+ _minFontSize = 8;
3183
+ _height = 0;
3184
+ _lineHeight = 32;
3185
+ _margin = null;
3186
+ _mask = false;
3187
+ _opacity = 1;
3188
+ _pivot = new playcanvas.Vec2(0.5, 0.5);
3189
+ _pixelsPerUnit = null;
3190
+ _spriteAsset = '';
3191
+ _spriteFrame = 0;
3192
+ _text = '';
3193
+ _textureAsset = '';
3194
+ _type = 'group';
3195
+ _useInput = false;
3196
+ _width = 0;
3197
+ _wrapLines = false;
2856
3198
  /** @ignore */
2857
3199
  constructor() {
2858
3200
  super('element');
2859
- this._anchor = new playcanvas.Vec4(0.5, 0.5, 0.5, 0.5);
2860
- this._autoWidth = true;
2861
- this._autoHeight = true;
2862
- this._autoFitWidth = false;
2863
- this._autoFitHeight = false;
2864
- this._color = new playcanvas.Color(1, 1, 1, 1);
2865
- this._enableMarkup = false;
2866
- this._fontAsset = '';
2867
- this._fontSize = 32;
2868
- this._maxFontSize = 32;
2869
- this._minFontSize = 8;
2870
- this._height = 0;
2871
- this._lineHeight = 32;
2872
- this._margin = null;
2873
- this._mask = false;
2874
- this._opacity = 1;
2875
- this._pivot = new playcanvas.Vec2(0.5, 0.5);
2876
- this._pixelsPerUnit = null;
2877
- this._spriteAsset = '';
2878
- this._spriteFrame = 0;
2879
- this._text = '';
2880
- this._textureAsset = '';
2881
- this._type = 'group';
2882
- this._useInput = false;
2883
- this._width = 0;
2884
- this._wrapLines = false;
2885
3201
  }
2886
3202
  initComponent() {
2887
- var _a, _b;
2888
3203
  const component = this.component;
2889
3204
  if (!component) {
2890
3205
  return;
2891
3206
  }
2892
3207
  // Text elements render through their own material; enable fog on it so 3D text respects
2893
3208
  // scene fog. Image/group elements have no text material, so guard the access.
2894
- if ((_a = component._text) === null || _a === void 0 ? void 0 : _a._material) {
3209
+ if (component._text?._material) {
2895
3210
  component._text._material.useFog = true;
2896
3211
  }
2897
3212
  // The engine establishes element masking in ElementComponent._onInsert, which fires when an
2898
3213
  // entity is inserted into the hierarchy. Web-components inserts the entity first and adds
2899
3214
  // the element component afterwards, so that pass is missed. Re-dirty the mask state here so
2900
3215
  // masks (e.g. a scroll view viewport) correctly clip this element and any added at runtime.
2901
- (_b = component._dirtifyMask) === null || _b === void 0 ? void 0 : _b.call(component);
3216
+ component._dirtifyMask?.();
2902
3217
  }
2903
3218
  getInitialComponentData() {
2904
3219
  const data = {
@@ -3461,7 +3776,7 @@ class ElementComponentElement extends ComponentElement {
3461
3776
  this.enableMarkup = parseBool(newValue, false);
3462
3777
  break;
3463
3778
  case 'font-asset':
3464
- this.fontAsset = newValue;
3779
+ this.fontAsset = newValue ?? '';
3465
3780
  break;
3466
3781
  case 'font-size':
3467
3782
  this.fontSize = parseNumber(newValue, 32, name);
@@ -3494,16 +3809,16 @@ class ElementComponentElement extends ComponentElement {
3494
3809
  this.pixelsPerUnit = parseNumber(newValue, null, name);
3495
3810
  break;
3496
3811
  case 'sprite-asset':
3497
- this.spriteAsset = newValue;
3812
+ this.spriteAsset = newValue ?? '';
3498
3813
  break;
3499
3814
  case 'sprite-frame':
3500
3815
  this.spriteFrame = parseNumber(newValue, 0, name);
3501
3816
  break;
3502
3817
  case 'text':
3503
- this.text = newValue;
3818
+ this.text = newValue ?? '';
3504
3819
  break;
3505
3820
  case 'texture-asset':
3506
- this.textureAsset = newValue;
3821
+ this.textureAsset = newValue ?? '';
3507
3822
  break;
3508
3823
  case 'type':
3509
3824
  this.type = parseEnum(newValue, ['group', 'image', 'text'], 'group', name);
@@ -3531,16 +3846,16 @@ customElements.define('pc-element', ElementComponentElement);
3531
3846
  * @category Components
3532
3847
  */
3533
3848
  class LayoutChildComponentElement extends ComponentElement {
3849
+ _minWidth = 0;
3850
+ _minHeight = 0;
3851
+ _maxWidth = null;
3852
+ _maxHeight = null;
3853
+ _fitWidthProportion = 0;
3854
+ _fitHeightProportion = 0;
3855
+ _excludeFromLayout = false;
3534
3856
  /** @ignore */
3535
3857
  constructor() {
3536
3858
  super('layoutchild');
3537
- this._minWidth = 0;
3538
- this._minHeight = 0;
3539
- this._maxWidth = null;
3540
- this._maxHeight = null;
3541
- this._fitWidthProportion = 0;
3542
- this._fitHeightProportion = 0;
3543
- this._excludeFromLayout = false;
3544
3859
  }
3545
3860
  getInitialComponentData() {
3546
3861
  return {
@@ -3741,18 +4056,18 @@ const fittings = new Map([
3741
4056
  * @category Components
3742
4057
  */
3743
4058
  class LayoutGroupComponentElement extends ComponentElement {
4059
+ _orientation = 'horizontal';
4060
+ _reverseX = false;
4061
+ _reverseY = false;
4062
+ _alignment = new playcanvas.Vec2(0, 1);
4063
+ _padding = new playcanvas.Vec4(0, 0, 0, 0);
4064
+ _spacing = new playcanvas.Vec2(0, 0);
4065
+ _widthFitting = 'none';
4066
+ _heightFitting = 'none';
4067
+ _wrap = false;
3744
4068
  /** @ignore */
3745
4069
  constructor() {
3746
4070
  super('layoutgroup');
3747
- this._orientation = 'horizontal';
3748
- this._reverseX = false;
3749
- this._reverseY = false;
3750
- this._alignment = new playcanvas.Vec2(0, 1);
3751
- this._padding = new playcanvas.Vec4(0, 0, 0, 0);
3752
- this._spacing = new playcanvas.Vec2(0, 0);
3753
- this._widthFitting = 'none';
3754
- this._heightFitting = 'none';
3755
- this._wrap = false;
3756
4071
  }
3757
4072
  getInitialComponentData() {
3758
4073
  return {
@@ -3780,10 +4095,9 @@ class LayoutGroupComponentElement extends ComponentElement {
3780
4095
  * @param value - The orientation.
3781
4096
  */
3782
4097
  set orientation(value) {
3783
- var _a;
3784
4098
  this._orientation = value;
3785
4099
  if (this.component) {
3786
- this.component.orientation = (_a = orientations$1.get(value)) !== null && _a !== void 0 ? _a : playcanvas.ORIENTATION_HORIZONTAL;
4100
+ this.component.orientation = orientations$1.get(value) ?? playcanvas.ORIENTATION_HORIZONTAL;
3787
4101
  }
3788
4102
  }
3789
4103
  /**
@@ -3884,10 +4198,9 @@ class LayoutGroupComponentElement extends ComponentElement {
3884
4198
  * @param value - The width fitting mode.
3885
4199
  */
3886
4200
  set widthFitting(value) {
3887
- var _a;
3888
4201
  this._widthFitting = value;
3889
4202
  if (this.component) {
3890
- this.component.widthFitting = (_a = fittings.get(value)) !== null && _a !== void 0 ? _a : playcanvas.FITTING_NONE;
4203
+ this.component.widthFitting = fittings.get(value) ?? playcanvas.FITTING_NONE;
3891
4204
  }
3892
4205
  }
3893
4206
  /**
@@ -3903,10 +4216,9 @@ class LayoutGroupComponentElement extends ComponentElement {
3903
4216
  * @param value - The height fitting mode.
3904
4217
  */
3905
4218
  set heightFitting(value) {
3906
- var _a;
3907
4219
  this._heightFitting = value;
3908
4220
  if (this.component) {
3909
- this.component.heightFitting = (_a = fittings.get(value)) !== null && _a !== void 0 ? _a : playcanvas.FITTING_NONE;
4221
+ this.component.heightFitting = fittings.get(value) ?? playcanvas.FITTING_NONE;
3910
4222
  }
3911
4223
  }
3912
4224
  /**
@@ -4002,28 +4314,28 @@ const shadowTypes = new Map([
4002
4314
  * @category Components
4003
4315
  */
4004
4316
  class LightComponentElement extends ComponentElement {
4317
+ _castShadows = false;
4318
+ _color = new playcanvas.Color(1, 1, 1);
4319
+ _innerConeAngle = 40;
4320
+ _intensity = 1;
4321
+ _normalOffsetBias = 0.05;
4322
+ _outerConeAngle = 45;
4323
+ _range = 10;
4324
+ _shadowBias = 0.2;
4325
+ _shadowDistance = 16;
4326
+ _shadowIntensity = 1;
4327
+ _shadowResolution = 1024;
4328
+ _shadowType = 'pcf3-32f';
4329
+ _type = 'directional';
4330
+ _vsmBias = 0.01;
4331
+ _vsmBlurSize = 11;
4332
+ _penumbraSize = 1;
4333
+ _penumbraFalloff = 1;
4334
+ _shadowSamples = 16;
4335
+ _shadowBlockerSamples = 16;
4005
4336
  /** @ignore */
4006
4337
  constructor() {
4007
4338
  super('light');
4008
- this._castShadows = false;
4009
- this._color = new playcanvas.Color(1, 1, 1);
4010
- this._innerConeAngle = 40;
4011
- this._intensity = 1;
4012
- this._normalOffsetBias = 0.05;
4013
- this._outerConeAngle = 45;
4014
- this._range = 10;
4015
- this._shadowBias = 0.2;
4016
- this._shadowDistance = 16;
4017
- this._shadowIntensity = 1;
4018
- this._shadowResolution = 1024;
4019
- this._shadowType = 'pcf3-32f';
4020
- this._type = 'directional';
4021
- this._vsmBias = 0.01;
4022
- this._vsmBlurSize = 11;
4023
- this._penumbraSize = 1;
4024
- this._penumbraFalloff = 1;
4025
- this._shadowSamples = 16;
4026
- this._shadowBlockerSamples = 16;
4027
4339
  }
4028
4340
  getInitialComponentData() {
4029
4341
  return {
@@ -4257,10 +4569,9 @@ class LightComponentElement extends ComponentElement {
4257
4569
  * - `pcss-32f` - Percentage-closer soft shadow with 32-bit depth.
4258
4570
  */
4259
4571
  set shadowType(value) {
4260
- var _a;
4261
4572
  this._shadowType = value;
4262
4573
  if (this.component) {
4263
- this.component.shadowType = (_a = shadowTypes.get(value)) !== null && _a !== void 0 ? _a : playcanvas.SHADOW_PCF3_32F;
4574
+ this.component.shadowType = shadowTypes.get(value) ?? playcanvas.SHADOW_PCF3_32F;
4264
4575
  }
4265
4576
  }
4266
4577
  /**
@@ -4488,20 +4799,19 @@ customElements.define('pc-light', LightComponentElement);
4488
4799
  * @category Components
4489
4800
  */
4490
4801
  class ParticleSystemComponentElement extends ComponentElement {
4802
+ _asset = '';
4491
4803
  /** @ignore */
4492
4804
  constructor() {
4493
4805
  super('particlesystem');
4494
- this._asset = '';
4495
4806
  }
4496
4807
  getInitialComponentData() {
4497
- var _a;
4498
4808
  const asset = AssetElement.get(this._asset);
4499
4809
  if (!asset) {
4500
4810
  return {};
4501
4811
  }
4502
4812
  if (asset.resource.colorMapAsset) {
4503
4813
  const id = asset.resource.colorMapAsset;
4504
- const colorMapAsset = (_a = AssetElement.get(id)) === null || _a === void 0 ? void 0 : _a.id;
4814
+ const colorMapAsset = AssetElement.get(id)?.id;
4505
4815
  if (colorMapAsset) {
4506
4816
  asset.resource.colorMapAsset = colorMapAsset;
4507
4817
  }
@@ -4527,9 +4837,8 @@ class ParticleSystemComponentElement extends ComponentElement {
4527
4837
  }
4528
4838
  }
4529
4839
  async _loadAsset() {
4530
- var _a;
4531
- const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
4532
- const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;
4840
+ const appElement = await this.closestApp?.ready();
4841
+ const app = appElement?.app;
4533
4842
  const asset = AssetElement.get(this._asset);
4534
4843
  if (!asset) {
4535
4844
  return;
@@ -4604,148 +4913,2183 @@ class ParticleSystemComponentElement extends ComponentElement {
4604
4913
  super.attributeChangedCallback(name, _oldValue, newValue);
4605
4914
  switch (name) {
4606
4915
  case 'asset':
4607
- this.asset = newValue;
4916
+ this.asset = newValue ?? '';
4608
4917
  break;
4609
4918
  }
4610
4919
  }
4611
4920
  }
4612
4921
  customElements.define('pc-particles', ParticleSystemComponentElement);
4613
4922
 
4614
- /**
4615
- * The MaterialElement interface provides properties and methods for manipulating
4616
- * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-material/ | `<pc-material>`} elements.
4923
+ const blendTypes = new Map([
4924
+ ['none', playcanvas.BLEND_NONE],
4925
+ ['normal', playcanvas.BLEND_NORMAL],
4926
+ ['additive', playcanvas.BLEND_ADDITIVE],
4927
+ ['additive-alpha', playcanvas.BLEND_ADDITIVEALPHA],
4928
+ ['premultiplied', playcanvas.BLEND_PREMULTIPLIED],
4929
+ ['multiplicative', playcanvas.BLEND_MULTIPLICATIVE],
4930
+ ['multiplicative-2x', playcanvas.BLEND_MULTIPLICATIVE2X],
4931
+ ['screen', playcanvas.BLEND_SCREEN],
4932
+ ['min', playcanvas.BLEND_MIN],
4933
+ ['max', playcanvas.BLEND_MAX],
4934
+ ['subtractive', playcanvas.BLEND_SUBTRACTIVE]
4935
+ ]);
4936
+ const cullModes = new Map([
4937
+ ['none', playcanvas.CULLFACE_NONE],
4938
+ ['back', playcanvas.CULLFACE_BACK],
4939
+ ['front', playcanvas.CULLFACE_FRONT],
4940
+ ['front-and-back', playcanvas.CULLFACE_FRONTANDBACK]
4941
+ ]);
4942
+ const fresnelModels = new Map([
4943
+ ['none', playcanvas.FRESNEL_NONE],
4944
+ ['schlick', playcanvas.FRESNEL_SCHLICK]
4945
+ ]);
4946
+ const occludeSpeculars = new Map([
4947
+ ['none', playcanvas.SPECOCC_NONE],
4948
+ ['ao', playcanvas.SPECOCC_AO],
4949
+ ['gloss-dependent', playcanvas.SPECOCC_GLOSSDEPENDENT]
4950
+ ]);
4951
+ const opacityDithers = ['none', 'bayer8', 'bluenoise', 'ignnoise'];
4952
+ const colorChannels = ['r', 'g', 'b', 'a', 'rgb'];
4953
+ const scalarChannels = ['r', 'g', 'b', 'a'];
4954
+ /**
4955
+ * The attributes that contradict a `roughness-*` attribute: each one carries the opposite
4956
+ * interpretation of a value the aliases also write. The `gloss-map-*` modifiers are deliberately
4957
+ * absent - they only configure the shared slot (tiling, offset, channel and so on) and carry no
4958
+ * interpretation of their own, so they are the supported way to configure a `roughness-map`.
4959
+ */
4960
+ const glossConflicts = ['gloss', 'gloss-invert', 'gloss-map'];
4961
+ /** The aliases those attributes contradict. */
4962
+ const roughnessAliases = ['roughness', 'roughness-map'];
4963
+ /**
4964
+ * The MaterialElement interface provides properties and methods for manipulating
4965
+ * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-material/ | `<pc-material>`} elements.
4617
4966
  * The MaterialElement interface also inherits the properties and methods of the
4618
4967
  * {@link HTMLElement} interface.
4619
4968
  *
4620
4969
  * A `pc-material` must be a direct child of `pc-app` — elements placed elsewhere log a warning
4621
4970
  * and never create a material. Elements inserted while the application is already running are
4622
4971
  * created on insertion.
4972
+ *
4973
+ * The element is metal/rough by default: unlike a bare `StandardMaterial` it enables the metalness
4974
+ * workflow, which is what the `metalness-*` attributes assume and what glTF means by PBR. It also
4975
+ * defaults `metalness` to 0 rather than the engine's 1, because those two defaults have to be
4976
+ * chosen together - the engine's 1 is unreachable under its own `useMetalness` of false, and with
4977
+ * the workflow on it would make every material fully metallic, so `<pc-material diffuse="crimson">`
4978
+ * would render as dark tinted reflections of an environment that may not exist rather than as a
4979
+ * crimson surface. `metalness="1"` remains one attribute away.
4980
+ *
4981
+ * The `roughness` and `roughness-map` attributes are aliases for `gloss` and `gloss-map` that
4982
+ * additionally invert the gloss channel; do not mix the two families on one element.
4983
+ *
4984
+ * The two aliases are documented here rather than on an accessor, because they resolve to the
4985
+ * `gloss` properties and would otherwise inherit gloss's description - which reads inverted.
4986
+ *
4987
+ * @attribute {number} roughness - The roughness of the material, from 0 (shiny) to 1 (rough). An
4988
+ * alias for `gloss` that also inverts it, so do not combine it with the `gloss` attributes.
4989
+ * @attribute {string} roughness-map - The id of the `pc-asset` to use as the roughness map. An
4990
+ * alias for `gloss-map` that also inverts the gloss channel, so do not combine it with the `gloss`
4991
+ * attributes.
4623
4992
  */
4624
4993
  class MaterialElement extends HTMLElement {
4625
- constructor() {
4626
- super(...arguments);
4627
- this._diffuse = new playcanvas.Color(1, 1, 1);
4628
- this._diffuseMap = '';
4629
- this._metalnessMap = '';
4630
- this._normalMap = '';
4631
- this._roughnessMap = '';
4632
- this.material = null;
4633
- }
4994
+ _alphaTest = 0;
4995
+ _alphaToCoverage = false;
4996
+ _aoIntensity = 1;
4997
+ _aoMap = '';
4998
+ _aoMapChannel = 'g';
4999
+ _aoMapOffset = new playcanvas.Vec2(0, 0);
5000
+ _aoMapRotation = 0;
5001
+ _aoMapTiling = new playcanvas.Vec2(1, 1);
5002
+ _aoMapUv = 0;
5003
+ _blendType = 'none';
5004
+ _bumpiness = 1;
5005
+ _cull = 'back';
5006
+ _depthBias = 0;
5007
+ _depthTest = true;
5008
+ _depthWrite = true;
5009
+ _diffuse = new playcanvas.Color(1, 1, 1);
5010
+ _diffuseMap = '';
5011
+ _diffuseMapChannel = 'rgb';
5012
+ _diffuseMapOffset = new playcanvas.Vec2(0, 0);
5013
+ _diffuseMapRotation = 0;
5014
+ _diffuseMapTiling = new playcanvas.Vec2(1, 1);
5015
+ _diffuseMapUv = 0;
5016
+ _emissive = new playcanvas.Color(0, 0, 0);
5017
+ _emissiveIntensity = 1;
5018
+ _emissiveMap = '';
5019
+ _emissiveMapChannel = 'rgb';
5020
+ _emissiveMapOffset = new playcanvas.Vec2(0, 0);
5021
+ _emissiveMapRotation = 0;
5022
+ _emissiveMapTiling = new playcanvas.Vec2(1, 1);
5023
+ _emissiveMapUv = 0;
5024
+ _enableGGXSpecular = false;
5025
+ _fresnelModel = 'schlick';
5026
+ _gloss = 0.25;
5027
+ _glossInvert = false;
5028
+ _glossMap = '';
5029
+ _glossMapChannel = 'g';
5030
+ _glossMapOffset = new playcanvas.Vec2(0, 0);
5031
+ _glossMapRotation = 0;
5032
+ _glossMapTiling = new playcanvas.Vec2(1, 1);
5033
+ _glossMapUv = 0;
5034
+ _heightMap = '';
5035
+ _heightMapChannel = 'g';
5036
+ _heightMapFactor = 1;
5037
+ _heightMapOffset = new playcanvas.Vec2(0, 0);
5038
+ _heightMapRotation = 0;
5039
+ _heightMapTiling = new playcanvas.Vec2(1, 1);
5040
+ _heightMapUv = 0;
5041
+ _metalness = 0;
5042
+ _metalnessMap = '';
5043
+ _metalnessMapChannel = 'g';
5044
+ _metalnessMapOffset = new playcanvas.Vec2(0, 0);
5045
+ _metalnessMapRotation = 0;
5046
+ _metalnessMapTiling = new playcanvas.Vec2(1, 1);
5047
+ _metalnessMapUv = 0;
5048
+ _normalMap = '';
5049
+ _normalMapOffset = new playcanvas.Vec2(0, 0);
5050
+ _normalMapRotation = 0;
5051
+ _normalMapTiling = new playcanvas.Vec2(1, 1);
5052
+ _normalMapUv = 0;
5053
+ _occludeDirect = false;
5054
+ _occludeSpecular = 'ao';
5055
+ _opacity = 1;
5056
+ _opacityDither = 'none';
5057
+ _opacityFadesSpecular = true;
5058
+ _opacityMap = '';
5059
+ _opacityMapChannel = 'a';
5060
+ _opacityMapOffset = new playcanvas.Vec2(0, 0);
5061
+ _opacityMapRotation = 0;
5062
+ _opacityMapTiling = new playcanvas.Vec2(1, 1);
5063
+ _opacityMapUv = 0;
5064
+ _slopeDepthBias = 0;
5065
+ _specular = new playcanvas.Color(0, 0, 0);
5066
+ _specularityFactor = 1;
5067
+ _twoSidedLighting = false;
5068
+ _useFog = true;
5069
+ _useLighting = true;
5070
+ // Diverges from the engine default of false - see the class docblock and createMaterial()
5071
+ _useMetalness = true;
5072
+ _useMetalnessSpecularColor = false;
5073
+ _useSkybox = true;
5074
+ _useTonemap = true;
5075
+ /**
5076
+ * Pending `load` handlers, one per texture slot. A slot's handler is torn down when the slot is
5077
+ * reassigned or the element disconnects, so a late-arriving asset can never write a texture the
5078
+ * element no longer wants.
5079
+ */
5080
+ _mapHandles = new Map();
5081
+ _updateScheduled = false;
5082
+ _glossConflictWarned = false;
5083
+ material = null;
4634
5084
  async connectedCallback() {
4635
- var _a, _b;
4636
- const appElement = (_b = (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-app')) !== null && _b !== void 0 ? _b : null;
5085
+ const appElement = this.parentElement?.closest('pc-app') ?? null;
4637
5086
  // Materials must be direct children of pc-app (matches the boot query ':scope > pc-material')
4638
5087
  if (!appElement || this.parentElement !== appElement) {
4639
5088
  console.warn(`pc-material '${this.id}' must be a direct child of pc-app - material not created`);
4640
5089
  return;
4641
5090
  }
4642
- await appElement.ready();
4643
- // The element may have been removed or re-parented while waiting for the app
4644
- if (!this.isConnected || this.parentElement !== appElement)
4645
- return;
4646
- // Materials present at startup are created by AppElement's boot; this branch handles
4647
- // elements inserted (or re-inserted) after the app is already running
4648
- if (!this.material) {
4649
- if (!appElement.app)
4650
- return; // pc-app is re-connecting; its own boot will create this
4651
- this.createMaterial();
5091
+ await appElement.ready();
5092
+ // The element may have been removed or re-parented while waiting for the app
5093
+ if (!this.isConnected || this.parentElement !== appElement)
5094
+ return;
5095
+ // Materials present at startup are created by AppElement's boot; this branch handles
5096
+ // elements inserted (or re-inserted) after the app is already running
5097
+ if (!this.material) {
5098
+ if (!appElement.app)
5099
+ return; // pc-app is re-connecting; its own boot will create this
5100
+ this.createMaterial();
5101
+ }
5102
+ }
5103
+ createMaterial() {
5104
+ const material = new playcanvas.StandardMaterial();
5105
+ this.material = material;
5106
+ material.alphaTest = this._alphaTest;
5107
+ material.alphaToCoverage = this._alphaToCoverage;
5108
+ material.aoIntensity = this._aoIntensity;
5109
+ material.aoMapChannel = this._aoMapChannel;
5110
+ material.aoMapOffset = this._aoMapOffset;
5111
+ material.aoMapRotation = this._aoMapRotation;
5112
+ material.aoMapTiling = this._aoMapTiling;
5113
+ material.aoMapUv = this._aoMapUv;
5114
+ material.blendType = blendTypes.get(this._blendType) ?? playcanvas.BLEND_NONE;
5115
+ material.bumpiness = this._bumpiness;
5116
+ material.cull = cullModes.get(this._cull) ?? playcanvas.CULLFACE_BACK;
5117
+ material.depthBias = this._depthBias;
5118
+ material.depthTest = this._depthTest;
5119
+ material.depthWrite = this._depthWrite;
5120
+ material.diffuse = this._diffuse;
5121
+ material.diffuseMapChannel = this._diffuseMapChannel;
5122
+ material.diffuseMapOffset = this._diffuseMapOffset;
5123
+ material.diffuseMapRotation = this._diffuseMapRotation;
5124
+ material.diffuseMapTiling = this._diffuseMapTiling;
5125
+ material.diffuseMapUv = this._diffuseMapUv;
5126
+ material.emissive = this._emissive;
5127
+ material.emissiveIntensity = this._emissiveIntensity;
5128
+ material.emissiveMapChannel = this._emissiveMapChannel;
5129
+ material.emissiveMapOffset = this._emissiveMapOffset;
5130
+ material.emissiveMapRotation = this._emissiveMapRotation;
5131
+ material.emissiveMapTiling = this._emissiveMapTiling;
5132
+ material.emissiveMapUv = this._emissiveMapUv;
5133
+ material.enableGGXSpecular = this._enableGGXSpecular;
5134
+ material.fresnelModel = fresnelModels.get(this._fresnelModel) ?? playcanvas.FRESNEL_SCHLICK;
5135
+ material.gloss = this._gloss;
5136
+ material.glossInvert = this._glossInvert;
5137
+ material.glossMapChannel = this._glossMapChannel;
5138
+ material.glossMapOffset = this._glossMapOffset;
5139
+ material.glossMapRotation = this._glossMapRotation;
5140
+ material.glossMapTiling = this._glossMapTiling;
5141
+ material.glossMapUv = this._glossMapUv;
5142
+ material.heightMapChannel = this._heightMapChannel;
5143
+ material.heightMapFactor = this._heightMapFactor;
5144
+ material.heightMapOffset = this._heightMapOffset;
5145
+ material.heightMapRotation = this._heightMapRotation;
5146
+ material.heightMapTiling = this._heightMapTiling;
5147
+ material.heightMapUv = this._heightMapUv;
5148
+ material.metalness = this._metalness;
5149
+ material.metalnessMapChannel = this._metalnessMapChannel;
5150
+ material.metalnessMapOffset = this._metalnessMapOffset;
5151
+ material.metalnessMapRotation = this._metalnessMapRotation;
5152
+ material.metalnessMapTiling = this._metalnessMapTiling;
5153
+ material.metalnessMapUv = this._metalnessMapUv;
5154
+ material.normalMapOffset = this._normalMapOffset;
5155
+ material.normalMapRotation = this._normalMapRotation;
5156
+ material.normalMapTiling = this._normalMapTiling;
5157
+ material.normalMapUv = this._normalMapUv;
5158
+ // @ts-ignore the engine's generated .d.ts types occludeDirect as a number, but its own
5159
+ // JSDoc documents it as a boolean and its runtime default is `false`
5160
+ material.occludeDirect = this._occludeDirect;
5161
+ material.occludeSpecular = occludeSpeculars.get(this._occludeSpecular) ?? playcanvas.SPECOCC_AO;
5162
+ material.opacity = this._opacity;
5163
+ material.opacityDither = this._opacityDither;
5164
+ material.opacityFadesSpecular = this._opacityFadesSpecular;
5165
+ material.opacityMapChannel = this._opacityMapChannel;
5166
+ material.opacityMapOffset = this._opacityMapOffset;
5167
+ material.opacityMapRotation = this._opacityMapRotation;
5168
+ material.opacityMapTiling = this._opacityMapTiling;
5169
+ material.opacityMapUv = this._opacityMapUv;
5170
+ material.slopeDepthBias = this._slopeDepthBias;
5171
+ material.specular = this._specular;
5172
+ material.specularityFactor = this._specularityFactor;
5173
+ material.twoSidedLighting = this._twoSidedLighting;
5174
+ material.useFog = this._useFog;
5175
+ material.useLighting = this._useLighting;
5176
+ // The engine defaults to the older specular/gloss workflow, in which metalnessMap is never
5177
+ // sampled at all - useMetalness drives the LIT_METALNESS define. This element defaults the
5178
+ // other way, so that `metalness-map` does what its name says.
5179
+ material.useMetalness = this._useMetalness;
5180
+ material.useMetalnessSpecularColor = this._useMetalnessSpecularColor;
5181
+ material.useSkybox = this._useSkybox;
5182
+ material.useTonemap = this._useTonemap;
5183
+ // Texture slots resolve a pc-asset id, which may not have loaded yet
5184
+ this.aoMap = this._aoMap;
5185
+ this.diffuseMap = this._diffuseMap;
5186
+ this.emissiveMap = this._emissiveMap;
5187
+ this.glossMap = this._glossMap;
5188
+ this.heightMap = this._heightMap;
5189
+ this.metalnessMap = this._metalnessMap;
5190
+ this.normalMap = this._normalMap;
5191
+ this.opacityMap = this._opacityMap;
5192
+ material.update();
5193
+ }
5194
+ disconnectedCallback() {
5195
+ for (const handle of this._mapHandles.values()) {
5196
+ handle.off();
5197
+ }
5198
+ this._mapHandles.clear();
5199
+ if (this.material) {
5200
+ this.material.destroy();
5201
+ this.material = null;
5202
+ }
5203
+ }
5204
+ /**
5205
+ * Coalesces `material.update()` across a burst of attribute or property writes, so that setting
5206
+ * a dozen attributes in one parse costs one update rather than a dozen.
5207
+ */
5208
+ _scheduleUpdate() {
5209
+ if (this._updateScheduled)
5210
+ return;
5211
+ this._updateScheduled = true;
5212
+ queueMicrotask(() => {
5213
+ this._updateScheduled = false;
5214
+ this.material?.update();
5215
+ });
5216
+ }
5217
+ /**
5218
+ * Warns when a `roughness-*` attribute is combined with one that carries the opposite
5219
+ * interpretation of the same value. They write the same engine properties but disagree about
5220
+ * whether the channel is inverted, so the result would depend on attribute order rather than
5221
+ * on intent.
5222
+ *
5223
+ * Called from both families rather than only from the roughness branches, because the two
5224
+ * orderings are equally wrong and only one of them would otherwise be caught. The conflict is
5225
+ * a property of the element rather than of any one write - and an upgrading element already
5226
+ * has all of its attributes, so every branch would otherwise report the same clash - so the
5227
+ * warning latches and reports once per episode, clearing when the clash is resolved.
5228
+ */
5229
+ _warnGlossConflict() {
5230
+ const quote = (names) => `'${names.join('\', \'')}'`;
5231
+ const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
5232
+ const gloss = glossConflicts.filter(name => this.hasAttribute(name));
5233
+ if (roughness.length === 0 || gloss.length === 0) {
5234
+ this._glossConflictWarned = false;
5235
+ return;
5236
+ }
5237
+ if (this._glossConflictWarned)
5238
+ return;
5239
+ this._glossConflictWarned = true;
5240
+ console.warn(`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
5241
+ 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.');
5242
+ }
5243
+ /**
5244
+ * Points a texture slot at the resource of a `pc-asset`, waiting for the asset to load when it
5245
+ * has not already. An empty id clears the slot.
5246
+ *
5247
+ * @param id - The id of the `pc-asset`, or an empty string to clear the slot.
5248
+ * @param slot - The material property to write.
5249
+ */
5250
+ setMap(id, slot) {
5251
+ // Drop any load still pending for this slot - its texture is no longer the one we want
5252
+ this._mapHandles.get(slot)?.off();
5253
+ this._mapHandles.delete(slot);
5254
+ if (!this.material)
5255
+ return;
5256
+ if (!id) {
5257
+ this.material[slot] = null;
5258
+ this._scheduleUpdate();
5259
+ return;
5260
+ }
5261
+ const asset = AssetElement.get(id);
5262
+ if (!asset)
5263
+ return;
5264
+ if (asset.loaded) {
5265
+ this._applyMap(slot, asset.resource);
5266
+ return;
5267
+ }
5268
+ this._mapHandles.set(slot, asset.once('load', () => {
5269
+ this._mapHandles.delete(slot);
5270
+ this._applyMap(slot, asset.resource);
5271
+ }));
5272
+ }
5273
+ /**
5274
+ * @param slot - The material property to write.
5275
+ * @param texture - The loaded texture.
5276
+ */
5277
+ _applyMap(slot, texture) {
5278
+ if (!this.material)
5279
+ return;
5280
+ this.material[slot] = texture;
5281
+ texture.anisotropy = 4;
5282
+ this._scheduleUpdate();
5283
+ }
5284
+ /**
5285
+ * Sets the alpha test reference value. Fragments with an opacity below this value are discarded.
5286
+ * @param value - The alpha test reference value.
5287
+ */
5288
+ set alphaTest(value) {
5289
+ this._alphaTest = value;
5290
+ if (this.material) {
5291
+ this.material.alphaTest = value;
5292
+ this._scheduleUpdate();
5293
+ }
5294
+ }
5295
+ /**
5296
+ * Gets the alpha test reference value.
5297
+ * @returns The alpha test reference value.
5298
+ */
5299
+ get alphaTest() {
5300
+ return this._alphaTest;
5301
+ }
5302
+ /**
5303
+ * Sets whether to use alpha to coverage, which resolves transparency using multisampling.
5304
+ * @param value - The alpha to coverage flag.
5305
+ */
5306
+ set alphaToCoverage(value) {
5307
+ this._alphaToCoverage = value;
5308
+ if (this.material) {
5309
+ this.material.alphaToCoverage = value;
5310
+ this._scheduleUpdate();
5311
+ }
5312
+ }
5313
+ /**
5314
+ * Gets whether to use alpha to coverage.
5315
+ * @returns The alpha to coverage flag.
5316
+ */
5317
+ get alphaToCoverage() {
5318
+ return this._alphaToCoverage;
5319
+ }
5320
+ /**
5321
+ * Sets the strength of the ambient occlusion map, from 0 to 1.
5322
+ * @param value - The ambient occlusion intensity.
5323
+ */
5324
+ set aoIntensity(value) {
5325
+ this._aoIntensity = value;
5326
+ if (this.material) {
5327
+ this.material.aoIntensity = value;
5328
+ this._scheduleUpdate();
5329
+ }
5330
+ }
5331
+ /**
5332
+ * Gets the strength of the ambient occlusion map.
5333
+ * @returns The ambient occlusion intensity.
5334
+ */
5335
+ get aoIntensity() {
5336
+ return this._aoIntensity;
5337
+ }
5338
+ /**
5339
+ * Sets the id of the `pc-asset` to use as the ambient occlusion map.
5340
+ * @param value - The asset id.
5341
+ */
5342
+ set aoMap(value) {
5343
+ this._aoMap = value;
5344
+ this.setMap(value, 'aoMap');
5345
+ }
5346
+ /**
5347
+ * Gets the id of the `pc-asset` used as the ambient occlusion map.
5348
+ * @returns The asset id.
5349
+ */
5350
+ get aoMap() {
5351
+ return this._aoMap;
5352
+ }
5353
+ /**
5354
+ * Sets the color channel of the ambient occlusion map to sample.
5355
+ * @param value - The channel.
5356
+ */
5357
+ set aoMapChannel(value) {
5358
+ this._aoMapChannel = value;
5359
+ if (this.material) {
5360
+ this.material.aoMapChannel = value;
5361
+ this._scheduleUpdate();
5362
+ }
5363
+ }
5364
+ /**
5365
+ * Gets the color channel of the ambient occlusion map to sample.
5366
+ * @returns The channel.
5367
+ */
5368
+ get aoMapChannel() {
5369
+ return this._aoMapChannel;
5370
+ }
5371
+ /**
5372
+ * Sets the 2D offset of the ambient occlusion map.
5373
+ * @param value - The offset.
5374
+ */
5375
+ set aoMapOffset(value) {
5376
+ this._aoMapOffset = value;
5377
+ if (this.material) {
5378
+ this.material.aoMapOffset = value;
5379
+ this._scheduleUpdate();
5380
+ }
5381
+ }
5382
+ /**
5383
+ * Gets the 2D offset of the ambient occlusion map.
5384
+ * @returns The offset.
5385
+ */
5386
+ get aoMapOffset() {
5387
+ return this._aoMapOffset;
5388
+ }
5389
+ /**
5390
+ * Sets the 2D rotation of the ambient occlusion map, in degrees.
5391
+ * @param value - The rotation.
5392
+ */
5393
+ set aoMapRotation(value) {
5394
+ this._aoMapRotation = value;
5395
+ if (this.material) {
5396
+ this.material.aoMapRotation = value;
5397
+ this._scheduleUpdate();
5398
+ }
5399
+ }
5400
+ /**
5401
+ * Gets the 2D rotation of the ambient occlusion map.
5402
+ * @returns The rotation.
5403
+ */
5404
+ get aoMapRotation() {
5405
+ return this._aoMapRotation;
5406
+ }
5407
+ /**
5408
+ * Sets the 2D tiling of the ambient occlusion map.
5409
+ * @param value - The tiling.
5410
+ */
5411
+ set aoMapTiling(value) {
5412
+ this._aoMapTiling = value;
5413
+ if (this.material) {
5414
+ this.material.aoMapTiling = value;
5415
+ this._scheduleUpdate();
5416
+ }
5417
+ }
5418
+ /**
5419
+ * Gets the 2D tiling of the ambient occlusion map.
5420
+ * @returns The tiling.
5421
+ */
5422
+ get aoMapTiling() {
5423
+ return this._aoMapTiling;
5424
+ }
5425
+ /**
5426
+ * Sets the UV channel the ambient occlusion map samples.
5427
+ * @param value - The UV channel.
5428
+ */
5429
+ set aoMapUv(value) {
5430
+ this._aoMapUv = value;
5431
+ if (this.material) {
5432
+ this.material.aoMapUv = value;
5433
+ this._scheduleUpdate();
5434
+ }
5435
+ }
5436
+ /**
5437
+ * Gets the UV channel the ambient occlusion map samples.
5438
+ * @returns The UV channel.
5439
+ */
5440
+ get aoMapUv() {
5441
+ return this._aoMapUv;
5442
+ }
5443
+ /**
5444
+ * Sets how the material is blended with the scene behind it.
5445
+ * @param value - The blend type.
5446
+ */
5447
+ set blendType(value) {
5448
+ this._blendType = value;
5449
+ if (this.material) {
5450
+ this.material.blendType = blendTypes.get(value) ?? playcanvas.BLEND_NONE;
5451
+ this._scheduleUpdate();
5452
+ }
5453
+ }
5454
+ /**
5455
+ * Gets how the material is blended with the scene behind it.
5456
+ * @returns The blend type.
5457
+ */
5458
+ get blendType() {
5459
+ return this._blendType;
5460
+ }
5461
+ /**
5462
+ * Sets the strength of the normal map, where 0 is flat and 1 is the map's full effect.
5463
+ * @param value - The bumpiness.
5464
+ */
5465
+ set bumpiness(value) {
5466
+ this._bumpiness = value;
5467
+ if (this.material) {
5468
+ this.material.bumpiness = value;
5469
+ this._scheduleUpdate();
5470
+ }
5471
+ }
5472
+ /**
5473
+ * Gets the strength of the normal map.
5474
+ * @returns The bumpiness.
5475
+ */
5476
+ get bumpiness() {
5477
+ return this._bumpiness;
5478
+ }
5479
+ /**
5480
+ * Sets which faces of a mesh are culled.
5481
+ * @param value - The cull mode.
5482
+ */
5483
+ set cull(value) {
5484
+ this._cull = value;
5485
+ if (this.material) {
5486
+ this.material.cull = cullModes.get(value) ?? playcanvas.CULLFACE_BACK;
5487
+ this._scheduleUpdate();
5488
+ }
5489
+ }
5490
+ /**
5491
+ * Gets which faces of a mesh are culled.
5492
+ * @returns The cull mode.
5493
+ */
5494
+ get cull() {
5495
+ return this._cull;
5496
+ }
5497
+ /**
5498
+ * Sets the offset applied to the depth of a fragment, used to resolve z-fighting.
5499
+ * @param value - The depth bias.
5500
+ */
5501
+ set depthBias(value) {
5502
+ this._depthBias = value;
5503
+ if (this.material) {
5504
+ this.material.depthBias = value;
5505
+ this._scheduleUpdate();
5506
+ }
5507
+ }
5508
+ /**
5509
+ * Gets the offset applied to the depth of a fragment.
5510
+ * @returns The depth bias.
5511
+ */
5512
+ get depthBias() {
5513
+ return this._depthBias;
5514
+ }
5515
+ /**
5516
+ * Sets whether fragments are tested against the depth buffer.
5517
+ * @param value - The depth test flag.
5518
+ */
5519
+ set depthTest(value) {
5520
+ this._depthTest = value;
5521
+ if (this.material) {
5522
+ this.material.depthTest = value;
5523
+ this._scheduleUpdate();
5524
+ }
5525
+ }
5526
+ /**
5527
+ * Gets whether fragments are tested against the depth buffer.
5528
+ * @returns The depth test flag.
5529
+ */
5530
+ get depthTest() {
5531
+ return this._depthTest;
5532
+ }
5533
+ /**
5534
+ * Sets whether fragments write to the depth buffer.
5535
+ * @param value - The depth write flag.
5536
+ */
5537
+ set depthWrite(value) {
5538
+ this._depthWrite = value;
5539
+ if (this.material) {
5540
+ this.material.depthWrite = value;
5541
+ this._scheduleUpdate();
5542
+ }
5543
+ }
5544
+ /**
5545
+ * Gets whether fragments write to the depth buffer.
5546
+ * @returns The depth write flag.
5547
+ */
5548
+ get depthWrite() {
5549
+ return this._depthWrite;
5550
+ }
5551
+ /**
5552
+ * Sets the diffuse color of the material. With the metalness workflow this doubles as the
5553
+ * specular color where the surface is metallic.
5554
+ * @param value - The diffuse color.
5555
+ */
5556
+ set diffuse(value) {
5557
+ this._diffuse = value;
5558
+ if (this.material) {
5559
+ this.material.diffuse = value;
5560
+ this._scheduleUpdate();
5561
+ }
5562
+ }
5563
+ /**
5564
+ * Gets the diffuse color of the material.
5565
+ * @returns The diffuse color.
5566
+ */
5567
+ get diffuse() {
5568
+ return this._diffuse;
5569
+ }
5570
+ /**
5571
+ * Sets the id of the `pc-asset` to use as the diffuse map.
5572
+ * @param value - The asset id.
5573
+ */
5574
+ set diffuseMap(value) {
5575
+ this._diffuseMap = value;
5576
+ this.setMap(value, 'diffuseMap');
5577
+ }
5578
+ /**
5579
+ * Gets the id of the `pc-asset` used as the diffuse map.
5580
+ * @returns The asset id.
5581
+ */
5582
+ get diffuseMap() {
5583
+ return this._diffuseMap;
5584
+ }
5585
+ /**
5586
+ * Sets the color channels of the diffuse map to sample.
5587
+ * @param value - The channels.
5588
+ */
5589
+ set diffuseMapChannel(value) {
5590
+ this._diffuseMapChannel = value;
5591
+ if (this.material) {
5592
+ this.material.diffuseMapChannel = value;
5593
+ this._scheduleUpdate();
5594
+ }
5595
+ }
5596
+ /**
5597
+ * Gets the color channels of the diffuse map to sample.
5598
+ * @returns The channels.
5599
+ */
5600
+ get diffuseMapChannel() {
5601
+ return this._diffuseMapChannel;
5602
+ }
5603
+ /**
5604
+ * Sets the 2D offset of the diffuse map.
5605
+ * @param value - The offset.
5606
+ */
5607
+ set diffuseMapOffset(value) {
5608
+ this._diffuseMapOffset = value;
5609
+ if (this.material) {
5610
+ this.material.diffuseMapOffset = value;
5611
+ this._scheduleUpdate();
5612
+ }
5613
+ }
5614
+ /**
5615
+ * Gets the 2D offset of the diffuse map.
5616
+ * @returns The offset.
5617
+ */
5618
+ get diffuseMapOffset() {
5619
+ return this._diffuseMapOffset;
5620
+ }
5621
+ /**
5622
+ * Sets the 2D rotation of the diffuse map, in degrees.
5623
+ * @param value - The rotation.
5624
+ */
5625
+ set diffuseMapRotation(value) {
5626
+ this._diffuseMapRotation = value;
5627
+ if (this.material) {
5628
+ this.material.diffuseMapRotation = value;
5629
+ this._scheduleUpdate();
5630
+ }
5631
+ }
5632
+ /**
5633
+ * Gets the 2D rotation of the diffuse map.
5634
+ * @returns The rotation.
5635
+ */
5636
+ get diffuseMapRotation() {
5637
+ return this._diffuseMapRotation;
5638
+ }
5639
+ /**
5640
+ * Sets the 2D tiling of the diffuse map.
5641
+ * @param value - The tiling.
5642
+ */
5643
+ set diffuseMapTiling(value) {
5644
+ this._diffuseMapTiling = value;
5645
+ if (this.material) {
5646
+ this.material.diffuseMapTiling = value;
5647
+ this._scheduleUpdate();
5648
+ }
5649
+ }
5650
+ /**
5651
+ * Gets the 2D tiling of the diffuse map.
5652
+ * @returns The tiling.
5653
+ */
5654
+ get diffuseMapTiling() {
5655
+ return this._diffuseMapTiling;
5656
+ }
5657
+ /**
5658
+ * Sets the UV channel the diffuse map samples.
5659
+ * @param value - The UV channel.
5660
+ */
5661
+ set diffuseMapUv(value) {
5662
+ this._diffuseMapUv = value;
5663
+ if (this.material) {
5664
+ this.material.diffuseMapUv = value;
5665
+ this._scheduleUpdate();
5666
+ }
5667
+ }
5668
+ /**
5669
+ * Gets the UV channel the diffuse map samples.
5670
+ * @returns The UV channel.
5671
+ */
5672
+ get diffuseMapUv() {
5673
+ return this._diffuseMapUv;
5674
+ }
5675
+ /**
5676
+ * Sets the emissive color of the material, which is added to the lit result.
5677
+ * @param value - The emissive color.
5678
+ */
5679
+ set emissive(value) {
5680
+ this._emissive = value;
5681
+ if (this.material) {
5682
+ this.material.emissive = value;
5683
+ this._scheduleUpdate();
5684
+ }
5685
+ }
5686
+ /**
5687
+ * Gets the emissive color of the material.
5688
+ * @returns The emissive color.
5689
+ */
5690
+ get emissive() {
5691
+ return this._emissive;
5692
+ }
5693
+ /**
5694
+ * Sets the multiplier applied to the emissive color and map.
5695
+ * @param value - The emissive intensity.
5696
+ */
5697
+ set emissiveIntensity(value) {
5698
+ this._emissiveIntensity = value;
5699
+ if (this.material) {
5700
+ this.material.emissiveIntensity = value;
5701
+ this._scheduleUpdate();
5702
+ }
5703
+ }
5704
+ /**
5705
+ * Gets the multiplier applied to the emissive color and map.
5706
+ * @returns The emissive intensity.
5707
+ */
5708
+ get emissiveIntensity() {
5709
+ return this._emissiveIntensity;
5710
+ }
5711
+ /**
5712
+ * Sets the id of the `pc-asset` to use as the emissive map.
5713
+ * @param value - The asset id.
5714
+ */
5715
+ set emissiveMap(value) {
5716
+ this._emissiveMap = value;
5717
+ this.setMap(value, 'emissiveMap');
5718
+ }
5719
+ /**
5720
+ * Gets the id of the `pc-asset` used as the emissive map.
5721
+ * @returns The asset id.
5722
+ */
5723
+ get emissiveMap() {
5724
+ return this._emissiveMap;
5725
+ }
5726
+ /**
5727
+ * Sets the color channels of the emissive map to sample.
5728
+ * @param value - The channels.
5729
+ */
5730
+ set emissiveMapChannel(value) {
5731
+ this._emissiveMapChannel = value;
5732
+ if (this.material) {
5733
+ this.material.emissiveMapChannel = value;
5734
+ this._scheduleUpdate();
5735
+ }
5736
+ }
5737
+ /**
5738
+ * Gets the color channels of the emissive map to sample.
5739
+ * @returns The channels.
5740
+ */
5741
+ get emissiveMapChannel() {
5742
+ return this._emissiveMapChannel;
5743
+ }
5744
+ /**
5745
+ * Sets the 2D offset of the emissive map.
5746
+ * @param value - The offset.
5747
+ */
5748
+ set emissiveMapOffset(value) {
5749
+ this._emissiveMapOffset = value;
5750
+ if (this.material) {
5751
+ this.material.emissiveMapOffset = value;
5752
+ this._scheduleUpdate();
5753
+ }
5754
+ }
5755
+ /**
5756
+ * Gets the 2D offset of the emissive map.
5757
+ * @returns The offset.
5758
+ */
5759
+ get emissiveMapOffset() {
5760
+ return this._emissiveMapOffset;
5761
+ }
5762
+ /**
5763
+ * Sets the 2D rotation of the emissive map, in degrees.
5764
+ * @param value - The rotation.
5765
+ */
5766
+ set emissiveMapRotation(value) {
5767
+ this._emissiveMapRotation = value;
5768
+ if (this.material) {
5769
+ this.material.emissiveMapRotation = value;
5770
+ this._scheduleUpdate();
5771
+ }
5772
+ }
5773
+ /**
5774
+ * Gets the 2D rotation of the emissive map.
5775
+ * @returns The rotation.
5776
+ */
5777
+ get emissiveMapRotation() {
5778
+ return this._emissiveMapRotation;
5779
+ }
5780
+ /**
5781
+ * Sets the 2D tiling of the emissive map.
5782
+ * @param value - The tiling.
5783
+ */
5784
+ set emissiveMapTiling(value) {
5785
+ this._emissiveMapTiling = value;
5786
+ if (this.material) {
5787
+ this.material.emissiveMapTiling = value;
5788
+ this._scheduleUpdate();
5789
+ }
5790
+ }
5791
+ /**
5792
+ * Gets the 2D tiling of the emissive map.
5793
+ * @returns The tiling.
5794
+ */
5795
+ get emissiveMapTiling() {
5796
+ return this._emissiveMapTiling;
5797
+ }
5798
+ /**
5799
+ * Sets the UV channel the emissive map samples.
5800
+ * @param value - The UV channel.
5801
+ */
5802
+ set emissiveMapUv(value) {
5803
+ this._emissiveMapUv = value;
5804
+ if (this.material) {
5805
+ this.material.emissiveMapUv = value;
5806
+ this._scheduleUpdate();
5807
+ }
5808
+ }
5809
+ /**
5810
+ * Gets the UV channel the emissive map samples.
5811
+ * @returns The UV channel.
5812
+ */
5813
+ get emissiveMapUv() {
5814
+ return this._emissiveMapUv;
5815
+ }
5816
+ /**
5817
+ * Sets whether to use the GGX specular model, which supports anisotropy.
5818
+ * @param value - The GGX specular flag.
5819
+ */
5820
+ set enableGGXSpecular(value) {
5821
+ this._enableGGXSpecular = value;
5822
+ if (this.material) {
5823
+ this.material.enableGGXSpecular = value;
5824
+ this._scheduleUpdate();
5825
+ }
5826
+ }
5827
+ /**
5828
+ * Gets whether to use the GGX specular model.
5829
+ * @returns The GGX specular flag.
5830
+ */
5831
+ get enableGGXSpecular() {
5832
+ return this._enableGGXSpecular;
5833
+ }
5834
+ /**
5835
+ * Sets the Fresnel model used for specular reflections at grazing angles.
5836
+ * @param value - The Fresnel model.
5837
+ */
5838
+ set fresnelModel(value) {
5839
+ this._fresnelModel = value;
5840
+ if (this.material) {
5841
+ this.material.fresnelModel = fresnelModels.get(value) ?? playcanvas.FRESNEL_SCHLICK;
5842
+ this._scheduleUpdate();
5843
+ }
5844
+ }
5845
+ /**
5846
+ * Gets the Fresnel model used for specular reflections at grazing angles.
5847
+ * @returns The Fresnel model.
5848
+ */
5849
+ get fresnelModel() {
5850
+ return this._fresnelModel;
5851
+ }
5852
+ /**
5853
+ * Sets the glossiness of the material, from 0 (rough) to 1 (shiny). See also `roughness`.
5854
+ * @param value - The gloss.
5855
+ */
5856
+ set gloss(value) {
5857
+ this._gloss = value;
5858
+ if (this.material) {
5859
+ this.material.gloss = value;
5860
+ this._scheduleUpdate();
5861
+ }
5862
+ }
5863
+ /**
5864
+ * Gets the glossiness of the material.
5865
+ * @returns The gloss.
5866
+ */
5867
+ get gloss() {
5868
+ return this._gloss;
5869
+ }
5870
+ /**
5871
+ * Sets whether the gloss value and map are inverted, which makes the material treat them as
5872
+ * roughness. Setting `roughness` or `roughness-map` enables this automatically.
5873
+ * @param value - The gloss invert flag.
5874
+ */
5875
+ set glossInvert(value) {
5876
+ this._glossInvert = value;
5877
+ if (this.material) {
5878
+ this.material.glossInvert = value;
5879
+ this._scheduleUpdate();
5880
+ }
5881
+ }
5882
+ /**
5883
+ * Gets whether the gloss value and map are inverted.
5884
+ * @returns The gloss invert flag.
5885
+ */
5886
+ get glossInvert() {
5887
+ return this._glossInvert;
5888
+ }
5889
+ /**
5890
+ * Sets the id of the `pc-asset` to use as the gloss map. See also `roughnessMap`.
5891
+ * @param value - The asset id.
5892
+ */
5893
+ set glossMap(value) {
5894
+ this._glossMap = value;
5895
+ this.setMap(value, 'glossMap');
5896
+ }
5897
+ /**
5898
+ * Gets the id of the `pc-asset` used as the gloss map.
5899
+ * @returns The asset id.
5900
+ */
5901
+ get glossMap() {
5902
+ return this._glossMap;
5903
+ }
5904
+ /**
5905
+ * Sets the color channel of the gloss map to sample.
5906
+ * @param value - The channel.
5907
+ */
5908
+ set glossMapChannel(value) {
5909
+ this._glossMapChannel = value;
5910
+ if (this.material) {
5911
+ this.material.glossMapChannel = value;
5912
+ this._scheduleUpdate();
5913
+ }
5914
+ }
5915
+ /**
5916
+ * Gets the color channel of the gloss map to sample.
5917
+ * @returns The channel.
5918
+ */
5919
+ get glossMapChannel() {
5920
+ return this._glossMapChannel;
5921
+ }
5922
+ /**
5923
+ * Sets the 2D offset of the gloss map.
5924
+ * @param value - The offset.
5925
+ */
5926
+ set glossMapOffset(value) {
5927
+ this._glossMapOffset = value;
5928
+ if (this.material) {
5929
+ this.material.glossMapOffset = value;
5930
+ this._scheduleUpdate();
5931
+ }
5932
+ }
5933
+ /**
5934
+ * Gets the 2D offset of the gloss map.
5935
+ * @returns The offset.
5936
+ */
5937
+ get glossMapOffset() {
5938
+ return this._glossMapOffset;
5939
+ }
5940
+ /**
5941
+ * Sets the 2D rotation of the gloss map, in degrees.
5942
+ * @param value - The rotation.
5943
+ */
5944
+ set glossMapRotation(value) {
5945
+ this._glossMapRotation = value;
5946
+ if (this.material) {
5947
+ this.material.glossMapRotation = value;
5948
+ this._scheduleUpdate();
5949
+ }
5950
+ }
5951
+ /**
5952
+ * Gets the 2D rotation of the gloss map.
5953
+ * @returns The rotation.
5954
+ */
5955
+ get glossMapRotation() {
5956
+ return this._glossMapRotation;
5957
+ }
5958
+ /**
5959
+ * Sets the 2D tiling of the gloss map.
5960
+ * @param value - The tiling.
5961
+ */
5962
+ set glossMapTiling(value) {
5963
+ this._glossMapTiling = value;
5964
+ if (this.material) {
5965
+ this.material.glossMapTiling = value;
5966
+ this._scheduleUpdate();
5967
+ }
5968
+ }
5969
+ /**
5970
+ * Gets the 2D tiling of the gloss map.
5971
+ * @returns The tiling.
5972
+ */
5973
+ get glossMapTiling() {
5974
+ return this._glossMapTiling;
5975
+ }
5976
+ /**
5977
+ * Sets the UV channel the gloss map samples.
5978
+ * @param value - The UV channel.
5979
+ */
5980
+ set glossMapUv(value) {
5981
+ this._glossMapUv = value;
5982
+ if (this.material) {
5983
+ this.material.glossMapUv = value;
5984
+ this._scheduleUpdate();
5985
+ }
5986
+ }
5987
+ /**
5988
+ * Gets the UV channel the gloss map samples.
5989
+ * @returns The UV channel.
5990
+ */
5991
+ get glossMapUv() {
5992
+ return this._glossMapUv;
5993
+ }
5994
+ /**
5995
+ * Sets the id of the `pc-asset` to use as the height map, which drives parallax mapping.
5996
+ * @param value - The asset id.
5997
+ */
5998
+ set heightMap(value) {
5999
+ this._heightMap = value;
6000
+ this.setMap(value, 'heightMap');
6001
+ }
6002
+ /**
6003
+ * Gets the id of the `pc-asset` used as the height map.
6004
+ * @returns The asset id.
6005
+ */
6006
+ get heightMap() {
6007
+ return this._heightMap;
6008
+ }
6009
+ /**
6010
+ * Sets the color channel of the height map to sample.
6011
+ * @param value - The channel.
6012
+ */
6013
+ set heightMapChannel(value) {
6014
+ this._heightMapChannel = value;
6015
+ if (this.material) {
6016
+ this.material.heightMapChannel = value;
6017
+ this._scheduleUpdate();
6018
+ }
6019
+ }
6020
+ /**
6021
+ * Gets the color channel of the height map to sample.
6022
+ * @returns The channel.
6023
+ */
6024
+ get heightMapChannel() {
6025
+ return this._heightMapChannel;
6026
+ }
6027
+ /**
6028
+ * Sets the strength of the parallax effect driven by the height map.
6029
+ * @param value - The height map factor.
6030
+ */
6031
+ set heightMapFactor(value) {
6032
+ this._heightMapFactor = value;
6033
+ if (this.material) {
6034
+ this.material.heightMapFactor = value;
6035
+ this._scheduleUpdate();
6036
+ }
6037
+ }
6038
+ /**
6039
+ * Gets the strength of the parallax effect driven by the height map.
6040
+ * @returns The height map factor.
6041
+ */
6042
+ get heightMapFactor() {
6043
+ return this._heightMapFactor;
6044
+ }
6045
+ /**
6046
+ * Sets the 2D offset of the height map.
6047
+ * @param value - The offset.
6048
+ */
6049
+ set heightMapOffset(value) {
6050
+ this._heightMapOffset = value;
6051
+ if (this.material) {
6052
+ this.material.heightMapOffset = value;
6053
+ this._scheduleUpdate();
6054
+ }
6055
+ }
6056
+ /**
6057
+ * Gets the 2D offset of the height map.
6058
+ * @returns The offset.
6059
+ */
6060
+ get heightMapOffset() {
6061
+ return this._heightMapOffset;
6062
+ }
6063
+ /**
6064
+ * Sets the 2D rotation of the height map, in degrees.
6065
+ * @param value - The rotation.
6066
+ */
6067
+ set heightMapRotation(value) {
6068
+ this._heightMapRotation = value;
6069
+ if (this.material) {
6070
+ this.material.heightMapRotation = value;
6071
+ this._scheduleUpdate();
6072
+ }
6073
+ }
6074
+ /**
6075
+ * Gets the 2D rotation of the height map.
6076
+ * @returns The rotation.
6077
+ */
6078
+ get heightMapRotation() {
6079
+ return this._heightMapRotation;
6080
+ }
6081
+ /**
6082
+ * Sets the 2D tiling of the height map.
6083
+ * @param value - The tiling.
6084
+ */
6085
+ set heightMapTiling(value) {
6086
+ this._heightMapTiling = value;
6087
+ if (this.material) {
6088
+ this.material.heightMapTiling = value;
6089
+ this._scheduleUpdate();
6090
+ }
6091
+ }
6092
+ /**
6093
+ * Gets the 2D tiling of the height map.
6094
+ * @returns The tiling.
6095
+ */
6096
+ get heightMapTiling() {
6097
+ return this._heightMapTiling;
6098
+ }
6099
+ /**
6100
+ * Sets the UV channel the height map samples.
6101
+ * @param value - The UV channel.
6102
+ */
6103
+ set heightMapUv(value) {
6104
+ this._heightMapUv = value;
6105
+ if (this.material) {
6106
+ this.material.heightMapUv = value;
6107
+ this._scheduleUpdate();
6108
+ }
6109
+ }
6110
+ /**
6111
+ * Gets the UV channel the height map samples.
6112
+ * @returns The UV channel.
6113
+ */
6114
+ get heightMapUv() {
6115
+ return this._heightMapUv;
6116
+ }
6117
+ /**
6118
+ * Sets how metallic the surface is, from 0 (dielectric) to 1 (metal).
6119
+ * @param value - The metalness.
6120
+ */
6121
+ set metalness(value) {
6122
+ this._metalness = value;
6123
+ if (this.material) {
6124
+ this.material.metalness = value;
6125
+ this._scheduleUpdate();
6126
+ }
6127
+ }
6128
+ /**
6129
+ * Gets how metallic the surface is.
6130
+ * @returns The metalness.
6131
+ */
6132
+ get metalness() {
6133
+ return this._metalness;
6134
+ }
6135
+ /**
6136
+ * Sets the id of the `pc-asset` to use as the metalness map.
6137
+ * @param value - The asset id.
6138
+ */
6139
+ set metalnessMap(value) {
6140
+ this._metalnessMap = value;
6141
+ this.setMap(value, 'metalnessMap');
6142
+ }
6143
+ /**
6144
+ * Gets the id of the `pc-asset` used as the metalness map.
6145
+ * @returns The asset id.
6146
+ */
6147
+ get metalnessMap() {
6148
+ return this._metalnessMap;
6149
+ }
6150
+ /**
6151
+ * Sets the color channel of the metalness map to sample.
6152
+ * @param value - The channel.
6153
+ */
6154
+ set metalnessMapChannel(value) {
6155
+ this._metalnessMapChannel = value;
6156
+ if (this.material) {
6157
+ this.material.metalnessMapChannel = value;
6158
+ this._scheduleUpdate();
6159
+ }
6160
+ }
6161
+ /**
6162
+ * Gets the color channel of the metalness map to sample.
6163
+ * @returns The channel.
6164
+ */
6165
+ get metalnessMapChannel() {
6166
+ return this._metalnessMapChannel;
6167
+ }
6168
+ /**
6169
+ * Sets the 2D offset of the metalness map.
6170
+ * @param value - The offset.
6171
+ */
6172
+ set metalnessMapOffset(value) {
6173
+ this._metalnessMapOffset = value;
6174
+ if (this.material) {
6175
+ this.material.metalnessMapOffset = value;
6176
+ this._scheduleUpdate();
6177
+ }
6178
+ }
6179
+ /**
6180
+ * Gets the 2D offset of the metalness map.
6181
+ * @returns The offset.
6182
+ */
6183
+ get metalnessMapOffset() {
6184
+ return this._metalnessMapOffset;
6185
+ }
6186
+ /**
6187
+ * Sets the 2D rotation of the metalness map, in degrees.
6188
+ * @param value - The rotation.
6189
+ */
6190
+ set metalnessMapRotation(value) {
6191
+ this._metalnessMapRotation = value;
6192
+ if (this.material) {
6193
+ this.material.metalnessMapRotation = value;
6194
+ this._scheduleUpdate();
6195
+ }
6196
+ }
6197
+ /**
6198
+ * Gets the 2D rotation of the metalness map.
6199
+ * @returns The rotation.
6200
+ */
6201
+ get metalnessMapRotation() {
6202
+ return this._metalnessMapRotation;
6203
+ }
6204
+ /**
6205
+ * Sets the 2D tiling of the metalness map.
6206
+ * @param value - The tiling.
6207
+ */
6208
+ set metalnessMapTiling(value) {
6209
+ this._metalnessMapTiling = value;
6210
+ if (this.material) {
6211
+ this.material.metalnessMapTiling = value;
6212
+ this._scheduleUpdate();
6213
+ }
6214
+ }
6215
+ /**
6216
+ * Gets the 2D tiling of the metalness map.
6217
+ * @returns The tiling.
6218
+ */
6219
+ get metalnessMapTiling() {
6220
+ return this._metalnessMapTiling;
6221
+ }
6222
+ /**
6223
+ * Sets the UV channel the metalness map samples.
6224
+ * @param value - The UV channel.
6225
+ */
6226
+ set metalnessMapUv(value) {
6227
+ this._metalnessMapUv = value;
6228
+ if (this.material) {
6229
+ this.material.metalnessMapUv = value;
6230
+ this._scheduleUpdate();
6231
+ }
6232
+ }
6233
+ /**
6234
+ * Gets the UV channel the metalness map samples.
6235
+ * @returns The UV channel.
6236
+ */
6237
+ get metalnessMapUv() {
6238
+ return this._metalnessMapUv;
6239
+ }
6240
+ /**
6241
+ * Sets the id of the `pc-asset` to use as the normal map.
6242
+ * @param value - The asset id.
6243
+ */
6244
+ set normalMap(value) {
6245
+ this._normalMap = value;
6246
+ this.setMap(value, 'normalMap');
6247
+ }
6248
+ /**
6249
+ * Gets the id of the `pc-asset` used as the normal map.
6250
+ * @returns The asset id.
6251
+ */
6252
+ get normalMap() {
6253
+ return this._normalMap;
6254
+ }
6255
+ /**
6256
+ * Sets the 2D offset of the normal map.
6257
+ * @param value - The offset.
6258
+ */
6259
+ set normalMapOffset(value) {
6260
+ this._normalMapOffset = value;
6261
+ if (this.material) {
6262
+ this.material.normalMapOffset = value;
6263
+ this._scheduleUpdate();
6264
+ }
6265
+ }
6266
+ /**
6267
+ * Gets the 2D offset of the normal map.
6268
+ * @returns The offset.
6269
+ */
6270
+ get normalMapOffset() {
6271
+ return this._normalMapOffset;
6272
+ }
6273
+ /**
6274
+ * Sets the 2D rotation of the normal map, in degrees.
6275
+ * @param value - The rotation.
6276
+ */
6277
+ set normalMapRotation(value) {
6278
+ this._normalMapRotation = value;
6279
+ if (this.material) {
6280
+ this.material.normalMapRotation = value;
6281
+ this._scheduleUpdate();
6282
+ }
6283
+ }
6284
+ /**
6285
+ * Gets the 2D rotation of the normal map.
6286
+ * @returns The rotation.
6287
+ */
6288
+ get normalMapRotation() {
6289
+ return this._normalMapRotation;
6290
+ }
6291
+ /**
6292
+ * Sets the 2D tiling of the normal map.
6293
+ * @param value - The tiling.
6294
+ */
6295
+ set normalMapTiling(value) {
6296
+ this._normalMapTiling = value;
6297
+ if (this.material) {
6298
+ this.material.normalMapTiling = value;
6299
+ this._scheduleUpdate();
6300
+ }
6301
+ }
6302
+ /**
6303
+ * Gets the 2D tiling of the normal map.
6304
+ * @returns The tiling.
6305
+ */
6306
+ get normalMapTiling() {
6307
+ return this._normalMapTiling;
6308
+ }
6309
+ /**
6310
+ * Sets the UV channel the normal map samples.
6311
+ * @param value - The UV channel.
6312
+ */
6313
+ set normalMapUv(value) {
6314
+ this._normalMapUv = value;
6315
+ if (this.material) {
6316
+ this.material.normalMapUv = value;
6317
+ this._scheduleUpdate();
6318
+ }
6319
+ }
6320
+ /**
6321
+ * Gets the UV channel the normal map samples.
6322
+ * @returns The UV channel.
6323
+ */
6324
+ get normalMapUv() {
6325
+ return this._normalMapUv;
6326
+ }
6327
+ /**
6328
+ * Sets whether ambient occlusion also attenuates direct lighting.
6329
+ * @param value - The occlude direct flag.
6330
+ */
6331
+ set occludeDirect(value) {
6332
+ this._occludeDirect = value;
6333
+ if (this.material) {
6334
+ // @ts-ignore see createMaterial() - the engine mistypes occludeDirect as a number
6335
+ this.material.occludeDirect = value;
6336
+ this._scheduleUpdate();
6337
+ }
6338
+ }
6339
+ /**
6340
+ * Gets whether ambient occlusion also attenuates direct lighting.
6341
+ * @returns The occlude direct flag.
6342
+ */
6343
+ get occludeDirect() {
6344
+ return this._occludeDirect;
6345
+ }
6346
+ /**
6347
+ * Sets how specular reflections are occluded.
6348
+ * @param value - The specular occlusion mode.
6349
+ */
6350
+ set occludeSpecular(value) {
6351
+ this._occludeSpecular = value;
6352
+ if (this.material) {
6353
+ this.material.occludeSpecular = occludeSpeculars.get(value) ?? playcanvas.SPECOCC_AO;
6354
+ this._scheduleUpdate();
6355
+ }
6356
+ }
6357
+ /**
6358
+ * Gets how specular reflections are occluded.
6359
+ * @returns The specular occlusion mode.
6360
+ */
6361
+ get occludeSpecular() {
6362
+ return this._occludeSpecular;
6363
+ }
6364
+ /**
6365
+ * Sets the opacity of the material, from 0 (transparent) to 1 (opaque), which requires a
6366
+ * `blend-type` other than `none` to have any visible effect.
6367
+ * @param value - The opacity.
6368
+ */
6369
+ set opacity(value) {
6370
+ this._opacity = value;
6371
+ if (this.material) {
6372
+ this.material.opacity = value;
6373
+ this._scheduleUpdate();
6374
+ }
6375
+ }
6376
+ /**
6377
+ * Gets the opacity of the material, which requires a `blend-type` other than `none` to have
6378
+ * any visible effect.
6379
+ * @returns The opacity.
6380
+ */
6381
+ get opacity() {
6382
+ return this._opacity;
6383
+ }
6384
+ /**
6385
+ * Sets the dithering used to render opacity, which approximates transparency without blending.
6386
+ * @param value - The dither mode.
6387
+ */
6388
+ set opacityDither(value) {
6389
+ this._opacityDither = value;
6390
+ if (this.material) {
6391
+ this.material.opacityDither = value;
6392
+ this._scheduleUpdate();
6393
+ }
6394
+ }
6395
+ /**
6396
+ * Gets the dithering used to render opacity.
6397
+ * @returns The dither mode.
6398
+ */
6399
+ get opacityDither() {
6400
+ return this._opacityDither;
6401
+ }
6402
+ /**
6403
+ * Sets whether specular highlights fade out as the material becomes transparent.
6404
+ * @param value - The opacity fades specular flag.
6405
+ */
6406
+ set opacityFadesSpecular(value) {
6407
+ this._opacityFadesSpecular = value;
6408
+ if (this.material) {
6409
+ this.material.opacityFadesSpecular = value;
6410
+ this._scheduleUpdate();
6411
+ }
6412
+ }
6413
+ /**
6414
+ * Gets whether specular highlights fade out as the material becomes transparent.
6415
+ * @returns The opacity fades specular flag.
6416
+ */
6417
+ get opacityFadesSpecular() {
6418
+ return this._opacityFadesSpecular;
6419
+ }
6420
+ /**
6421
+ * Sets the id of the `pc-asset` to use as the opacity map.
6422
+ * @param value - The asset id.
6423
+ */
6424
+ set opacityMap(value) {
6425
+ this._opacityMap = value;
6426
+ this.setMap(value, 'opacityMap');
6427
+ }
6428
+ /**
6429
+ * Gets the id of the `pc-asset` used as the opacity map.
6430
+ * @returns The asset id.
6431
+ */
6432
+ get opacityMap() {
6433
+ return this._opacityMap;
6434
+ }
6435
+ /**
6436
+ * Sets the color channel of the opacity map to sample.
6437
+ * @param value - The channel.
6438
+ */
6439
+ set opacityMapChannel(value) {
6440
+ this._opacityMapChannel = value;
6441
+ if (this.material) {
6442
+ this.material.opacityMapChannel = value;
6443
+ this._scheduleUpdate();
6444
+ }
6445
+ }
6446
+ /**
6447
+ * Gets the color channel of the opacity map to sample.
6448
+ * @returns The channel.
6449
+ */
6450
+ get opacityMapChannel() {
6451
+ return this._opacityMapChannel;
6452
+ }
6453
+ /**
6454
+ * Sets the 2D offset of the opacity map.
6455
+ * @param value - The offset.
6456
+ */
6457
+ set opacityMapOffset(value) {
6458
+ this._opacityMapOffset = value;
6459
+ if (this.material) {
6460
+ this.material.opacityMapOffset = value;
6461
+ this._scheduleUpdate();
6462
+ }
6463
+ }
6464
+ /**
6465
+ * Gets the 2D offset of the opacity map.
6466
+ * @returns The offset.
6467
+ */
6468
+ get opacityMapOffset() {
6469
+ return this._opacityMapOffset;
6470
+ }
6471
+ /**
6472
+ * Sets the 2D rotation of the opacity map, in degrees.
6473
+ * @param value - The rotation.
6474
+ */
6475
+ set opacityMapRotation(value) {
6476
+ this._opacityMapRotation = value;
6477
+ if (this.material) {
6478
+ this.material.opacityMapRotation = value;
6479
+ this._scheduleUpdate();
6480
+ }
6481
+ }
6482
+ /**
6483
+ * Gets the 2D rotation of the opacity map.
6484
+ * @returns The rotation.
6485
+ */
6486
+ get opacityMapRotation() {
6487
+ return this._opacityMapRotation;
6488
+ }
6489
+ /**
6490
+ * Sets the 2D tiling of the opacity map.
6491
+ * @param value - The tiling.
6492
+ */
6493
+ set opacityMapTiling(value) {
6494
+ this._opacityMapTiling = value;
6495
+ if (this.material) {
6496
+ this.material.opacityMapTiling = value;
6497
+ this._scheduleUpdate();
6498
+ }
6499
+ }
6500
+ /**
6501
+ * Gets the 2D tiling of the opacity map.
6502
+ * @returns The tiling.
6503
+ */
6504
+ get opacityMapTiling() {
6505
+ return this._opacityMapTiling;
6506
+ }
6507
+ /**
6508
+ * Sets the UV channel the opacity map samples.
6509
+ * @param value - The UV channel.
6510
+ */
6511
+ set opacityMapUv(value) {
6512
+ this._opacityMapUv = value;
6513
+ if (this.material) {
6514
+ this.material.opacityMapUv = value;
6515
+ this._scheduleUpdate();
6516
+ }
6517
+ }
6518
+ /**
6519
+ * Gets the UV channel the opacity map samples.
6520
+ * @returns The UV channel.
6521
+ */
6522
+ get opacityMapUv() {
6523
+ return this._opacityMapUv;
6524
+ }
6525
+ /**
6526
+ * Sets the roughness of the material, from 0 (shiny) to 1 (rough). This is an alias for `gloss`
6527
+ * that also inverts the gloss channel, so do not combine it with the `gloss` attributes.
6528
+ * @param value - The roughness.
6529
+ */
6530
+ set roughness(value) {
6531
+ this.gloss = value;
6532
+ this.glossInvert = true;
6533
+ }
6534
+ /**
6535
+ * Gets the roughness of the material.
6536
+ * @returns The roughness.
6537
+ */
6538
+ get roughness() {
6539
+ return this._gloss;
6540
+ }
6541
+ /**
6542
+ * Sets the id of the `pc-asset` to use as the roughness map. This is an alias for `glossMap`
6543
+ * that also inverts the gloss channel, so do not combine it with the `gloss` attributes.
6544
+ * @param value - The asset id.
6545
+ */
6546
+ set roughnessMap(value) {
6547
+ this.glossMap = value;
6548
+ this.glossInvert = true;
6549
+ }
6550
+ /**
6551
+ * Gets the id of the `pc-asset` used as the roughness map.
6552
+ * @returns The asset id.
6553
+ */
6554
+ get roughnessMap() {
6555
+ return this._glossMap;
6556
+ }
6557
+ /**
6558
+ * Sets the depth offset applied in proportion to a surface's slope, used to resolve z-fighting.
6559
+ * @param value - The slope depth bias.
6560
+ */
6561
+ set slopeDepthBias(value) {
6562
+ this._slopeDepthBias = value;
6563
+ if (this.material) {
6564
+ this.material.slopeDepthBias = value;
6565
+ this._scheduleUpdate();
6566
+ }
6567
+ }
6568
+ /**
6569
+ * Gets the depth offset applied in proportion to a surface's slope.
6570
+ * @returns The slope depth bias.
6571
+ */
6572
+ get slopeDepthBias() {
6573
+ return this._slopeDepthBias;
6574
+ }
6575
+ /**
6576
+ * Sets the specular color of the material, which applies only when the metalness workflow is
6577
+ * disabled or `use-metalness-specular-color` is enabled.
6578
+ * @param value - The specular color.
6579
+ */
6580
+ set specular(value) {
6581
+ this._specular = value;
6582
+ if (this.material) {
6583
+ this.material.specular = value;
6584
+ this._scheduleUpdate();
6585
+ }
6586
+ }
6587
+ /**
6588
+ * Gets the specular color of the material, which applies only when the metalness workflow is
6589
+ * disabled or `use-metalness-specular-color` is enabled.
6590
+ * @returns The specular color.
6591
+ */
6592
+ get specular() {
6593
+ return this._specular;
6594
+ }
6595
+ /**
6596
+ * Sets the strength of specular reflections at direct angles, from 0 to 1, which applies only
6597
+ * when `use-metalness-specular-color` is enabled.
6598
+ * @param value - The specularity factor.
6599
+ */
6600
+ set specularityFactor(value) {
6601
+ this._specularityFactor = value;
6602
+ if (this.material) {
6603
+ this.material.specularityFactor = value;
6604
+ this._scheduleUpdate();
4652
6605
  }
4653
6606
  }
4654
- createMaterial() {
4655
- this.material = new playcanvas.StandardMaterial();
4656
- this.material.glossInvert = false;
4657
- this.material.useMetalness = false;
4658
- this.material.diffuse = this._diffuse;
4659
- this.diffuseMap = this._diffuseMap;
4660
- this.metalnessMap = this._metalnessMap;
4661
- this.normalMap = this._normalMap;
4662
- this.roughnessMap = this._roughnessMap;
4663
- this.material.update();
6607
+ /**
6608
+ * Gets the strength of specular reflections at direct angles, which applies only when
6609
+ * `use-metalness-specular-color` is enabled.
6610
+ * @returns The specularity factor.
6611
+ */
6612
+ get specularityFactor() {
6613
+ return this._specularityFactor;
4664
6614
  }
4665
- disconnectedCallback() {
6615
+ /**
6616
+ * Sets whether back faces are lit as though their normals were flipped.
6617
+ * @param value - The two sided lighting flag.
6618
+ */
6619
+ set twoSidedLighting(value) {
6620
+ this._twoSidedLighting = value;
4666
6621
  if (this.material) {
4667
- this.material.destroy();
4668
- this.material = null;
6622
+ this.material.twoSidedLighting = value;
6623
+ this._scheduleUpdate();
4669
6624
  }
4670
6625
  }
4671
- setMap(map, property) {
6626
+ /**
6627
+ * Gets whether back faces are lit as though their normals were flipped.
6628
+ * @returns The two sided lighting flag.
6629
+ */
6630
+ get twoSidedLighting() {
6631
+ return this._twoSidedLighting;
6632
+ }
6633
+ /**
6634
+ * Sets whether the material is affected by scene fog.
6635
+ * @param value - The use fog flag.
6636
+ */
6637
+ set useFog(value) {
6638
+ this._useFog = value;
4672
6639
  if (this.material) {
4673
- const asset = AssetElement.get(map);
4674
- if (asset) {
4675
- if (asset.loaded) {
4676
- this.material[property] = asset.resource;
4677
- this.material[property].anisotropy = 4;
4678
- }
4679
- else {
4680
- asset.once('load', () => {
4681
- this.material[property] = asset.resource;
4682
- this.material[property].anisotropy = 4;
4683
- this.material.update();
4684
- });
4685
- }
4686
- }
6640
+ this.material.useFog = value;
6641
+ this._scheduleUpdate();
4687
6642
  }
4688
6643
  }
4689
- set diffuse(value) {
4690
- this._diffuse = value;
6644
+ /**
6645
+ * Gets whether the material is affected by scene fog.
6646
+ * @returns The use fog flag.
6647
+ */
6648
+ get useFog() {
6649
+ return this._useFog;
6650
+ }
6651
+ /**
6652
+ * Sets whether the material is affected by scene lights. When disabled the material renders
6653
+ * unlit, using the diffuse color and map alone.
6654
+ * @param value - The use lighting flag.
6655
+ */
6656
+ set useLighting(value) {
6657
+ this._useLighting = value;
4691
6658
  if (this.material) {
4692
- this.material.diffuse = value;
6659
+ this.material.useLighting = value;
6660
+ this._scheduleUpdate();
4693
6661
  }
4694
6662
  }
4695
- get diffuse() {
4696
- return this._diffuse;
6663
+ /**
6664
+ * Gets whether the material is affected by scene lights.
6665
+ * @returns The use lighting flag.
6666
+ */
6667
+ get useLighting() {
6668
+ return this._useLighting;
4697
6669
  }
4698
- set diffuseMap(value) {
4699
- this._diffuseMap = value;
4700
- this.setMap(value, 'diffuseMap');
6670
+ /**
6671
+ * Sets whether to use the metalness workflow rather than the older specular workflow. Unlike a
6672
+ * bare `StandardMaterial` this defaults to `true`, because the `metalness-*` attributes have no
6673
+ * effect without it.
6674
+ * @param value - The use metalness flag.
6675
+ */
6676
+ set useMetalness(value) {
6677
+ this._useMetalness = value;
6678
+ if (this.material) {
6679
+ this.material.useMetalness = value;
6680
+ this._scheduleUpdate();
6681
+ }
4701
6682
  }
4702
- get diffuseMap() {
4703
- return this._diffuseMap;
6683
+ /**
6684
+ * Gets whether to use the metalness workflow.
6685
+ * @returns The use metalness flag.
6686
+ */
6687
+ get useMetalness() {
6688
+ return this._useMetalness;
4704
6689
  }
4705
- set metalnessMap(value) {
4706
- this._metalnessMap = value;
4707
- this.setMap(value, 'metalnessMap');
6690
+ /**
6691
+ * Sets whether the specular color tints reflections while the metalness workflow is in use.
6692
+ * @param value - The use metalness specular color flag.
6693
+ */
6694
+ set useMetalnessSpecularColor(value) {
6695
+ this._useMetalnessSpecularColor = value;
6696
+ if (this.material) {
6697
+ this.material.useMetalnessSpecularColor = value;
6698
+ this._scheduleUpdate();
6699
+ }
4708
6700
  }
4709
- get metalnessMap() {
4710
- return this._metalnessMap;
6701
+ /**
6702
+ * Gets whether the specular color tints reflections while the metalness workflow is in use.
6703
+ * @returns The use metalness specular color flag.
6704
+ */
6705
+ get useMetalnessSpecularColor() {
6706
+ return this._useMetalnessSpecularColor;
4711
6707
  }
4712
- set normalMap(value) {
4713
- this._normalMap = value;
4714
- this.setMap(value, 'normalMap');
6708
+ /**
6709
+ * Sets whether the material is lit by the scene's skybox.
6710
+ * @param value - The use skybox flag.
6711
+ */
6712
+ set useSkybox(value) {
6713
+ this._useSkybox = value;
6714
+ if (this.material) {
6715
+ this.material.useSkybox = value;
6716
+ this._scheduleUpdate();
6717
+ }
4715
6718
  }
4716
- get normalMap() {
4717
- return this._normalMap;
6719
+ /**
6720
+ * Gets whether the material is lit by the scene's skybox.
6721
+ * @returns The use skybox flag.
6722
+ */
6723
+ get useSkybox() {
6724
+ return this._useSkybox;
4718
6725
  }
4719
- set roughnessMap(value) {
4720
- this._roughnessMap = value;
4721
- this.setMap(value, 'glossMap');
6726
+ /**
6727
+ * Sets whether the camera's tone mapping is applied to the material.
6728
+ * @param value - The use tonemap flag.
6729
+ */
6730
+ set useTonemap(value) {
6731
+ this._useTonemap = value;
6732
+ if (this.material) {
6733
+ this.material.useTonemap = value;
6734
+ this._scheduleUpdate();
6735
+ }
4722
6736
  }
4723
- get roughnessMap() {
4724
- return this._roughnessMap;
6737
+ /**
6738
+ * Gets whether the camera's tone mapping is applied to the material.
6739
+ * @returns The use tonemap flag.
6740
+ */
6741
+ get useTonemap() {
6742
+ return this._useTonemap;
4725
6743
  }
4726
6744
  static get(id) {
4727
6745
  const materialElement = document.querySelector(`pc-material[id="${id}"]`);
4728
- return materialElement === null || materialElement === void 0 ? void 0 : materialElement.material;
6746
+ return materialElement?.material;
4729
6747
  }
4730
6748
  static get observedAttributes() {
4731
- return ['diffuse', 'diffuse-map', 'metalness-map', 'normal-map', 'roughness-map'];
6749
+ return [
6750
+ 'alpha-test',
6751
+ 'alpha-to-coverage',
6752
+ 'ao-intensity',
6753
+ 'ao-map',
6754
+ 'ao-map-channel',
6755
+ 'ao-map-offset',
6756
+ 'ao-map-rotation',
6757
+ 'ao-map-tiling',
6758
+ 'ao-map-uv',
6759
+ 'blend-type',
6760
+ 'bumpiness',
6761
+ 'cull',
6762
+ 'depth-bias',
6763
+ 'depth-test',
6764
+ 'depth-write',
6765
+ 'diffuse',
6766
+ 'diffuse-map',
6767
+ 'diffuse-map-channel',
6768
+ 'diffuse-map-offset',
6769
+ 'diffuse-map-rotation',
6770
+ 'diffuse-map-tiling',
6771
+ 'diffuse-map-uv',
6772
+ 'emissive',
6773
+ 'emissive-intensity',
6774
+ 'emissive-map',
6775
+ 'emissive-map-channel',
6776
+ 'emissive-map-offset',
6777
+ 'emissive-map-rotation',
6778
+ 'emissive-map-tiling',
6779
+ 'emissive-map-uv',
6780
+ 'enable-ggx-specular',
6781
+ 'fresnel-model',
6782
+ 'gloss',
6783
+ 'gloss-invert',
6784
+ 'gloss-map',
6785
+ 'gloss-map-channel',
6786
+ 'gloss-map-offset',
6787
+ 'gloss-map-rotation',
6788
+ 'gloss-map-tiling',
6789
+ 'gloss-map-uv',
6790
+ 'height-map',
6791
+ 'height-map-channel',
6792
+ 'height-map-factor',
6793
+ 'height-map-offset',
6794
+ 'height-map-rotation',
6795
+ 'height-map-tiling',
6796
+ 'height-map-uv',
6797
+ 'metalness',
6798
+ 'metalness-map',
6799
+ 'metalness-map-channel',
6800
+ 'metalness-map-offset',
6801
+ 'metalness-map-rotation',
6802
+ 'metalness-map-tiling',
6803
+ 'metalness-map-uv',
6804
+ 'normal-map',
6805
+ 'normal-map-offset',
6806
+ 'normal-map-rotation',
6807
+ 'normal-map-tiling',
6808
+ 'normal-map-uv',
6809
+ 'occlude-direct',
6810
+ 'occlude-specular',
6811
+ 'opacity',
6812
+ 'opacity-dither',
6813
+ 'opacity-fades-specular',
6814
+ 'opacity-map',
6815
+ 'opacity-map-channel',
6816
+ 'opacity-map-offset',
6817
+ 'opacity-map-rotation',
6818
+ 'opacity-map-tiling',
6819
+ 'opacity-map-uv',
6820
+ 'roughness',
6821
+ 'roughness-map',
6822
+ 'slope-depth-bias',
6823
+ 'specular',
6824
+ 'specularity-factor',
6825
+ 'two-sided-lighting',
6826
+ 'use-fog',
6827
+ 'use-lighting',
6828
+ 'use-metalness',
6829
+ 'use-metalness-specular-color',
6830
+ 'use-skybox',
6831
+ 'use-tonemap'
6832
+ ];
4732
6833
  }
6834
+ // newValue is null when an attribute is removed, which several branches below rely on. The
6835
+ // other elements still declare it as `string`; widening those surfaces 21 real removal bugs of
6836
+ // the #309 shape, which is its own change rather than a signature tweak.
4733
6837
  attributeChangedCallback(name, _oldValue, newValue) {
4734
6838
  switch (name) {
6839
+ case 'alpha-test':
6840
+ this.alphaTest = parseNumber(newValue, 0, name);
6841
+ break;
6842
+ case 'alpha-to-coverage':
6843
+ this.alphaToCoverage = parseBool(newValue, false);
6844
+ break;
6845
+ case 'ao-intensity':
6846
+ this.aoIntensity = parseNumber(newValue, 1, name);
6847
+ break;
6848
+ case 'ao-map':
6849
+ this.aoMap = newValue ?? '';
6850
+ break;
6851
+ case 'ao-map-channel':
6852
+ this.aoMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
6853
+ break;
6854
+ case 'ao-map-offset':
6855
+ this.aoMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6856
+ break;
6857
+ case 'ao-map-rotation':
6858
+ this.aoMapRotation = parseNumber(newValue, 0, name);
6859
+ break;
6860
+ case 'ao-map-tiling':
6861
+ this.aoMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
6862
+ break;
6863
+ case 'ao-map-uv':
6864
+ this.aoMapUv = parseNumber(newValue, 0, name);
6865
+ break;
6866
+ case 'blend-type':
6867
+ this.blendType = parseEnum(newValue, blendTypes, 'none', name);
6868
+ break;
6869
+ case 'bumpiness':
6870
+ this.bumpiness = parseNumber(newValue, 1, name);
6871
+ break;
6872
+ case 'cull':
6873
+ this.cull = parseEnum(newValue, cullModes, 'back', name);
6874
+ break;
6875
+ case 'depth-bias':
6876
+ this.depthBias = parseNumber(newValue, 0, name);
6877
+ break;
6878
+ case 'depth-test':
6879
+ this.depthTest = parseBool(newValue, true);
6880
+ break;
6881
+ case 'depth-write':
6882
+ this.depthWrite = parseBool(newValue, true);
6883
+ break;
4735
6884
  case 'diffuse':
4736
- this.diffuse = parseColor(newValue, playcanvas.Color.WHITE, name);
6885
+ this.diffuse = parseColor(newValue, new playcanvas.Color(1, 1, 1), name);
4737
6886
  break;
4738
6887
  case 'diffuse-map':
4739
- this.diffuseMap = newValue;
6888
+ this.diffuseMap = newValue ?? '';
6889
+ break;
6890
+ case 'diffuse-map-channel':
6891
+ this.diffuseMapChannel = parseEnum(newValue, colorChannels, 'rgb', name);
6892
+ break;
6893
+ case 'diffuse-map-offset':
6894
+ this.diffuseMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6895
+ break;
6896
+ case 'diffuse-map-rotation':
6897
+ this.diffuseMapRotation = parseNumber(newValue, 0, name);
6898
+ break;
6899
+ case 'diffuse-map-tiling':
6900
+ this.diffuseMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
6901
+ break;
6902
+ case 'diffuse-map-uv':
6903
+ this.diffuseMapUv = parseNumber(newValue, 0, name);
6904
+ break;
6905
+ case 'emissive':
6906
+ this.emissive = parseColor(newValue, new playcanvas.Color(0, 0, 0), name);
6907
+ break;
6908
+ case 'emissive-intensity':
6909
+ this.emissiveIntensity = parseNumber(newValue, 1, name);
6910
+ break;
6911
+ case 'emissive-map':
6912
+ this.emissiveMap = newValue ?? '';
6913
+ break;
6914
+ case 'emissive-map-channel':
6915
+ this.emissiveMapChannel = parseEnum(newValue, colorChannels, 'rgb', name);
6916
+ break;
6917
+ case 'emissive-map-offset':
6918
+ this.emissiveMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6919
+ break;
6920
+ case 'emissive-map-rotation':
6921
+ this.emissiveMapRotation = parseNumber(newValue, 0, name);
6922
+ break;
6923
+ case 'emissive-map-tiling':
6924
+ this.emissiveMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
6925
+ break;
6926
+ case 'emissive-map-uv':
6927
+ this.emissiveMapUv = parseNumber(newValue, 0, name);
6928
+ break;
6929
+ case 'enable-ggx-specular':
6930
+ this.enableGGXSpecular = parseBool(newValue, false);
6931
+ break;
6932
+ case 'fresnel-model':
6933
+ this.fresnelModel = parseEnum(newValue, fresnelModels, 'schlick', name);
6934
+ break;
6935
+ case 'gloss':
6936
+ this.gloss = parseNumber(newValue, 0.25, name);
6937
+ this._warnGlossConflict();
6938
+ break;
6939
+ case 'gloss-invert':
6940
+ this.glossInvert = parseBool(newValue, false);
6941
+ this._warnGlossConflict();
6942
+ break;
6943
+ case 'gloss-map':
6944
+ this.glossMap = newValue ?? '';
6945
+ this._warnGlossConflict();
6946
+ break;
6947
+ case 'gloss-map-channel':
6948
+ this.glossMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
6949
+ break;
6950
+ case 'gloss-map-offset':
6951
+ this.glossMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6952
+ break;
6953
+ case 'gloss-map-rotation':
6954
+ this.glossMapRotation = parseNumber(newValue, 0, name);
6955
+ break;
6956
+ case 'gloss-map-tiling':
6957
+ this.glossMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
6958
+ break;
6959
+ case 'gloss-map-uv':
6960
+ this.glossMapUv = parseNumber(newValue, 0, name);
6961
+ break;
6962
+ case 'height-map':
6963
+ this.heightMap = newValue ?? '';
6964
+ break;
6965
+ case 'height-map-channel':
6966
+ this.heightMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
6967
+ break;
6968
+ case 'height-map-factor':
6969
+ this.heightMapFactor = parseNumber(newValue, 1, name);
6970
+ break;
6971
+ case 'height-map-offset':
6972
+ this.heightMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6973
+ break;
6974
+ case 'height-map-rotation':
6975
+ this.heightMapRotation = parseNumber(newValue, 0, name);
6976
+ break;
6977
+ case 'height-map-tiling':
6978
+ this.heightMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
6979
+ break;
6980
+ case 'height-map-uv':
6981
+ this.heightMapUv = parseNumber(newValue, 0, name);
6982
+ break;
6983
+ case 'metalness':
6984
+ this.metalness = parseNumber(newValue, 0, name);
4740
6985
  break;
4741
6986
  case 'metalness-map':
4742
- this.metalnessMap = newValue;
6987
+ this.metalnessMap = newValue ?? '';
6988
+ break;
6989
+ case 'metalness-map-channel':
6990
+ this.metalnessMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
6991
+ break;
6992
+ case 'metalness-map-offset':
6993
+ this.metalnessMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
6994
+ break;
6995
+ case 'metalness-map-rotation':
6996
+ this.metalnessMapRotation = parseNumber(newValue, 0, name);
6997
+ break;
6998
+ case 'metalness-map-tiling':
6999
+ this.metalnessMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
7000
+ break;
7001
+ case 'metalness-map-uv':
7002
+ this.metalnessMapUv = parseNumber(newValue, 0, name);
4743
7003
  break;
4744
7004
  case 'normal-map':
4745
- this.normalMap = newValue;
7005
+ this.normalMap = newValue ?? '';
7006
+ break;
7007
+ case 'normal-map-offset':
7008
+ this.normalMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
7009
+ break;
7010
+ case 'normal-map-rotation':
7011
+ this.normalMapRotation = parseNumber(newValue, 0, name);
7012
+ break;
7013
+ case 'normal-map-tiling':
7014
+ this.normalMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
7015
+ break;
7016
+ case 'normal-map-uv':
7017
+ this.normalMapUv = parseNumber(newValue, 0, name);
7018
+ break;
7019
+ case 'occlude-direct':
7020
+ this.occludeDirect = parseBool(newValue, false);
7021
+ break;
7022
+ case 'occlude-specular':
7023
+ this.occludeSpecular = parseEnum(newValue, occludeSpeculars, 'ao', name);
7024
+ break;
7025
+ case 'opacity':
7026
+ this.opacity = parseNumber(newValue, 1, name);
7027
+ break;
7028
+ case 'opacity-dither':
7029
+ this.opacityDither = parseEnum(newValue, opacityDithers, 'none', name);
7030
+ break;
7031
+ case 'opacity-fades-specular':
7032
+ this.opacityFadesSpecular = parseBool(newValue, true);
7033
+ break;
7034
+ case 'opacity-map':
7035
+ this.opacityMap = newValue ?? '';
7036
+ break;
7037
+ case 'opacity-map-channel':
7038
+ this.opacityMapChannel = parseEnum(newValue, scalarChannels, 'a', name);
7039
+ break;
7040
+ case 'opacity-map-offset':
7041
+ this.opacityMapOffset = parseVec2(newValue, new playcanvas.Vec2(0, 0), name);
7042
+ break;
7043
+ case 'opacity-map-rotation':
7044
+ this.opacityMapRotation = parseNumber(newValue, 0, name);
7045
+ break;
7046
+ case 'opacity-map-tiling':
7047
+ this.opacityMapTiling = parseVec2(newValue, new playcanvas.Vec2(1, 1), name);
7048
+ break;
7049
+ case 'opacity-map-uv':
7050
+ this.opacityMapUv = parseNumber(newValue, 0, name);
7051
+ break;
7052
+ case 'roughness':
7053
+ // Aliases gloss, and inverts it so the value reads as roughness. Removing the
7054
+ // attribute restores the engine's uninverted interpretation.
7055
+ this.gloss = parseNumber(newValue, 0.25, name);
7056
+ this.glossInvert = newValue !== null;
7057
+ this._warnGlossConflict();
4746
7058
  break;
4747
7059
  case 'roughness-map':
4748
- this.roughnessMap = newValue;
7060
+ this.glossMap = newValue ?? '';
7061
+ this.glossInvert = newValue !== null;
7062
+ this._warnGlossConflict();
7063
+ break;
7064
+ case 'slope-depth-bias':
7065
+ this.slopeDepthBias = parseNumber(newValue, 0, name);
7066
+ break;
7067
+ case 'specular':
7068
+ this.specular = parseColor(newValue, new playcanvas.Color(0, 0, 0), name);
7069
+ break;
7070
+ case 'specularity-factor':
7071
+ this.specularityFactor = parseNumber(newValue, 1, name);
7072
+ break;
7073
+ case 'two-sided-lighting':
7074
+ this.twoSidedLighting = parseBool(newValue, false);
7075
+ break;
7076
+ case 'use-fog':
7077
+ this.useFog = parseBool(newValue, true);
7078
+ break;
7079
+ case 'use-lighting':
7080
+ this.useLighting = parseBool(newValue, true);
7081
+ break;
7082
+ case 'use-metalness':
7083
+ this.useMetalness = parseBool(newValue, true);
7084
+ break;
7085
+ case 'use-metalness-specular-color':
7086
+ this.useMetalnessSpecularColor = parseBool(newValue, false);
7087
+ break;
7088
+ case 'use-skybox':
7089
+ this.useSkybox = parseBool(newValue, true);
7090
+ break;
7091
+ case 'use-tonemap':
7092
+ this.useTonemap = parseBool(newValue, true);
4749
7093
  break;
4750
7094
  }
4751
7095
  }
@@ -4765,13 +7109,13 @@ customElements.define('pc-material', MaterialElement);
4765
7109
  * @category Components
4766
7110
  */
4767
7111
  class RenderComponentElement extends ComponentElement {
7112
+ _castShadows = true;
7113
+ _material = '';
7114
+ _receiveShadows = true;
7115
+ _type = 'box';
4768
7116
  /** @ignore */
4769
7117
  constructor() {
4770
7118
  super('render');
4771
- this._castShadows = true;
4772
- this._material = '';
4773
- this._receiveShadows = true;
4774
- this._type = 'box';
4775
7119
  }
4776
7120
  getInitialComponentData() {
4777
7121
  return {
@@ -4828,8 +7172,13 @@ class RenderComponentElement extends ComponentElement {
4828
7172
  */
4829
7173
  set material(value) {
4830
7174
  this._material = value;
4831
- if (this.component) {
4832
- this.component.material = MaterialElement.get(value);
7175
+ const material = MaterialElement.get(value);
7176
+ // Guarded like every other reference attribute in the library. Assigning an unresolved
7177
+ // lookup used to write `undefined` straight through to every mesh instance, and the
7178
+ // engine's MeshInstance setter takes that literally - it clears the material and skips
7179
+ // the ref/transparency/key bookkeeping, leaving the mesh with no material at all.
7180
+ if (this.component && material) {
7181
+ this.component.material = material;
4833
7182
  }
4834
7183
  }
4835
7184
  /**
@@ -4866,7 +7215,7 @@ class RenderComponentElement extends ComponentElement {
4866
7215
  this.castShadows = parseBool(newValue, true);
4867
7216
  break;
4868
7217
  case 'material':
4869
- this.material = newValue;
7218
+ this.material = newValue ?? '';
4870
7219
  break;
4871
7220
  case 'receive-shadows':
4872
7221
  this.receiveShadows = parseBool(newValue, true);
@@ -4888,45 +7237,45 @@ customElements.define('pc-render', RenderComponentElement);
4888
7237
  * @category Components
4889
7238
  */
4890
7239
  class RigidBodyComponentElement extends ComponentElement {
7240
+ /**
7241
+ * The angular damping of the rigidbody.
7242
+ */
7243
+ _angularDamping = 0;
7244
+ /**
7245
+ * The angular factor of the rigidbody.
7246
+ */
7247
+ _angularFactor = new playcanvas.Vec3(1, 1, 1);
7248
+ /**
7249
+ * The friction of the rigidbody.
7250
+ */
7251
+ _friction = 0.5;
7252
+ /**
7253
+ * The linear damping of the rigidbody.
7254
+ */
7255
+ _linearDamping = 0;
7256
+ /**
7257
+ * The linear factor of the rigidbody.
7258
+ */
7259
+ _linearFactor = new playcanvas.Vec3(1, 1, 1);
7260
+ /**
7261
+ * The mass of the rigidbody.
7262
+ */
7263
+ _mass = 1;
7264
+ /**
7265
+ * The restitution of the rigidbody.
7266
+ */
7267
+ _restitution = 0;
7268
+ /**
7269
+ * The rolling friction of the rigidbody.
7270
+ */
7271
+ _rollingFriction = 0;
7272
+ /**
7273
+ * The type of the rigidbody.
7274
+ */
7275
+ _type = 'static';
4891
7276
  /** @ignore */
4892
7277
  constructor() {
4893
7278
  super('rigidbody');
4894
- /**
4895
- * The angular damping of the rigidbody.
4896
- */
4897
- this._angularDamping = 0;
4898
- /**
4899
- * The angular factor of the rigidbody.
4900
- */
4901
- this._angularFactor = new playcanvas.Vec3(1, 1, 1);
4902
- /**
4903
- * The friction of the rigidbody.
4904
- */
4905
- this._friction = 0.5;
4906
- /**
4907
- * The linear damping of the rigidbody.
4908
- */
4909
- this._linearDamping = 0;
4910
- /**
4911
- * The linear factor of the rigidbody.
4912
- */
4913
- this._linearFactor = new playcanvas.Vec3(1, 1, 1);
4914
- /**
4915
- * The mass of the rigidbody.
4916
- */
4917
- this._mass = 1;
4918
- /**
4919
- * The restitution of the rigidbody.
4920
- */
4921
- this._restitution = 0;
4922
- /**
4923
- * The rolling friction of the rigidbody.
4924
- */
4925
- this._rollingFriction = 0;
4926
- /**
4927
- * The type of the rigidbody.
4928
- */
4929
- this._type = 'static';
4930
7279
  }
4931
7280
  getInitialComponentData() {
4932
7281
  return {
@@ -5076,15 +7425,15 @@ customElements.define('pc-rigidbody', RigidBodyComponentElement);
5076
7425
  * @category Components
5077
7426
  */
5078
7427
  class ScreenComponentElement extends ComponentElement {
7428
+ _screenSpace = false;
7429
+ _resolution = new playcanvas.Vec2(640, 320);
7430
+ _referenceResolution = new playcanvas.Vec2(640, 320);
7431
+ _priority = 0;
7432
+ _blend = false;
7433
+ _scaleBlend = 0.5;
5079
7434
  /** @ignore */
5080
7435
  constructor() {
5081
7436
  super('screen');
5082
- this._screenSpace = false;
5083
- this._resolution = new playcanvas.Vec2(640, 320);
5084
- this._referenceResolution = new playcanvas.Vec2(640, 320);
5085
- this._priority = 0;
5086
- this._blend = false;
5087
- this._scaleBlend = 0.5;
5088
7437
  }
5089
7438
  getInitialComponentData() {
5090
7439
  return {
@@ -5207,13 +7556,13 @@ const orientations = new Map([
5207
7556
  * @category Components
5208
7557
  */
5209
7558
  class ScrollbarComponentElement extends ComponentElement {
7559
+ _orientation = 'horizontal';
7560
+ _value = 0;
7561
+ _handleSize = 0.5;
7562
+ _handle = '';
5210
7563
  /** @ignore */
5211
7564
  constructor() {
5212
7565
  super('scrollbar');
5213
- this._orientation = 'horizontal';
5214
- this._value = 0;
5215
- this._handleSize = 0.5;
5216
- this._handle = '';
5217
7566
  }
5218
7567
  getInitialComponentData() {
5219
7568
  const data = {
@@ -5240,10 +7589,9 @@ class ScrollbarComponentElement extends ComponentElement {
5240
7589
  * @param value - The orientation.
5241
7590
  */
5242
7591
  set orientation(value) {
5243
- var _a;
5244
7592
  this._orientation = value;
5245
7593
  if (this.component) {
5246
- this.component.orientation = (_a = orientations.get(value)) !== null && _a !== void 0 ? _a : playcanvas.ORIENTATION_HORIZONTAL;
7594
+ this.component.orientation = orientations.get(value) ?? playcanvas.ORIENTATION_HORIZONTAL;
5247
7595
  }
5248
7596
  }
5249
7597
  /**
@@ -5328,7 +7676,7 @@ class ScrollbarComponentElement extends ComponentElement {
5328
7676
  this.handleSize = parseNumber(newValue, 0.5, name);
5329
7677
  break;
5330
7678
  case 'handle':
5331
- this.handle = newValue;
7679
+ this.handle = newValue ?? '';
5332
7680
  break;
5333
7681
  }
5334
7682
  }
@@ -5353,22 +7701,22 @@ const visibilities = new Map([
5353
7701
  * @category Components
5354
7702
  */
5355
7703
  class ScrollViewComponentElement extends ComponentElement {
7704
+ _horizontal = true;
7705
+ _vertical = true;
7706
+ _scrollMode = 'bounce';
7707
+ _bounceAmount = 0.1;
7708
+ _friction = 0.05;
7709
+ _useMouseWheel = true;
7710
+ _mouseWheelSensitivity = new playcanvas.Vec2(1, 1);
7711
+ _horizontalScrollbarVisibility = 'when-required';
7712
+ _verticalScrollbarVisibility = 'when-required';
7713
+ _viewport = '';
7714
+ _content = '';
7715
+ _horizontalScrollbar = '';
7716
+ _verticalScrollbar = '';
5356
7717
  /** @ignore */
5357
7718
  constructor() {
5358
7719
  super('scrollview');
5359
- this._horizontal = true;
5360
- this._vertical = true;
5361
- this._scrollMode = 'bounce';
5362
- this._bounceAmount = 0.1;
5363
- this._friction = 0.05;
5364
- this._useMouseWheel = true;
5365
- this._mouseWheelSensitivity = new playcanvas.Vec2(1, 1);
5366
- this._horizontalScrollbarVisibility = 'when-required';
5367
- this._verticalScrollbarVisibility = 'when-required';
5368
- this._viewport = '';
5369
- this._content = '';
5370
- this._horizontalScrollbar = '';
5371
- this._verticalScrollbar = '';
5372
7720
  }
5373
7721
  getInitialComponentData() {
5374
7722
  const data = {
@@ -5447,10 +7795,9 @@ class ScrollViewComponentElement extends ComponentElement {
5447
7795
  * @param value - The scroll mode.
5448
7796
  */
5449
7797
  set scrollMode(value) {
5450
- var _a;
5451
7798
  this._scrollMode = value;
5452
7799
  if (this.component) {
5453
- this.component.scrollMode = (_a = scrollModes.get(value)) !== null && _a !== void 0 ? _a : playcanvas.SCROLL_MODE_BOUNCE;
7800
+ this.component.scrollMode = scrollModes.get(value) ?? playcanvas.SCROLL_MODE_BOUNCE;
5454
7801
  }
5455
7802
  }
5456
7803
  /**
@@ -5536,10 +7883,9 @@ class ScrollViewComponentElement extends ComponentElement {
5536
7883
  * @param value - The horizontal scrollbar visibility.
5537
7884
  */
5538
7885
  set horizontalScrollbarVisibility(value) {
5539
- var _a;
5540
7886
  this._horizontalScrollbarVisibility = value;
5541
7887
  if (this.component) {
5542
- this.component.horizontalScrollbarVisibility = (_a = visibilities.get(value)) !== null && _a !== void 0 ? _a : playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
7888
+ this.component.horizontalScrollbarVisibility = visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
5543
7889
  }
5544
7890
  }
5545
7891
  /**
@@ -5555,10 +7901,9 @@ class ScrollViewComponentElement extends ComponentElement {
5555
7901
  * @param value - The vertical scrollbar visibility.
5556
7902
  */
5557
7903
  set verticalScrollbarVisibility(value) {
5558
- var _a;
5559
7904
  this._verticalScrollbarVisibility = value;
5560
7905
  if (this.component) {
5561
- this.component.verticalScrollbarVisibility = (_a = visibilities.get(value)) !== null && _a !== void 0 ? _a : playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
7906
+ this.component.verticalScrollbarVisibility = visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
5562
7907
  }
5563
7908
  }
5564
7909
  /**
@@ -5693,16 +8038,16 @@ class ScrollViewComponentElement extends ComponentElement {
5693
8038
  this.verticalScrollbarVisibility = parseEnum(newValue, visibilities, 'when-required', name);
5694
8039
  break;
5695
8040
  case 'viewport':
5696
- this.viewport = newValue;
8041
+ this.viewport = newValue ?? '';
5697
8042
  break;
5698
8043
  case 'content':
5699
- this.content = newValue;
8044
+ this.content = newValue ?? '';
5700
8045
  break;
5701
8046
  case 'horizontal-scrollbar':
5702
- this.horizontalScrollbar = newValue;
8047
+ this.horizontalScrollbar = newValue ?? '';
5703
8048
  break;
5704
8049
  case 'vertical-scrollbar':
5705
- this.verticalScrollbar = newValue;
8050
+ this.verticalScrollbar = newValue ?? '';
5706
8051
  break;
5707
8052
  }
5708
8053
  }
@@ -5734,24 +8079,28 @@ customElements.define('pc-scrollview', ScrollViewComponentElement);
5734
8079
  *
5735
8080
  * The element becomes ready once its script instance has been created by the parent
5736
8081
  * `<pc-scripts>` element.
8082
+ *
8083
+ * @fires {CustomEvent} scriptattributeschange - Fired when the script's attributes change. The
8084
+ * `detail` carries the new `attributes` object. Bubbles.
8085
+ * @fires {CustomEvent} scriptenablechange - Fired when the script's enabled state changes. The
8086
+ * `detail` carries the new `enabled` state. Bubbles.
8087
+ * @fires {CustomEvent} scriptnamechange - Fired when the script is renamed on a live element. The
8088
+ * `detail` carries `oldName` and `newName`. Bubbles.
5737
8089
  */
5738
8090
  class ScriptElement extends AsyncElement {
5739
- constructor() {
5740
- super(...arguments);
5741
- this._attributes = {};
5742
- this._enabled = true;
5743
- /**
5744
- * Whether readiness has been signalled. Creation can happen more than once over an
5745
- * element's life (a runtime `name` change recreates the instance), but `ready` is a
5746
- * one-shot signal, so only the first successful creation fires it.
5747
- */
5748
- this._readySignalled = false;
5749
- /**
5750
- * The Script instance created for this element by its parent `<pc-scripts>` element.
5751
- * @ignore
5752
- */
5753
- this._script = null;
5754
- }
8091
+ _attributes = {};
8092
+ _enabled = true;
8093
+ /**
8094
+ * Whether readiness has been signalled. Creation can happen more than once over an
8095
+ * element's life (a runtime `name` change recreates the instance), but `ready` is a
8096
+ * one-shot signal, so only the first successful creation fires it.
8097
+ */
8098
+ _readySignalled = false;
8099
+ /**
8100
+ * The Script instance created for this element by its parent `<pc-scripts>` element.
8101
+ * @ignore
8102
+ */
8103
+ _script = null;
5755
8104
  /**
5756
8105
  * Sets the attributes of the script as an object. Values are converted with the same rules
5757
8106
  * as the `attributes` attribute: `asset:`/`entity:` references and `vec2:`/`vec3:`/`vec4:`/
@@ -5761,7 +8110,7 @@ class ScriptElement extends AsyncElement {
5761
8110
  * @param value - The attributes of the script.
5762
8111
  */
5763
8112
  set scriptAttributes(value) {
5764
- this._attributes = value !== null && value !== void 0 ? value : {};
8113
+ this._attributes = value ?? {};
5765
8114
  this.dispatchEvent(new CustomEvent('scriptattributeschange', {
5766
8115
  detail: { attributes: this._attributes },
5767
8116
  bubbles: true
@@ -5813,8 +8162,7 @@ class ScriptElement extends AsyncElement {
5813
8162
  * @returns The name.
5814
8163
  */
5815
8164
  get name() {
5816
- var _a;
5817
- return (_a = this.getAttribute('name')) !== null && _a !== void 0 ? _a : '';
8165
+ return this.getAttribute('name') ?? '';
5818
8166
  }
5819
8167
  /**
5820
8168
  * Gets the {@link Script} instance created for this element. Returns `null` until the
@@ -5826,10 +8174,9 @@ class ScriptElement extends AsyncElement {
5826
8174
  return this._script;
5827
8175
  }
5828
8176
  connectedCallback() {
5829
- var _a;
5830
8177
  // Script instances are created by the parent pc-scripts element, so an element placed
5831
8178
  // anywhere else is inert and never becomes ready - warn rather than hang silently
5832
- if (((_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.tagName) !== 'PC-SCRIPTS') {
8179
+ if (this.parentElement?.tagName !== 'PC-SCRIPTS') {
5833
8180
  console.warn(`pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`);
5834
8181
  }
5835
8182
  }
@@ -5981,8 +8328,7 @@ const vectorConversion = (length, Ctor) => {
5981
8328
  * @returns The color, or `raw`.
5982
8329
  */
5983
8330
  const colorConversion = (rest, raw) => {
5984
- var _a;
5985
- const components = (_a = parseComponents(rest, 4)) !== null && _a !== void 0 ? _a : parseComponents(rest, 3);
8331
+ const components = parseComponents(rest, 4) ?? parseComponents(rest, 3);
5986
8332
  if (components) {
5987
8333
  return new playcanvas.Color(components);
5988
8334
  }
@@ -5990,7 +8336,7 @@ const colorConversion = (rest, raw) => {
5990
8336
  return raw;
5991
8337
  };
5992
8338
  /**
5993
- * The conversion prefixes recognised in script attribute values, mapped to the conversion each
8339
+ * The conversion prefixes recognized in script attribute values, mapped to the conversion each
5994
8340
  * performs. These keys are the single source of truth for the prefix vocabulary: they drive both
5995
8341
  * the conversion in `convertAttributes` and the has-a-prefix test in `setScriptProperty`, so a
5996
8342
  * prefix added here is automatically known to both.
@@ -6006,10 +8352,10 @@ const CONVERSIONS = new Map([
6006
8352
  /**
6007
8353
  * Matches a value against the conversion prefixes. A prefix is the text before the first colon,
6008
8354
  * so a value whose remainder itself contains colons (`asset:a:b`) still resolves, and a value
6009
- * with an unrecognised prefix (`https://...`) or no colon does not match.
8355
+ * with an unrecognized prefix (`https://...`) or no colon does not match.
6010
8356
  * @param value - The value to inspect.
6011
8357
  * @returns The matching converter and the text after the prefix, or `null` if the value carries
6012
- * no recognised prefix.
8358
+ * no recognized prefix.
6013
8359
  */
6014
8360
  const matchConversion = (value) => {
6015
8361
  const index = value.indexOf(':');
@@ -6048,6 +8394,7 @@ const findCaseMatch = (script, key) => {
6048
8394
  * @category Components
6049
8395
  */
6050
8396
  class ScriptComponentElement extends ComponentElement {
8397
+ observer;
6051
8398
  /** @ignore */
6052
8399
  constructor() {
6053
8400
  super('script');
@@ -6302,8 +8649,7 @@ class ScriptComponentElement extends ComponentElement {
6302
8649
  * @param scriptElement - The `pc-script` element holding the attributes.
6303
8650
  */
6304
8651
  applyInlineAttributes(script, scriptElement) {
6305
- var _a;
6306
- const scriptName = (_a = scriptElement.getAttribute('name')) !== null && _a !== void 0 ? _a : '';
8652
+ const scriptName = scriptElement.getAttribute('name') ?? '';
6307
8653
  for (const attr of Array.from(scriptElement.attributes)) {
6308
8654
  if (!isReservedAttribute(attr.name)) {
6309
8655
  this.setScriptProperty(script, scriptName, attr.name, attr.value);
@@ -6318,7 +8664,6 @@ class ScriptComponentElement extends ComponentElement {
6318
8664
  * @param attributeName - The name of the changed attribute.
6319
8665
  */
6320
8666
  applyScriptProperty(scriptElement, attributeName) {
6321
- var _a;
6322
8667
  const script = this.scriptFor(scriptElement);
6323
8668
  if (!script)
6324
8669
  return;
@@ -6331,7 +8676,7 @@ class ScriptComponentElement extends ComponentElement {
6331
8676
  }
6332
8677
  return;
6333
8678
  }
6334
- this.setScriptProperty(script, (_a = scriptElement.getAttribute('name')) !== null && _a !== void 0 ? _a : '', attributeName, value);
8679
+ this.setScriptProperty(script, scriptElement.getAttribute('name') ?? '', attributeName, value);
6335
8680
  }
6336
8681
  /**
6337
8682
  * Applies one attribute string to a script property. A string-typed attribute takes the
@@ -6446,9 +8791,8 @@ class ScriptComponentElement extends ComponentElement {
6446
8791
  }
6447
8792
  }
6448
8793
  disconnectedCallback() {
6449
- var _a;
6450
8794
  this.observer.disconnect();
6451
- (_a = super.disconnectedCallback) === null || _a === void 0 ? void 0 : _a.call(this);
8795
+ super.disconnectedCallback?.();
6452
8796
  }
6453
8797
  /**
6454
8798
  * Gets the underlying PlayCanvas script component.
@@ -6469,16 +8813,16 @@ customElements.define('pc-scripts', ScriptComponentElement);
6469
8813
  * @category Components
6470
8814
  */
6471
8815
  class SoundComponentElement extends ComponentElement {
8816
+ _distanceModel = 'linear';
8817
+ _maxDistance = 10000;
8818
+ _pitch = 1;
8819
+ _positional = false;
8820
+ _refDistance = 1;
8821
+ _rollOffFactor = 1;
8822
+ _volume = 1;
6472
8823
  /** @ignore */
6473
8824
  constructor() {
6474
8825
  super('sound');
6475
- this._distanceModel = 'linear';
6476
- this._maxDistance = 10000;
6477
- this._pitch = 1;
6478
- this._positional = false;
6479
- this._refDistance = 1;
6480
- this._rollOffFactor = 1;
6481
- this._volume = 1;
6482
8826
  }
6483
8827
  getInitialComponentData() {
6484
8828
  return {
@@ -6664,25 +9008,37 @@ customElements.define('pc-sounds', SoundComponentElement);
6664
9008
  * methods of the {@link AsyncElement} interface.
6665
9009
  */
6666
9010
  class SoundSlotElement extends AsyncElement {
6667
- constructor() {
6668
- super(...arguments);
6669
- this._asset = '';
6670
- this._autoPlay = false;
6671
- this._duration = null;
6672
- this._loop = false;
6673
- this._name = '';
6674
- this._overlap = false;
6675
- this._pitch = 1;
6676
- this._startTime = 0;
6677
- this._volume = 1;
6678
- /**
6679
- * The sound slot.
6680
- */
6681
- this.soundSlot = null;
6682
- }
9011
+ _asset = '';
9012
+ _autoPlay = false;
9013
+ _duration = null;
9014
+ _loop = false;
9015
+ _name = '';
9016
+ _overlap = false;
9017
+ _pitch = 1;
9018
+ _startTime = 0;
9019
+ _volume = 1;
9020
+ /**
9021
+ * The `<pc-sounds>` this slot was added to, captured at connect time.
9022
+ *
9023
+ * `disconnectedCallback` cannot rediscover it: by the time the element is disconnected its
9024
+ * `parentElement` is already `null`, so a lookup would both fail to find the component and
9025
+ * emit a misleading "must be a direct child" warning for what is an ordinary removal.
9026
+ */
9027
+ _soundElement = null;
9028
+ /**
9029
+ * The sound slot.
9030
+ */
9031
+ soundSlot = null;
6683
9032
  async connectedCallback() {
6684
- var _a;
6685
- await ((_a = this.soundElement) === null || _a === void 0 ? void 0 : _a.ready());
9033
+ const soundElement = this.soundElement;
9034
+ await soundElement?.ready();
9035
+ // The element may have been removed, or its parent torn down, while we were waiting. A
9036
+ // <pc-app> disconnects before its children, so by the time we resume the component can
9037
+ // already be gone - see the matching guard in disconnectedCallback below.
9038
+ const component = soundElement?.component;
9039
+ if (!this.isConnected || !component) {
9040
+ return;
9041
+ }
6686
9042
  const options = {
6687
9043
  autoPlay: this._autoPlay,
6688
9044
  loop: this._loop,
@@ -6694,7 +9050,8 @@ class SoundSlotElement extends AsyncElement {
6694
9050
  if (this._duration) {
6695
9051
  options.duration = this._duration;
6696
9052
  }
6697
- this.soundSlot = this.soundElement.component.addSlot(this._name, options);
9053
+ this._soundElement = soundElement;
9054
+ this.soundSlot = component.addSlot(this._name, options);
6698
9055
  this.asset = this._asset;
6699
9056
  if (this._autoPlay) {
6700
9057
  this.soundSlot.play();
@@ -6702,10 +9059,12 @@ class SoundSlotElement extends AsyncElement {
6702
9059
  this._onReady();
6703
9060
  }
6704
9061
  disconnectedCallback() {
6705
- var _a, _b;
6706
- // The component is null if the parent <pc-sound> (or the whole <pc-app>) is being
6707
- // torn down — parents disconnect first and have already removed the component.
6708
- (_b = (_a = this.soundElement) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.removeSlot(this._name);
9062
+ // Uses the cached parent rather than a fresh lookup, since parentElement is already null
9063
+ // by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
9064
+ // being torn down — parents disconnect first and have already removed the component.
9065
+ this._soundElement?.component?.removeSlot(this._name);
9066
+ this._soundElement = null;
9067
+ this.soundSlot = null;
6709
9068
  }
6710
9069
  get soundElement() {
6711
9070
  const soundElement = this.parentElement;
@@ -6720,10 +9079,9 @@ class SoundSlotElement extends AsyncElement {
6720
9079
  * @param value - The asset.
6721
9080
  */
6722
9081
  set asset(value) {
6723
- var _a;
6724
9082
  this._asset = value;
6725
9083
  if (this.soundSlot) {
6726
- const id = (_a = AssetElement.get(value)) === null || _a === void 0 ? void 0 : _a.id;
9084
+ const id = AssetElement.get(value)?.id;
6727
9085
  if (id) {
6728
9086
  this.soundSlot.asset = id;
6729
9087
  }
@@ -6878,7 +9236,7 @@ class SoundSlotElement extends AsyncElement {
6878
9236
  attributeChangedCallback(name, _oldValue, newValue) {
6879
9237
  switch (name) {
6880
9238
  case 'asset':
6881
- this.asset = newValue;
9239
+ this.asset = newValue ?? '';
6882
9240
  break;
6883
9241
  case 'auto-play':
6884
9242
  this.autoPlay = parseBool(newValue, false);
@@ -6890,7 +9248,7 @@ class SoundSlotElement extends AsyncElement {
6890
9248
  this.loop = parseBool(newValue, false);
6891
9249
  break;
6892
9250
  case 'name':
6893
- this.name = newValue;
9251
+ this.name = newValue ?? '';
6894
9252
  break;
6895
9253
  case 'overlap':
6896
9254
  this.overlap = parseBool(newValue, false);
@@ -6918,15 +9276,15 @@ customElements.define('pc-sound', SoundSlotElement);
6918
9276
  * @category Components
6919
9277
  */
6920
9278
  class GSplatComponentElement extends ComponentElement {
9279
+ _asset = '';
9280
+ _castShadows = false;
9281
+ _lodBaseDistance = 5;
9282
+ _lodMultiplier = 3;
9283
+ _lodRangeMin = 0;
9284
+ _lodRangeMax = 99;
6921
9285
  /** @ignore */
6922
9286
  constructor() {
6923
9287
  super('gsplat');
6924
- this._asset = '';
6925
- this._castShadows = false;
6926
- this._lodBaseDistance = 5;
6927
- this._lodMultiplier = 3;
6928
- this._lodRangeMin = 0;
6929
- this._lodRangeMax = 99;
6930
9288
  }
6931
9289
  getInitialComponentData() {
6932
9290
  return {
@@ -7075,7 +9433,7 @@ class GSplatComponentElement extends ComponentElement {
7075
9433
  super.attributeChangedCallback(name, _oldValue, newValue);
7076
9434
  switch (name) {
7077
9435
  case 'asset':
7078
- this.asset = newValue;
9436
+ this.asset = newValue ?? '';
7079
9437
  break;
7080
9438
  case 'cast-shadows':
7081
9439
  this.castShadows = parseBool(newValue, false);
@@ -7104,11 +9462,8 @@ customElements.define('pc-gsplat', GSplatComponentElement);
7104
9462
  * {@link HTMLElement} interface.
7105
9463
  */
7106
9464
  class ModelElement extends AsyncElement {
7107
- constructor() {
7108
- super(...arguments);
7109
- this._asset = '';
7110
- this._entity = null;
7111
- }
9465
+ _asset = '';
9466
+ _entity = null;
7112
9467
  connectedCallback() {
7113
9468
  this._loadModel();
7114
9469
  this._onReady();
@@ -7140,10 +9495,9 @@ class ModelElement extends AsyncElement {
7140
9495
  }
7141
9496
  }
7142
9497
  async _loadModel() {
7143
- var _a;
7144
9498
  this._unloadModel();
7145
- const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
7146
- const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;
9499
+ const appElement = await this.closestApp?.ready();
9500
+ const app = appElement?.app;
7147
9501
  const asset = AssetElement.get(this._asset);
7148
9502
  if (!asset) {
7149
9503
  return;
@@ -7159,8 +9513,7 @@ class ModelElement extends AsyncElement {
7159
9513
  }
7160
9514
  }
7161
9515
  _unloadModel() {
7162
- var _a;
7163
- (_a = this._entity) === null || _a === void 0 ? void 0 : _a.destroy();
9516
+ this._entity?.destroy();
7164
9517
  this._entity = null;
7165
9518
  }
7166
9519
  /**
@@ -7186,7 +9539,7 @@ class ModelElement extends AsyncElement {
7186
9539
  attributeChangedCallback(name, _oldValue, newValue) {
7187
9540
  switch (name) {
7188
9541
  case 'asset':
7189
- this.asset = newValue;
9542
+ this.asset = newValue ?? '';
7190
9543
  break;
7191
9544
  }
7192
9545
  }
@@ -7200,60 +9553,84 @@ customElements.define('pc-model', ModelElement);
7200
9553
  * {@link HTMLElement} interface.
7201
9554
  */
7202
9555
  class SceneElement extends AsyncElement {
7203
- constructor() {
7204
- super(...arguments);
7205
- /**
7206
- * The fog type of the scene.
7207
- */
7208
- this._fog = 'none';
7209
- /**
7210
- * The color of the fog.
7211
- */
7212
- this._fogColor = new playcanvas.Color(1, 1, 1);
7213
- /**
7214
- * The density of the fog.
7215
- */
7216
- this._fogDensity = 0;
7217
- /**
7218
- * The start distance of the fog.
7219
- */
7220
- this._fogStart = 0;
7221
- /**
7222
- * The end distance of the fog.
7223
- */
7224
- this._fogEnd = 1000;
7225
- /**
7226
- * The gravity of the scene.
7227
- */
7228
- this._gravity = new playcanvas.Vec3(0, -9.81, 0);
7229
- this._scene = null;
7230
- }
7231
9556
  /**
7232
- * The PlayCanvas scene instance. Available once the element is ready — await
9557
+ * The fog type of the scene.
9558
+ */
9559
+ _fog = 'none';
9560
+ /**
9561
+ * The color of the fog.
9562
+ */
9563
+ _fogColor = new playcanvas.Color(1, 1, 1);
9564
+ /**
9565
+ * The density of the fog.
9566
+ */
9567
+ _fogDensity = 0;
9568
+ /**
9569
+ * The start distance of the fog.
9570
+ */
9571
+ _fogStart = 0;
9572
+ /**
9573
+ * The end distance of the fog.
9574
+ */
9575
+ _fogEnd = 1000;
9576
+ /**
9577
+ * The gravity of the scene.
9578
+ */
9579
+ _gravity = new playcanvas.Vec3(0, -9.81, 0);
9580
+ _scene = null;
9581
+ /**
9582
+ * The PlayCanvas scene instance. `null` until the element is ready — await
7233
9583
  * {@link whenReady} or the element's `ready()` promise before accessing it.
7234
- * @returns The scene instance.
9584
+ * @returns The scene instance, or `null`.
7235
9585
  */
7236
9586
  get scene() {
7237
9587
  return this._scene;
7238
9588
  }
7239
9589
  async connectedCallback() {
7240
- var _a;
7241
- await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
7242
- this._scene = this.closestApp.app.scene;
9590
+ const appElement = this.closestApp;
9591
+ if (!appElement) {
9592
+ console.warn('pc-scene must be a descendant of pc-app - scene settings not applied');
9593
+ return;
9594
+ }
9595
+ await appElement.ready();
9596
+ // The element may have been removed or re-parented while waiting for the app. Matches the
9597
+ // guard in AssetElement and MaterialElement, but compares closestApp rather than
9598
+ // parentElement because pc-scene resolves its app by ancestor rather than direct child.
9599
+ // Without this, a scene re-parented mid-await would take its Scene from the app it started
9600
+ // under while _applyGravity resolved the app it ended up under, splitting the two.
9601
+ if (!this.isConnected || this.closestApp !== appElement) {
9602
+ return;
9603
+ }
9604
+ // The application is gone if the tree was torn down while we awaited readiness. There is
9605
+ // nothing to configure and nothing the author can act on, so this stays silent.
9606
+ const app = appElement.app;
9607
+ if (!app) {
9608
+ return;
9609
+ }
9610
+ this._scene = app.scene;
7243
9611
  this.updateSceneSettings();
7244
9612
  this._onReady();
7245
9613
  }
7246
9614
  updateSceneSettings() {
7247
- if (this.scene) {
7248
- this.scene.fog.type = this._fog;
7249
- this.scene.fog.color = this._fogColor;
7250
- this.scene.fog.density = this._fogDensity;
7251
- this.scene.fog.start = this._fogStart;
7252
- this.scene.fog.end = this._fogEnd;
7253
- const appElement = this.parentElement;
7254
- appElement.app.systems.rigidbody.gravity.copy(this._gravity);
9615
+ if (this._scene) {
9616
+ this._scene.fog.type = this._fog;
9617
+ this._scene.fog.color = this._fogColor;
9618
+ this._scene.fog.density = this._fogDensity;
9619
+ this._scene.fog.start = this._fogStart;
9620
+ this._scene.fog.end = this._fogEnd;
9621
+ this._applyGravity(this._gravity);
7255
9622
  }
7256
9623
  }
9624
+ /**
9625
+ * Applies gravity to the rigid body system. Resolved through `closestApp` rather than
9626
+ * `parentElement` so that a `<pc-scene>` nested inside a wrapper element behaves the same as
9627
+ * a direct child, matching how `connectedCallback` resolves the application.
9628
+ *
9629
+ * @param value - The gravity to apply.
9630
+ */
9631
+ _applyGravity(value) {
9632
+ this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
9633
+ }
7257
9634
  /**
7258
9635
  * Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
7259
9636
  * `none`.
@@ -7346,9 +9723,8 @@ class SceneElement extends AsyncElement {
7346
9723
  */
7347
9724
  set gravity(value) {
7348
9725
  this._gravity = value;
7349
- if (this.scene) {
7350
- const appElement = this.parentElement;
7351
- appElement.app.systems.rigidbody.gravity.copy(value);
9726
+ if (this._scene) {
9727
+ this._applyGravity(value);
7352
9728
  }
7353
9729
  }
7354
9730
  /**
@@ -7393,19 +9769,16 @@ customElements.define('pc-scene', SceneElement);
7393
9769
  * methods of the {@link HTMLElement} interface.
7394
9770
  */
7395
9771
  class SkyElement extends AsyncElement {
7396
- constructor() {
7397
- super(...arguments);
7398
- this._asset = '';
7399
- this._center = new playcanvas.Vec3(0, 0.01, 0);
7400
- this._intensity = 1;
7401
- this._rotation = new playcanvas.Vec3();
7402
- this._level = 0;
7403
- this._lighting = false;
7404
- this._scale = new playcanvas.Vec3(100, 100, 100);
7405
- this._type = 'infinite';
7406
- this._scene = null;
7407
- this._appElement = null;
7408
- }
9772
+ _asset = '';
9773
+ _center = new playcanvas.Vec3(0, 0.01, 0);
9774
+ _intensity = 1;
9775
+ _rotation = new playcanvas.Vec3();
9776
+ _level = 0;
9777
+ _lighting = false;
9778
+ _scale = new playcanvas.Vec3(100, 100, 100);
9779
+ _type = 'infinite';
9780
+ _scene = null;
9781
+ _appElement = null;
7409
9782
  connectedCallback() {
7410
9783
  this._loadSkybox();
7411
9784
  this._onReady();
@@ -7437,9 +9810,8 @@ class SkyElement extends AsyncElement {
7437
9810
  this._scene.skyboxMip = this._level;
7438
9811
  }
7439
9812
  async _loadSkybox() {
7440
- var _a;
7441
- const appElement = await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
7442
- const app = appElement === null || appElement === void 0 ? void 0 : appElement.app;
9813
+ const appElement = await this.closestApp?.ready();
9814
+ const app = appElement?.app;
7443
9815
  if (!appElement || !app) {
7444
9816
  return;
7445
9817
  }
@@ -7460,7 +9832,6 @@ class SkyElement extends AsyncElement {
7460
9832
  }
7461
9833
  }
7462
9834
  _unloadSkybox() {
7463
- var _a, _b, _c;
7464
9835
  const scene = this._scene;
7465
9836
  if (!scene)
7466
9837
  return;
@@ -7468,12 +9839,12 @@ class SkyElement extends AsyncElement {
7468
9839
  // If the owning application has already been destroyed (removing a <pc-app>
7469
9840
  // disconnects it before its children), the scene, graphics device and skybox
7470
9841
  // textures have all been destroyed along with it — nothing left to clean up.
7471
- if (!((_a = this._appElement) === null || _a === void 0 ? void 0 : _a.app))
9842
+ if (!this._appElement?.app)
7472
9843
  return;
7473
- (_b = scene.skybox) === null || _b === void 0 ? void 0 : _b.destroy();
9844
+ scene.skybox?.destroy();
7474
9845
  // @ts-ignore
7475
9846
  scene.skybox = null;
7476
- (_c = scene.envAtlas) === null || _c === void 0 ? void 0 : _c.destroy();
9847
+ scene.envAtlas?.destroy();
7477
9848
  // @ts-ignore
7478
9849
  scene.envAtlas = null;
7479
9850
  }
@@ -7620,7 +9991,7 @@ class SkyElement extends AsyncElement {
7620
9991
  attributeChangedCallback(name, _oldValue, newValue) {
7621
9992
  switch (name) {
7622
9993
  case 'asset':
7623
- this.asset = newValue;
9994
+ this.asset = newValue ?? '';
7624
9995
  break;
7625
9996
  case 'center':
7626
9997
  this.center = parseVec3(newValue, new playcanvas.Vec3(0, 0.01, 0), name);