@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/src/material.ts CHANGED
@@ -1,8 +1,107 @@
1
- import { Color, StandardMaterial, Texture } from 'playcanvas';
1
+ import {
2
+ BLEND_ADDITIVE,
3
+ BLEND_ADDITIVEALPHA,
4
+ BLEND_MAX,
5
+ BLEND_MIN,
6
+ BLEND_MULTIPLICATIVE,
7
+ BLEND_MULTIPLICATIVE2X,
8
+ BLEND_NONE,
9
+ BLEND_NORMAL,
10
+ BLEND_PREMULTIPLIED,
11
+ BLEND_SCREEN,
12
+ BLEND_SUBTRACTIVE,
13
+ Color,
14
+ CULLFACE_BACK,
15
+ CULLFACE_FRONT,
16
+ CULLFACE_FRONTANDBACK,
17
+ CULLFACE_NONE,
18
+ FRESNEL_NONE,
19
+ FRESNEL_SCHLICK,
20
+ SPECOCC_AO,
21
+ SPECOCC_GLOSSDEPENDENT,
22
+ SPECOCC_NONE,
23
+ StandardMaterial,
24
+ Vec2,
25
+ type EventHandle,
26
+ type Texture
27
+ } from 'playcanvas';
2
28
 
3
29
  import { AppElement } from './app';
4
30
  import { AssetElement } from './asset';
5
- import { parseColor } from './utils';
31
+ import { parseBool, parseColor, parseEnum, parseNumber, parseVec2 } from './parse';
32
+
33
+ type BlendType = 'none' | 'normal' | 'additive' | 'additive-alpha' | 'premultiplied' |
34
+ 'multiplicative' | 'multiplicative-2x' | 'screen' | 'min' | 'max' | 'subtractive';
35
+
36
+ const blendTypes = new Map<BlendType, number>([
37
+ ['none', BLEND_NONE],
38
+ ['normal', BLEND_NORMAL],
39
+ ['additive', BLEND_ADDITIVE],
40
+ ['additive-alpha', BLEND_ADDITIVEALPHA],
41
+ ['premultiplied', BLEND_PREMULTIPLIED],
42
+ ['multiplicative', BLEND_MULTIPLICATIVE],
43
+ ['multiplicative-2x', BLEND_MULTIPLICATIVE2X],
44
+ ['screen', BLEND_SCREEN],
45
+ ['min', BLEND_MIN],
46
+ ['max', BLEND_MAX],
47
+ ['subtractive', BLEND_SUBTRACTIVE]
48
+ ]);
49
+
50
+ type CullMode = 'none' | 'back' | 'front' | 'front-and-back';
51
+
52
+ const cullModes = new Map<CullMode, number>([
53
+ ['none', CULLFACE_NONE],
54
+ ['back', CULLFACE_BACK],
55
+ ['front', CULLFACE_FRONT],
56
+ ['front-and-back', CULLFACE_FRONTANDBACK]
57
+ ]);
58
+
59
+ type FresnelModel = 'none' | 'schlick';
60
+
61
+ const fresnelModels = new Map<FresnelModel, number>([
62
+ ['none', FRESNEL_NONE],
63
+ ['schlick', FRESNEL_SCHLICK]
64
+ ]);
65
+
66
+ type OccludeSpecular = 'none' | 'ao' | 'gloss-dependent';
67
+
68
+ const occludeSpeculars = new Map<OccludeSpecular, number>([
69
+ ['none', SPECOCC_NONE],
70
+ ['ao', SPECOCC_AO],
71
+ ['gloss-dependent', SPECOCC_GLOSSDEPENDENT]
72
+ ]);
73
+
74
+ // The DITHER_* constants are strings whose values are exactly these names, so a parsed value is
75
+ // assigned to the material unchanged rather than mapped through a table.
76
+ type OpacityDither = 'none' | 'bayer8' | 'bluenoise' | 'ignnoise';
77
+
78
+ const opacityDithers: OpacityDither[] = ['none', 'bayer8', 'bluenoise', 'ignnoise'];
79
+
80
+ type ColorChannel = 'r' | 'g' | 'b' | 'a' | 'rgb';
81
+
82
+ const colorChannels: ColorChannel[] = ['r', 'g', 'b', 'a', 'rgb'];
83
+
84
+ type ScalarChannel = 'r' | 'g' | 'b' | 'a';
85
+
86
+ const scalarChannels: ScalarChannel[] = ['r', 'g', 'b', 'a'];
87
+
88
+ /**
89
+ * The attributes that contradict a `roughness-*` attribute: each one carries the opposite
90
+ * interpretation of a value the aliases also write. The `gloss-map-*` modifiers are deliberately
91
+ * absent - they only configure the shared slot (tiling, offset, channel and so on) and carry no
92
+ * interpretation of their own, so they are the supported way to configure a `roughness-map`.
93
+ */
94
+ const glossConflicts = ['gloss', 'gloss-invert', 'gloss-map'];
95
+
96
+ /** The aliases those attributes contradict. */
97
+ const roughnessAliases = ['roughness', 'roughness-map'];
98
+
99
+ /**
100
+ * The texture slots a `pc-material` can populate. Each is backed by a `pc-asset` id rather than a
101
+ * `Texture`, so the element can be authored before the asset has loaded.
102
+ */
103
+ type TextureSlot = 'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' |
104
+ 'metalnessMap' | 'normalMap' | 'opacityMap';
6
105
 
7
106
  /**
8
107
  * The MaterialElement interface provides properties and methods for manipulating
@@ -13,17 +112,199 @@ import { parseColor } from './utils';
13
112
  * A `pc-material` must be a direct child of `pc-app` — elements placed elsewhere log a warning
14
113
  * and never create a material. Elements inserted while the application is already running are
15
114
  * created on insertion.
115
+ *
116
+ * The element is metal/rough by default: unlike a bare `StandardMaterial` it enables the metalness
117
+ * workflow, which is what the `metalness-*` attributes assume and what glTF means by PBR. It also
118
+ * defaults `metalness` to 0 rather than the engine's 1, because those two defaults have to be
119
+ * chosen together - the engine's 1 is unreachable under its own `useMetalness` of false, and with
120
+ * the workflow on it would make every material fully metallic, so `<pc-material diffuse="crimson">`
121
+ * would render as dark tinted reflections of an environment that may not exist rather than as a
122
+ * crimson surface. `metalness="1"` remains one attribute away.
123
+ *
124
+ * The `roughness` and `roughness-map` attributes are aliases for `gloss` and `gloss-map` that
125
+ * additionally invert the gloss channel; do not mix the two families on one element.
126
+ *
127
+ * The two aliases are documented here rather than on an accessor, because they resolve to the
128
+ * `gloss` properties and would otherwise inherit gloss's description - which reads inverted.
129
+ *
130
+ * @attribute {number} roughness - The roughness of the material, from 0 (shiny) to 1 (rough). An
131
+ * alias for `gloss` that also inverts it, so do not combine it with the `gloss` attributes.
132
+ * @attribute {string} roughness-map - The id of the `pc-asset` to use as the roughness map. An
133
+ * alias for `gloss-map` that also inverts the gloss channel, so do not combine it with the `gloss`
134
+ * attributes.
16
135
  */
