@playcanvas/web-components 0.1.9 → 0.1.10

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.
@@ -1,8 +1,18 @@
1
- import { PROJECTION_ORTHOGRAPHIC, PROJECTION_PERSPECTIVE, CameraComponent, Color, Vec4, XRTYPE_VR } from 'playcanvas';
1
+ import { CameraComponent, Color, Vec4, GAMMA_NONE, GAMMA_SRGB, PROJECTION_ORTHOGRAPHIC, PROJECTION_PERSPECTIVE, TONEMAP_LINEAR, TONEMAP_FILMIC, TONEMAP_NEUTRAL, TONEMAP_ACES2, TONEMAP_ACES, TONEMAP_HEJL, TONEMAP_NONE, XRTYPE_VR } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
4
  import { parseColor, parseVec4 } from '../utils';
5
5
 
6
+ const tonemaps = new Map([
7
+ ['none', TONEMAP_NONE],
8
+ ['linear', TONEMAP_LINEAR],
9
+ ['filmic', TONEMAP_FILMIC],
10
+ ['hejl', TONEMAP_HEJL],
11
+ ['aces', TONEMAP_ACES],
12
+ ['aces2', TONEMAP_ACES2],
13
+ ['neutral', TONEMAP_NEUTRAL]
14
+ ]);
15
+
6
16
  /**
7
17
  * The CameraComponentElement interface provides properties and methods for manipulating
8
18
  * `<pc-camera>` elements. The CameraComponentElement interface also inherits the properties and
@@ -29,6 +39,8 @@ class CameraComponentElement extends ComponentElement {
29
39
 
30
40
  private _frustumCulling = true;
31
41
 
42
+ private _gamma: 'none' | 'srgb' = 'srgb';
43
+
32
44
  private _nearClip = 0.1;
33
45
 
34
46
  private _orthographic = false;
@@ -41,6 +53,8 @@ class CameraComponentElement extends ComponentElement {
41
53
 
42
54
  private _scissorRect = new Vec4(0, 0, 1, 1);
43
55
 
56
+ private _tonemap: 'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2' | 'neutral' = 'none';
57
+
44
58
  /** @ignore */
45
59
  constructor() {
46
60
  super('camera');
@@ -57,12 +71,14 @@ class CameraComponentElement extends ComponentElement {
57
71
  flipFaces: this._flipFaces,
58
72
  fov: this._fov,
59
73
  frustumCulling: this._frustumCulling,
74
+ gammaCorrection: this._gamma === 'srgb' ? GAMMA_SRGB : GAMMA_NONE,
60
75
  nearClip: this._nearClip,
61
76
  orthographic: this._orthographic,
62
77
  orthoHeight: this._orthoHeight,
63
78
  priority: this._priority,
64
79
  rect: this._rect,
65
- scissorRect: this._scissorRect
80
+ scissorRect: this._scissorRect,
81
+ toneMapping: tonemaps.get(this._tonemap)
66
82
  };
67
83
  }
68
84
 
@@ -266,6 +282,25 @@ class CameraComponentElement extends ComponentElement {
266
282
  return this._frustumCulling;
267
283
  }
268
284
 
