Skip to content
Merged
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore:
- "e2e/**"
90 changes: 90 additions & 0 deletions e2e/case/particleRenderer-emit-mesh-size-over-lifetime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @title Particle Emit Mesh Size Over Lifetime
* @category Particle
*/
import {
Burst,
Camera,
Color,
CurveKey,
Engine,
Entity,
Logger,
ParticleCompositeCurve,
ParticleCurve,
ParticleMaterial,
ParticleRenderer,
ParticleRenderMode,
PrimitiveMesh,
SphereShape,
Vector3,
WebGLEngine
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

// Create engine
WebGLEngine.create({
canvas: "canvas"
}).then((engine) => {
Logger.enable();

const rootEntity = engine.sceneManager.activeScene.createRootEntity("Root");

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.position = new Vector3(0, 0, 12);
const camera = cameraEntity.addComponent(Camera);
camera.fieldOfView = 60;
camera.nearClipPlane = 0.3;
camera.farClipPlane = 1000;

const particleEntity = createSizeOverLifetimeParticle(engine);
rootEntity.addChild(particleEntity);

updateForE2E(engine, 300);
initScreenshot(engine, camera);
});

function createSizeOverLifetimeParticle(engine: Engine): Entity {
const particleEntity = new Entity(engine, "MeshSizeOverLifetimeParticle");

const particleRenderer = particleEntity.addComponent(ParticleRenderer);

const material = new ParticleMaterial(engine);
material.baseColor = new Color(1.0, 0.5, 0.2, 1.0);
particleRenderer.setMaterial(material);

particleRenderer.renderMode = ParticleRenderMode.Mesh;
particleRenderer.mesh = PrimitiveMesh.createCuboid(engine);

const generator = particleRenderer.generator;
generator.useAutoRandomSeed = false;
const { main, emission, sizeOverLifetime } = generator;

main.startLifetime.constant = 10;
main.startSpeed.constant = 0.5;
main.startSize.constant = 0.7;

emission.rateOverTime.constant = 0;
emission.addBurst(new Burst(0, new ParticleCompositeCurve(16)));
const shape = new SphereShape();
shape.radius = 2.5;
emission.shape = shape;

sizeOverLifetime.enabled = true;
sizeOverLifetime.separateAxes = true;
sizeOverLifetime.sizeX = new ParticleCompositeCurve(
new ParticleCurve(new CurveKey(0, 0.3), new CurveKey(1, 0.5)),
new ParticleCurve(new CurveKey(0, 1.2), new CurveKey(1, 2.0))
);
sizeOverLifetime.sizeY = new ParticleCompositeCurve(
new ParticleCurve(new CurveKey(0, 0.35), new CurveKey(1, 0.2)),
new ParticleCurve(new CurveKey(0, 1.0), new CurveKey(1, 3.0))
);
sizeOverLifetime.sizeZ = new ParticleCompositeCurve(
new ParticleCurve(new CurveKey(0, 0.4), new CurveKey(1, 0.3)),
new ParticleCurve(new CurveKey(0, 0.9), new CurveKey(1, 1.5))
);

return particleEntity;
}
6 changes: 6 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,12 @@ export const E2E_CONFIG = {
threshold: 0,
diffPercentage: 0.02553
},
particleEmitMeshSizeOverLifetime: {
category: "Particle",
caseFileName: "particleRenderer-emit-mesh-size-over-lifetime",
threshold: 0,
diffPercentage: 0.005
},
particleEmitBillboardStretched: {
category: "Particle",
caseFileName: "particleRenderer-emit-billboard-stretched",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/core/src/particle/ParticleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ export class ParticleGenerator {
this.velocityOverLifetime._resetRandomSeed(seed);
this.forceOverLifetime._resetRandomSeed(seed);
this.limitVelocityOverLifetime._resetRandomSeed(seed);
this.sizeOverLifetime._resetRandomSeed(seed);
this.rotationOverLifetime._resetRandomSeed(seed);
this.colorOverLifetime._resetRandomSeed(seed);
this.noise._resetRandomSeed(seed);
Expand Down Expand Up @@ -1008,8 +1009,13 @@ export class ParticleGenerator {
instanceVertices[offset + 20] = colorOverLifetime._colorGradientRand.random();
}

// Noise and size-over-lifetime temporarily share slot 21 (a_Random0.z), so noise takes precedence
// Track independent module randomness and instance layout optimization in #3075
const sizeOverLifetime = this.sizeOverLifetime;
if (this.noise.enabled) {
instanceVertices[offset + 21] = this.noise._noiseRand.random();
} else if (sizeOverLifetime.enabled && sizeOverLifetime._isRandomCurveMode()) {
instanceVertices[offset + 21] = sizeOverLifetime._sizeRand.random();
}

const rotationOverLifetime = this.rotationOverLifetime;
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/particle/modules/ParticleCompositeCurve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,13 @@ export class ParticleCompositeCurve {
return this._mode === ParticleCurveMode.TwoConstants || this._mode === ParticleCurveMode.TwoCurves;
}

/**
* @internal
*/
_isRandomCurveMode(): boolean {
return this._mode === ParticleCurveMode.TwoCurves;
}

/**
* @internal
*/
Expand Down
34 changes: 27 additions & 7 deletions packages/core/src/particle/modules/SizeOverLifetimeModule.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Rand } from "@galacean/engine-math";
import { deepClone, ignoreClone } from "../../clone/CloneManager";
import { ShaderData } from "../../shader/ShaderData";
import { ShaderMacro } from "../../shader/ShaderMacro";
import { ShaderProperty } from "../../shader/ShaderProperty";
import { ParticleGenerator } from "../ParticleGenerator";
import { ParticleCurveMode } from "../enums/ParticleCurveMode";
import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds";
import { ParticleCompositeCurve } from "./ParticleCompositeCurve";
import { CurveKey, ParticleCurve } from "./ParticleCurve";
import { ParticleGeneratorModule } from "./ParticleGeneratorModule";
Expand All @@ -23,6 +25,10 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
static readonly _maxCurveYProperty = ShaderProperty.getByName("renderer_SOLMaxCurveY");
static readonly _maxCurveZProperty = ShaderProperty.getByName("renderer_SOLMaxCurveZ");

/** @internal */
@ignoreClone
_sizeRand = new Rand(0, ParticleRandomSubSeeds.SizeOverLifetime);

private _separateAxes = false;
@deepClone
private _sizeX: ParticleCompositeCurve;
Expand Down Expand Up @@ -130,18 +136,15 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
const sizeZ = this.sizeZ;

const separateAxes = this.separateAxes;
const isRandomCurveMode = separateAxes
? sizeX.mode === ParticleCurveMode.TwoCurves &&
sizeY.mode === ParticleCurveMode.TwoCurves &&
sizeZ.mode === ParticleCurveMode.TwoCurves
: sizeX.mode === ParticleCurveMode.TwoCurves;
const isRandomCurveMode = this._isRandomCurveMode();

const isCurveMode =
isRandomCurveMode || separateAxes
isRandomCurveMode ||
(separateAxes
? sizeX.mode === ParticleCurveMode.Curve &&
sizeY.mode === ParticleCurveMode.Curve &&
sizeZ.mode === ParticleCurveMode.Curve
: sizeX.mode === ParticleCurveMode.Curve;
: sizeX.mode === ParticleCurveMode.Curve);

if (isCurveMode) {
shaderData.setFloatArray(SizeOverLifetimeModule._maxCurveXProperty, sizeX.curveMax._getTypeArray());
Expand Down Expand Up @@ -169,4 +172,21 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
this._isCurveMacro = this._enableMacro(shaderData, this._isCurveMacro, isCurveMacro);
this._isRandomTwoMacro = this._enableMacro(shaderData, this._isRandomTwoMacro, isRandomTwoMacro);
}

/**
* @internal
*/
_isRandomCurveMode(): boolean {
const { sizeX, sizeY, sizeZ, separateAxes } = this;
return separateAxes
? sizeX._isRandomCurveMode() && sizeY._isRandomCurveMode() && sizeZ._isRandomCurveMode()
: sizeX._isRandomCurveMode();
}

/**
* @internal
*/
_resetRandomSeed(seed: number): void {
this._sizeRand.reset(seed, ParticleRandomSubSeeds.SizeOverLifetime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,23 @@ vec2 computeParticleSizeBillboard(Attributes attributes, in vec2 size, in float

#ifdef RENDERER_MODE_MESH
vec3 computeParticleSizeMesh(Attributes attributes, in vec3 size, in float normalizedAge) {
#ifdef RENDERER_SOL_CURVE
size *= evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge);
#endif
#ifdef RENDERER_SOL_RANDOM_CURVES
size *= mix(evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge),
evaluateParticleCurve(u_SOLSizeGradientMax, normalizedAge),
attributes.a_Random0.z);
#endif
#ifdef RENDERER_SOL_CURVE_SEPARATE
size *= vec3(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge),
evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge),
evaluateParticleCurve(renderer_SOLMinCurveZ, normalizedAge));
#endif
#ifdef RENDERER_SOL_RANDOM_CURVES_SEPARATE
size *= vec3(mix(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge),
evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge),
attributes.a_Random0.z),
mix(evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge),
evaluateParticleCurve(renderer_SOLMaxCurveY, normalizedAge),
attributes.a_Random0.z),
mix(evaluateParticleCurve(renderer_SOLMinCurveZ, normalizedAge),
evaluateParticleCurve(renderer_SOLMaxCurveZ, normalizedAge),
attributes.a_Random0.z));
#ifdef RENDERER_SOL_CURVE_MODE
float lifeSizeX = evaluateParticleCurve(renderer_SOLMaxCurveX, normalizedAge);
#ifdef RENDERER_SOL_IS_RANDOM_TWO
lifeSizeX = mix(evaluateParticleCurve(renderer_SOLMinCurveX, normalizedAge), lifeSizeX, attributes.a_Random0.z);
#endif

