Skip to content

Commit

Permalink
Added mesh particles example (#6291)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed Apr 29, 2024
1 parent 3b97f7e commit 118938d
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 14 deletions.
6 changes: 6 additions & 0 deletions examples/src/examples/graphics/particles-mesh/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @type {import('../../../../types.mjs').ExampleConfig}
*/
export default {
WEBGPU_ENABLED: true
};
173 changes: 173 additions & 0 deletions examples/src/examples/graphics/particles-mesh/example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import * as pc from 'playcanvas';
import { deviceType, rootPath } from '@examples/utils';

const canvas = document.getElementById('application-canvas');
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('No canvas found');
}

const assets = {
torus: new pc.Asset('heart', 'container', { url: rootPath + '/static/assets/models/torus.glb' }),
script: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
{ url: rootPath + '/static/assets/cubemaps/helipad-env-atlas.png' },
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false }
)
};

const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: rootPath + '/static/lib/glslang/glslang.js',
twgslUrl: rootPath + '/static/lib/twgsl/twgsl.js'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
createOptions.mouse = new pc.Mouse(document.body);
createOptions.touch = new pc.TouchDevice(document.body);
createOptions.keyboard = new pc.Keyboard(document.body);

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ParticleSystemComponentSystem,
pc.ScriptComponentSystem
];
createOptions.resourceHandlers =
[
pc.TextureHandler,
pc.ContainerHandler,
pc.ScriptHandler
];

const app = new pc.AppBase(canvas);
app.init(createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {
app.start();

// setup skydome
app.scene.skyboxIntensity = 0.5;
app.scene.skyboxMip = 2;
app.scene.envAtlas = assets.helipad.resource;

// Create an Entity with a camera component
const cameraEntity = new pc.Entity();
cameraEntity.addComponent('camera', {
clearColor: new pc.Color(0, 0, 0.05)
});
cameraEntity.rotateLocal(0, 0, 0);
cameraEntity.setPosition(0, 4, 20);

cameraEntity.addComponent('script');
cameraEntity.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2,
distanceMax: 50,
frameOnStart: false
}
});
cameraEntity.script.create('orbitCameraInputMouse');
cameraEntity.script.create('orbitCameraInputTouch');

app.root.addChild(cameraEntity);
cameraEntity.script.orbitCamera.pivotPoint = new pc.Vec3(0, 5, 0);

// Create an Entity for the ground
const material = new pc.StandardMaterial();
material.gloss = 0.6;
material.metalness = 0.4;
material.useMetalness = true;
material.update();

const ground = new pc.Entity();
ground.addComponent('render', {
type: 'box',
material: material
});
ground.setLocalScale(10, 1, 10);
ground.setLocalPosition(0, -0.5, 0);
app.root.addChild(ground);

// Create a directional light
const lightDirEntity = new pc.Entity();
lightDirEntity.addComponent('light', {
type: 'directional',
color: new pc.Color(1, 1, 1),
intensity: 1,
castShadows: false
});
lightDirEntity.setLocalEulerAngles(25, 0, -80);
app.root.addChild(lightDirEntity);

// make particles move in different directions
const localVelocityCurve = new pc.CurveSet([
[0, 0, 0.5, 8],
[0, 0, 0.5, 8],
[0, 0, 0.5, 8]
]);
const localVelocityCurve2 = new pc.CurveSet([
[0, 0, 0.5, -8],
[0, 0, 0.5, -8],
[0, 0, 0.5, -8]
]);

// increasing gravity
const worldVelocityCurve = new pc.CurveSet([
[0, 0],
[0, 0, 0.2, 12, 1, -2],
[0, 0]
]);

// color changes throughout lifetime
const colorCurve = new pc.CurveSet([
[0, 1, 0.25, 1, 0.375, 0.5, 0.5, 0], // r
[0, 0, 0.125, 0.25, 0.25, 0.5, 0.375, 0.75, 0.5, 1], // g
[0, 0, 1, 0.3] // b
]);