285
+ /**
286
+ * Sets the gamma correction of the camera.
287
+ * @param value - The gamma correction.
288
+ */
289
+ set gamma(value: 'none' | 'srgb') {
290
+ this._gamma = value;
291
+ if (this.component) {
292
+ this.component.gammaCorrection = value === 'srgb' ? GAMMA_SRGB : GAMMA_NONE;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Gets the gamma correction of the camera.
298
+ * @returns The gamma correction.
299
+ */
300
+ get gamma(): 'none' | 'srgb' {
301
+ return this._gamma;
302
+ }
303
+
269
304
  /**
270
305
  * Sets the near clip distance of the camera.
271
306
  * @param value - The near clip distance.
@@ -380,6 +415,25 @@ class CameraComponentElement extends ComponentElement {
380
415
  return this._scissorRect;
381
416
  }
382
417
 
418
+ /**
419
+ * Sets the tone mapping of the camera.
420
+ * @param value - The tone mapping.
421
+ */
422
+ set tonemap(value: 'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2' | 'neutral') {
423
+ this._tonemap = value;
424
+ if (this.component) {
425
+ this.component.toneMapping = tonemaps.get(value) ?? TONEMAP_NONE;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Gets the tone mapping of the camera.
431
+ * @returns The tone mapping.
432
+ */
433
+ get tonemap(): 'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2' | 'neutral' {
434
+ return this._tonemap;
435
+ }
436
+
383
437
  static get observedAttributes() {
384
438
  return [
385
439
  ...super.observedAttributes,
@@ -392,12 +446,14 @@ class CameraComponentElement extends ComponentElement {
392
446
  'flip-faces',
393
447
  'fov',
394
448
  'frustum-culling',
449
+ 'gamma',
395
450
  'near-clip',
396
451
  'orthographic',
397
452
  'ortho-height',
398
453
  'priority',
399
454
  'rect',
400
- 'scissor-rect'
455
+ 'scissor-rect',
456
+ 'tonemap'
401
457
  ];
402
458
  }
403
459
 
@@ -432,6 +488,9 @@ class CameraComponentElement extends ComponentElement {
432
488
  case 'frustum-culling':
433
489
  this.frustumCulling = newValue !== 'false';
434
490
  break;
491
+ case 'gamma':
492
+ this.gamma = newValue as 'none' | 'srgb';
493
+ break;
435
494
  case 'near-clip':
436
495
  this.nearClip = parseFloat(newValue);
437
496
  break;
@@ -450,6 +509,9 @@ class CameraComponentElement extends ComponentElement {
450
509
  case 'scissor-rect':
451
510
  this.scissorRect = parseVec4(newValue);
452
511
  break;
512
+ case 'tonemap':
513
+ this.tonemap = newValue as 'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2' | 'neutral';
514
+ break;
453
515
  }
454
516
  }
455
517
  }
@@ -1,8 +1,20 @@
1
- import { Color, LightComponent } from 'playcanvas';
1
+ import { Color, LightComponent, SHADOW_PCF1_16F, SHADOW_PCF1_32F, SHADOW_PCF3_16F, SHADOW_PCF3_32F, SHADOW_PCF5_16F, SHADOW_PCF5_32F, SHADOW_PCSS_32F, SHADOW_VSM_16F, SHADOW_VSM_32F } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
4
  import { parseColor } from '../utils';
5
5
 
6
+ const shadowTypes = new Map([
7
+ ['pcf1-16f', SHADOW_PCF1_16F],
8
+ ['pcf1-32f', SHADOW_PCF1_32F],
9
+ ['pcf3-16f', SHADOW_PCF3_16F],
10
+ ['pcf3-32f', SHADOW_PCF3_32F],
11
+ ['pcf5-16f', SHADOW_PCF5_16F],
12
+ ['pcf5-32f', SHADOW_PCF5_32F],
13
+ ['vsm-16f', SHADOW_VSM_16F],
14
+ ['vsm-32f', SHADOW_VSM_32F],
15
+ ['pcss-32f', SHADOW_PCSS_32F]
16
+ ]);
17
+
6
18
  /**
7
19
  * The LightComponentElement interface provides properties and methods for manipulating
8
20
  * `<pc-light>` elements. The LightComponentElement interface also inherits the properties and
@@ -29,10 +41,16 @@ class LightComponentElement extends ComponentElement {
29
41
 
30
42
  private _shadowDistance = 16;
31
43
 
44
+ private _shadowIntensity = 1;
45
+
32
46
  private _shadowResolution = 1024;
33
47
 
48
+ private _shadowType: 'pcf1-16f' | 'pcf1-32f' | 'pcf3-16f' | 'pcf3-32f' | 'pcf5-16f' | 'pcf5-32f' | 'vsm-16f' | 'vsm-32f' | 'pcss-32f' = 'pcf3-32f';
49
+
34
50
  private _type = 'directional';
35
51
 
52
+ private _vsmBias = 0.01;
53
+
36
54
  /** @ignore */
37
55
  constructor() {
38
56
  super('light');
@@ -49,8 +67,11 @@ class LightComponentElement extends ComponentElement {
49
67
  range: this._range,
50
68
  shadowBias: this._shadowBias,
51
69
  shadowDistance: this._shadowDistance,
70
+ shadowIntensity: this._shadowIntensity,
52
71
  shadowResolution: this._shadowResolution,
53
- type: this._type
72
+ shadowType: shadowTypes.get(this._shadowType),
73
+ type: this._type,
74
+ vsmBias: this._vsmBias
54
75
  };
55
76
  }
56
77
 
@@ -233,6 +254,25 @@ class LightComponentElement extends ComponentElement {
233
254
  return this._shadowDistance;
234
255
  }
235
256
 
257
+ /**
258
+ * Sets the shadow intensity of the light.
259
+ * @param value - The shadow intensity.
260
+ */
261
+ set shadowIntensity(value: number) {
262
+ this._shadowIntensity = value;
263
+ if (this.component) {
264
+ this.component.shadowIntensity = value;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Gets the shadow intensity of the light.
270
+ * @returns The shadow intensity.
271
+ */
272
+ get shadowIntensity() {
273
+ return this._shadowIntensity;
274
+ }
275
+
236
276
  /**
237
277
  * Sets the shadow resolution of the light.
238
278
  * @param value - The shadow resolution.
@@ -252,6 +292,35 @@ class LightComponentElement extends ComponentElement {
252
292
  return this._shadowResolution;
253
293
  }
254
294
 
295
+ /**
296
+ * Sets the shadow type of the light.
297
+ * @param value - The shadow type. Can be:
298
+ *
299
+ * - `pcf1-16f` - 1-tap percentage-closer filtered shadow map with 16-bit depth.
300
+ * - `pcf1-32f` - 1-tap percentage-closer filtered shadow map with 32-bit depth.
301
+ * - `pcf3-16f` - 3-tap percentage-closer filtered shadow map with 16-bit depth.
302
+ * - `pcf3-32f` - 3-tap percentage-closer filtered shadow map with 32-bit depth.
303
+ * - `pcf5-16f` - 5-tap percentage-closer filtered shadow map with 16-bit depth.
304
+ * - `pcf5-32f` - 5-tap percentage-closer filtered shadow map with 32-bit depth.
305
+ * - `vsm-16f` - Variance shadow map with 16-bit depth.
306
+ * - `vsm-32f` - Variance shadow map with 32-bit depth.
307
+ * - `pcss-32f` - Percentage-closer soft shadow with 32-bit depth.
308
+ */
309
+ set shadowType(value: 'pcf1-16f' | 'pcf1-32f' | 'pcf3-16f' | 'pcf3-32f' | 'pcf5-16f' | 'pcf5-32f' | 'vsm-16f' | 'vsm-32f' | 'pcss-32f') {
310
+ this._shadowType = value;
311
+ if (this.component) {
312
+ this.component.shadowType = shadowTypes.get(value) ?? SHADOW_PCF3_32F;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Gets the shadow type of the light.
318
+ * @returns The shadow type.
319
+ */
320
+ get shadowType() {
321
+ return this._shadowType;
322
+ }
323
+
255
324
  /**
256
325
  * Sets the type of the light.
257
326
  * @param value - The type.
@@ -276,6 +345,25 @@ class LightComponentElement extends ComponentElement {
276
345
  return this._type;
277
346
  }
278
347
 
348
+ /**
349
+ * Sets the VSM bias of the light.
350
+ * @param value - The VSM bias.
351
+ */
352
+ set vsmBias(value: number) {
353
+ this._vsmBias = value;
354
+ if (this.component) {
355
+ this.component.vsmBias = value;
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Gets the VSM bias of the light.
361
+ * @returns The VSM bias.
362
+ */
363
+ get vsmBias() {
364
+ return this._vsmBias;
365
+ }
366
+
279
367
  static get observedAttributes() {
280
368
  return [
281
369
  ...super.observedAttributes,
@@ -288,8 +376,11 @@ class LightComponentElement extends ComponentElement {
288
376
  'range',
289
377
  'shadow-bias',
290
378
  'shadow-distance',
379
+ 'shadow-intensity',
291
380
  'shadow-resolution',
292
- 'type'
381
+ 'shadow-type',
382
+ 'type',
383
+ 'vsm-bias'
293
384
  ];
294
385
  }
295
386
 
@@ -327,9 +418,18 @@ class LightComponentElement extends ComponentElement {
327
418
  case 'shadow-resolution':
328
419
  this.shadowResolution = Number(newValue);
329
420
  break;
421
+ case 'shadow-intensity':
422
+ this.shadowIntensity = Number(newValue);
423
+ break;
424
+ case 'shadow-type':
425
+ this.shadowType = newValue as 'pcf1-16f' | 'pcf1-32f' | 'pcf3-16f' | 'pcf3-32f' | 'pcf5-16f' | 'pcf5-32f' | 'vsm-16f' | 'vsm-32f' | 'pcss-32f';
426
+ break;
330
427
  case 'type':
331
428
  this.type = newValue;
332
429
  break;
430
+ case 'vsm-bias':
431
+ this.vsmBias = Number(newValue);
432
+ break;
333
433
  }
334
434
  }
335
435
  }
@@ -4,10 +4,6 @@ import { ComponentElement } from './component';
4
4
  import { ScriptElement } from './script';
5
5
  import { AssetElement } from '../asset';
6
6
 
7
- const tmpV2 = new Vec2();
8
- const tmpV3 = new Vec3();
9
- const tmpV4 = new Vec4();
10
-
11
7
  // Add these interfaces at the top of the file, after the imports
12
8
  interface ScriptAttributesChangeEvent extends CustomEvent {
13
9
  detail: { attributes: any };
@@ -76,23 +72,36 @@ class ScriptComponentElement extends ComponentElement {
76
72
  }
77
73
  }
78
74
 
79
- // Handle vectors
80
- if (Array.isArray(value)) {
81
- if (target[key] instanceof Vec2) {
82
- target[key] = tmpV2.set(value[0], value[1]);
75
+ // Handle arrays
76
+ if (value && typeof value === 'object' && Array.isArray(value)) {
77
+ // If it's an array of objects, recursively apply to each object
78
+ if (value.length > 0 && typeof value[0] === 'object') {
79
+ target[key] = value.map((item) => {
80
+ const obj = {};
81
+ for (const itemKey in item) {
82
+ applyValue(obj, itemKey, item[itemKey]);
83
+ }
84
+ return obj;
85
+ });
86
+ return;
87
+ }
88
+
89
+ // Handle vectors
90
+ if (value.length === 2 && typeof value[0] === 'number') {
91
+ target[key] = new Vec2(value[0], value[1]);
83
92
  return;
84
93
  }
85
- if (target[key] instanceof Vec3) {
86
- target[key] = tmpV3.set(value[0], value[1], value[2]);
94
+ if (value.length === 3 && typeof value[0] === 'number') {
95
+ target[key] = new Vec3(value[0], value[1], value[2]);
87
96
  return;
88
97
  }
89
- if (target[key] instanceof Vec4) {
90
- target[key] = tmpV4.set(value[0], value[1], value[2], value[3]);
98
+ if (value.length === 4 && typeof value[0] === 'number') {
99
+ target[key] = new Vec4(value[0], value[1], value[2], value[3]);
91
100
  return;
92
101
  }
93
102
  }
94
103
 
95
- // Handle nested objects
104
+ // Handle nested objects (non-array)
96
105
  if (value && typeof value === 'object' && !Array.isArray(value)) {
97
106
  if (!target[key] || typeof target[key] !== 'object') {
98
107
  target[key] = {};
package/src/entity.ts CHANGED
@@ -39,6 +39,11 @@ class EntityElement extends AsyncElement {
39
39
  */
40
40
  private _tags: string[] = [];
41
41
 
42
+ /**
43
+ * The pointer event listeners for the entity.
44
+ */
45
+ private _listeners: { [key: string]: EventListener[] } = {};
46
+
42
47
  /**
43
48
  * The PlayCanvas entity instance.
44
49
  */
@@ -72,6 +77,31 @@ class EntityElement extends AsyncElement {
72
77
  if (tags) {
73
78
  this.entity.tags.add(tags.split(',').map(tag => tag.trim()));
74
79
  }
80
+
81
+ // Handle pointer events
82
+ const pointerEvents = [
83
+ 'onpointerenter',
84
+ 'onpointerleave',
85
+ 'onpointerdown',
86
+ 'onpointerup',
87
+ 'onpointermove'
88
+ ];
89
+
90
+ pointerEvents.forEach((eventName) => {
91
+ const handler = this.getAttribute(eventName);
92
+ if (handler) {
93
+ const eventType = eventName.substring(2); // remove 'on' prefix
94
+ const eventHandler = (event: Event) => {
95
+ try {
96
+ /* eslint-disable-next-line no-new-func */
97
+ new Function('event', handler).call(this, event);
98
+ } catch (e) {
99
+ console.error('Error in event handler:', e);
100
+ }
101
+ };
102
+ this.addEventListener(eventType, eventHandler);
103
+ }
104
+ });
75
105
  }
76
106
 
77
107
  buildHierarchy(app: Application) {
@@ -240,7 +270,19 @@ class EntityElement extends AsyncElement {
240
270
  }
241
271
 
242
272
  static get observedAttributes() {
243
- return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags'];
273
+ return [
274
+ 'enabled',
275
+ 'name',
276
+ 'position',
277
+ 'rotation',
278
+ 'scale',
279
+ 'tags',
280
+ 'onpointerenter',
281
+ 'onpointerleave',
282
+ 'onpointerdown',
283
+ 'onpointerup',
284
+ 'onpointermove'
285
+ ];
244
286
  }
245
287
 
246
288
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -263,7 +305,52 @@ class EntityElement extends AsyncElement {
263
305
  case 'tags':
264
306
  this.tags = newValue.split(',').map(tag => tag.trim());
265
307
  break;
308
+ case 'onpointerenter':
309
+ case 'onpointerleave':
310
+ case 'onpointerdown':
311
+ case 'onpointerup':
312
+ case 'onpointermove':
313
+ if (newValue) {
314
+ const eventName = name.substring(2);
315
+ // Use Function.prototype.bind to avoid new Function
316
+ const handler = (event: Event) => {
317
+ try {
318
+ const handlerStr = this.getAttribute(eventName) || '';
319
+ /* eslint-disable-next-line no-new-func */
320
+ new Function('event', handlerStr).call(this, event);
321
+ } catch (e) {
322
+ console.error('Error in event handler:', e);
323
+ }
324
+ };
325
+ this.addEventListener(eventName, handler);
326
+ }
327
+ break;
328
+ }
329
+ }
330
+
331
+ addEventListener(type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) {
332
+ if (!this._listeners[type]) {
333
+ this._listeners[type] = [];
266
334
  }
335
+ this._listeners[type].push(listener);
336
+ super.addEventListener(type, listener, options);
337
+ if (type.startsWith('pointer')) {
338
+ this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
339
+ }
340
+ }
341
+
342
+ removeEventListener(type: string, listener: EventListener, options?: boolean | EventListenerOptions) {
343
+ if (this._listeners[type]) {
344
+ this._listeners[type] = this._listeners[type].filter(l => l !== listener);
345
+ }
346
+ super.removeEventListener(type, listener, options);
347
+ if (type.startsWith('pointer')) {
348
+ this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
349
+ }
350
+ }
351
+
352
+ hasListeners(type: string): boolean {
353
+ return Boolean(this._listeners[type]?.length);
267
354
  }
268
355
  }
269
356
 
package/src/fog.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { Color } from 'playcanvas';
2
+
3
+ import { parseColor } from './utils';
4
+
5
+ /**
6
+ * The FogElement interface provides properties and methods for manipulating
7
+ * `<pc-fog>` elements. The FogElement interface also inherits the properties and
8
+ * methods of the {@link HTMLElement} interface.
9
+ */
10
+ class FogElement extends HTMLElement {
11
+ private _color = new Color(0.5, 0.5, 0.5);
12
+
13
+ private _density = 0.001;
14
+
15
+ private _end = 100;
16
+
17
+ private _start = 0;
18
+
19
+ private _type: 'linear' | 'exp' | 'exp2' = 'linear';
20
+
21
+ private dispatchFogUpdate() {
22
+ const event = new CustomEvent('fogupdate', {
23
+ bubbles: true,
24
+ detail: {
25
+ color: this._color,
26
+ density: this._density,
27
+ end: this._end,
28
+ start: this._start,
29
+ type: this._type
30
+ }
31
+ });
32
+ this.dispatchEvent(event);
33
+ }
34
+
35
+ set color(value: Color) {
36
+ this._color = value;
37
+ this.dispatchFogUpdate();
38
+ }
39
+
40
+ get color(): Color {
41
+ return this._color;
42
+ }
43
+
44
+ set density(value: number) {
45
+ this._density = value;
46
+ this.dispatchFogUpdate();
47
+ }
48
+
49
+ get density(): number {
50
+ return this._density;
51
+ }
52
+
53
+ set end(value: number) {
54
+ this._end = value;
55
+ this.dispatchFogUpdate();
56
+ }
57
+
58
+ get end(): number {
59
+ return this._end;
60
+ }
61
+
62
+ set start(value: number) {
63
+ this._start = value;
64
+ this.dispatchFogUpdate();
65
+ }
66
+
67
+ get start(): number {
68
+ return this._start;
69
+ }
70
+
71
+ set type(value: 'linear' | 'exp' | 'exp2') {
72
+ this._type = value;
73
+ this.dispatchFogUpdate();
74
+ }
75
+
76
+ get type(): 'linear' | 'exp' | 'exp2' {
77
+ return this._type;
78
+ }
79
+
80
+ static get observedAttributes() {
81
+ return [
82
+ 'color',
83
+ 'density',
84
+ 'end',
85
+ 'start',
86
+ 'type'
87
+ ];
88
+ }
89
+
90
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
91
+ switch (name) {
92
+ case 'color':
93
+ this.color = parseColor(newValue);
94
+ break;
95
+ case 'density':
96
+ this.density = parseFloat(newValue);
97
+ break;
98
+ case 'end':
99
+ this.end = parseFloat(newValue);
100
+ break;
101
+ case 'start':
102
+ this.start = parseFloat(newValue);
103
+ break;
104
+ case 'type':
105
+ if (newValue === 'linear' || newValue === 'exp' || newValue === 'exp2') {
106
+ this.type = newValue;
107
+ }
108
+ break;
109
+ }
110
+ }
111
+
112
+ constructor() {
113
+ super();
114
+ // Dispatch initial fog state
115
+ this.dispatchFogUpdate();
116
+ }
117
+ }
118
+
119
+ customElements.define('pc-fog', FogElement);
120
+
121
+ export { FogElement };
package/src/material.ts CHANGED
@@ -23,8 +23,8 @@ class MaterialElement extends HTMLElement {
23
23
 
24
24
  createMaterial() {
25
25
  this.material = new StandardMaterial();
26
- this.material.glossInvert = true;
27
- this.material.useMetalness = true;
26
+ this.material.glossInvert = false;
27
+ this.material.useMetalness = false;
28
28
  this.material.diffuse = this._diffuse;
29
29
  this.diffuseMap = this._diffuseMap;
30
30
  this.metalnessMap = this._metalnessMap;