@playcanvas/web-components 0.1.6 → 0.1.8

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.
@@ -2,6 +2,7 @@ import { ScriptComponent, Script, Vec2, Vec3, Vec4 } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
4
  import { ScriptElement } from './script';
5
+ import { AssetElement } from '../asset';
5
6
 
6
7
  const tmpV2 = new Vec2();
7
8
  const tmpV3 = new Vec3();
@@ -59,24 +60,50 @@ class ScriptComponentElement extends ComponentElement {
59
60
 
60
61
  private applyAttributes(script: any, attributes: string | null) {
61
62
  try {
62
- // Parse the attributes string into an object and set them on the script
63
63
  const attributesObject = attributes ? JSON.parse(attributes) : {};
64
- for (const key in attributesObject) {
65
- const value = attributesObject[key];
66
- if (Array.isArray(value) && script[key] instanceof Vec2) {
67
- script[key] = tmpV2.set(value[0], value[1]);
68
- continue;
64
+
65
+ const applyValue = (target: any, key: string, value: any) => {
66
+ // Handle asset references
67
+ if (typeof value === 'string' && value.startsWith('asset:')) {
68
+ const assetId = value.slice(6);
69
+ const assetElement = document.querySelector(`pc-asset#${assetId}`) as AssetElement;
70
+ if (assetElement) {
71
+ target[key] = assetElement.asset;
72
+ return;
73
+ }
69
74
  }
70
- if (Array.isArray(value) && script[key] instanceof Vec3) {
71
- script[key] = tmpV3.set(value[0], value[1], value[2]);
72
- continue;
75
+
76
+ // Handle vectors
77
+ if (Array.isArray(value)) {
78
+ if (target[key] instanceof Vec2) {
79
+ target[key] = tmpV2.set(value[0], value[1]);
80
+ return;
81
+ }
82
+ if (target[key] instanceof Vec3) {
83
+ target[key] = tmpV3.set(value[0], value[1], value[2]);
84
+ return;
85
+ }
86
+ if (target[key] instanceof Vec4) {
87
+ target[key] = tmpV4.set(value[0], value[1], value[2], value[3]);
88
+ return;
89
+ }
73
90
  }
74
- if (Array.isArray(value) && script[key] instanceof Vec4) {
75
- script[key] = tmpV4.set(value[0], value[1], value[2], value[3]);
76
- continue;
91
+
92
+ // Handle nested objects
93
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
94
+ if (!target[key] || typeof target[key] !== 'object') {
95
+ target[key] = {};
96
+ }
97
+ for (const nestedKey in value) {
98
+ applyValue(target[key], nestedKey, value[nestedKey]);
99
+ }
100
+ } else {
101
+ target[key] = value;
77
102
  }
103
+ };
78
104
 
79
- script[key] = value;
105
+ for (const key in attributesObject) {
106
+ applyValue(script, key, attributesObject[key]);
80
107
  }
81
108
  } catch (error) {
82
109
  console.error(`Error parsing attributes JSON string ${attributes}:`, error);
package/src/entity.ts CHANGED
@@ -55,12 +55,14 @@ class EntityElement extends AsyncElement {
55
55
  this.entity = new Entity(this._name, app);
56
56
 
57
57
  // Initialize from attributes
58
+ const enabledAttr = this.getAttribute('enabled');
58
59
  const nameAttr = this.getAttribute('name');
59
60
  const positionAttr = this.getAttribute('position');
60
61
  const rotationAttr = this.getAttribute('rotation');
61
62
  const scaleAttr = this.getAttribute('scale');
62
63
  const tagsAttr = this.getAttribute('tags');
63
64
 
65
+ if (enabledAttr) this.enabled = enabledAttr !== 'false';
64
66
  if (nameAttr) this.name = nameAttr;
65
67
  if (positionAttr) this.position = parseVec3(positionAttr);
66
68
  if (rotationAttr) this.rotation = parseVec3(rotationAttr);
package/src/model.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { ContainerResource, Entity } from 'playcanvas';
2
+
1
3
  import { AssetElement } from './asset';
2
4
  import { AsyncElement } from './async-element';
3
5
 
@@ -7,51 +9,77 @@ import { AsyncElement } from './async-element';
7
9
  class ModelElement extends AsyncElement {
8
10
  private _asset: string = '';
9
11
 
10
- async connectedCallback() {
11
- await this.closestApp?.ready();
12
-
13
- const asset = this.getAttribute('asset');
14
- if (asset) {
15
- this.asset = asset;
16
- }
12
+ private _entity: Entity | null = null;
17
13
 
14
+ connectedCallback() {
15
+ this._loadModel();
18
16
  this._onReady();
19
17
  }
20
18
 
21
- _loadModel() {
22
- const asset = AssetElement.get(this._asset);
23
- if (!asset) {
24
- return;
25
- }
26
- const entity = asset.resource.instantiateRenderEntity();
19
+ disconnectedCallback() {
20
+ this._unloadModel();
21
+ }
22
+
23
+ private _instantiate(container: ContainerResource) {
24
+ this._entity = container.instantiateRenderEntity();
27
25
 
28
- if (asset.resource.animations.length > 0) {
29
- entity.addComponent('anim');
30
- entity.anim.assignAnimation('animation', asset.resource.animations[0].resource);
26
+ // @ts-ignore
27
+ if (container.animations.length > 0) {
28
+ this._entity.addComponent('anim');
29
+ // @ts-ignore
30
+ this._entity.anim.assignAnimation('animation', container.animations[0].resource);
31
31
  }
32
32
 
33
33
  const parentEntityElement = this.closestEntity;
34
34
  if (parentEntityElement) {
35
35
  parentEntityElement.ready().then(() => {
36
- parentEntityElement.entity!.addChild(entity);
36
+ parentEntityElement.entity!.addChild(this._entity!);
37
37
  });
38
38
  } else {
39
39
  const appElement = this.closestApp;
40
40
  if (appElement) {
41
41
  appElement.ready().then(() => {
42
- appElement.app!.root.addChild(entity);
42
+ appElement.app!.root.addChild(this._entity!);
43
43
  });
44
44
  }
45
45
  }
46
46
  }
47
47
 
48
+ private async _loadModel() {
49
+ this._unloadModel();
50
+
51
+ const appElement = await this.closestApp?.ready();
52
+ const app = appElement?.app;
53
+
54
+ const asset = AssetElement.get(this._asset);
55
+ if (!asset) {
56
+ return;
57
+ }
58
+
59
+ if (asset.loaded) {
60
+ this._instantiate(asset.resource);
61
+ } else {
62
+ asset.once('load', () => {
63
+ this._instantiate(asset.resource);
64
+ });
65
+ app!.assets.load(asset);
66
+ }
67
+ }
68
+
69
+ private _unloadModel() {
70
+ this._entity?.destroy();
71
+ this._entity = null;
72
+ }
73
+
48
74
  /**
49
75
  * Sets the asset ID of the model.
50
76
  * @param value - The asset ID.
51
77
  */
52
78
  set asset(value: string) {
53
79
  this._asset = value;
54
- this._loadModel();
80
+ if (this.isConnected) {
81
+ this._loadModel();
82
+ }
55
83
  }
56
84
 
57
85
  /**
package/src/sky.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EnvLighting, LAYERID_SKYBOX, Quat, Texture, Vec3 } from 'playcanvas';
1
+ import { Asset, EnvLighting, LAYERID_SKYBOX, Quat, Scene, Texture, Vec3 } from 'playcanvas';
2
2
 
3
3
  import { AssetElement } from './asset';
4
4
  import { AsyncElement } from './async-element';
@@ -18,56 +18,90 @@ class SkyElement extends AsyncElement {
18
18
 
19
19
  private _level = 0;
20
20
 
21
+ private _lighting = false;
22
+
21
23
  private _scale = new Vec3(100, 100, 100);
22
24
 
23
25
  private _type: 'box' | 'dome' | 'infinite' | 'none' = 'infinite';
24
26
 
25
- async connectedCallback() {
26
- await this.closestApp?.ready();
27
-
28
- this.asset = this.getAttribute('asset') || '';
27
+ private _scene: Scene | null = null;
29
28
 
29
+ connectedCallback() {
30
+ this._loadSkybox();
30
31
  this._onReady();
31
32
  }
32
33
 
33
- getScene() {
34
- const app = this.closestApp!.app;
34
+ disconnectedCallback() {
35
+ this._unloadSkybox();
36
+ }
37
+
38
+ private _generateSkybox(asset: Asset) {
39
+ if (!this._scene) return;
40
+
41
+ const source = asset.resource as Texture;
42
+
43
+ const skybox = EnvLighting.generateSkyboxCubemap(source);
44
+ skybox.anisotropy = 4;
45
+ this._scene.skybox = skybox;
46
+
47
+ if (this._lighting) {
48
+ const lighting = EnvLighting.generateLightingSource(source);
49
+ const envAtlas = EnvLighting.generateAtlas(lighting);
50
+ this._scene.envAtlas = envAtlas;
51
+ }
52
+
53
+ const layer = this._scene.layers.getLayerById(LAYERID_SKYBOX);
54
+ if (layer) {
55
+ layer.enabled = this._type !== 'none';
56
+ }
57
+
58
+ this._scene.sky.type = this._type;
59
+ this._scene.sky.node.setLocalScale(this._scale);
60
+ this._scene.sky.center = this._center;
61
+ this._scene.skyboxIntensity = this._intensity;
62
+ }
63
+
64
+ private async _loadSkybox() {
65
+ const appElement = await this.closestApp?.ready();
66
+ const app = appElement?.app;
35
67
  if (!app) {
36
68
  return;
37
69
  }
38
- return app.scene;
39
- }
40
70
 
41
- private initSkybox(source: Texture) {
42
- source.anisotropy = 4;
71
+ const asset = AssetElement.get(this._asset);
72
+ if (!asset) {
73
+ return;
74
+ }
43
75
 
44
- const skybox = EnvLighting.generateSkyboxCubemap(source);
45
- const lighting = EnvLighting.generateLightingSource(source);
46
- const envAtlas = EnvLighting.generateAtlas(lighting);
47
- const app = this.closestApp!.app;
48
- if (app) {
49
- app.scene.envAtlas = envAtlas;
50
- app.scene.skybox = skybox;
51
-
52
- const layer = app.scene.layers.getLayerById(LAYERID_SKYBOX);
53
- if (layer) {
54
- layer.enabled = this._type !== 'none';
55
- }
76
+ this._scene = app.scene;
56
77
 
57
- app.scene.sky.type = this._type;
58
- app.scene.sky.node.setLocalScale(this._scale);
59
- app.scene.sky.center = this._center;
78
+ if (asset.loaded) {
79
+ this._generateSkybox(asset);
80
+ } else {
81
+ asset.once('load', () => {
82
+ this._generateSkybox(asset);
83
+ });
84
+ app.assets.load(asset);
60
85
  }
61
86
  }
62
87
 
88
+ private _unloadSkybox() {
89
+ if (!this._scene) return;
90
+
91
+ this._scene.skybox?.destroy();
92
+ // @ts-ignore
93
+ this._scene.skybox = null;
94
+ this._scene.envAtlas?.destroy();
95
+ // @ts-ignore
96
+ this._scene.envAtlas = null;
97
+
98
+ this._scene = null;
99
+ }
100
+
63
101
  set asset(value: string) {
64
102
  this._asset = value;
65
- const scene = this.getScene();
66
- if (scene) {
67
- const asset = AssetElement.get(value);
68
- if (asset) {
69
- this.initSkybox(asset.resource);
70
- }
103
+ if (this.isConnected) {
104
+ this._loadSkybox();
71
105
  }
72
106
  }
73
107
 
@@ -77,9 +111,8 @@ class SkyElement extends AsyncElement {
77
111
 
78
112
  set center(value: Vec3) {
79
113
  this._center = value;
80
- const scene = this.getScene();
81
- if (scene) {
82
- scene.sky.center = this._center;
114
+ if (this._scene) {
115
+ this._scene.sky.center = this._center;
83
116
  }
84
117
  }
85
118
 
@@ -89,9 +122,8 @@ class SkyElement extends AsyncElement {
89
122
 
90
123
  set intensity(value: number) {
91
124
  this._intensity = value;
92
- const scene = this.getScene();
93
- if (scene) {
94
- scene.skyboxIntensity = this._intensity;
125
+ if (this._scene) {
126
+ this._scene.skyboxIntensity = this._intensity;
95
127
  }
96
128
  }
97
129
 
@@ -99,11 +131,29 @@ class SkyElement extends AsyncElement {
99
131
  return this._intensity;
100
132
  }
101
133
 
134
+ set level(value: number) {
135
+ this._level = value;
136
+ if (this._scene) {
137
+ this._scene.skyboxMip = this._level;
138
+ }
139
+ }
140
+
141
+ get level() {
142
+ return this._level;
143
+ }
144
+
145
+ set lighting(value: boolean) {
146
+ this._lighting = value;
147
+ }
148
+
149
+ get lighting() {
150
+ return this._lighting;
151
+ }
152
+
102
153
  set rotation(value: Vec3) {
103
154
  this._rotation = value;
104
- const scene = this.getScene();
105
- if (scene) {
106
- scene.skyboxRotation = new Quat().setFromEulerAngles(value);
155
+ if (this._scene) {
156
+ this._scene.skyboxRotation = new Quat().setFromEulerAngles(value);
107
157
  }
108
158
  }
109
159
 
@@ -113,9 +163,8 @@ class SkyElement extends AsyncElement {
113
163
 
114
164
  set scale(value: Vec3) {
115
165
  this._scale = value;
116
- const scene = this.getScene();
117
- if (scene) {
118
- scene.sky.node.setLocalScale(this._scale);
166
+ if (this._scene) {
167
+ this._scene.sky.node.setLocalScale(this._scale);
119
168
  }
120
169
  }
121
170
 
@@ -123,24 +172,11 @@ class SkyElement extends AsyncElement {
123
172
  return this._scale;
124
173
  }
125
174
 
126
- set level(value: number) {
127
- this._level = value;
128
- const scene = this.getScene();
129
- if (scene) {
130
- scene.skyboxMip = this._level;
131
- }
132
- }
133
-
134
- get level() {
135
- return this._level;
136
- }
137
-
138
175
  set type(value: 'box' | 'dome' | 'infinite' | 'none') {
139
176
  this._type = value;
140
- const scene = this.getScene();
141
- if (scene) {
142
- scene.sky.type = this._type;
143
- const layer = scene.layers.getLayerById(LAYERID_SKYBOX);
177
+ if (this._scene) {
178
+ this._scene.sky.type = this._type;
179
+ const layer = this._scene.layers.getLayerById(LAYERID_SKYBOX);
144
180
  if (layer) {
145
181
  layer.enabled = this._type !== 'none';
146
182
  }
@@ -152,7 +188,7 @@ class SkyElement extends AsyncElement {
152
188
  }
153
189
 
154
190
  static get observedAttributes() {
155
- return ['asset', 'center', 'intensity', 'level', 'rotation', 'scale', 'type'];
191
+ return ['asset', 'center', 'intensity', 'level', 'lighting', 'rotation', 'scale', 'type'];
156
192
  }
157
193
 
158
194
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -166,12 +202,15 @@ class SkyElement extends AsyncElement {
166
202
  case 'intensity':
167
203
  this.intensity = parseFloat(newValue);
168
204
  break;
169
- case 'rotation':
170
- this.rotation = parseVec3(newValue);
171
- break;
172
205
  case 'level':
173
206
  this.level = parseInt(newValue, 10);
174
207
  break;
208
+ case 'lighting':
209
+ this.lighting = this.hasAttribute(name);
210
+ break;
211
+ case 'rotation':
212
+ this.rotation = parseVec3(newValue);
213
+ break;
175
214
  case 'scale':
176
215
  this.scale = parseVec3(newValue);
177
216
  break;
package/src/utils.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
2
2
 
3
+ import { CSS_COLORS } from './colors';
4
+
3
5
  /**
4
6
  * Parse a color string into a Color object. String can be in the format of '#rgb', '#rgba',
5
7
  * '#rrggbb', '#rrggbbaa', or a string of 3 or 4 comma-delimited numbers.
@@ -8,11 +10,17 @@ import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
8
10
  * @returns The parsed Color object.
9
11
  */
10
12
  export const parseColor = (value: string): Color => {
13
+ // Check if it's a CSS color name first
14
+ const hexColor = CSS_COLORS[value.toLowerCase()];
15
+ if (hexColor) {
16
+ return new Color().fromString(hexColor);
17
+ }
18
+
11
19
  if (value.startsWith('#')) {
12
20
  return new Color().fromString(value);
13
21
  }
14
22
 
15
- const components = value.split(',').map(Number);
23
+ const components = value.split(' ').map(Number);
16
24
  return new Color(components);
17
25
  };
18
26
 
@@ -23,7 +31,7 @@ export const parseColor = (value: string): Color => {
23
31
  * @returns The parsed Quat object.
24
32
  */
25
33
  export const parseQuat = (value: string): Quat => {
26
- const [x, y, z] = value.split(',').map(Number);
34
+ const [x, y, z] = value.split(' ').map(Number);
27
35
  const q = new Quat();
28
36
  q.setFromEulerAngles(x, y, z);
29
37
  return q;
@@ -36,7 +44,7 @@ export const parseQuat = (value: string): Quat => {
36
44
  * @returns The parsed Vec2 object.
37
45
  */
38
46
  export const parseVec2 = (value: string): Vec2 => {
39
- const components = value.split(',').map(Number);
47
+ const components = value.split(' ').map(Number);
40
48
  return new Vec2(components);
41
49
  };
42
50
 
@@ -47,7 +55,7 @@ export const parseVec2 = (value: string): Vec2 => {
47
55
  * @returns The parsed Vec3 object.
48
56
  */
49
57
  export const parseVec3 = (value: string): Vec3 => {
50
- const components = value.split(',').map(Number);
58
+ const components = value.split(' ').map(Number);
51
59
  return new Vec3(components);
52
60
  };
53
61
 
@@ -58,6 +66,6 @@ export const parseVec3 = (value: string): Vec3 => {
58
66
  * @returns The parsed Vec4 object.
59
67
  */
60
68
  export const parseVec4 = (value: string): Vec4 => {
61
- const components = value.split(',').map(Number);
69
+ const components = value.split(' ').map(Number);
62
70
  return new Vec4(components);
63
71
  };