Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added mesh particles example #6291

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
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