// Create entity for particle system
const entity = new pc.Entity('Emitter');
app.root.addChild(entity);
entity.setLocalPosition(0, 1, 0);

// when texture is loaded add particlesystem component to entity
entity.addComponent('particlesystem', {
numParticles: 150,
lifetime: 1,
rate: 0.01,
scaleGraph: new pc.Curve([0, 0.2, 1, 0.7]),
velocityGraph: worldVelocityCurve,
localVelocityGraph: localVelocityCurve,
localVelocityGraph2: localVelocityCurve2,
colorGraph: colorCurve,
emitterShape: pc.EMITTERSHAPE_SPHERE,
emitterRadius: 1,

// mesh asset and rendering settings
renderAsset: assets.torus.resource.renders[0],
blendType: pc.BLEND_NONE,
depthWrite: true,
lighting: true,
halfLambert: true,
alignToMotion: true
});
});

export { app };
20 changes: 6 additions & 14 deletions src/scene/particle-system/particle-emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
BUFFER_DYNAMIC,
CULLFACE_NONE,
FILTER_LINEAR, FILTER_NEAREST,
INDEXFORMAT_UINT16,
INDEXFORMAT_UINT32,
PIXELFORMAT_RGBA8, PIXELFORMAT_RGBA32F,
PRIMITIVE_TRIANGLES,
SEMANTIC_ATTR0, SEMANTIC_ATTR1, SEMANTIC_ATTR2, SEMANTIC_ATTR3, SEMANTIC_ATTR4, SEMANTIC_TEXCOORD0,
Expand Down Expand Up @@ -576,15 +576,7 @@ class ParticleEmitter {

particleTexHeight = (this.useCpu || this.pack8) ? 4 : 2;

this.useMesh = false;
if (this.mesh) {
const totalVertCount = this.numParticles * this.mesh.vertexBuffer.numVertices;
if (totalVertCount > 65535) {
Debug.warn('WARNING: particle system can\'t render mesh particles because numParticles * numVertices is more than 65k. Reverting to quad particles.');
} else {
this.useMesh = true;
}
}
this.useMesh = !!this.mesh;

this.numParticlesPot = math.nextPowerOfTwo(this.numParticles);
this.rebuildGraphs();
Expand Down Expand Up @@ -990,7 +982,7 @@ class ParticleEmitter {
this.vertexBuffer = new VertexBuffer(this.graphicsDevice, particleFormat, psysVertCount, {
usage: BUFFER_DYNAMIC
});
this.indexBuffer = new IndexBuffer(this.graphicsDevice, INDEXFORMAT_UINT16, psysIndexCount);
this.indexBuffer = new IndexBuffer(this.graphicsDevice, INDEXFORMAT_UINT32, psysIndexCount);
} else {
const elements = [{
semantic: SEMANTIC_ATTR0,
Expand Down Expand Up @@ -1018,7 +1010,7 @@ class ParticleEmitter {
this.vertexBuffer = new VertexBuffer(this.graphicsDevice, particleFormat, psysVertCount, {
usage: BUFFER_DYNAMIC
});
this.indexBuffer = new IndexBuffer(this.graphicsDevice, INDEXFORMAT_UINT16, psysIndexCount);
this.indexBuffer = new IndexBuffer(this.graphicsDevice, INDEXFORMAT_UINT32, psysIndexCount);
}

// Fill the vertex buffer
Expand Down Expand Up @@ -1065,8 +1057,8 @@ class ParticleEmitter {

// Fill the index buffer
let dst = 0;
const indices = new Uint16Array(this.indexBuffer.lock());
if (this.useMesh) meshData = new Uint16Array(this.mesh.indexBuffer[0].lock());
const indices = new Uint32Array(this.indexBuffer.lock());
if (this.useMesh) meshData = new Uint32Array(this.mesh.indexBuffer[0].lock());
for (let i = 0; i < numParticles; i++) {
if (!this.useMesh) {
const baseIndex = i * 4;
Expand Down

0 comments on commit 118938d

Please sign in to comment.