@playcanvas/web-components 0.2.1 → 0.2.3

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.
package/src/app.ts CHANGED
@@ -26,7 +26,7 @@ class AppElement extends AsyncElement {
26
26
 
27
27
  private _stencil = true;
28
28
 
29
- private _highResolution = false;
29
+ private _highResolution = true;
30
30
 
31
31
  private _hierarchyReady = false;
32
32
 
@@ -193,21 +193,33 @@ class AppElement extends AsyncElement {
193
193
  };
194
194
  }
195
195
 
196
+ // New helper to convert CSS coordinates to canvas (picker) coordinates
197
+ private _getPickerCoordinates(event: PointerEvent): { x: number, y: number } {
198
+ // Get the canvas' bounding rectangle in CSS pixels.
199
+ const canvasRect = this._canvas!.getBoundingClientRect();
200
+ // Compute scale factors based on canvas actual resolution vs. its CSS display size.
201
+ const scaleX = this._canvas!.width / canvasRect.width;
202
+ const scaleY = this._canvas!.height / canvasRect.height;
203
+ // Convert the client coordinates accordingly.
204
+ const x = (event.clientX - canvasRect.left) * scaleX;
205
+ const y = (event.clientY - canvasRect.top) * scaleY;
206
+ return { x, y };
207
+ }
208
+
196
209
  _onPointerMove(event: PointerEvent) {
197
210
  if (!this._picker || !this.app) return;
198
211
 
199
212
  const camera = this.app!.root.findComponent('camera') as CameraComponent;
200
213
  if (!camera) return;
201
214
 
202
- const canvasRect = this._canvas!.getBoundingClientRect();
203
- const x = event.clientX - canvasRect.left;
204
- const y = event.clientY - canvasRect.top;
215
+ // Use the helper to convert event coordinates into canvas/picker coordinates.
216
+ const { x, y } = this._getPickerCoordinates(event);
205
217
 
206
218
  this._picker.prepare(camera, this.app!.scene);
207
219
  const selection = this._picker.getSelection(x, y);
208
220
 
209
221
  // Get the currently hovered entity by walking up the hierarchy
210
- let newHoverEntity = null;
222
+ let newHoverEntity: EntityElement | null = null;
211
223
  if (selection.length > 0) {
212
224
  let currentNode: GraphNode | null = selection[0].node;
213
225
  while (currentNode !== null) {
@@ -245,9 +257,8 @@ class AppElement extends AsyncElement {
245
257
  const camera = this.app!.root.findComponent('camera') as CameraComponent;
246
258
  if (!camera) return;
247
259
 
248
- const canvasRect = this._canvas!.getBoundingClientRect();
249
- const x = event.clientX - canvasRect.left;
250
- const y = event.clientY - canvasRect.top;
260
+ // Convert the event's pointer coordinates
261
+ const { x, y } = this._getPickerCoordinates(event);
251
262
 
252
263
  this._picker.prepare(camera, this.app!.scene);
253
264
  const selection = this._picker.getSelection(x, y);
@@ -271,9 +282,8 @@ class AppElement extends AsyncElement {
271
282
  const camera = this.app!.root.findComponent('camera') as CameraComponent;
272
283
  if (!camera) return;
273
284
 
274
- const canvasRect = this._canvas!.getBoundingClientRect();
275
- const x = event.clientX - canvasRect.left;
276
- const y = event.clientY - canvasRect.top;
285
+ // Convert CSS coordinates to picker coordinates
286
+ const { x, y } = this._getPickerCoordinates(event);
277
287
 
278
288
  this._picker.prepare(camera, this.app!.scene);
279
289
  const selection = this._picker.getSelection(x, y);
package/src/asset.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Asset } from 'playcanvas';
2
2
 
3
+ import { MeshoptDecoder } from '../lib/meshopt_decoder.module.js';
4
+
3
5
  const extToType = new Map([
4
6
  ['bin', 'binary'],
5
7
  ['css', 'css'],
@@ -20,6 +22,45 @@ const extToType = new Map([
20
22
  ['webp', 'texture']
21
23
  ]);
22
24
 
25
+
26
+ // provide buffer view callback so we can handle models compressed with MeshOptimizer
27
+ // https://github.com/zeux/meshoptimizer
28
+ const processBufferView = (
29
+ gltfBuffer: any,
30
+ buffers: Array<any>,
31
+ continuation: (err: string, result: any) => void
32
+ ) => {
33
+ if (gltfBuffer.extensions && gltfBuffer.extensions.EXT_meshopt_compression) {
34
+ const extensionDef = gltfBuffer.extensions.EXT_meshopt_compression;
35
+
36
+ Promise.all([MeshoptDecoder.ready, buffers[extensionDef.buffer]]).then((promiseResult) => {
37
+ const buffer = promiseResult[1];
38
+
39
+ const byteOffset = extensionDef.byteOffset || 0;
40
+ const byteLength = extensionDef.byteLength || 0;
41
+
42
+ const count = extensionDef.count;
43
+ const stride = extensionDef.byteStride;
44
+
45
+ const result = new Uint8Array(count * stride);
46
+ const source = new Uint8Array(buffer.buffer, buffer.byteOffset + byteOffset, byteLength);
47
+
48
+ MeshoptDecoder.decodeGltfBuffer(
49
+ result,
50
+ count,
51
+ stride,
52
+ source,
53
+ extensionDef.mode,
54
+ extensionDef.filter
55
+ );
56
+
57
+ continuation(null, result);
58
+ });
59
+ } else {
60
+ continuation(null, null);
61
+ }
62
+ };
63
+
23
64
  /**
24
65
  * The AssetElement interface provides properties and methods for manipulating
25
66
  * {@link https://developer.playcanvas.com/user-manual/engine/web-components/tags/pc-asset/ | `<pc-asset>`} elements.
@@ -54,10 +95,21 @@ class AssetElement extends HTMLElement {
54
95
  return;
55
96
  }
56
97
 
57
- this.asset = new Asset(id, type, { url: src });
98
+ if (type === 'container') {
99
+ this.asset = new Asset(id, type, { url: src }, undefined, {
100
+ // @ts-ignore TODO no definition in pc
101
+ bufferView: {
102
+ processAsync: processBufferView.bind(this)
103
+ }
104
+ });
105
+ } else {
106
+ this.asset = new Asset(id, type, { url: src });
107
+ }
108
+
58
109
  this.asset.preload = !this._lazy;
59
110
  }
60
111
 
112
+
61
113
  destroyAsset() {
62
114
  if (this.asset) {
63
115
  this.asset.unload();
@@ -0,0 +1,155 @@
1
+ import { ParticleSystemComponent } from 'playcanvas';
2
+
3
+ import { ComponentElement } from './component';
4
+ import { AssetElement } from '../asset';
5
+
6
+ /**
7
+ * The ParticleSystemComponentElement interface provides properties and methods for manipulating
8
+ * {@link https://developer.playcanvas.com/user-manual/engine/web-components/tags/pc-particles/ | `<pc-particles>`} elements.
9
+ * The ParticleSystemComponentElement interface also inherits the properties and methods of the
10
+ * {@link HTMLElement} interface.
11
+ *
12
+ * @category Components
13
+ */
14
+ class ParticleSystemComponentElement extends ComponentElement {
15
+ private _asset: string = '';
16
+
17
+ /** @ignore */
18
+ constructor() {
19
+ super('particlesystem');
20
+ }
21
+
22
+ getInitialComponentData() {
23
+ const asset = AssetElement.get(this._asset);
24
+ if (!asset) {
25
+ return {};
26
+ }
27
+
28
+ if ((asset.resource as any).colorMapAsset) {
29
+ const id = (asset.resource as any).colorMapAsset;
30
+ const colorMapAsset = AssetElement.get(id)?.id;
31
+ if (colorMapAsset) {
32
+ (asset.resource as any).colorMapAsset = colorMapAsset;
33
+ }
34
+ }
35
+
36
+ return asset.resource;
37
+ }
38
+
39
+ /**
40
+ * Gets the underlying PlayCanvas particle system component.
41
+ * @returns The particle system component.
42
+ */
43
+ get component(): ParticleSystemComponent | null {
44
+ return super.component as ParticleSystemComponent | null;
45
+ }
46
+
47
+ private applyConfig(resource: any) {
48
+ if (!this.component) {
49
+ return;
50
+ }
51
+
52
+ // Set all the config properties on the component
53
+ for (const key in resource) {
54
+ if (resource.hasOwnProperty(key)) {
55
+ (this.component as any)[key] = resource[key];
56
+ }
57
+ }
58
+ }
59
+
60
+ private async _loadAsset() {
61
+ const appElement = await this.closestApp?.ready();
62
+ const app = appElement?.app;
63
+
64
+ const asset = AssetElement.get(this._asset);
65
+ if (!asset) {
66
+ return;
67
+ }
68
+
69
+ if (asset.loaded) {
70
+ this.applyConfig(asset.resource);
71
+ } else {
72
+ asset.once('load', () => {
73
+ this.applyConfig(asset.resource);
74
+ });
75
+ app!.assets.load(asset);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Sets the id of the `pc-asset` to use for the model.
81
+ * @param value - The asset ID.
82
+ */
83
+ set asset(value: string) {
84
+ this._asset = value;
85
+ if (this.isConnected) {
86
+ this._loadAsset();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Gets the id of the `pc-asset` to use for the model.
92
+ * @returns The asset ID.
93
+ */
94
+ get asset(): string {
95
+ return this._asset;
96
+ }
97
+
98
+ // Control methods
99
+ /**
100
+ * Starts playing the particle system
101
+ */
102
+ play() {
103
+ if (this.component) {
104
+ this.component.play();
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Pauses the particle system
110
+ */
111
+ pause() {
112
+ if (this.component) {
113
+ this.component.pause();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Resets the particle system
119
+ */
120
+ reset() {
121
+ if (this.component) {
122
+ this.component.reset();
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Stops the particle system
128
+ */
129
+ stop() {
130
+ if (this.component) {
131
+ this.component.stop();
132
+ }
133
+ }
134
+
135
+ static get observedAttributes() {
136
+ return [
137
+ ...super.observedAttributes,
138
+ 'asset'
139
+ ];
140
+ }
141
+
142
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
143
+ super.attributeChangedCallback(name, _oldValue, newValue);
144
+
145
+ switch (name) {
146
+ case 'asset':
147
+ this.asset = newValue;
148
+ break;
149
+ }
150
+ }
151
+ }
152
+
153
+ customElements.define('pc-particles', ParticleSystemComponentElement);
154
+
155
+ export { ParticleSystemComponentElement };
package/src/index.ts CHANGED
@@ -20,8 +20,8 @@ import { CameraComponentElement } from './components/camera-component';
20
20
  import { CollisionComponentElement } from './components/collision-component';
21
21
  import { ComponentElement } from './components/component';
22
22
  import { ElementComponentElement } from './components/element-component';
23
- import { SplatComponentElement } from './components/splat-component';
24
23
  import { LightComponentElement } from './components/light-component';
24
+ import { ParticleSystemComponentElement } from './components/particlesystem-component';
25
25
  import { RenderComponentElement } from './components/render-component';
26
26
  import { RigidBodyComponentElement } from './components/rigidbody-component';
27
27
  import { ScreenComponentElement } from './components/screen-component';
@@ -29,6 +29,7 @@ import { ScriptComponentElement } from './components/script-component';
29
29
  import { ScriptElement } from './components/script';
30
30
  import { SoundComponentElement } from './components/sound-component';
31
31
  import { SoundSlotElement } from './components/sound-slot';
32
+ import { SplatComponentElement } from './components/splat-component';
32
33
  import { MaterialElement } from './material';
33
34
  import { ModelElement } from './model';
34
35
  import { SceneElement } from './scene';
@@ -44,7 +45,7 @@ export {
44
45
  CollisionComponentElement,
45
46
  ComponentElement,
46
47
  ElementComponentElement,
47
- SplatComponentElement,
48
+ ParticleSystemComponentElement,
48
49
  LightComponentElement,
49
50
  ListenerComponentElement,
50
51
  RenderComponentElement,
@@ -54,6 +55,7 @@ export {
54
55
  ScriptElement,
55
56
  SoundComponentElement,
56
57
  SoundSlotElement,
58
+ SplatComponentElement,
57
59
  MaterialElement,
58
60
  ModelElement,
59
61
  SceneElement,
package/src/scene.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Color, Scene } from 'playcanvas';
1
+ import { Color, Scene, Vec3 } from 'playcanvas';
2
2
 
3
+ import { AppElement } from './app';
3
4
  import { AsyncElement } from './async-element';
4
- import { parseColor } from './utils';
5
+ import { parseColor, parseVec3 } from './utils';
5
6
 
6
7
  /**
7
8
  * The SceneElement interface provides properties and methods for manipulating
@@ -35,6 +36,11 @@ class SceneElement extends AsyncElement {
35
36
  */
36
37
  private _fogEnd = 1000;
37
38
 
39
+ /**
40
+ * The gravity of the scene.
41
+ */
42
+ private _gravity = new Vec3(0, -9.81, 0);
43
+
38
44
  /**
39
45
  * The PlayCanvas scene instance.
40
46
  */
@@ -56,7 +62,9 @@ class SceneElement extends AsyncElement {
56
62
  this.scene.fog.density = this._fogDensity;
57
63
  this.scene.fog.start = this._fogStart;
58
64
  this.scene.fog.end = this._fogEnd;
59
- // ... set other properties on the scene as well
65
+
66
+ const appElement = this.parentElement as AppElement;
67
+ appElement.app!.systems.rigidbody!.gravity.copy(this._gravity);
60
68
  }
61
69
  }
62
70
 
@@ -155,8 +163,28 @@ class SceneElement extends AsyncElement {
155
163
  return this._fogEnd;
156
164
  }
157
165
 
166
+ /**
167
+ * Sets the gravity of the scene.
168
+ * @param value - The gravity.
169
+ */
170
+ set gravity(value: Vec3) {
171
+ this._gravity = value;
172
+ if (this.scene) {
173
+ const appElement = this.parentElement as AppElement;
174
+ appElement.app!.systems.rigidbody!.gravity.copy(value);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Gets the gravity of the scene.
180
+ * @returns The gravity.
181
+ */
182
+ get gravity() {
183
+ return this._gravity;
184
+ }
185
+
158
186
  static get observedAttributes() {
159
- return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end'];
187
+ return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
160
188
  }
161
189
 
162
190
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -176,6 +204,9 @@ class SceneElement extends AsyncElement {
176
204
  case 'fog-end':
177
205
  this.fogEnd = parseFloat(newValue);
178
206
  break;
207
+ case 'gravity':
208
+ this.gravity = parseVec3(newValue);
209
+ break;
179
210
  // ... handle other attributes as well
180
211
  }
181
212
  }