Skip to content

Commit 7df3099

Browse files
authored
Add support for particle systems (#139)
* Add support for particle systems * Add newline * Add newline
1 parent 0ee40b7 commit 7df3099

File tree

8 files changed

+277
-2
lines changed

8 files changed

+277
-2
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"numParticles": 100,
3+
"lifetime": 10,
4+
"rate": 0.1,
5+
"colorMapAsset": "snowflake",
6+
"emitterExtents": [ 15, 0, 10 ],
7+
"startAngle": 360,
8+
"startAngle2": -360,
9+
"alphaGraph": {
10+
"keys": [ 0, 0, 0.5, 0.5, 0.9, 0.9, 1, 0 ]
11+
},
12+
"rotationSpeedGraph": {
13+
"keys": [ 0, 100 ]
14+
},
15+
"rotationSpeedGraph2": {
16+
"keys": [ 0, -100 ]
17+
},
18+
"scaleGraph": {
19+
"keys": [ 0, 0.1 ]
20+
},
21+
"velocityGraph": {
22+
"keys": [
23+
[ 0, 0 ],
24+
[ 0, -0.7 ],
25+
[ 0, 0 ]
26+
]
27+
},
28+
"velocityGraph2": {
29+
"keys": [
30+
[ 0, 0 ],
31+
[ 0, -0.4 ],
32+
[ 0, 0 ]
33+
]
34+
}
35+
}
847 Bytes
Loading
6.88 KB
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"numParticles": 200,
3+
"lifetime": 2,
4+
"rate": 0.01,
5+
"colorMapAsset": "spark",
6+
"colorGraph": {
7+
"keys": [
8+
[0, 1, 0.25, 1, 0.375, 0.5, 0.5, 0],
9+
[0, 0, 0.125, 0.25, 0.25, 0.5, 0.375, 0.75, 0.5, 1],
10+
[0, 0, 1, 0]
11+
]
12+
},
13+
"localVelocityGraph": {
14+
"keys": [
15+
[0, 0, 1, 8],
16+
[0, 0, 1, 6],
17+
[0, 0, 1, 0]
18+
]
19+
},
20+
"localVelocityGraph2": {
21+
"keys": [
22+
[0, 0, 1, -8],
23+
[0, 0, 1, -6],
24+
[0, 0, 1, 0]
25+
]
26+
},
27+
"rotationSpeedGraph": {
28+
"keys": [0, 360]
29+
},
30+
"scaleGraph": {
31+
"keys": [0, 0, 0.5, 0.3, 0.8, 0.2, 1, 0.1]
32+
},
33+
"velocityGraph": {
34+
"keys": [
35+
[0, 0],
36+
[0, 0, 0.2, 6, 1, -48],
37+
[0, 0]
38+
]
39+
}
40+
}

examples/basic-particles.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6+
<title>PlayCanvas Web Components - Basic Particles</title>
7+
<script type="importmap">
8+
{
9+
"imports": {
10+
"playcanvas": "../node_modules/playcanvas/build/playcanvas.mjs"
11+
}
12+
}
13+
</script>
14+
<script type="module" src="../dist/pwc.mjs"></script>
15+
<link rel="stylesheet" href="css/example.css">
16+
</head>
17+
<body>
18+
<pc-app>
19+
<!-- Assets -->
20+
<pc-asset src="assets/particles/snowflake.png" id="snowflake"></pc-asset>
21+
<pc-asset src="assets/particles/spark.png" id="spark"></pc-asset>
22+
<pc-asset src="assets/particles/snow.json" id="snow"></pc-asset>
23+
<pc-asset src="assets/particles/sparks.json" id="sparks"></pc-asset>
24+
<!-- Scene -->
25+
<pc-scene>
26+
<!-- Camera -->
27+
<pc-entity name="camera" position="0 0 10">
28+
<pc-camera clear-color="black"></pc-camera>
29+
</pc-entity>
30+
<!-- Snow -->
31+
<pc-entity name="snow" position="0 5 0">
32+
<pc-particles asset="snow"></pc-particles>
33+
</pc-entity>
34+
<!-- Sparks -->
35+
<pc-entity name="sparks">
36+
<pc-particles asset="sparks"></pc-particles>
37+
</pc-entity>
38+
</pc-scene>
39+
</pc-app>
40+
<script type="module" src="js/example.mjs"></script>
41+
</body>
42+
</html>

examples/js/example-list.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const examples = [
33
{ name: 'Annotations', path: 'annotations.html' },
44
{ name: 'AR Avatar', path: 'ar-avatar.html' },
55
{ name: 'Basic Shapes', path: 'basic-shapes.html' },
6+
{ name: 'Basic Particles', path: 'basic-particles.html' },
67
{ name: 'Car Configurator', path: 'car-configurator.html' },
78
{ name: 'FPS Controller', path: 'fps-controller.html' },
89
{ name: 'Gaussian Splatting', path: 'splat.html' },
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 };

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ import { CameraComponentElement } from './components/camera-component';
2020
import { CollisionComponentElement } from './components/collision-component';
2121
import { ComponentElement } from './components/component';
2222
import { ElementComponentElement } from './components/element-component';
23-
import { SplatComponentElement } from './components/splat-component';
2423
import { LightComponentElement } from './components/light-component';
24+
import { ParticleSystemComponentElement } from './components/particlesystem-component';
2525
import { RenderComponentElement } from './components/render-component';
2626
import { RigidBodyComponentElement } from './components/rigidbody-component';
2727
import { ScreenComponentElement } from './components/screen-component';
2828
import { ScriptComponentElement } from './components/script-component';
2929
import { ScriptElement } from './components/script';
3030
import { SoundComponentElement } from './components/sound-component';
3131
import { SoundSlotElement } from './components/sound-slot';
32+
import { SplatComponentElement } from './components/splat-component';
3233
import { MaterialElement } from './material';
3334
import { ModelElement } from './model';
3435
import { SceneElement } from './scene';
@@ -44,7 +45,7 @@ export {
4445
CollisionComponentElement,
4546
ComponentElement,
4647
ElementComponentElement,
47-
SplatComponentElement,
48+
ParticleSystemComponentElement,
4849
LightComponentElement,
4950
ListenerComponentElement,
5051
RenderComponentElement,
@@ -54,6 +55,7 @@ export {
5455
ScriptElement,
5556
SoundComponentElement,
5657
SoundSlotElement,
58+
SplatComponentElement,
5759
MaterialElement,
5860
ModelElement,
5961
SceneElement,

0 commit comments

Comments
 (0)