#ifdef RENDERER_SOL_IS_SEPARATE
float lifeSizeY = evaluateParticleCurve(renderer_SOLMaxCurveY, normalizedAge);
float lifeSizeZ = evaluateParticleCurve(renderer_SOLMaxCurveZ, normalizedAge);
#ifdef RENDERER_SOL_IS_RANDOM_TWO
lifeSizeY = mix(evaluateParticleCurve(renderer_SOLMinCurveY, normalizedAge), lifeSizeY, attributes.a_Random0.z);
lifeSizeZ = mix(evaluateParticleCurve(renderer_SOLMinCurveZ, normalizedAge), lifeSizeZ, attributes.a_Random0.z);
#endif
size *= vec3(lifeSizeX, lifeSizeY, lifeSizeZ);
#else
size *= lifeSizeX;
#endif
#endif
return size;
}
Expand Down
4 changes: 4 additions & 0 deletions tests/src/core/particle/ParticleCurve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ describe("ParticleCurve tests", () => {
expect((zeroConstant as any)._isZero()).to.equal(true);
expect((zeroConstant as any)._isCurveMode()).to.equal(false);
expect((zeroConstant as any)._isRandomMode()).to.equal(false);
expect((zeroConstant as any)._isRandomCurveMode()).to.equal(false);

const twoConstants = new ParticleCompositeCurve(-1, 1);
expect((twoConstants as any)._isZero()).to.equal(false);
expect((twoConstants as any)._isCurveMode()).to.equal(false);
expect((twoConstants as any)._isRandomMode()).to.equal(true);
expect((twoConstants as any)._isRandomCurveMode()).to.equal(false);

const zeroCurve = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 0)));
expect((zeroCurve as any)._isZero()).to.equal(true);
expect((zeroCurve as any)._isCurveMode()).to.equal(true);
expect((zeroCurve as any)._isRandomMode()).to.equal(false);
expect((zeroCurve as any)._isRandomCurveMode()).to.equal(false);

const twoCurves = new ParticleCompositeCurve(
new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 0)),
Expand All @@ -73,6 +76,7 @@ describe("ParticleCurve tests", () => {
expect((twoCurves as any)._isZero()).to.equal(false);
expect((twoCurves as any)._isCurveMode()).to.equal(true);
expect((twoCurves as any)._isRandomMode()).to.equal(true);
expect((twoCurves as any)._isRandomCurveMode()).to.equal(true);
});

it("Add and remove", () => {
Expand Down
Loading
Loading