diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts index f987a09a6..ea0722f3b 100644 --- a/packages/core/src/particle/ParticleGenerator.ts +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -237,7 +237,7 @@ export class ParticleGenerator { /** * @internal */ - _emit(time: number, count: number): void { + _emit(playTime: number, count: number): void { if (this.emission.enabled) { // Wait the existing particles to be retired const notRetireParticleCount = this._getNotRetiredParticleCount(); @@ -250,7 +250,7 @@ export class ParticleGenerator { const shape = this.emission.shape; for (let i = 0; i < count; i++) { if (shape?.enabled) { - shape._generatePositionAndDirection(this.emission._shapeRand, time, position, direction); + shape._generatePositionAndDirection(this.emission._shapeRand, playTime, position, direction); const positionScale = this.main._getPositionScale(); position.multiply(positionScale); direction.normalize().multiply(positionScale); @@ -258,7 +258,7 @@ export class ParticleGenerator { position.set(0, 0, 0); direction.set(0, 0, -1); } - this._addNewParticle(position, direction, transform, time); + this._addNewParticle(position, direction, transform, playTime); } } } @@ -669,7 +669,7 @@ export class ParticleGenerator { } } - private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, time: number): void { + private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, playTime: number): void { const firstFreeElement = this._firstFreeElement; let nextFreeElement = firstFreeElement + 1; if (nextFreeElement >= this._currentParticleCount) { @@ -711,9 +711,7 @@ export class ParticleGenerator { const offset = firstFreeElement * ParticleBufferUtils.instanceVertexFloatStride; // Position - instanceVertices[offset] = position.x; - instanceVertices[offset + 1] = position.y; - instanceVertices[offset + 2] = position.z; + position.copyToArray(instanceVertices, offset); // Start life time instanceVertices[offset + ParticleBufferUtils.startLifeTimeOffset] = main.startLifetime.evaluate( @@ -722,12 +720,10 @@ export class ParticleGenerator { ); // Direction - instanceVertices[offset + 4] = direction.x; - instanceVertices[offset + 5] = direction.y; - instanceVertices[offset + 6] = direction.z; + direction.copyToArray(instanceVertices, offset + 4); // Time - instanceVertices[offset + ParticleBufferUtils.timeOffset] = time; + instanceVertices[offset + ParticleBufferUtils.timeOffset] = playTime; // Color const startColor = ParticleGenerator._tempColor0; @@ -736,19 +732,19 @@ export class ParticleGenerator { startColor.toLinear(startColor); } - instanceVertices[offset + 8] = startColor.r; - instanceVertices[offset + 9] = startColor.g; - instanceVertices[offset + 10] = startColor.b; - instanceVertices[offset + 11] = startColor.a; + startColor.copyToArray(instanceVertices, offset + 8); + + const duration = this.main.duration; + const normalizedEmitAge = (playTime % duration) / duration; // Start size const startSizeRand = main._startSizeRand; if (main.startSize3D) { - instanceVertices[offset + 12] = main.startSizeX.evaluate(undefined, startSizeRand.random()); - instanceVertices[offset + 13] = main.startSizeY.evaluate(undefined, startSizeRand.random()); - instanceVertices[offset + 14] = main.startSizeZ.evaluate(undefined, startSizeRand.random()); + instanceVertices[offset + 12] = main.startSizeX.evaluate(normalizedEmitAge, startSizeRand.random()); + instanceVertices[offset + 13] = main.startSizeY.evaluate(normalizedEmitAge, startSizeRand.random()); + instanceVertices[offset + 14] = main.startSizeZ.evaluate(normalizedEmitAge, startSizeRand.random()); } else { - const size = main.startSize.evaluate(undefined, startSizeRand.random()); + const size = main.startSize.evaluate(normalizedEmitAge, startSizeRand.random()); instanceVertices[offset + 12] = size; instanceVertices[offset + 13] = size; instanceVertices[offset + 14] = size; @@ -815,15 +811,10 @@ export class ParticleGenerator { if (this.main.simulationSpace === ParticleSimulationSpace.World) { // Simulation world position - instanceVertices[offset + 27] = pos.x; - instanceVertices[offset + 28] = pos.y; - instanceVertices[offset + 29] = pos.z; + pos.copyToArray(instanceVertices, offset + 27); // Simulation world position - instanceVertices[offset + 30] = rot.x; - instanceVertices[offset + 31] = rot.y; - instanceVertices[offset + 32] = rot.z; - instanceVertices[offset + 33] = rot.w; + rot.copyToArray(instanceVertices, offset + 30); } // Simulation UV diff --git a/packages/core/src/particle/modules/ParticleCompositeCurve.ts b/packages/core/src/particle/modules/ParticleCompositeCurve.ts index a4ad00215..587855ff0 100644 --- a/packages/core/src/particle/modules/ParticleCompositeCurve.ts +++ b/packages/core/src/particle/modules/ParticleCompositeCurve.ts @@ -1,8 +1,8 @@ import { Vector2 } from "@galacean/engine-math"; import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { UpdateFlagManager } from "../../UpdateFlagManager"; import { ParticleCurveMode } from "../enums/ParticleCurveMode"; import { CurveKey, ParticleCurve } from "./ParticleCurve"; -import { UpdateFlagManager } from "../../UpdateFlagManager"; /** * Particle composite curve. @@ -175,6 +175,11 @@ export class ParticleCompositeCurve { return this.constant; case ParticleCurveMode.TwoConstants: return this.constantMin + (this.constantMax - this.constantMin) * lerpFactor; + case ParticleCurveMode.Curve: + return this.curve?._evaluate(time); + case ParticleCurveMode.TwoCurves: + const min = this.curveMin?._evaluate(time); + return min + (this.curveMax?._evaluate(time) - min) * lerpFactor; default: break; } diff --git a/packages/core/src/particle/modules/ParticleCurve.ts b/packages/core/src/particle/modules/ParticleCurve.ts index 7a2989b85..fce99c6b4 100644 --- a/packages/core/src/particle/modules/ParticleCurve.ts +++ b/packages/core/src/particle/modules/ParticleCurve.ts @@ -67,9 +67,10 @@ export class ParticleCurve { * @param index - The remove key index */ removeKey(index: number): void { - this._keys.splice(index, 1); + const keys = this._keys; + const removeKey = keys[index]; + keys.splice(index, 1); this._typeArrayDirty = true; - const removeKey = this._keys[index]; removeKey._unRegisterOnValueChanged(this._updateDispatch); this._updateDispatch(); } @@ -86,6 +87,32 @@ export class ParticleCurve { this._typeArrayDirty = true; } + /** + * @internal + */ + _evaluate(normalizedAge: number): number { + const { keys } = this; + const { length } = keys; + + for (let i = 0; i < length; i++) { + const key = keys[i]; + const { time } = key; + if (normalizedAge <= time) { + if (i === 0) { + // Small than first key + return key.value; + } else { + // Between two keys + const { time: lastTime, value: lastValue } = keys[i - 1]; + const age = (normalizedAge - lastTime) / (time - lastTime); + return lastValue + (key.value - lastValue) * age; + } + } + } + // Large than last key + return keys[length - 1].value; + } + /** * @internal */ diff --git a/tests/src/core/particle/ParticleCurve.test.ts b/tests/src/core/particle/ParticleCurve.test.ts new file mode 100644 index 000000000..6bf926ee4 --- /dev/null +++ b/tests/src/core/particle/ParticleCurve.test.ts @@ -0,0 +1,74 @@ +import { CurveKey, ParticleCompositeCurve, ParticleCurve, ParticleCurveMode } from "@galacean/engine-core"; +import { describe, expect, it } from "vitest"; + +describe("ParticleCurve tests", () => { + it("Constructor with const params", () => { + const gradient = new ParticleCompositeCurve(0.5); + expect(gradient.mode).to.equal(ParticleCurveMode.Constant); + expect(gradient.evaluate(undefined, undefined)).to.equal(0.5); + }); + + it("Constructor with two const params", () => { + const gradient = new ParticleCompositeCurve(0.5, 0.2); + expect(gradient.mode).to.equal(ParticleCurveMode.TwoConstants); + expect(gradient.evaluate(undefined, 0.5)).to.equal(0.35); + expect(gradient.evaluate(undefined, 0.0)).to.equal(0.5); + expect(gradient.evaluate(undefined, 1.0)).to.equal(0.2); + }); + + it("Constructor with curve params", () => { + const gradient0 = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0.333))); + expect(gradient0.mode).to.equal(ParticleCurveMode.Curve); + expect(gradient0.evaluate(0.2, undefined)).to.equal(0.333); + + const gradient1 = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0.3), new CurveKey(0.6, 0.7))); + expect(gradient1.evaluate(0.0, undefined)).to.equal(0.3); + expect(gradient1.evaluate(0.5, undefined)).to.equal(0.6333333333333333); + expect(gradient1.evaluate(0.6, undefined)).to.equal(0.7); + expect(gradient1.evaluate(0.9, undefined)).to.equal(0.7); + expect(gradient1.evaluate(1.0, undefined)).to.equal(0.7); + }); + + it("Constructor with two curve params", () => { + const curveMin = new ParticleCurve(new CurveKey(0, 0.3), new CurveKey(0.6, 0.7)); + const curveMax = new ParticleCurve(new CurveKey(0.4, 0.5), new CurveKey(1.0, 0.8)); + + const compositeCurve = new ParticleCompositeCurve(curveMin, curveMax); + + expect(compositeCurve.evaluate(0.0, 0.0)).to.equal(0.3); + expect(compositeCurve.evaluate(0.5, 0.0)).to.equal(0.6333333333333333); + expect(compositeCurve.evaluate(0.6, 0.0)).to.equal(0.7); + expect(compositeCurve.evaluate(0.9, 0.0)).to.equal(0.7); + expect(compositeCurve.evaluate(1.0, 0.0)).to.equal(0.7); + + expect(compositeCurve.evaluate(0.0, 1.0)).to.equal(0.5); + expect(compositeCurve.evaluate(0.5, 1.0)).to.equal(0.55); + expect(compositeCurve.evaluate(0.6, 1.0)).to.equal(0.6); + expect(compositeCurve.evaluate(0.9, 1.0)).to.equal(0.75); + expect(compositeCurve.evaluate(1.0, 1.0)).to.equal(0.8); + + expect(compositeCurve.evaluate(0.6, 0.5)).to.equal(0.6499999999999999); + }); + + it("Add and remove", () => { + const curve = new ParticleCurve(new CurveKey(0, 0.3), new CurveKey(0.6, 0.7)); + + expect(curve.keys.length).to.equal(2); + + + + curve.addKey(new CurveKey(0, 0.4)); + + expect(curve.keys.length).to.equal(3); + + expect(curve.keys[0].value).to.equal(0.3); + + + curve.removeKey(2); + + expect(curve.keys.length).to.equal(2); + + + curve.removeKey(0); + + + expect(curve.keys.length).to.equal(1); + + expect(curve.keys[0].time).to.equal(0.0); + + expect(curve.keys[0].value).to.equal(0.4); + + + curve.removeKey(0); + + expect(curve.keys.length).to.equal(0); + }); +});