17
136
  class MaterialElement extends HTMLElement {
137
+ private _alphaTest = 0;
138
+
139
+ private _alphaToCoverage = false;
140
+
141
+ private _aoIntensity = 1;
142
+
143
+ private _aoMap = '';
144
+
145
+ private _aoMapChannel: ScalarChannel = 'g';
146
+
147
+ private _aoMapOffset = new Vec2(0, 0);
148
+
149
+ private _aoMapRotation = 0;
150
+
151
+ private _aoMapTiling = new Vec2(1, 1);
152
+
153
+ private _aoMapUv = 0;
154
+
155
+ private _blendType: BlendType = 'none';
156
+
157
+ private _bumpiness = 1;
158
+
159
+ private _cull: CullMode = 'back';
160
+
161
+ private _depthBias = 0;
162
+
163
+ private _depthTest = true;
164
+
165
+ private _depthWrite = true;
166
+
18
167
  private _diffuse = new Color(1, 1, 1);
19
168
 
20
169
  private _diffuseMap = '';
21
170
 
171
+ private _diffuseMapChannel: ColorChannel = 'rgb';
172
+
173
+ private _diffuseMapOffset = new Vec2(0, 0);
174
+
175
+ private _diffuseMapRotation = 0;
176
+
177
+ private _diffuseMapTiling = new Vec2(1, 1);
178
+
179
+ private _diffuseMapUv = 0;
180
+
181
+ private _emissive = new Color(0, 0, 0);
182
+
183
+ private _emissiveIntensity = 1;
184
+
185
+ private _emissiveMap = '';
186
+
187
+ private _emissiveMapChannel: ColorChannel = 'rgb';
188
+
189
+ private _emissiveMapOffset = new Vec2(0, 0);
190
+
191
+ private _emissiveMapRotation = 0;
192
+
193
+ private _emissiveMapTiling = new Vec2(1, 1);
194
+
195
+ private _emissiveMapUv = 0;
196
+
197
+ private _enableGGXSpecular = false;
198
+
199
+ private _fresnelModel: FresnelModel = 'schlick';
200
+
201
+ private _gloss = 0.25;
202
+
203
+ private _glossInvert = false;
204
+
205
+ private _glossMap = '';
206
+
207
+ private _glossMapChannel: ScalarChannel = 'g';
208
+
209
+ private _glossMapOffset = new Vec2(0, 0);
210
+
211
+ private _glossMapRotation = 0;
212
+
213
+ private _glossMapTiling = new Vec2(1, 1);
214
+
215
+ private _glossMapUv = 0;
216
+
217
+ private _heightMap = '';
218
+
219
+ private _heightMapChannel: ScalarChannel = 'g';
220
+
221
+ private _heightMapFactor = 1;
222
+
223
+ private _heightMapOffset = new Vec2(0, 0);
224
+
225
+ private _heightMapRotation = 0;
226
+
227
+ private _heightMapTiling = new Vec2(1, 1);
228
+
229
+ private _heightMapUv = 0;
230
+
231
+ private _metalness = 0;
232
+
22
233
  private _metalnessMap = '';
23
234
 
235
+ private _metalnessMapChannel: ScalarChannel = 'g';
236
+
237
+ private _metalnessMapOffset = new Vec2(0, 0);
238
+
239
+ private _metalnessMapRotation = 0;
240
+
241
+ private _metalnessMapTiling = new Vec2(1, 1);
242
+
243
+ private _metalnessMapUv = 0;
244
+
24
245
  private _normalMap = '';
25
246
 
26
- private _roughnessMap = '';
247
+ private _normalMapOffset = new Vec2(0, 0);
248
+
249
+ private _normalMapRotation = 0;
250
+
251
+ private _normalMapTiling = new Vec2(1, 1);
252
+
253
+ private _normalMapUv = 0;
254
+
255
+ private _occludeDirect = false;
256
+
257
+ private _occludeSpecular: OccludeSpecular = 'ao';
258
+
259
+ private _opacity = 1;
260
+
261
+ private _opacityDither: OpacityDither = 'none';
262
+
263
+ private _opacityFadesSpecular = true;
264
+
265
+ private _opacityMap = '';
266
+
267
+ private _opacityMapChannel: ScalarChannel = 'a';
268
+
269
+ private _opacityMapOffset = new Vec2(0, 0);
270
+
271
+ private _opacityMapRotation = 0;
272
+
273
+ private _opacityMapTiling = new Vec2(1, 1);
274
+
275
+ private _opacityMapUv = 0;
276
+
277
+ private _slopeDepthBias = 0;
278
+
279
+ private _specular = new Color(0, 0, 0);
280
+
281
+ private _specularityFactor = 1;
282
+
283
+ private _twoSidedLighting = false;
284
+
285
+ private _useFog = true;
286
+
287
+ private _useLighting = true;
288
+
289
+ // Diverges from the engine default of false - see the class docblock and createMaterial()
290
+ private _useMetalness = true;
291
+
292
+ private _useMetalnessSpecularColor = false;
293
+
294
+ private _useSkybox = true;
295
+
296
+ private _useTonemap = true;
297
+
298
+ /**
299
+ * Pending `load` handlers, one per texture slot. A slot's handler is torn down when the slot is
300
+ * reassigned or the element disconnects, so a late-arriving asset can never write a texture the
301
+ * element no longer wants.
302
+ */
303
+ private _mapHandles = new Map<TextureSlot, EventHandle>();
304
+
305
+ private _updateScheduled = false;
306
+
307
+ private _glossConflictWarned = false;
27
308
 
28
309
  material: StandardMaterial | null = null;
29
310
 
@@ -50,114 +331,2176 @@ class MaterialElement extends HTMLElement {
50
331
  }
51
332
 
52
333
  createMaterial() {
53
- this.material = new StandardMaterial();
54
- this.material.glossInvert = false;
55
- this.material.useMetalness = false;
56
- this.material.diffuse = this._diffuse;
334
+ const material = new StandardMaterial();
335
+ this.material = material;
336
+
337
+ material.alphaTest = this._alphaTest;
338
+ material.alphaToCoverage = this._alphaToCoverage;
339
+ material.aoIntensity = this._aoIntensity;
340
+ material.aoMapChannel = this._aoMapChannel;
341
+ material.aoMapOffset = this._aoMapOffset;
342
+ material.aoMapRotation = this._aoMapRotation;
343
+ material.aoMapTiling = this._aoMapTiling;
344
+ material.aoMapUv = this._aoMapUv;
345
+ material.blendType = blendTypes.get(this._blendType) ?? BLEND_NONE;
346
+ material.bumpiness = this._bumpiness;
347
+ material.cull = cullModes.get(this._cull) ?? CULLFACE_BACK;
348
+ material.depthBias = this._depthBias;
349
+ material.depthTest = this._depthTest;
350
+ material.depthWrite = this._depthWrite;
351
+ material.diffuse = this._diffuse;
352
+ material.diffuseMapChannel = this._diffuseMapChannel;
353
+ material.diffuseMapOffset = this._diffuseMapOffset;
354
+ material.diffuseMapRotation = this._diffuseMapRotation;
355
+ material.diffuseMapTiling = this._diffuseMapTiling;
356
+ material.diffuseMapUv = this._diffuseMapUv;
357
+ material.emissive = this._emissive;
358
+ material.emissiveIntensity = this._emissiveIntensity;
359
+ material.emissiveMapChannel = this._emissiveMapChannel;
360
+ material.emissiveMapOffset = this._emissiveMapOffset;
361
+ material.emissiveMapRotation = this._emissiveMapRotation;
362
+ material.emissiveMapTiling = this._emissiveMapTiling;
363
+ material.emissiveMapUv = this._emissiveMapUv;
364
+ material.enableGGXSpecular = this._enableGGXSpecular;
365
+ material.fresnelModel = fresnelModels.get(this._fresnelModel) ?? FRESNEL_SCHLICK;
366
+ material.gloss = this._gloss;
367
+ material.glossInvert = this._glossInvert;
368
+ material.glossMapChannel = this._glossMapChannel;
369
+ material.glossMapOffset = this._glossMapOffset;
370
+ material.glossMapRotation = this._glossMapRotation;
371
+ material.glossMapTiling = this._glossMapTiling;
372
+ material.glossMapUv = this._glossMapUv;
373
+ material.heightMapChannel = this._heightMapChannel;
374
+ material.heightMapFactor = this._heightMapFactor;
375
+ material.heightMapOffset = this._heightMapOffset;
376
+ material.heightMapRotation = this._heightMapRotation;
377
+ material.heightMapTiling = this._heightMapTiling;
378
+ material.heightMapUv = this._heightMapUv;
379
+ material.metalness = this._metalness;
380
+ material.metalnessMapChannel = this._metalnessMapChannel;
381
+ material.metalnessMapOffset = this._metalnessMapOffset;
382
+ material.metalnessMapRotation = this._metalnessMapRotation;
383
+ material.metalnessMapTiling = this._metalnessMapTiling;
384
+ material.metalnessMapUv = this._metalnessMapUv;
385
+ material.normalMapOffset = this._normalMapOffset;
386
+ material.normalMapRotation = this._normalMapRotation;
387
+ material.normalMapTiling = this._normalMapTiling;
388
+ material.normalMapUv = this._normalMapUv;
389
+ // @ts-ignore the engine's generated .d.ts types occludeDirect as a number, but its own
390
+ // JSDoc documents it as a boolean and its runtime default is `false`
391
+ material.occludeDirect = this._occludeDirect;
392
+ material.occludeSpecular = occludeSpeculars.get(this._occludeSpecular) ?? SPECOCC_AO;
393
+ material.opacity = this._opacity;
394
+ material.opacityDither = this._opacityDither;
395
+ material.opacityFadesSpecular = this._opacityFadesSpecular;
396
+ material.opacityMapChannel = this._opacityMapChannel;
397
+ material.opacityMapOffset = this._opacityMapOffset;
398
+ material.opacityMapRotation = this._opacityMapRotation;
399
+ material.opacityMapTiling = this._opacityMapTiling;
400
+ material.opacityMapUv = this._opacityMapUv;
401
+ material.slopeDepthBias = this._slopeDepthBias;
402
+ material.specular = this._specular;
403
+ material.specularityFactor = this._specularityFactor;
404
+ material.twoSidedLighting = this._twoSidedLighting;
405
+ material.useFog = this._useFog;
406
+ material.useLighting = this._useLighting;
407
+
408
+ // The engine defaults to the older specular/gloss workflow, in which metalnessMap is never
409
+ // sampled at all - useMetalness drives the LIT_METALNESS define. This element defaults the
410
+ // other way, so that `metalness-map` does what its name says.
411
+ material.useMetalness = this._useMetalness;
412
+ material.useMetalnessSpecularColor = this._useMetalnessSpecularColor;
413
+ material.useSkybox = this._useSkybox;
414
+ material.useTonemap = this._useTonemap;
415
+
416
+ // Texture slots resolve a pc-asset id, which may not have loaded yet
417
+ this.aoMap = this._aoMap;
57
418
  this.diffuseMap = this._diffuseMap;
419
+ this.emissiveMap = this._emissiveMap;
420
+ this.glossMap = this._glossMap;
421
+ this.heightMap = this._heightMap;
58
422
  this.metalnessMap = this._metalnessMap;
59
423
  this.normalMap = this._normalMap;
60
- this.roughnessMap = this._roughnessMap;
61
- this.material.update();
424
+ this.opacityMap = this._opacityMap;
425
+
426
+ material.update();
62
427
  }
63
428
 
64
429
  disconnectedCallback() {
430
+ for (const handle of this._mapHandles.values()) {
431
+ handle.off();
432
+ }
433
+ this._mapHandles.clear();
434
+
65
435
  if (this.material) {
66
436
  this.material.destroy();
67
437
  this.material = null;
68
438
  }
69
439
  }
70
440
 
71
- setMap(map: string, property: 'diffuseMap' | 'metalnessMap' | 'normalMap' | 'glossMap') {
441
+ /**
442
+ * Coalesces `material.update()` across a burst of attribute or property writes, so that setting
443
+ * a dozen attributes in one parse costs one update rather than a dozen.
444
+ */
445
+ private _scheduleUpdate() {
446
+ if (this._updateScheduled) return;
447
+ this._updateScheduled = true;
448
+ queueMicrotask(() => {
449
+ this._updateScheduled = false;
450
+ this.material?.update();
451
+ });
452
+ }
453
+
454
+ /**
455
+ * Warns when a `roughness-*` attribute is combined with one that carries the opposite
456
+ * interpretation of the same value. They write the same engine properties but disagree about
457
+ * whether the channel is inverted, so the result would depend on attribute order rather than
458
+ * on intent.
459
+ *
460
+ * Called from both families rather than only from the roughness branches, because the two
461
+ * orderings are equally wrong and only one of them would otherwise be caught. The conflict is
462
+ * a property of the element rather than of any one write - and an upgrading element already
463
+ * has all of its attributes, so every branch would otherwise report the same clash - so the
464
+ * warning latches and reports once per episode, clearing when the clash is resolved.
465
+ */
466
+ private _warnGlossConflict() {
467
+ const quote = (names: string[]) => `'${names.join('\', \'')}'`;
468
+
469
+ const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
470
+ const gloss = glossConflicts.filter(name => this.hasAttribute(name));
471
+
472
+ if (roughness.length === 0 || gloss.length === 0) {
473
+ this._glossConflictWarned = false;
474
+ return;
475
+ }
476
+
477
+ if (this._glossConflictWarned) return;
478
+ this._glossConflictWarned = true;
479
+
480
+ console.warn(`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
481
+ 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.');
482
+ }
483
+
484
+ /**
485
+ * Points a texture slot at the resource of a `pc-asset`, waiting for the asset to load when it
486
+ * has not already. An empty id clears the slot.
487
+ *
488
+ * @param id - The id of the `pc-asset`, or an empty string to clear the slot.
489
+ * @param slot - The material property to write.
490
+ */
491
+ setMap(id: string, slot: TextureSlot) {
492
+ // Drop any load still pending for this slot - its texture is no longer the one we want
493
+ this._mapHandles.get(slot)?.off();
494
+ this._mapHandles.delete(slot);
495
+
496
+ if (!this.material) return;
497
+
498
+ if (!id) {
499
+ this.material[slot] = null;
500
+ this._scheduleUpdate();
501
+ return;
502
+ }
503
+
504
+ const asset = AssetElement.get(id);
505
+ if (!asset) return;
506
+
507
+ if (asset.loaded) {
508
+ this._applyMap(slot, asset.resource as Texture);
509
+ return;
510
+ }
511
+
512
+ this._mapHandles.set(slot, asset.once('load', () => {
513
+ this._mapHandles.delete(slot);
514
+ this._applyMap(slot, asset.resource as Texture);
515
+ }));
516
+ }
517
+
518
+ /**
519
+ * @param slot - The material property to write.
520
+ * @param texture - The loaded texture.
521
+ */
522
+ private _applyMap(slot: TextureSlot, texture: Texture) {
523
+ if (!this.material) return;
524
+ this.material[slot] = texture;
525
+ texture.anisotropy = 4;
526
+ this._scheduleUpdate();
527
+ }
528
+
529
+ /**
530
+ * Sets the alpha test reference value. Fragments with an opacity below this value are discarded.
531
+ * @param value - The alpha test reference value.
532
+ */
533
+ set alphaTest(value: number) {
534
+ this._alphaTest = value;
535
+ if (this.material) {
536
+ this.material.alphaTest = value;
537
+ this._scheduleUpdate();
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Gets the alpha test reference value.
543
+ * @returns The alpha test reference value.
544
+ */
545
+ get alphaTest() {
546
+ return this._alphaTest;
547
+ }
548
+
549
+ /**
550
+ * Sets whether to use alpha to coverage, which resolves transparency using multisampling.
551
+ * @param value - The alpha to coverage flag.
552
+ */
553
+ set alphaToCoverage(value: boolean) {
554
+ this._alphaToCoverage = value;
555
+ if (this.material) {
556
+ this.material.alphaToCoverage = value;
557
+ this._scheduleUpdate();
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Gets whether to use alpha to coverage.
563
+ * @returns The alpha to coverage flag.
564
+ */
565
+ get alphaToCoverage() {
566
+ return this._alphaToCoverage;
567
+ }
568
+
569
+ /**
570
+ * Sets the strength of the ambient occlusion map, from 0 to 1.
571
+ * @param value - The ambient occlusion intensity.
572
+ */
573
+ set aoIntensity(value: number) {
574
+ this._aoIntensity = value;
575
+ if (this.material) {
576
+ this.material.aoIntensity = value;
577
+ this._scheduleUpdate();
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Gets the strength of the ambient occlusion map.
583
+ * @returns The ambient occlusion intensity.
584
+ */
585
+ get aoIntensity() {
586
+ return this._aoIntensity;
587
+ }
588
+
589
+ /**
590
+ * Sets the id of the `pc-asset` to use as the ambient occlusion map.
591
+ * @param value - The asset id.
592
+ */
593
+ set aoMap(value: string) {
594
+ this._aoMap = value;
595
+ this.setMap(value, 'aoMap');
596
+ }
597
+
598
+ /**
599
+ * Gets the id of the `pc-asset` used as the ambient occlusion map.
600
+ * @returns The asset id.
601
+ */
602
+ get aoMap() {
603
+ return this._aoMap;
604
+ }
605
+
606
+ /**
607
+ * Sets the color channel of the ambient occlusion map to sample.
608
+ * @param value - The channel.
609
+ */
610
+ set aoMapChannel(value: ScalarChannel) {
611
+ this._aoMapChannel = value;
612
+ if (this.material) {
613
+ this.material.aoMapChannel = value;
614
+ this._scheduleUpdate();
615
+ }
616
+ }
617
+
618
+ /**
619
+ * Gets the color channel of the ambient occlusion map to sample.
620
+ * @returns The channel.
621
+ */
622
+ get aoMapChannel(): ScalarChannel {
623
+ return this._aoMapChannel;
624
+ }
625
+
626
+ /**
627
+ * Sets the 2D offset of the ambient occlusion map.
628
+ * @param value - The offset.
629
+ */
630
+ set aoMapOffset(value: Vec2) {
631
+ this._aoMapOffset = value;
632
+ if (this.material) {
633
+ this.material.aoMapOffset = value;
634
+ this._scheduleUpdate();
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Gets the 2D offset of the ambient occlusion map.
640
+ * @returns The offset.
641
+ */
642
+ get aoMapOffset() {
643
+ return this._aoMapOffset;
644
+ }
645
+
646
+ /**
647
+ * Sets the 2D rotation of the ambient occlusion map, in degrees.
648
+ * @param value - The rotation.
649
+ */
650
+ set aoMapRotation(value: number) {
651
+ this._aoMapRotation = value;
652
+ if (this.material) {
653
+ this.material.aoMapRotation = value;
654
+ this._scheduleUpdate();
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Gets the 2D rotation of the ambient occlusion map.
660
+ * @returns The rotation.
661
+ */
662
+ get aoMapRotation() {
663
+ return this._aoMapRotation;
664
+ }
665
+
666
+ /**
667
+ * Sets the 2D tiling of the ambient occlusion map.
668
+ * @param value - The tiling.
669
+ */
670
+ set aoMapTiling(value: Vec2) {
671
+ this._aoMapTiling = value;
672
+ if (this.material) {
673
+ this.material.aoMapTiling = value;
674
+ this._scheduleUpdate();
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Gets the 2D tiling of the ambient occlusion map.
680
+ * @returns The tiling.
681
+ */
682
+ get aoMapTiling() {
683
+ return this._aoMapTiling;
684
+ }
685
+
686
+ /**
687
+ * Sets the UV channel the ambient occlusion map samples.
688
+ * @param value - The UV channel.
689
+ */
690
+ set aoMapUv(value: number) {
691
+ this._aoMapUv = value;
692
+ if (this.material) {
693
+ this.material.aoMapUv = value;
694
+ this._scheduleUpdate();
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Gets the UV channel the ambient occlusion map samples.
700
+ * @returns The UV channel.
701
+ */
702
+ get aoMapUv() {
703
+ return this._aoMapUv;
704
+ }
705
+
706
+ /**
707
+ * Sets how the material is blended with the scene behind it.
708
+ * @param value - The blend type.
709
+ */
710
+ set blendType(value: BlendType) {
711
+ this._blendType = value;
712
+ if (this.material) {
713
+ this.material.blendType = blendTypes.get(value) ?? BLEND_NONE;
714
+ this._scheduleUpdate();
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Gets how the material is blended with the scene behind it.
720
+ * @returns The blend type.
721
+ */
722
+ get blendType(): BlendType {
723
+ return this._blendType;
724
+ }
725
+
726
+ /**
727
+ * Sets the strength of the normal map, where 0 is flat and 1 is the map's full effect.
728
+ * @param value - The bumpiness.
729
+ */
730
+ set bumpiness(value: number) {
731
+ this._bumpiness = value;
732
+ if (this.material) {
733
+ this.material.bumpiness = value;
734
+ this._scheduleUpdate();
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Gets the strength of the normal map.
740
+ * @returns The bumpiness.
741
+ */
742
+ get bumpiness() {
743
+ return this._bumpiness;
744
+ }
745
+
746
+ /**
747
+ * Sets which faces of a mesh are culled.
748
+ * @param value - The cull mode.
749
+ */
750
+ set cull(value: CullMode) {
751
+ this._cull = value;
752
+ if (this.material) {
753
+ this.material.cull = cullModes.get(value) ?? CULLFACE_BACK;
754
+ this._scheduleUpdate();
755
+ }
756
+ }
757
+
758
+ /**
759
+ * Gets which faces of a mesh are culled.
760
+ * @returns The cull mode.
761
+ */
762
+ get cull(): CullMode {
763
+ return this._cull;
764
+ }
765
+
766
+ /**
767
+ * Sets the offset applied to the depth of a fragment, used to resolve z-fighting.
768
+ * @param value - The depth bias.
769
+ */
770
+ set depthBias(value: number) {
771
+ this._depthBias = value;
772
+ if (this.material) {
773
+ this.material.depthBias = value;
774
+ this._scheduleUpdate();
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Gets the offset applied to the depth of a fragment.
780
+ * @returns The depth bias.
781
+ */
782
+ get depthBias() {
783
+ return this._depthBias;
784
+ }
785
+
786
+ /**
787
+ * Sets whether fragments are tested against the depth buffer.
788
+ * @param value - The depth test flag.
789
+ */
790
+ set depthTest(value: boolean) {
791
+ this._depthTest = value;
792
+ if (this.material) {
793
+ this.material.depthTest = value;
794
+ this._scheduleUpdate();
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Gets whether fragments are tested against the depth buffer.
800
+ * @returns The depth test flag.
801
+ */
802
+ get depthTest() {
803
+ return this._depthTest;
804
+ }
805
+
806
+ /**
807
+ * Sets whether fragments write to the depth buffer.
808
+ * @param value - The depth write flag.
809
+ */
810
+ set depthWrite(value: boolean) {
811
+ this._depthWrite = value;
72
812
  if (this.material) {
73
- const asset = AssetElement.get(map);
74
- if (asset) {
75
- if (asset.loaded) {
76
- this.material[property] = asset.resource as Texture;
77
- this.material[property]!.anisotropy = 4;
78
- } else {
79
- asset.once('load', () => {
80
- this.material![property] = asset.resource as Texture;
81
- this.material![property]!.anisotropy = 4;
82
- this.material!.update();
83
- });
84
- }
85
- }
813
+ this.material.depthWrite = value;
814
+ this._scheduleUpdate();
86
815
  }
87
816
  }
88
817
 
818
+ /**
819
+ * Gets whether fragments write to the depth buffer.
820
+ * @returns The depth write flag.
821
+ */
822
+ get depthWrite() {
823
+ return this._depthWrite;
824
+ }
825
+
826
+ /**
827
+ * Sets the diffuse color of the material. With the metalness workflow this doubles as the
828
+ * specular color where the surface is metallic.
829
+ * @param value - The diffuse color.
830
+ */
89
831
  set diffuse(value: Color) {
90
832
  this._diffuse = value;
91
833
  if (this.material) {
92
834
  this.material.diffuse = value;
835
+ this._scheduleUpdate();
93
836
  }
94
837
  }
95
838
 
839
+ /**
840
+ * Gets the diffuse color of the material.
841
+ * @returns The diffuse color.
842
+ */
96
843
  get diffuse(): Color {
97
844
  return this._diffuse;
98
845
  }
99
846
 
847
+ /**
848
+ * Sets the id of the `pc-asset` to use as the diffuse map.
849
+ * @param value - The asset id.
850
+ */
100
851
  set diffuseMap(value: string) {
101
852
  this._diffuseMap = value;
102
853
  this.setMap(value, 'diffuseMap');
103
854
  }
104
855
 
856
+ /**
857
+ * Gets the id of the `pc-asset` used as the diffuse map.
858
+ * @returns The asset id.
859
+ */
105
860
  get diffuseMap() {
106
861
  return this._diffuseMap;
107
862
  }
108
863
 
109
- set metalnessMap(value: string) {
110
- this._metalnessMap = value;
111
- this.setMap(value, 'metalnessMap');
864
+ /**
865
+ * Sets the color channels of the diffuse map to sample.
866
+ * @param value - The channels.
867
+ */
868
+ set diffuseMapChannel(value: ColorChannel) {
869
+ this._diffuseMapChannel = value;
870
+ if (this.material) {
871
+ this.material.diffuseMapChannel = value;
872
+ this._scheduleUpdate();
873
+ }
112
874
  }
113
875
 
114
- get metalnessMap() {
115
- return this._metalnessMap;
876
+ /**
877
+ * Gets the color channels of the diffuse map to sample.
878
+ * @returns The channels.
879
+ */
880
+ get diffuseMapChannel(): ColorChannel {
881
+ return this._diffuseMapChannel;
116
882
  }
117
883
 
118
- set normalMap(value: string) {
119
- this._normalMap = value;
120
- this.setMap(value, 'normalMap');
884
+ /**
885
+ * Sets the 2D offset of the diffuse map.
886
+ * @param value - The offset.
887
+ */
888
+ set diffuseMapOffset(value: Vec2) {
889
+ this._diffuseMapOffset = value;
890
+ if (this.material) {
891
+ this.material.diffuseMapOffset = value;
892
+ this._scheduleUpdate();
893
+ }
121
894
  }
122
895
 
123
- get normalMap() {
124
- return this._normalMap;
896
+ /**
897
+ * Gets the 2D offset of the diffuse map.
898
+ * @returns The offset.
899
+ */
900
+ get diffuseMapOffset() {
901
+ return this._diffuseMapOffset;
125
902
  }
126
903
 
127
- set roughnessMap(value: string) {
128
- this._roughnessMap = value;
129
- this.setMap(value, 'glossMap');
904
+ /**
905
+ * Sets the 2D rotation of the diffuse map, in degrees.
906
+ * @param value - The rotation.
907
+ */
908
+ set diffuseMapRotation(value: number) {
909
+ this._diffuseMapRotation = value;
910
+ if (this.material) {
911
+ this.material.diffuseMapRotation = value;
912
+ this._scheduleUpdate();
913
+ }
130
914
  }
131
915
 
132
- get roughnessMap() {
133
- return this._roughnessMap;
916
+ /**
917
+ * Gets the 2D rotation of the diffuse map.
918
+ * @returns The rotation.
919
+ */
920
+ get diffuseMapRotation() {
921
+ return this._diffuseMapRotation;
134
922
  }
135
923
 
136
- static get(id: string) {
137
- const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
138
- return materialElement?.material;
924
+ /**
925
+ * Sets the 2D tiling of the diffuse map.
926
+ * @param value - The tiling.
927
+ */
928
+ set diffuseMapTiling(value: Vec2) {
929
+ this._diffuseMapTiling = value;
930
+ if (this.material) {
931
+ this.material.diffuseMapTiling = value;
932
+ this._scheduleUpdate();
933
+ }
139
934
  }
140
935
 
141
- static get observedAttributes() {
142
- return ['diffuse', 'diffuse-map', 'metalness-map', 'normal-map', 'roughness-map'];
936
+ /**
937
+ * Gets the 2D tiling of the diffuse map.
938
+ * @returns The tiling.
939
+ */
940
+ get diffuseMapTiling() {
941
+ return this._diffuseMapTiling;
143
942
  }
144
943
 
145
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
146
- switch (name) {
147
- case 'diffuse':
148
- this.diffuse = parseColor(newValue, Color.WHITE, name);
149
- break;
150
- case 'diffuse-map':
151
- this.diffuseMap = newValue;
152
- break;
153
- case 'metalness-map':
154
- this.metalnessMap = newValue;
155
- break;
156
- case 'normal-map':
157
- this.normalMap = newValue;
158
- break;
159
- case 'roughness-map':
160
- this.roughnessMap = newValue;
944
+ /**
945
+ * Sets the UV channel the diffuse map samples.
946
+ * @param value - The UV channel.
947
+ */
948
+ set diffuseMapUv(value: number) {
949
+ this._diffuseMapUv = value;
950
+ if (this.material) {
951
+ this.material.diffuseMapUv = value;
952
+ this._scheduleUpdate();
953
+ }
954
+ }
955
+
956
+ /**
957
+ * Gets the UV channel the diffuse map samples.
958
+ * @returns The UV channel.
959
+ */
960
+ get diffuseMapUv() {
961
+ return this._diffuseMapUv;
962
+ }
963
+
964
+ /**
965
+ * Sets the emissive color of the material, which is added to the lit result.
966
+ * @param value - The emissive color.
967
+ */
968
+ set emissive(value: Color) {
969
+ this._emissive = value;
970
+ if (this.material) {
971
+ this.material.emissive = value;
972
+ this._scheduleUpdate();
973
+ }
974
+ }
975
+
976
+ /**
977
+ * Gets the emissive color of the material.
978
+ * @returns The emissive color.
979
+ */
980
+ get emissive(): Color {
981
+ return this._emissive;
982
+ }
983
+
984
+ /**
985
+ * Sets the multiplier applied to the emissive color and map.
986
+ * @param value - The emissive intensity.
987
+ */
988
+ set emissiveIntensity(value: number) {
989
+ this._emissiveIntensity = value;
990
+ if (this.material) {
991
+ this.material.emissiveIntensity = value;
992
+ this._scheduleUpdate();
993
+ }
994
+ }
995
+
996
+ /**
997
+ * Gets the multiplier applied to the emissive color and map.
998
+ * @returns The emissive intensity.
999
+ */
1000
+ get emissiveIntensity() {
1001
+ return this._emissiveIntensity;
1002
+ }
1003
+
1004
+ /**
1005
+ * Sets the id of the `pc-asset` to use as the emissive map.
1006
+ * @param value - The asset id.
1007
+ */
1008
+ set emissiveMap(value: string) {
1009
+ this._emissiveMap = value;
1010
+ this.setMap(value, 'emissiveMap');
1011
+ }
1012
+
1013
+ /**
1014
+ * Gets the id of the `pc-asset` used as the emissive map.
1015
+ * @returns The asset id.
1016
+ */
1017
+ get emissiveMap() {
1018
+ return this._emissiveMap;
1019
+ }
1020
+
1021
+ /**
1022
+ * Sets the color channels of the emissive map to sample.
1023
+ * @param value - The channels.
1024
+ */
1025
+ set emissiveMapChannel(value: ColorChannel) {
1026
+ this._emissiveMapChannel = value;
1027
+ if (this.material) {
1028
+ this.material.emissiveMapChannel = value;
1029
+ this._scheduleUpdate();
1030
+ }
1031
+ }
1032
+
1033
+ /**
1034
+ * Gets the color channels of the emissive map to sample.
1035
+ * @returns The channels.
1036
+ */
1037
+ get emissiveMapChannel(): ColorChannel {
1038
+ return this._emissiveMapChannel;
1039
+ }
1040
+
1041
+ /**
1042
+ * Sets the 2D offset of the emissive map.
1043
+ * @param value - The offset.
1044
+ */
1045
+ set emissiveMapOffset(value: Vec2) {
1046
+ this._emissiveMapOffset = value;
1047
+ if (this.material) {
1048
+ this.material.emissiveMapOffset = value;
1049
+ this._scheduleUpdate();
1050
+ }
1051
+ }
1052
+
1053
+ /**
1054
+ * Gets the 2D offset of the emissive map.
1055
+ * @returns The offset.
1056
+ */
1057
+ get emissiveMapOffset() {
1058
+ return this._emissiveMapOffset;
1059
+ }
1060
+
1061
+ /**
1062
+ * Sets the 2D rotation of the emissive map, in degrees.
1063
+ * @param value - The rotation.
1064
+ */
1065
+ set emissiveMapRotation(value: number) {
1066
+ this._emissiveMapRotation = value;
1067
+ if (this.material) {
1068
+ this.material.emissiveMapRotation = value;
1069
+ this._scheduleUpdate();
1070
+ }
1071
+ }
1072
+
1073
+ /**
1074
+ * Gets the 2D rotation of the emissive map.
1075
+ * @returns The rotation.
1076
+ */
1077
+ get emissiveMapRotation() {
1078
+ return this._emissiveMapRotation;
1079
+ }
1080
+
1081
+ /**
1082
+ * Sets the 2D tiling of the emissive map.
1083
+ * @param value - The tiling.
1084
+ */
1085
+ set emissiveMapTiling(value: Vec2) {
1086
+ this._emissiveMapTiling = value;
1087
+ if (this.material) {
1088
+ this.material.emissiveMapTiling = value;
1089
+ this._scheduleUpdate();
1090
+ }
1091
+ }
1092
+
1093
+ /**
1094
+ * Gets the 2D tiling of the emissive map.
1095
+ * @returns The tiling.
1096
+ */
1097
+ get emissiveMapTiling() {
1098
+ return this._emissiveMapTiling;
1099
+ }
1100
+
1101
+ /**
1102
+ * Sets the UV channel the emissive map samples.
1103
+ * @param value - The UV channel.
1104
+ */
1105
+ set emissiveMapUv(value: number) {
1106
+ this._emissiveMapUv = value;
1107
+ if (this.material) {
1108
+ this.material.emissiveMapUv = value;
1109
+ this._scheduleUpdate();
1110
+ }
1111
+ }
1112
+
1113
+ /**
1114
+ * Gets the UV channel the emissive map samples.
1115
+ * @returns The UV channel.
1116
+ */
1117
+ get emissiveMapUv() {
1118
+ return this._emissiveMapUv;
1119
+ }
1120
+
1121
+ /**
1122
+ * Sets whether to use the GGX specular model, which supports anisotropy.
1123
+ * @param value - The GGX specular flag.
1124
+ */
1125
+ set enableGGXSpecular(value: boolean) {
1126
+ this._enableGGXSpecular = value;
1127
+ if (this.material) {
1128
+ this.material.enableGGXSpecular = value;
1129
+ this._scheduleUpdate();
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * Gets whether to use the GGX specular model.
1135
+ * @returns The GGX specular flag.
1136
+ */
1137
+ get enableGGXSpecular() {
1138
+ return this._enableGGXSpecular;
1139
+ }
1140
+
1141
+ /**
1142
+ * Sets the Fresnel model used for specular reflections at grazing angles.
1143
+ * @param value - The Fresnel model.
1144
+ */
1145
+ set fresnelModel(value: FresnelModel) {
1146
+ this._fresnelModel = value;
1147
+ if (this.material) {
1148
+ this.material.fresnelModel = fresnelModels.get(value) ?? FRESNEL_SCHLICK;
1149
+ this._scheduleUpdate();
1150
+ }
1151
+ }
1152
+
1153
+ /**
1154
+ * Gets the Fresnel model used for specular reflections at grazing angles.
1155
+ * @returns The Fresnel model.
1156
+ */
1157
+ get fresnelModel(): FresnelModel {
1158
+ return this._fresnelModel;
1159
+ }
1160
+
1161
+ /**
1162
+ * Sets the glossiness of the material, from 0 (rough) to 1 (shiny). See also `roughness`.
1163
+ * @param value - The gloss.
1164
+ */
1165
+ set gloss(value: number) {
1166
+ this._gloss = value;
1167
+ if (this.material) {
1168
+ this.material.gloss = value;
1169
+ this._scheduleUpdate();
1170
+ }
1171
+ }
1172
+
1173
+ /**
1174
+ * Gets the glossiness of the material.
1175
+ * @returns The gloss.
1176
+ */
1177
+ get gloss() {
1178
+ return this._gloss;
1179
+ }
1180
+
1181
+ /**
1182
+ * Sets whether the gloss value and map are inverted, which makes the material treat them as
1183
+ * roughness. Setting `roughness` or `roughness-map` enables this automatically.
1184
+ * @param value - The gloss invert flag.
1185
+ */
1186
+ set glossInvert(value: boolean) {
1187
+ this._glossInvert = value;
1188
+ if (this.material) {
1189
+ this.material.glossInvert = value;
1190
+ this._scheduleUpdate();
1191
+ }
1192
+ }
1193
+
1194
+ /**
1195
+ * Gets whether the gloss value and map are inverted.
1196
+ * @returns The gloss invert flag.
1197
+ */
1198
+ get glossInvert() {
1199
+ return this._glossInvert;
1200
+ }
1201
+
1202
+ /**
1203
+ * Sets the id of the `pc-asset` to use as the gloss map. See also `roughnessMap`.
1204
+ * @param value - The asset id.
1205
+ */
1206
+ set glossMap(value: string) {
1207
+ this._glossMap = value;
1208
+ this.setMap(value, 'glossMap');
1209
+ }
1210
+
1211
+ /**
1212
+ * Gets the id of the `pc-asset` used as the gloss map.
1213
+ * @returns The asset id.
1214
+ */
1215
+ get glossMap() {
1216
+ return this._glossMap;
1217
+ }
1218
+
1219
+ /**
1220
+ * Sets the color channel of the gloss map to sample.
1221
+ * @param value - The channel.
1222
+ */
1223
+ set glossMapChannel(value: ScalarChannel) {
1224
+ this._glossMapChannel = value;
1225
+ if (this.material) {
1226
+ this.material.glossMapChannel = value;
1227
+ this._scheduleUpdate();
1228
+ }
1229
+ }
1230
+
1231
+ /**
1232
+ * Gets the color channel of the gloss map to sample.
1233
+ * @returns The channel.
1234
+ */
1235
+ get glossMapChannel(): ScalarChannel {
1236
+ return this._glossMapChannel;
1237
+ }
1238
+
1239
+ /**
1240
+ * Sets the 2D offset of the gloss map.
1241
+ * @param value - The offset.
1242
+ */
1243
+ set glossMapOffset(value: Vec2) {
1244
+ this._glossMapOffset = value;
1245
+ if (this.material) {
1246
+ this.material.glossMapOffset = value;
1247
+ this._scheduleUpdate();
1248
+ }
1249
+ }
1250
+
1251
+ /**
1252
+ * Gets the 2D offset of the gloss map.
1253
+ * @returns The offset.
1254
+ */
1255
+ get glossMapOffset() {
1256
+ return this._glossMapOffset;
1257
+ }
1258
+
1259
+ /**
1260
+ * Sets the 2D rotation of the gloss map, in degrees.
1261
+ * @param value - The rotation.
1262
+ */
1263
+ set glossMapRotation(value: number) {
1264
+ this._glossMapRotation = value;
1265
+ if (this.material) {
1266
+ this.material.glossMapRotation = value;
1267
+ this._scheduleUpdate();
1268
+ }
1269
+ }
1270
+
1271
+ /**
1272
+ * Gets the 2D rotation of the gloss map.
1273
+ * @returns The rotation.
1274
+ */
1275
+ get glossMapRotation() {
1276
+ return this._glossMapRotation;
1277
+ }
1278
+
1279
+ /**
1280
+ * Sets the 2D tiling of the gloss map.
1281
+ * @param value - The tiling.
1282
+ */
1283
+ set glossMapTiling(value: Vec2) {
1284
+ this._glossMapTiling = value;
1285
+ if (this.material) {
1286
+ this.material.glossMapTiling = value;
1287
+ this._scheduleUpdate();
1288
+ }
1289
+ }
1290
+
1291
+ /**
1292
+ * Gets the 2D tiling of the gloss map.
1293
+ * @returns The tiling.
1294
+ */
1295
+ get glossMapTiling() {
1296
+ return this._glossMapTiling;
1297
+ }
1298
+
1299
+ /**
1300
+ * Sets the UV channel the gloss map samples.
1301
+ * @param value - The UV channel.
1302
+ */
1303
+ set glossMapUv(value: number) {
1304
+ this._glossMapUv = value;
1305
+ if (this.material) {
1306
+ this.material.glossMapUv = value;
1307
+ this._scheduleUpdate();
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * Gets the UV channel the gloss map samples.
1313
+ * @returns The UV channel.
1314
+ */
1315
+ get glossMapUv() {
1316
+ return this._glossMapUv;
1317
+ }
1318
+
1319
+ /**
1320
+ * Sets the id of the `pc-asset` to use as the height map, which drives parallax mapping.
1321
+ * @param value - The asset id.
1322
+ */
1323
+ set heightMap(value: string) {
1324
+ this._heightMap = value;
1325
+ this.setMap(value, 'heightMap');
1326
+ }
1327
+
1328
+ /**
1329
+ * Gets the id of the `pc-asset` used as the height map.
1330
+ * @returns The asset id.
1331
+ */
1332
+ get heightMap() {
1333
+ return this._heightMap;
1334
+ }
1335
+
1336
+ /**
1337
+ * Sets the color channel of the height map to sample.
1338
+ * @param value - The channel.
1339
+ */
1340
+ set heightMapChannel(value: ScalarChannel) {
1341
+ this._heightMapChannel = value;
1342
+ if (this.material) {
1343
+ this.material.heightMapChannel = value;
1344
+ this._scheduleUpdate();
1345
+ }
1346
+ }
1347
+
1348
+ /**
1349
+ * Gets the color channel of the height map to sample.
1350
+ * @returns The channel.
1351
+ */
1352
+ get heightMapChannel(): ScalarChannel {
1353
+ return this._heightMapChannel;
1354
+ }
1355
+
1356
+ /**
1357
+ * Sets the strength of the parallax effect driven by the height map.
1358
+ * @param value - The height map factor.
1359
+ */
1360
+ set heightMapFactor(value: number) {
1361
+ this._heightMapFactor = value;
1362
+ if (this.material) {
1363
+ this.material.heightMapFactor = value;
1364
+ this._scheduleUpdate();
1365
+ }
1366
+ }
1367
+
1368
+ /**
1369
+ * Gets the strength of the parallax effect driven by the height map.
1370
+ * @returns The height map factor.
1371
+ */
1372
+ get heightMapFactor() {
1373
+ return this._heightMapFactor;
1374
+ }
1375
+
1376
+ /**
1377
+ * Sets the 2D offset of the height map.
1378
+ * @param value - The offset.
1379
+ */
1380
+ set heightMapOffset(value: Vec2) {
1381
+ this._heightMapOffset = value;
1382
+ if (this.material) {
1383
+ this.material.heightMapOffset = value;
1384
+ this._scheduleUpdate();
1385
+ }
1386
+ }
1387
+
1388
+ /**
1389
+ * Gets the 2D offset of the height map.
1390
+ * @returns The offset.
1391
+ */
1392
+ get heightMapOffset() {
1393
+ return this._heightMapOffset;
1394
+ }
1395
+
1396
+ /**
1397
+ * Sets the 2D rotation of the height map, in degrees.
1398
+ * @param value - The rotation.
1399
+ */
1400
+ set heightMapRotation(value: number) {
1401
+ this._heightMapRotation = value;
1402
+ if (this.material) {
1403
+ this.material.heightMapRotation = value;
1404
+ this._scheduleUpdate();
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * Gets the 2D rotation of the height map.
1410
+ * @returns The rotation.
1411
+ */
1412
+ get heightMapRotation() {
1413
+ return this._heightMapRotation;
1414
+ }
1415
+
1416
+ /**
1417
+ * Sets the 2D tiling of the height map.
1418
+ * @param value - The tiling.
1419
+ */
1420
+ set heightMapTiling(value: Vec2) {
1421
+ this._heightMapTiling = value;
1422
+ if (this.material) {
1423
+ this.material.heightMapTiling = value;
1424
+ this._scheduleUpdate();
1425
+ }
1426
+ }
1427
+
1428
+ /**
1429
+ * Gets the 2D tiling of the height map.
1430
+ * @returns The tiling.
1431
+ */
1432
+ get heightMapTiling() {
1433
+ return this._heightMapTiling;
1434
+ }
1435
+
1436
+ /**
1437
+ * Sets the UV channel the height map samples.
1438
+ * @param value - The UV channel.
1439
+ */
1440
+ set heightMapUv(value: number) {
1441
+ this._heightMapUv = value;
1442
+ if (this.material) {
1443
+ this.material.heightMapUv = value;
1444
+ this._scheduleUpdate();
1445
+ }
1446
+ }
1447
+
1448
+ /**
1449
+ * Gets the UV channel the height map samples.
1450
+ * @returns The UV channel.
1451
+ */
1452
+ get heightMapUv() {
1453
+ return this._heightMapUv;
1454
+ }
1455
+
1456
+ /**
1457
+ * Sets how metallic the surface is, from 0 (dielectric) to 1 (metal).
1458
+ * @param value - The metalness.
1459
+ */
1460
+ set metalness(value: number) {
1461
+ this._metalness = value;
1462
+ if (this.material) {
1463
+ this.material.metalness = value;
1464
+ this._scheduleUpdate();
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * Gets how metallic the surface is.
1470
+ * @returns The metalness.
1471
+ */
1472
+ get metalness() {
1473
+ return this._metalness;
1474
+ }
1475
+
1476
+ /**
1477
+ * Sets the id of the `pc-asset` to use as the metalness map.
1478
+ * @param value - The asset id.
1479
+ */
1480
+ set metalnessMap(value: string) {
1481
+ this._metalnessMap = value;
1482
+ this.setMap(value, 'metalnessMap');
1483
+ }
1484
+
1485
+ /**
1486
+ * Gets the id of the `pc-asset` used as the metalness map.
1487
+ * @returns The asset id.
1488
+ */
1489
+ get metalnessMap() {
1490
+ return this._metalnessMap;
1491
+ }
1492
+
1493
+ /**
1494
+ * Sets the color channel of the metalness map to sample.
1495
+ * @param value - The channel.
1496
+ */
1497
+ set metalnessMapChannel(value: ScalarChannel) {
1498
+ this._metalnessMapChannel = value;
1499
+ if (this.material) {
1500
+ this.material.metalnessMapChannel = value;
1501
+ this._scheduleUpdate();
1502
+ }
1503
+ }
1504
+
1505
+ /**
1506
+ * Gets the color channel of the metalness map to sample.
1507
+ * @returns The channel.
1508
+ */
1509
+ get metalnessMapChannel(): ScalarChannel {
1510
+ return this._metalnessMapChannel;
1511
+ }
1512
+
1513
+ /**
1514
+ * Sets the 2D offset of the metalness map.
1515
+ * @param value - The offset.
1516
+ */
1517
+ set metalnessMapOffset(value: Vec2) {
1518
+ this._metalnessMapOffset = value;
1519
+ if (this.material) {
1520
+ this.material.metalnessMapOffset = value;
1521
+ this._scheduleUpdate();
1522
+ }
1523
+ }
1524
+
1525
+ /**
1526
+ * Gets the 2D offset of the metalness map.
1527
+ * @returns The offset.
1528
+ */
1529
+ get metalnessMapOffset() {
1530
+ return this._metalnessMapOffset;
1531
+ }
1532
+
1533
+ /**
1534
+ * Sets the 2D rotation of the metalness map, in degrees.
1535
+ * @param value - The rotation.
1536
+ */
1537
+ set metalnessMapRotation(value: number) {
1538
+ this._metalnessMapRotation = value;
1539
+ if (this.material) {
1540
+ this.material.metalnessMapRotation = value;
1541
+ this._scheduleUpdate();
1542
+ }
1543
+ }
1544
+
1545
+ /**
1546
+ * Gets the 2D rotation of the metalness map.
1547
+ * @returns The rotation.
1548
+ */
1549
+ get metalnessMapRotation() {
1550
+ return this._metalnessMapRotation;
1551
+ }
1552
+
1553
+ /**
1554
+ * Sets the 2D tiling of the metalness map.
1555
+ * @param value - The tiling.
1556
+ */
1557
+ set metalnessMapTiling(value: Vec2) {
1558
+ this._metalnessMapTiling = value;
1559
+ if (this.material) {
1560
+ this.material.metalnessMapTiling = value;
1561
+ this._scheduleUpdate();
1562
+ }
1563
+ }
1564
+
1565
+ /**
1566
+ * Gets the 2D tiling of the metalness map.
1567
+ * @returns The tiling.
1568
+ */
1569
+ get metalnessMapTiling() {
1570
+ return this._metalnessMapTiling;
1571
+ }
1572
+
1573
+ /**
1574
+ * Sets the UV channel the metalness map samples.
1575
+ * @param value - The UV channel.
1576
+ */
1577
+ set metalnessMapUv(value: number) {
1578
+ this._metalnessMapUv = value;
1579
+ if (this.material) {
1580
+ this.material.metalnessMapUv = value;
1581
+ this._scheduleUpdate();
1582
+ }
1583
+ }
1584
+
1585
+ /**
1586
+ * Gets the UV channel the metalness map samples.
1587
+ * @returns The UV channel.
1588
+ */
1589
+ get metalnessMapUv() {
1590
+ return this._metalnessMapUv;
1591
+ }
1592
+
1593
+ /**
1594
+ * Sets the id of the `pc-asset` to use as the normal map.
1595
+ * @param value - The asset id.
1596
+ */
1597
+ set normalMap(value: string) {
1598
+ this._normalMap = value;
1599
+ this.setMap(value, 'normalMap');
1600
+ }
1601
+
1602
+ /**
1603
+ * Gets the id of the `pc-asset` used as the normal map.
1604
+ * @returns The asset id.
1605
+ */
1606
+ get normalMap() {
1607
+ return this._normalMap;
1608
+ }
1609
+
1610
+ /**
1611
+ * Sets the 2D offset of the normal map.
1612
+ * @param value - The offset.
1613
+ */
1614
+ set normalMapOffset(value: Vec2) {
1615
+ this._normalMapOffset = value;
1616
+ if (this.material) {
1617
+ this.material.normalMapOffset = value;
1618
+ this._scheduleUpdate();
1619
+ }
1620
+ }
1621
+
1622
+ /**
1623
+ * Gets the 2D offset of the normal map.
1624
+ * @returns The offset.
1625
+ */
1626
+ get normalMapOffset() {
1627
+ return this._normalMapOffset;
1628
+ }
1629
+
1630
+ /**
1631
+ * Sets the 2D rotation of the normal map, in degrees.
1632
+ * @param value - The rotation.
1633
+ */
1634
+ set normalMapRotation(value: number) {
1635
+ this._normalMapRotation = value;
1636
+ if (this.material) {
1637
+ this.material.normalMapRotation = value;
1638
+ this._scheduleUpdate();
1639
+ }
1640
+ }
1641
+
1642
+ /**
1643
+ * Gets the 2D rotation of the normal map.
1644
+ * @returns The rotation.
1645
+ */
1646
+ get normalMapRotation() {
1647
+ return this._normalMapRotation;
1648
+ }
1649
+
1650
+ /**
1651
+ * Sets the 2D tiling of the normal map.
1652
+ * @param value - The tiling.
1653
+ */
1654
+ set normalMapTiling(value: Vec2) {
1655
+ this._normalMapTiling = value;
1656
+ if (this.material) {
1657
+ this.material.normalMapTiling = value;
1658
+ this._scheduleUpdate();
1659
+ }
1660
+ }
1661
+
1662
+ /**
1663
+ * Gets the 2D tiling of the normal map.
1664
+ * @returns The tiling.
1665
+ */
1666
+ get normalMapTiling() {
1667
+ return this._normalMapTiling;
1668
+ }
1669
+
1670
+ /**
1671
+ * Sets the UV channel the normal map samples.
1672
+ * @param value - The UV channel.
1673
+ */
1674
+ set normalMapUv(value: number) {
1675
+ this._normalMapUv = value;
1676
+ if (this.material) {
1677
+ this.material.normalMapUv = value;
1678
+ this._scheduleUpdate();
1679
+ }
1680
+ }
1681
+
1682
+ /**
1683
+ * Gets the UV channel the normal map samples.
1684
+ * @returns The UV channel.
1685
+ */
1686
+ get normalMapUv() {
1687
+ return this._normalMapUv;
1688
+ }
1689
+
1690
+ /**
1691
+ * Sets whether ambient occlusion also attenuates direct lighting.
1692
+ * @param value - The occlude direct flag.
1693
+ */
1694
+ set occludeDirect(value: boolean) {
1695
+ this._occludeDirect = value;
1696
+ if (this.material) {
1697
+ // @ts-ignore see createMaterial() - the engine mistypes occludeDirect as a number
1698
+ this.material.occludeDirect = value;
1699
+ this._scheduleUpdate();
1700
+ }
1701
+ }
1702
+
1703
+ /**
1704
+ * Gets whether ambient occlusion also attenuates direct lighting.
1705
+ * @returns The occlude direct flag.
1706
+ */
1707
+ get occludeDirect() {
1708
+ return this._occludeDirect;
1709
+ }
1710
+
1711
+ /**
1712
+ * Sets how specular reflections are occluded.
1713
+ * @param value - The specular occlusion mode.
1714
+ */
1715
+ set occludeSpecular(value: OccludeSpecular) {
1716
+ this._occludeSpecular = value;
1717
+ if (this.material) {
1718
+ this.material.occludeSpecular = occludeSpeculars.get(value) ?? SPECOCC_AO;
1719
+ this._scheduleUpdate();
1720
+ }
1721
+ }
1722
+
1723
+ /**
1724
+ * Gets how specular reflections are occluded.
1725
+ * @returns The specular occlusion mode.
1726
+ */
1727
+ get occludeSpecular(): OccludeSpecular {
1728
+ return this._occludeSpecular;
1729
+ }
1730
+
1731
+ /**
1732
+ * Sets the opacity of the material, from 0 (transparent) to 1 (opaque), which requires a
1733
+ * `blend-type` other than `none` to have any visible effect.
1734
+ * @param value - The opacity.
1735
+ */
1736
+ set opacity(value: number) {
1737
+ this._opacity = value;
1738
+ if (this.material) {
1739
+ this.material.opacity = value;
1740
+ this._scheduleUpdate();
1741
+ }
1742
+ }
1743
+
1744
+ /**
1745
+ * Gets the opacity of the material, which requires a `blend-type` other than `none` to have
1746
+ * any visible effect.
1747
+ * @returns The opacity.
1748
+ */
1749
+ get opacity() {
1750
+ return this._opacity;
1751
+ }
1752
+
1753
+ /**
1754
+ * Sets the dithering used to render opacity, which approximates transparency without blending.
1755
+ * @param value - The dither mode.
1756
+ */
1757
+ set opacityDither(value: OpacityDither) {
1758
+ this._opacityDither = value;
1759
+ if (this.material) {
1760
+ this.material.opacityDither = value;
1761
+ this._scheduleUpdate();
1762
+ }
1763
+ }
1764
+
1765
+ /**
1766
+ * Gets the dithering used to render opacity.
1767
+ * @returns The dither mode.
1768
+ */
1769
+ get opacityDither(): OpacityDither {
1770
+ return this._opacityDither;
1771
+ }
1772
+
1773
+ /**
1774
+ * Sets whether specular highlights fade out as the material becomes transparent.
1775
+ * @param value - The opacity fades specular flag.
1776
+ */
1777
+ set opacityFadesSpecular(value: boolean) {
1778
+ this._opacityFadesSpecular = value;
1779
+ if (this.material) {
1780
+ this.material.opacityFadesSpecular = value;
1781
+ this._scheduleUpdate();
1782
+ }
1783
+ }
1784
+
1785
+ /**
1786
+ * Gets whether specular highlights fade out as the material becomes transparent.
1787
+ * @returns The opacity fades specular flag.
1788
+ */
1789
+ get opacityFadesSpecular() {
1790
+ return this._opacityFadesSpecular;
1791
+ }
1792
+
1793
+ /**
1794
+ * Sets the id of the `pc-asset` to use as the opacity map.
1795
+ * @param value - The asset id.
1796
+ */
1797
+ set opacityMap(value: string) {
1798
+ this._opacityMap = value;
1799
+ this.setMap(value, 'opacityMap');
1800
+ }
1801
+
1802
+ /**
1803
+ * Gets the id of the `pc-asset` used as the opacity map.
1804
+ * @returns The asset id.
1805
+ */
1806
+ get opacityMap() {
1807
+ return this._opacityMap;
1808
+ }
1809
+
1810
+ /**
1811
+ * Sets the color channel of the opacity map to sample.
1812
+ * @param value - The channel.
1813
+ */
1814
+ set opacityMapChannel(value: ScalarChannel) {
1815
+ this._opacityMapChannel = value;
1816
+ if (this.material) {
1817
+ this.material.opacityMapChannel = value;
1818
+ this._scheduleUpdate();
1819
+ }
1820
+ }
1821
+
1822
+ /**
1823
+ * Gets the color channel of the opacity map to sample.
1824
+ * @returns The channel.
1825
+ */
1826
+ get opacityMapChannel(): ScalarChannel {
1827
+ return this._opacityMapChannel;
1828
+ }
1829
+
1830
+ /**
1831
+ * Sets the 2D offset of the opacity map.
1832
+ * @param value - The offset.
1833
+ */
1834
+ set opacityMapOffset(value: Vec2) {
1835
+ this._opacityMapOffset = value;
1836
+ if (this.material) {
1837
+ this.material.opacityMapOffset = value;
1838
+ this._scheduleUpdate();
1839
+ }
1840
+ }
1841
+
1842
+ /**
1843
+ * Gets the 2D offset of the opacity map.
1844
+ * @returns The offset.
1845
+ */
1846
+ get opacityMapOffset() {
1847
+ return this._opacityMapOffset;
1848
+ }
1849
+
1850
+ /**
1851
+ * Sets the 2D rotation of the opacity map, in degrees.
1852
+ * @param value - The rotation.
1853
+ */
1854
+ set opacityMapRotation(value: number) {
1855
+ this._opacityMapRotation = value;
1856
+ if (this.material) {
1857
+ this.material.opacityMapRotation = value;
1858
+ this._scheduleUpdate();
1859
+ }
1860
+ }
1861
+
1862
+ /**
1863
+ * Gets the 2D rotation of the opacity map.
1864
+ * @returns The rotation.
1865
+ */
1866
+ get opacityMapRotation() {
1867
+ return this._opacityMapRotation;
1868
+ }
1869
+
1870
+ /**
1871
+ * Sets the 2D tiling of the opacity map.
1872
+ * @param value - The tiling.
1873
+ */
1874
+ set opacityMapTiling(value: Vec2) {
1875
+ this._opacityMapTiling = value;
1876
+ if (this.material) {
1877
+ this.material.opacityMapTiling = value;
1878
+ this._scheduleUpdate();
1879
+ }
1880
+ }
1881
+
1882
+ /**
1883
+ * Gets the 2D tiling of the opacity map.
1884
+ * @returns The tiling.
1885
+ */
1886
+ get opacityMapTiling() {
1887
+ return this._opacityMapTiling;
1888
+ }
1889
+
1890
+ /**
1891
+ * Sets the UV channel the opacity map samples.
1892
+ * @param value - The UV channel.
1893
+ */
1894
+ set opacityMapUv(value: number) {
1895
+ this._opacityMapUv = value;
1896
+ if (this.material) {
1897
+ this.material.opacityMapUv = value;
1898
+ this._scheduleUpdate();
1899
+ }
1900
+ }
1901
+
1902
+ /**
1903
+ * Gets the UV channel the opacity map samples.
1904
+ * @returns The UV channel.
1905
+ */
1906
+ get opacityMapUv() {
1907
+ return this._opacityMapUv;
1908
+ }
1909
+
1910
+ /**
1911
+ * Sets the roughness of the material, from 0 (shiny) to 1 (rough). This is an alias for `gloss`
1912
+ * that also inverts the gloss channel, so do not combine it with the `gloss` attributes.
1913
+ * @param value - The roughness.
1914
+ */
1915
+ set roughness(value: number) {
1916
+ this.gloss = value;
1917
+ this.glossInvert = true;
1918
+ }
1919
+
1920
+ /**
1921
+ * Gets the roughness of the material.
1922
+ * @returns The roughness.
1923
+ */
1924
+ get roughness() {
1925
+ return this._gloss;
1926
+ }
1927
+
1928
+ /**
1929
+ * Sets the id of the `pc-asset` to use as the roughness map. This is an alias for `glossMap`
1930
+ * that also inverts the gloss channel, so do not combine it with the `gloss` attributes.
1931
+ * @param value - The asset id.
1932
+ */
1933
+ set roughnessMap(value: string) {
1934
+ this.glossMap = value;
1935
+ this.glossInvert = true;
1936
+ }
1937
+
1938
+ /**
1939
+ * Gets the id of the `pc-asset` used as the roughness map.
1940
+ * @returns The asset id.
1941
+ */
1942
+ get roughnessMap() {
1943
+ return this._glossMap;
1944
+ }
1945
+
1946
+ /**
1947
+ * Sets the depth offset applied in proportion to a surface's slope, used to resolve z-fighting.
1948
+ * @param value - The slope depth bias.
1949
+ */
1950
+ set slopeDepthBias(value: number) {
1951
+ this._slopeDepthBias = value;
1952
+ if (this.material) {
1953
+ this.material.slopeDepthBias = value;
1954
+ this._scheduleUpdate();
1955
+ }
1956
+ }
1957
+
1958
+ /**
1959
+ * Gets the depth offset applied in proportion to a surface's slope.
1960
+ * @returns The slope depth bias.
1961
+ */
1962
+ get slopeDepthBias() {
1963
+ return this._slopeDepthBias;
1964
+ }
1965
+
1966
+ /**
1967
+ * Sets the specular color of the material, which applies only when the metalness workflow is
1968
+ * disabled or `use-metalness-specular-color` is enabled.
1969
+ * @param value - The specular color.
1970
+ */
1971
+ set specular(value: Color) {
1972
+ this._specular = value;
1973
+ if (this.material) {
1974
+ this.material.specular = value;
1975
+ this._scheduleUpdate();
1976
+ }
1977
+ }
1978
+
1979
+ /**
1980
+ * Gets the specular color of the material, which applies only when the metalness workflow is
1981
+ * disabled or `use-metalness-specular-color` is enabled.
1982
+ * @returns The specular color.
1983
+ */
1984
+ get specular(): Color {
1985
+ return this._specular;
1986
+ }
1987
+
1988
+ /**
1989
+ * Sets the strength of specular reflections at direct angles, from 0 to 1, which applies only
1990
+ * when `use-metalness-specular-color` is enabled.
1991
+ * @param value - The specularity factor.
1992
+ */
1993
+ set specularityFactor(value: number) {
1994
+ this._specularityFactor = value;
1995
+ if (this.material) {
1996
+ this.material.specularityFactor = value;
1997
+ this._scheduleUpdate();
1998
+ }
1999
+ }
2000
+
2001
+ /**
2002
+ * Gets the strength of specular reflections at direct angles, which applies only when
2003
+ * `use-metalness-specular-color` is enabled.
2004
+ * @returns The specularity factor.
2005
+ */
2006
+ get specularityFactor() {
2007
+ return this._specularityFactor;
2008
+ }
2009
+
2010
+ /**
2011
+ * Sets whether back faces are lit as though their normals were flipped.
2012
+ * @param value - The two sided lighting flag.
2013
+ */
2014
+ set twoSidedLighting(value: boolean) {
2015
+ this._twoSidedLighting = value;
2016
+ if (this.material) {
2017
+ this.material.twoSidedLighting = value;
2018
+ this._scheduleUpdate();
2019
+ }
2020
+ }
2021
+
2022
+ /**
2023
+ * Gets whether back faces are lit as though their normals were flipped.
2024
+ * @returns The two sided lighting flag.
2025
+ */
2026
+ get twoSidedLighting() {
2027
+ return this._twoSidedLighting;
2028
+ }
2029
+
2030
+ /**
2031
+ * Sets whether the material is affected by scene fog.
2032
+ * @param value - The use fog flag.
2033
+ */
2034
+ set useFog(value: boolean) {
2035
+ this._useFog = value;
2036
+ if (this.material) {
2037
+ this.material.useFog = value;
2038
+ this._scheduleUpdate();
2039
+ }
2040
+ }
2041
+
2042
+ /**
2043
+ * Gets whether the material is affected by scene fog.
2044
+ * @returns The use fog flag.
2045
+ */
2046
+ get useFog() {
2047
+ return this._useFog;
2048
+ }
2049
+
2050
+ /**
2051
+ * Sets whether the material is affected by scene lights. When disabled the material renders
2052
+ * unlit, using the diffuse color and map alone.
2053
+ * @param value - The use lighting flag.
2054
+ */
2055
+ set useLighting(value: boolean) {
2056
+ this._useLighting = value;
2057
+ if (this.material) {
2058
+ this.material.useLighting = value;
2059
+ this._scheduleUpdate();
2060
+ }
2061
+ }
2062
+
2063
+ /**
2064
+ * Gets whether the material is affected by scene lights.
2065
+ * @returns The use lighting flag.
2066
+ */
2067
+ get useLighting() {
2068
+ return this._useLighting;
2069
+ }
2070
+
2071
+ /**
2072
+ * Sets whether to use the metalness workflow rather than the older specular workflow. Unlike a
2073
+ * bare `StandardMaterial` this defaults to `true`, because the `metalness-*` attributes have no
2074
+ * effect without it.
2075
+ * @param value - The use metalness flag.
2076
+ */
2077
+ set useMetalness(value: boolean) {
2078
+ this._useMetalness = value;
2079
+ if (this.material) {
2080
+ this.material.useMetalness = value;
2081
+ this._scheduleUpdate();
2082
+ }
2083
+ }
2084
+
2085
+ /**
2086
+ * Gets whether to use the metalness workflow.
2087
+ * @returns The use metalness flag.
2088
+ */
2089
+ get useMetalness() {
2090
+ return this._useMetalness;
2091
+ }
2092
+
2093
+ /**
2094
+ * Sets whether the specular color tints reflections while the metalness workflow is in use.
2095
+ * @param value - The use metalness specular color flag.
2096
+ */
2097
+ set useMetalnessSpecularColor(value: boolean) {
2098
+ this._useMetalnessSpecularColor = value;
2099
+ if (this.material) {
2100
+ this.material.useMetalnessSpecularColor = value;
2101
+ this._scheduleUpdate();
2102
+ }
2103
+ }
2104
+
2105
+ /**
2106
+ * Gets whether the specular color tints reflections while the metalness workflow is in use.
2107
+ * @returns The use metalness specular color flag.
2108
+ */
2109
+ get useMetalnessSpecularColor() {
2110
+ return this._useMetalnessSpecularColor;
2111
+ }
2112
+
2113
+ /**
2114
+ * Sets whether the material is lit by the scene's skybox.
2115
+ * @param value - The use skybox flag.
2116
+ */
2117
+ set useSkybox(value: boolean) {
2118
+ this._useSkybox = value;
2119
+ if (this.material) {
2120
+ this.material.useSkybox = value;
2121
+ this._scheduleUpdate();
2122
+ }
2123
+ }
2124
+
2125
+ /**
2126
+ * Gets whether the material is lit by the scene's skybox.
2127
+ * @returns The use skybox flag.
2128
+ */
2129
+ get useSkybox() {
2130
+ return this._useSkybox;
2131
+ }
2132
+
2133
+ /**
2134
+ * Sets whether the camera's tone mapping is applied to the material.
2135
+ * @param value - The use tonemap flag.
2136
+ */
2137
+ set useTonemap(value: boolean) {
2138
+ this._useTonemap = value;
2139
+ if (this.material) {
2140
+ this.material.useTonemap = value;
2141
+ this._scheduleUpdate();
2142
+ }
2143
+ }
2144
+
2145
+ /**
2146
+ * Gets whether the camera's tone mapping is applied to the material.
2147
+ * @returns The use tonemap flag.
2148
+ */
2149
+ get useTonemap() {
2150
+ return this._useTonemap;
2151
+ }
2152
+
2153
+ static get(id: string) {
2154
+ const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
2155
+ return materialElement?.material;
2156
+ }
2157
+
2158
+ static get observedAttributes() {
2159
+ return [
2160
+ 'alpha-test',
2161
+ 'alpha-to-coverage',
2162
+ 'ao-intensity',
2163
+ 'ao-map',
2164
+ 'ao-map-channel',
2165
+ 'ao-map-offset',
2166
+ 'ao-map-rotation',
2167
+ 'ao-map-tiling',
2168
+ 'ao-map-uv',
2169
+ 'blend-type',
2170
+ 'bumpiness',
2171
+ 'cull',
2172
+ 'depth-bias',
2173
+ 'depth-test',
2174
+ 'depth-write',
2175
+ 'diffuse',
2176
+ 'diffuse-map',
2177
+ 'diffuse-map-channel',
2178
+ 'diffuse-map-offset',
2179
+ 'diffuse-map-rotation',
2180
+ 'diffuse-map-tiling',
2181
+ 'diffuse-map-uv',
2182
+ 'emissive',
2183
+ 'emissive-intensity',
2184
+ 'emissive-map',
2185
+ 'emissive-map-channel',
2186
+ 'emissive-map-offset',
2187
+ 'emissive-map-rotation',
2188
+ 'emissive-map-tiling',
2189
+ 'emissive-map-uv',
2190
+ 'enable-ggx-specular',
2191
+ 'fresnel-model',
2192
+ 'gloss',
2193
+ 'gloss-invert',
2194
+ 'gloss-map',
2195
+ 'gloss-map-channel',
2196
+ 'gloss-map-offset',
2197
+ 'gloss-map-rotation',
2198
+ 'gloss-map-tiling',
2199
+ 'gloss-map-uv',
2200
+ 'height-map',
2201
+ 'height-map-channel',
2202
+ 'height-map-factor',
2203
+ 'height-map-offset',
2204
+ 'height-map-rotation',
2205
+ 'height-map-tiling',
2206
+ 'height-map-uv',
2207
+ 'metalness',
2208
+ 'metalness-map',
2209
+ 'metalness-map-channel',
2210
+ 'metalness-map-offset',
2211
+ 'metalness-map-rotation',
2212
+ 'metalness-map-tiling',
2213
+ 'metalness-map-uv',
2214
+ 'normal-map',
2215
+ 'normal-map-offset',
2216
+ 'normal-map-rotation',
2217
+ 'normal-map-tiling',
2218
+ 'normal-map-uv',
2219
+ 'occlude-direct',
2220
+ 'occlude-specular',
2221
+ 'opacity',
2222
+ 'opacity-dither',
2223
+ 'opacity-fades-specular',
2224
+ 'opacity-map',
2225
+ 'opacity-map-channel',
2226
+ 'opacity-map-offset',
2227
+ 'opacity-map-rotation',
2228
+ 'opacity-map-tiling',
2229
+ 'opacity-map-uv',
2230
+ 'roughness',
2231
+ 'roughness-map',
2232
+ 'slope-depth-bias',
2233
+ 'specular',
2234
+ 'specularity-factor',
2235
+ 'two-sided-lighting',
2236
+ 'use-fog',
2237
+ 'use-lighting',
2238
+ 'use-metalness',
2239
+ 'use-metalness-specular-color',
2240
+ 'use-skybox',
2241
+ 'use-tonemap'
2242
+ ];
2243
+ }
2244
+
2245
+ // newValue is null when an attribute is removed, which several branches below rely on. The
2246
+ // other elements still declare it as `string`; widening those surfaces 21 real removal bugs of
2247
+ // the #309 shape, which is its own change rather than a signature tweak.
2248
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
2249
+ switch (name) {
2250
+ case 'alpha-test':
2251
+ this.alphaTest = parseNumber(newValue, 0, name);
2252
+ break;
2253
+ case 'alpha-to-coverage':
2254
+ this.alphaToCoverage = parseBool(newValue, false);
2255
+ break;
2256
+ case 'ao-intensity':
2257
+ this.aoIntensity = parseNumber(newValue, 1, name);
2258
+ break;
2259
+ case 'ao-map':
2260
+ this.aoMap = newValue ?? '';
2261
+ break;
2262
+ case 'ao-map-channel':
2263
+ this.aoMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
2264
+ break;
2265
+ case 'ao-map-offset':
2266
+ this.aoMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2267
+ break;
2268
+ case 'ao-map-rotation':
2269
+ this.aoMapRotation = parseNumber(newValue, 0, name);
2270
+ break;
2271
+ case 'ao-map-tiling':
2272
+ this.aoMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2273
+ break;
2274
+ case 'ao-map-uv':
2275
+ this.aoMapUv = parseNumber(newValue, 0, name);
2276
+ break;
2277
+ case 'blend-type':
2278
+ this.blendType = parseEnum(newValue, blendTypes, 'none', name);
2279
+ break;
2280
+ case 'bumpiness':
2281
+ this.bumpiness = parseNumber(newValue, 1, name);
2282
+ break;
2283
+ case 'cull':
2284
+ this.cull = parseEnum(newValue, cullModes, 'back', name);
2285
+ break;
2286
+ case 'depth-bias':
2287
+ this.depthBias = parseNumber(newValue, 0, name);
2288
+ break;
2289
+ case 'depth-test':
2290
+ this.depthTest = parseBool(newValue, true);
2291
+ break;
2292
+ case 'depth-write':
2293
+ this.depthWrite = parseBool(newValue, true);
2294
+ break;
2295
+ case 'diffuse':
2296
+ this.diffuse = parseColor(newValue, new Color(1, 1, 1), name);
2297
+ break;
2298
+ case 'diffuse-map':
2299
+ this.diffuseMap = newValue ?? '';
2300
+ break;
2301
+ case 'diffuse-map-channel':
2302
+ this.diffuseMapChannel = parseEnum(newValue, colorChannels, 'rgb', name);
2303
+ break;
2304
+ case 'diffuse-map-offset':
2305
+ this.diffuseMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2306
+ break;
2307
+ case 'diffuse-map-rotation':
2308
+ this.diffuseMapRotation = parseNumber(newValue, 0, name);
2309
+ break;
2310
+ case 'diffuse-map-tiling':
2311
+ this.diffuseMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2312
+ break;
2313
+ case 'diffuse-map-uv':
2314
+ this.diffuseMapUv = parseNumber(newValue, 0, name);
2315
+ break;
2316
+ case 'emissive':
2317
+ this.emissive = parseColor(newValue, new Color(0, 0, 0), name);
2318
+ break;
2319
+ case 'emissive-intensity':
2320
+ this.emissiveIntensity = parseNumber(newValue, 1, name);
2321
+ break;
2322
+ case 'emissive-map':
2323
+ this.emissiveMap = newValue ?? '';
2324
+ break;
2325
+ case 'emissive-map-channel':
2326
+ this.emissiveMapChannel = parseEnum(newValue, colorChannels, 'rgb', name);
2327
+ break;
2328
+ case 'emissive-map-offset':
2329
+ this.emissiveMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2330
+ break;
2331
+ case 'emissive-map-rotation':
2332
+ this.emissiveMapRotation = parseNumber(newValue, 0, name);
2333
+ break;
2334
+ case 'emissive-map-tiling':
2335
+ this.emissiveMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2336
+ break;
2337
+ case 'emissive-map-uv':
2338
+ this.emissiveMapUv = parseNumber(newValue, 0, name);
2339
+ break;
2340
+ case 'enable-ggx-specular':
2341
+ this.enableGGXSpecular = parseBool(newValue, false);
2342
+ break;
2343
+ case 'fresnel-model':
2344
+ this.fresnelModel = parseEnum(newValue, fresnelModels, 'schlick', name);
2345
+ break;
2346
+ case 'gloss':
2347
+ this.gloss = parseNumber(newValue, 0.25, name);
2348
+ this._warnGlossConflict();
2349
+ break;
2350
+ case 'gloss-invert':
2351
+ this.glossInvert = parseBool(newValue, false);
2352
+ this._warnGlossConflict();
2353
+ break;
2354
+ case 'gloss-map':
2355
+ this.glossMap = newValue ?? '';
2356
+ this._warnGlossConflict();
2357
+ break;
2358
+ case 'gloss-map-channel':
2359
+ this.glossMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
2360
+ break;
2361
+ case 'gloss-map-offset':
2362
+ this.glossMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2363
+ break;
2364
+ case 'gloss-map-rotation':
2365
+ this.glossMapRotation = parseNumber(newValue, 0, name);
2366
+ break;
2367
+ case 'gloss-map-tiling':
2368
+ this.glossMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2369
+ break;
2370
+ case 'gloss-map-uv':
2371
+ this.glossMapUv = parseNumber(newValue, 0, name);
2372
+ break;
2373
+ case 'height-map':
2374
+ this.heightMap = newValue ?? '';
2375
+ break;
2376
+ case 'height-map-channel':
2377
+ this.heightMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
2378
+ break;
2379
+ case 'height-map-factor':
2380
+ this.heightMapFactor = parseNumber(newValue, 1, name);
2381
+ break;
2382
+ case 'height-map-offset':
2383
+ this.heightMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2384
+ break;
2385
+ case 'height-map-rotation':
2386
+ this.heightMapRotation = parseNumber(newValue, 0, name);
2387
+ break;
2388
+ case 'height-map-tiling':
2389
+ this.heightMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2390
+ break;
2391
+ case 'height-map-uv':
2392
+ this.heightMapUv = parseNumber(newValue, 0, name);
2393
+ break;
2394
+ case 'metalness':
2395
+ this.metalness = parseNumber(newValue, 0, name);
2396
+ break;
2397
+ case 'metalness-map':
2398
+ this.metalnessMap = newValue ?? '';
2399
+ break;
2400
+ case 'metalness-map-channel':
2401
+ this.metalnessMapChannel = parseEnum(newValue, scalarChannels, 'g', name);
2402
+ break;
2403
+ case 'metalness-map-offset':
2404
+ this.metalnessMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2405
+ break;
2406
+ case 'metalness-map-rotation':
2407
+ this.metalnessMapRotation = parseNumber(newValue, 0, name);
2408
+ break;
2409
+ case 'metalness-map-tiling':
2410
+ this.metalnessMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2411
+ break;
2412
+ case 'metalness-map-uv':
2413
+ this.metalnessMapUv = parseNumber(newValue, 0, name);
2414
+ break;
2415
+ case 'normal-map':
2416
+ this.normalMap = newValue ?? '';
2417
+ break;
2418
+ case 'normal-map-offset':
2419
+ this.normalMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2420
+ break;
2421
+ case 'normal-map-rotation':
2422
+ this.normalMapRotation = parseNumber(newValue, 0, name);
2423
+ break;
2424
+ case 'normal-map-tiling':
2425
+ this.normalMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2426
+ break;
2427
+ case 'normal-map-uv':
2428
+ this.normalMapUv = parseNumber(newValue, 0, name);
2429
+ break;
2430
+ case 'occlude-direct':
2431
+ this.occludeDirect = parseBool(newValue, false);
2432
+ break;
2433
+ case 'occlude-specular':
2434
+ this.occludeSpecular = parseEnum(newValue, occludeSpeculars, 'ao', name);
2435
+ break;
2436
+ case 'opacity':
2437
+ this.opacity = parseNumber(newValue, 1, name);
2438
+ break;
2439
+ case 'opacity-dither':
2440
+ this.opacityDither = parseEnum(newValue, opacityDithers, 'none', name);
2441
+ break;
2442
+ case 'opacity-fades-specular':
2443
+ this.opacityFadesSpecular = parseBool(newValue, true);
2444
+ break;
2445
+ case 'opacity-map':
2446
+ this.opacityMap = newValue ?? '';
2447
+ break;
2448
+ case 'opacity-map-channel':
2449
+ this.opacityMapChannel = parseEnum(newValue, scalarChannels, 'a', name);
2450
+ break;
2451
+ case 'opacity-map-offset':
2452
+ this.opacityMapOffset = parseVec2(newValue, new Vec2(0, 0), name);
2453
+ break;
2454
+ case 'opacity-map-rotation':
2455
+ this.opacityMapRotation = parseNumber(newValue, 0, name);
2456
+ break;
2457
+ case 'opacity-map-tiling':
2458
+ this.opacityMapTiling = parseVec2(newValue, new Vec2(1, 1), name);
2459
+ break;
2460
+ case 'opacity-map-uv':
2461
+ this.opacityMapUv = parseNumber(newValue, 0, name);
2462
+ break;
2463
+ case 'roughness':
2464
+ // Aliases gloss, and inverts it so the value reads as roughness. Removing the
2465
+ // attribute restores the engine's uninverted interpretation.
2466
+ this.gloss = parseNumber(newValue, 0.25, name);
2467
+ this.glossInvert = newValue !== null;
2468
+ this._warnGlossConflict();
2469
+ break;
2470
+ case 'roughness-map':
2471
+ this.glossMap = newValue ?? '';
2472
+ this.glossInvert = newValue !== null;
2473
+ this._warnGlossConflict();
2474
+ break;
2475
+ case 'slope-depth-bias':
2476
+ this.slopeDepthBias = parseNumber(newValue, 0, name);
2477
+ break;
2478
+ case 'specular':
2479
+ this.specular = parseColor(newValue, new Color(0, 0, 0), name);
2480
+ break;
2481
+ case 'specularity-factor':
2482
+ this.specularityFactor = parseNumber(newValue, 1, name);
2483
+ break;
2484
+ case 'two-sided-lighting':
2485
+ this.twoSidedLighting = parseBool(newValue, false);
2486
+ break;
2487
+ case 'use-fog':
2488
+ this.useFog = parseBool(newValue, true);
2489
+ break;
2490
+ case 'use-lighting':
2491
+ this.useLighting = parseBool(newValue, true);
2492
+ break;
2493
+ case 'use-metalness':
2494
+ this.useMetalness = parseBool(newValue, true);
2495
+ break;
2496
+ case 'use-metalness-specular-color':
2497
+ this.useMetalnessSpecularColor = parseBool(newValue, false);
2498
+ break;
2499
+ case 'use-skybox':
2500
+ this.useSkybox = parseBool(newValue, true);
2501
+ break;
2502
+ case 'use-tonemap':
2503
+ this.useTonemap = parseBool(newValue, true);
161
2504
  break;
162
2505
  }
163
2506
  }