Revert "ParticleRenderer support bounds and frustum culling (#1963)" (#2109)

This reverts commit 6d0fdd8658.
This commit is contained in:
ChenMo
2024-06-07 17:33:48 +08:00
committed by GitHub
parent 6d0fdd8658
commit df181dd33f
21 changed files with 154 additions and 2309 deletions

View File

@@ -1,343 +0,0 @@
/**
* @title Particle Dream
* @category Particle
*/
import {
AssetType,
BlendMode,
BoxShape,
Camera,
Color,
Engine,
Entity,
Logger,
ParticleCurveMode,
ParticleGradientMode,
ParticleMaterial,
ParticleRenderMode,
ParticleRenderer,
Texture2D,
Vector3,
WebGLEngine,
WebGLMode
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";
// Create engine
WebGLEngine.create({
canvas: "canvas",
graphicDeviceOptions: { webGLMode: WebGLMode.WebGL1 }
}).then((engine) => {
Logger.enable();
engine.canvas.resizeByClientSize();
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();
scene.background.solidColor = new Color(15 / 255, 38 / 255, 18 / 255, 1);
// Create camera
const cameraEntity = rootEntity.createChild("camera_entity");
cameraEntity.transform.position = new Vector3(0, 1, 3);
const camera = cameraEntity.addComponent(Camera);
camera.fieldOfView = 60;
engine.run();
engine.resourceManager
.load([
{
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture2D
},
{
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*eWTFRZPqfDMAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture2D
},
{
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*J8uhRoxJtYgAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture2D
},
{
url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*Ea3qRb1yCQMAAAAAAAAAAAAADil6AQ/original",
type: AssetType.Texture2D
}
])
.then((textures) => {
const fireEntity = createDebrisParticle(engine, <Texture2D>textures[0]);
createGlowParticle(fireEntity, <Texture2D>textures[1]);
createSparksParticle(fireEntity, <Texture2D>textures[2]);
createHighlightsParticle(fireEntity, <Texture2D>textures[3]);
cameraEntity.addChild(fireEntity);
setTimeout(() => {
updateForE2E(engine);
initScreenshot(engine, camera);
}, 2000);
});
});
function createDebrisParticle(engine: Engine, texture: Texture2D): Entity {
const particleEntity = new Entity(engine, "Debris");
particleEntity.transform.position.set(0, -7.5, -8);
const particleRenderer = particleEntity.addComponent(ParticleRenderer);
const material = new ParticleMaterial(engine);
material.baseColor = new Color(1.0, 1.0, 1.0, 1.0);
material.blendMode = BlendMode.Additive;
material.baseTexture = texture;
particleRenderer.setMaterial(material);
particleRenderer.priority = 2;
particleRenderer.generator.useAutoRandomSeed = false;
const { main, emission, sizeOverLifetime, colorOverLifetime, velocityOverLifetime } = particleRenderer.generator;
// Main module
main.startSpeed.constant = 0;
main.startSize.constantMin = 0.1;
main.startSize.constantMax = 1;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.startRotationZ.constantMin = 0;
main.startRotationZ.constantMax = 360;
main.startRotationZ.mode = ParticleCurveMode.TwoConstants;
main.startColor.constantMin.set(255 / 255, 255 / 255, 255 / 255, 1.0);
main.startColor.constantMax.set(13 / 255, 255 / 255, 0 / 255, 1.0);
main.startColor.mode = ParticleGradientMode.TwoConstants;
// Emission module
emission.rateOverTime.constant = 5;
const boxShape = new BoxShape();
boxShape.size.set(22, 1, 0);
emission.shape = boxShape;
// Color over lifetime module
colorOverLifetime.enabled = true;
colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
const gradient = colorOverLifetime.color.gradient;
gradient.alphaKeys[0].alpha = 0;
gradient.alphaKeys[1].alpha = 0;
gradient.addAlphaKey(0.2, 1.0);
gradient.addAlphaKey(0.8, 1.0);
// Size over lifetime module
sizeOverLifetime.enabled = true;
const keys = sizeOverLifetime.size.curve.keys;
keys[0].value = 1;
keys[1].value = 0;
// Velocity over lifetime module
velocityOverLifetime.enabled = true;
velocityOverLifetime.velocityX.constantMin = 2;
velocityOverLifetime.velocityX.constantMax = 1;
velocityOverLifetime.velocityX.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityY.constantMin = 4;
velocityOverLifetime.velocityY.constantMax = 2;
velocityOverLifetime.velocityY.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityZ.constantMin = 0;
velocityOverLifetime.velocityZ.constantMax = 0;
velocityOverLifetime.velocityZ.mode = ParticleCurveMode.TwoConstants;
return particleEntity;
}
function createGlowParticle(fireEntity: Entity, texture: Texture2D): void {
const particleEntity = fireEntity.createChild("Glow");
particleEntity.transform.position.set(-1.88, 0, 0);
const particleRenderer = particleEntity.addComponent(ParticleRenderer);
particleRenderer.renderMode = ParticleRenderMode.StretchBillboard;
particleRenderer.lengthScale = 2;
const material = new ParticleMaterial(fireEntity.engine);
material.blendMode = BlendMode.Additive;
material.baseTexture = texture;
particleRenderer.setMaterial(material);
particleRenderer.priority = 1;
const generator = particleRenderer.generator;
generator.useAutoRandomSeed = false;
const { main, emission, velocityOverLifetime, colorOverLifetime } = generator;
// Main module
main.startSpeed.constant = 0.0;
main.startSize.constantMin = 5;
main.startSize.constantMax = 9;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.startRotationZ.constantMin = 0;
main.startRotationZ.constantMax = 360;
main.startRotationZ.mode = ParticleCurveMode.TwoConstants;
main.startColor.constantMin = new Color(0 / 255, 157 / 255, 255 / 255, 64 / 255);
main.startColor.constantMax = new Color(13 / 255, 255 / 255, 0 / 255, 128 / 255);
main.startColor.mode = ParticleGradientMode.TwoConstants;
// Emission module
emission.rateOverTime.constant = 10;
const boxShape = new BoxShape();
boxShape.size.set(22, 1, 0);
emission.shape = boxShape;
// Velocity over lifetime module
velocityOverLifetime.enabled = true;
velocityOverLifetime.velocityX.constantMin = 2;
velocityOverLifetime.velocityX.constantMax = 1;
velocityOverLifetime.velocityX.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityY.constantMin = 4;
velocityOverLifetime.velocityY.constantMax = 2;
velocityOverLifetime.velocityY.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityZ.constantMin = 0;
velocityOverLifetime.velocityZ.constantMax = 0;
velocityOverLifetime.velocityZ.mode = ParticleCurveMode.TwoConstants;
// Color over lifetime module
colorOverLifetime.enabled = true;
colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
const gradient = colorOverLifetime.color.gradient;
gradient.alphaKeys[0].alpha = 0;
gradient.alphaKeys[1].alpha = 0;
gradient.addAlphaKey(0.2, 1.0);
}
function createSparksParticle(fireEntity: Entity, texture: Texture2D): void {
const particleEntity = fireEntity.createChild("Sparks");
particleEntity.transform.position.set(-1.54, 0, 0);
const particleRenderer = particleEntity.addComponent(ParticleRenderer);
const material = new ParticleMaterial(fireEntity.engine);
material.baseTexture = texture;
particleRenderer.setMaterial(material);
particleRenderer.priority = 0;
const { main, emission, colorOverLifetime, velocityOverLifetime } = particleRenderer.generator;
particleRenderer.generator.useAutoRandomSeed = false;
// Main module
main.startLifetime.constant = 5;
main.startSpeed.constant = 0;
main.startSize.constantMin = 0.05;
main.startSize.constantMax = 0.2;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.startRotationZ.constantMin = 0;
main.startRotationZ.constantMax = 360;
main.startRotationZ.mode = ParticleCurveMode.TwoConstants;
main.startColor.constant = new Color(37 / 255, 133 / 255, 255 / 255, 255 / 255);
// Emission module
emission.rateOverTime.constant = 30;
const boxShape = new BoxShape();
boxShape.size.set(22, 1, 0);
emission.shape = boxShape;
// Velocity over lifetime module
velocityOverLifetime.enabled = true;
velocityOverLifetime.velocityX.constantMin = 2;
velocityOverLifetime.velocityX.constantMax = 1;
velocityOverLifetime.velocityX.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityY.constantMin = 4;
velocityOverLifetime.velocityY.constantMax = 2;
velocityOverLifetime.velocityY.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityZ.constantMin = 0;
velocityOverLifetime.velocityZ.constantMax = 0;
velocityOverLifetime.velocityZ.mode = ParticleCurveMode.TwoConstants;
// Color over lifetime module
colorOverLifetime.enabled = true;
colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
const gradient = colorOverLifetime.color.gradient;
gradient.alphaKeys[0].alpha = 0;
gradient.alphaKeys[1].alpha = 0;
gradient.addAlphaKey(0.2, 1.0);
gradient.addAlphaKey(0.8, 1.0);
}
function createHighlightsParticle(fireEntity: Entity, texture: Texture2D): void {
const particleEntity = fireEntity.createChild("Highlights");
particleEntity.transform.position.set(-5.31, 0, 0);
const particleRenderer = particleEntity.addComponent(ParticleRenderer);
const material = new ParticleMaterial(fireEntity.engine);
material.blendMode = BlendMode.Additive;
material.baseTexture = texture;
particleRenderer.setMaterial(material);
particleRenderer.priority = 3;
const generator = particleRenderer.generator;
const { main, emission, sizeOverLifetime, colorOverLifetime, velocityOverLifetime } = generator;
generator.useAutoRandomSeed = false;
// Main module
main.startSpeed.constant = 0;
main.startSize.constantMin = 0.1;
main.startSize.constantMax = 7;
main.startSize.mode = ParticleCurveMode.TwoConstants;
main.startRotationZ.constantMin = 0;
main.startRotationZ.constantMax = 360;
main.startRotationZ.mode = ParticleCurveMode.TwoConstants;
main.startColor.constantMin.set(105 / 255, 198 / 255, 255 / 255, 64 / 255);
main.startColor.constantMax.set(13 / 255, 255 / 255, 0 / 255, 32 / 255);
main.startColor.mode = ParticleGradientMode.TwoConstants;
// Emission module
emission.rateOverTime.constant = 40;
const boxShape = new BoxShape();
boxShape.size.set(22, 1, 0);
emission.shape = boxShape;
// Velocity over lifetime module
velocityOverLifetime.enabled = true;
velocityOverLifetime.velocityX.constantMin = 3;
velocityOverLifetime.velocityX.constantMax = 2;
velocityOverLifetime.velocityX.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityY.constantMin = 4;
velocityOverLifetime.velocityY.constantMax = 2;
velocityOverLifetime.velocityY.mode = ParticleCurveMode.TwoConstants;
velocityOverLifetime.velocityZ.constantMin = 0;
velocityOverLifetime.velocityZ.constantMax = 0;
velocityOverLifetime.velocityZ.mode = ParticleCurveMode.TwoConstants;
// Color over lifetime module
colorOverLifetime.enabled = true;
colorOverLifetime.color.mode = ParticleGradientMode.Gradient;
const gradient = colorOverLifetime.color.gradient;
gradient.alphaKeys[0].alpha = 0;
gradient.alphaKeys[1].alpha = 0;
gradient.addAlphaKey(0.2, 1.0);
gradient.addAlphaKey(0.8, 1.0);
// Size over lifetime module
sizeOverLifetime.enabled = true;
const curve = sizeOverLifetime.size.curve;
sizeOverLifetime.size.mode = ParticleCurveMode.Curve;
curve.keys[0].value = 1;
curve.keys[1].value = 0;
}

View File

@@ -163,12 +163,5 @@ export const E2E_CONFIG = {
caseFileName: "physx-collision",
threshold: 0.1
}
},
Particle: {
meshopt: {
category: "Particle",
caseFileName: "particleRenderer-dream",
threshold: 0.3
}
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

View File

@@ -15,19 +15,6 @@ import { ParticleInstanceVertexAttribute } from "./enums/attributes/ParticleInst
* @internal
*/
export class ParticleBufferUtils {
static readonly instanceVertexStride = 152;
static readonly instanceVertexFloatStride = ParticleBufferUtils.instanceVertexStride / 4;
static readonly startLifeTimeOffset = 3;
static readonly timeOffset = 7;
static readonly simulationUVOffset = 34;
static readonly billboardIndexCount = 6;
static readonly boundsFloatStride = 8;
static readonly boundsTimeOffset = 6;
static readonly boundsMaxLifetimeOffset = 7;
readonly billboardVertexElement = new VertexElement(
ParticleBillboardVertexAttribute.cornerTextureCoordinate,
0,
@@ -49,6 +36,15 @@ export class ParticleBufferUtils {
new VertexElement(ParticleInstanceVertexAttribute.SimulationUV, 136, VertexElementFormat.Vector4, 1, 1)
];
readonly instanceVertexStride = 152;
readonly instanceVertexFloatStride = this.instanceVertexStride / 4;
readonly startLifeTimeOffset = 3;
readonly timeOffset = 7;
readonly simulationUVOffset = 34;
readonly billboardIndexCount = 6;
readonly billboardVertexBufferBinding: VertexBufferBinding;
readonly billboardIndexBufferBinding: IndexBufferBinding;
@@ -67,7 +63,7 @@ export class ParticleBufferUtils {
const indexBuffer = new Buffer(
engine,
BufferBindFlag.IndexBuffer,
ParticleBufferUtils.billboardIndexCount,
this.billboardIndexCount,
BufferUsage.Static,
false
);

View File

@@ -1,4 +1,4 @@
import { BoundingBox, Color, MathUtil, Matrix, Quaternion, Vector2, Vector3 } from "@galacean/engine-math";
import { Color, MathUtil, Quaternion, Vector3 } from "@galacean/engine-math";
import { Transform } from "../Transform";
import { deepClone, ignoreClone } from "../clone/CloneManager";
import { ColorSpace } from "../enums/ColorSpace";
@@ -14,7 +14,7 @@ import { SetDataOptions } from "../graphic/enums/SetDataOptions";
import { VertexAttribute } from "../mesh";
import { ShaderData } from "../shader";
import { Buffer } from "./../graphic/Buffer";
import { ParticleRenderer, ParticleUpdateFlags } from "./ParticleRenderer";
import { ParticleRenderer } from "./ParticleRenderer";
import { ParticleCurveMode } from "./enums/ParticleCurveMode";
import { ParticleGradientMode } from "./enums/ParticleGradientMode";
import { ParticleRenderMode } from "./enums/ParticleRenderMode";
@@ -27,40 +27,36 @@ import { RotationOverLifetimeModule } from "./modules/RotationOverLifetimeModule
import { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule";
import { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule";
import { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule";
import { ParticleBufferUtils } from "./ParticleBufferUtils";
import { ParticleCompositeCurve } from "./modules/ParticleCompositeCurve";
/**
* Particle Generator.
*/
export class ParticleGenerator {
private static _tempVector20 = new Vector2();
private static _tempVector21 = new Vector2();
private static _tempVector22 = new Vector2();
/** @internal */
private static _tempVector30 = new Vector3();
/** @internal */
private static _tempVector31 = new Vector3();
private static _tempMat = new Matrix();
/** @internal */
private static _tempColor0 = new Color();
/** @internal */
private static _tempParticleRenderers = new Array<ParticleRenderer>();
private static readonly _particleIncreaseCount = 128;
private static readonly _transformedBoundsIncreaseCount = 16;
/** Use auto random seed. */
useAutoRandomSeed = true;
/** Main module. */
@deepClone
readonly main: MainModule;
readonly main = new MainModule(this);
/** Emission module. */
@deepClone
readonly emission = new EmissionModule(this);
/** Velocity over lifetime module. */
@deepClone
readonly velocityOverLifetime: VelocityOverLifetimeModule;
readonly velocityOverLifetime = new VelocityOverLifetimeModule(this);
/** Size over lifetime module. */
@deepClone
readonly sizeOverLifetime: SizeOverLifetimeModule;
readonly sizeOverLifetime = new SizeOverLifetimeModule(this);
/** Rotation over lifetime module. */
@deepClone
readonly rotationOverLifetime = new RotationOverLifetimeModule(this);
@@ -113,14 +109,6 @@ export class ParticleGenerator {
@ignoreClone
private _instanceVertices: Float32Array;
private _randomSeed = 0;
@ignoreClone
private _transformedBoundsArray: Float32Array;
@ignoreClone
private _transformedBoundsCount = 0;
@ignoreClone
private _firstActiveTransformedBoundingBox = 0;
@ignoreClone
private _firstFreeTransformedBoundingBox = 0;
/**
* Whether the particle generator is contain alive or is still creating particles.
@@ -161,10 +149,6 @@ export class ParticleGenerator {
this._reorganizeGeometryBuffers();
this._resizeInstanceBuffer(true, ParticleGenerator._particleIncreaseCount);
this.main = new MainModule(this);
this.velocityOverLifetime = new VelocityOverLifetimeModule(this);
this.sizeOverLifetime = new SizeOverLifetimeModule(this);
this.emission.enabled = true;
}
@@ -215,8 +199,6 @@ export class ParticleGenerator {
this._firstNewElement = firstFreeElement;
this._playTime = 0;
this._firstActiveTransformedBoundingBox = this._firstFreeTransformedBoundingBox;
this.emission._reset();
}
}
@@ -262,7 +244,6 @@ export class ParticleGenerator {
* @internal
*/
_update(elapsedTime: number): void {
const lastAlive = this.isAlive;
const { main, emission } = this;
const duration = main.duration;
const lastPlayTime = this._playTime;
@@ -272,10 +253,6 @@ export class ParticleGenerator {
this._retireActiveParticles();
this._freeRetiredParticles();
if (main.simulationSpace === ParticleSimulationSpace.World) {
this._retireTransformedBounds();
}
if (emission.enabled && this._isPlaying) {
// If maxParticles is changed dynamically, currentParticleCount may be greater than maxParticles
if (this._currentParticleCount > main._maxParticleBuffer) {
@@ -284,27 +261,21 @@ export class ParticleGenerator {
this._resizeInstanceBuffer(false);
}
}
emission._emit(lastPlayTime, this._playTime);
if (!main.isLoop && this._playTime > duration) {
this._isPlaying = false;
}
}
if (this.isAlive) {
if (main.simulationSpace === ParticleSimulationSpace.World) {
this._generateTransformedBounds();
}
} else {
// Reset play time when is not playing and no active particles to avoid potential precision problems in GPU
// Reset play time when is not playing and no active particles to avoid potential precision problems in GPU
if (!this.isAlive) {
const discardTime = Math.min(emission._frameRateTime, Math.floor(this._playTime / duration) * duration);
this._playTime -= discardTime;
emission._frameRateTime -= discardTime;
}
if (this.isAlive !== lastAlive) {
this._renderer._onWorldVolumeChanged();
}
// Add new particles to vertex buffer when has wait process retired element or new particle
//
// Another choice is just add new particles to vertex buffer and render all particles ignore the retired particle in shader, especially billboards
@@ -371,7 +342,7 @@ export class ParticleGenerator {
primitive.addVertexElement(particleUtils.billboardVertexElement);
vertexBufferBindings.push(particleUtils.billboardVertexBufferBinding);
primitive.setIndexBufferBinding(particleUtils.billboardIndexBufferBinding);
this._subPrimitive.count = ParticleBufferUtils.billboardIndexCount;
this._subPrimitive.count = particleUtils.billboardIndexCount;
}
primitive.setVertexBufferBindings(vertexBufferBindings);
@@ -391,7 +362,8 @@ export class ParticleGenerator {
_resizeInstanceBuffer(isIncrease: boolean, increaseCount?: number): void {
this._instanceVertexBufferBinding?.buffer.destroy();
const stride = ParticleBufferUtils.instanceVertexStride;
const particleUtils = this._renderer.engine._particleBufferUtils;
const stride = particleUtils.instanceVertexStride;
const newParticleCount = isIncrease ? this._currentParticleCount + increaseCount : this.main._maxParticleBuffer;
const newByteLength = stride * newParticleCount;
const engine = this._renderer.engine;
@@ -408,22 +380,17 @@ export class ParticleGenerator {
const vertexBufferBinding = new VertexBufferBinding(vertexInstanceBuffer, stride);
const instanceVertices = new Float32Array(newByteLength / 4);
const lastInstanceVertices = this._instanceVertices;
if (lastInstanceVertices) {
const floatStride = ParticleBufferUtils.instanceVertexFloatStride;
const floatStride = particleUtils.instanceVertexFloatStride;
const firstFreeElement = this._firstFreeElement;
const firstRetiredElement = this._firstRetiredElement;
if (isIncrease) {
instanceVertices.set(new Float32Array(lastInstanceVertices.buffer, 0, firstFreeElement * floatStride));
const nextFreeElement = firstFreeElement + 1;
const freeEndOffset = (nextFreeElement + increaseCount) * floatStride;
instanceVertices.set(
new Float32Array(lastInstanceVertices.buffer, nextFreeElement * floatStride * 4),
freeEndOffset
);
const freeOffset = this._firstFreeElement * floatStride;
instanceVertices.set(new Float32Array(lastInstanceVertices.buffer, 0, freeOffset));
const freeEndOffset = (this._firstFreeElement + increaseCount) * floatStride;
instanceVertices.set(new Float32Array(lastInstanceVertices.buffer, freeOffset * 4), freeEndOffset);
// Maintain expanded pointers
this._firstNewElement > firstFreeElement && (this._firstNewElement += increaseCount);
@@ -534,122 +501,6 @@ export class ParticleGenerator {
_destroy(): void {
this._instanceVertexBufferBinding.buffer.destroy();
this._primitive.destroy();
this.emission._destroy();
}
/**
* @internal
*/
_updateBoundsSimulationLocal(bounds: BoundingBox): void {
const renderer = this._renderer;
// Get longest Lifetime
const maxLifetime = this.main.startLifetime._getMax();
const { _generatorBounds: generatorBounds, _transformedBounds: transformedBounds } = renderer;
if (renderer._isContainDirtyFlag(ParticleUpdateFlags.GeneratorVolume)) {
this._calculateGeneratorBounds(maxLifetime, generatorBounds);
renderer._setDirtyFlagFalse(ParticleUpdateFlags.GeneratorVolume);
}
if (renderer._isContainDirtyFlag(ParticleUpdateFlags.TransformVolume)) {
this._calculateTransformedBounds(maxLifetime, generatorBounds, transformedBounds);
renderer._setDirtyFlagFalse(ParticleUpdateFlags.TransformVolume);
}
this._addGravityToBounds(maxLifetime, transformedBounds, bounds);
}
/**
* @internal
*/
_updateBoundsSimulationWorld(bounds: BoundingBox): void {
const boundsArray = this._transformedBoundsArray;
const firstActiveElement = this._firstActiveTransformedBoundingBox;
const firstFreeElement = this._firstFreeTransformedBoundingBox;
const index = firstActiveElement * ParticleBufferUtils.boundsFloatStride;
bounds.min.copyFromArray(boundsArray, index);
bounds.max.copyFromArray(boundsArray, index + 3);
if (firstActiveElement < firstFreeElement) {
for (let i = firstActiveElement + 1; i < firstFreeElement; i++) {
this._mergeTransformedBounds(i, bounds);
}
} else {
for (let i = firstActiveElement + 1, n = this._transformedBoundsCount; i < n; i++) {
this._mergeTransformedBounds(i, bounds);
}
if (firstFreeElement > 0) {
for (let i = 0; i < firstFreeElement; i++) {
this._mergeTransformedBounds(i, bounds);
}
}
}
const maxLifetime = this.main.startLifetime._getMax();
this._addGravityToBounds(maxLifetime, bounds, bounds);
}
/**
* @internal
*/
_freeBoundsArray(): void {
this._transformedBoundsArray = null;
this._transformedBoundsCount = 0;
this._firstActiveTransformedBoundingBox = 0;
this._firstFreeTransformedBoundingBox = 0;
}
/**
* @internal
*/
_generateTransformedBounds(): void {
const renderer = this._renderer;
// Get longest Lifetime
const maxLifetime = this.main.startLifetime._getMax();
const generatorBounds = renderer._generatorBounds;
if (renderer._isContainDirtyFlag(ParticleUpdateFlags.GeneratorVolume)) {
this._calculateGeneratorBounds(maxLifetime, generatorBounds);
renderer._setDirtyFlagFalse(ParticleUpdateFlags.GeneratorVolume);
}
const { boundsFloatStride, boundsTimeOffset, boundsMaxLifetimeOffset } = ParticleBufferUtils;
const firstFreeElement = this._firstFreeTransformedBoundingBox;
if (renderer._isContainDirtyFlag(ParticleUpdateFlags.TransformVolume)) {
// Resize transformed bounds if needed
let nextFreeElement = firstFreeElement + 1;
if (nextFreeElement >= this._transformedBoundsCount) {
nextFreeElement = 0;
}
if (nextFreeElement === this._firstActiveTransformedBoundingBox) {
this._resizeTransformedBoundsArray();
nextFreeElement = firstFreeElement + 1;
}
// Generate transformed bounds
const transformedBounds = renderer._transformedBounds;
this._calculateTransformedBounds(maxLifetime, generatorBounds, transformedBounds);
const boundsOffset = firstFreeElement * boundsFloatStride;
const boundsArray = this._transformedBoundsArray;
transformedBounds.min.copyToArray(boundsArray, boundsOffset);
transformedBounds.max.copyToArray(boundsArray, boundsOffset + 3);
boundsArray[boundsOffset + boundsTimeOffset] = this._playTime;
boundsArray[boundsOffset + boundsMaxLifetimeOffset] = maxLifetime;
this._firstFreeTransformedBoundingBox = nextFreeElement;
renderer._setDirtyFlagFalse(ParticleUpdateFlags.TransformVolume);
} else {
let previousFreeElement = this._firstFreeTransformedBoundingBox - 1;
if (previousFreeElement < 0) {
previousFreeElement = this._transformedBoundsCount;
}
this._transformedBoundsArray[previousFreeElement * ParticleBufferUtils.boundsFloatStride + boundsTimeOffset] =
this._playTime;
}
}
private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, time: number): void {
@@ -661,12 +512,6 @@ export class ParticleGenerator {
const main = this.main;
// Check if can be expanded
// Using 'nextFreeElement' instead of 'freeElement' when comparing with '_firstRetiredElement'
// aids in definitively identifying the head and tail of the circular queue.
// Failure to adopt this approach may impede growth initiation
// due to the initial alignment of 'freeElement' and 'firstRetiredElement'.
if (nextFreeElement === this._firstRetiredElement) {
const increaseCount = Math.min(
ParticleGenerator._particleIncreaseCount,
@@ -688,10 +533,11 @@ export class ParticleGenerator {
rot = transform.worldRotationQuaternion;
}
const particleUtils = this._renderer.engine._particleBufferUtils;
const startSpeed = main.startSpeed.evaluate(undefined, main._startSpeedRand.random());
const instanceVertices = this._instanceVertices;
const offset = firstFreeElement * ParticleBufferUtils.instanceVertexFloatStride;
const offset = firstFreeElement * particleUtils.instanceVertexFloatStride;
// Position
instanceVertices[offset] = position.x;
@@ -699,7 +545,7 @@ export class ParticleGenerator {
instanceVertices[offset + 2] = position.z;
// Start life time
instanceVertices[offset + ParticleBufferUtils.startLifeTimeOffset] = main.startLifetime.evaluate(
instanceVertices[offset + particleUtils.startLifeTimeOffset] = main.startLifetime.evaluate(
undefined,
main._startLifeTimeRand.random()
);
@@ -710,7 +556,7 @@ export class ParticleGenerator {
instanceVertices[offset + 6] = direction.z;
// Time
instanceVertices[offset + ParticleBufferUtils.timeOffset] = time;
instanceVertices[offset + particleUtils.timeOffset] = time;
// Color
const startColor = ParticleGenerator._tempColor0;
@@ -808,12 +654,12 @@ export class ParticleGenerator {
// Simulation UV
if (this.textureSheetAnimation.enabled) {
const tillingInfo = this.textureSheetAnimation._tillingInfo;
instanceVertices[offset + ParticleBufferUtils.simulationUVOffset] = tillingInfo.x;
instanceVertices[offset + particleUtils.simulationUVOffset] = tillingInfo.x;
instanceVertices[offset + 35] = tillingInfo.y;
instanceVertices[offset + 36] = 0;
instanceVertices[offset + 37] = 0;
} else {
instanceVertices[offset + ParticleBufferUtils.simulationUVOffset] = 1;
instanceVertices[offset + particleUtils.simulationUVOffset] = 1;
instanceVertices[offset + 35] = 1;
instanceVertices[offset + 36] = 0;
instanceVertices[offset + 37] = 0;
@@ -824,17 +670,18 @@ export class ParticleGenerator {
private _retireActiveParticles(): void {
const engine = this._renderer.engine;
const particleUtils = engine._particleBufferUtils;
const frameCount = engine.time.frameCount;
const instanceVertices = this._instanceVertices;
while (this._firstActiveElement !== this._firstNewElement) {
const activeParticleOffset = this._firstActiveElement * ParticleBufferUtils.instanceVertexFloatStride;
const activeParticleTimeOffset = activeParticleOffset + ParticleBufferUtils.timeOffset;
const activeParticleOffset = this._firstActiveElement * particleUtils.instanceVertexFloatStride;
const activeParticleTimeOffset = activeParticleOffset + particleUtils.timeOffset;
const particleAge = this._playTime - instanceVertices[activeParticleTimeOffset];
// Use `Math.fround` to ensure the precision of comparison is same
if (Math.fround(particleAge) < instanceVertices[activeParticleOffset + ParticleBufferUtils.startLifeTimeOffset]) {
if (Math.fround(particleAge) < instanceVertices[activeParticleOffset + particleUtils.startLifeTimeOffset]) {
break;
}
@@ -850,12 +697,12 @@ export class ParticleGenerator {
}
private _freeRetiredParticles(): void {
const particleUtils = this._renderer.engine._particleBufferUtils;
const frameCount = this._renderer.engine.time.frameCount;
while (this._firstRetiredElement !== this._firstActiveElement) {
const offset =
this._firstRetiredElement * ParticleBufferUtils.instanceVertexFloatStride +
ParticleBufferUtils.startLifeTimeOffset;
this._firstRetiredElement * particleUtils.instanceVertexFloatStride + particleUtils.startLifeTimeOffset;
const age = frameCount - this._instanceVertices[offset];
// WebGL don't support map buffer range, so off this optimization
@@ -878,7 +725,7 @@ export class ParticleGenerator {
return;
}
const byteStride = ParticleBufferUtils.instanceVertexStride;
const byteStride = this._renderer.engine._particleBufferUtils.instanceVertexStride;
const start = firstActiveElement * byteStride;
const instanceBuffer = this._instanceVertexBufferBinding.buffer;
const dataBuffer = this._instanceVertices.buffer;
@@ -917,234 +764,4 @@ export class ParticleGenerator {
out.push(vertexBufferBinding);
return index;
}
private _resizeTransformedBoundsArray(): void {
const floatStride = ParticleBufferUtils.boundsFloatStride;
const increaseCount = ParticleGenerator._transformedBoundsIncreaseCount;
this._transformedBoundsCount += increaseCount;
const lastBoundsArray = this._transformedBoundsArray;
const boundsArray = new Float32Array(this._transformedBoundsCount * floatStride);
if (lastBoundsArray) {
const firstFreeElement = this._firstFreeTransformedBoundingBox;
boundsArray.set(new Float32Array(lastBoundsArray.buffer, 0, firstFreeElement * floatStride));
const nextFreeElement = firstFreeElement + 1;
const freeEndOffset = (nextFreeElement + increaseCount) * floatStride;
boundsArray.set(new Float32Array(lastBoundsArray.buffer, nextFreeElement * floatStride * 4), freeEndOffset);
const firstActiveElement = this._firstActiveTransformedBoundingBox;
if (firstActiveElement > firstFreeElement) {
this._firstActiveTransformedBoundingBox += increaseCount;
}
}
this._transformedBoundsArray = boundsArray;
}
private _retireTransformedBounds(): void {
const { boundsFloatStride, boundsTimeOffset, boundsMaxLifetimeOffset } = ParticleBufferUtils;
const boundsArray = this._transformedBoundsArray;
const firstFreeElement = this._firstFreeTransformedBoundingBox;
const count = this._transformedBoundsCount;
while (this._firstActiveTransformedBoundingBox !== firstFreeElement) {
const index = this._firstActiveTransformedBoundingBox * boundsFloatStride;
const age = this._playTime - boundsArray[index + boundsTimeOffset];
if (age <= boundsArray[index + boundsMaxLifetimeOffset]) {
break;
}
if (++this._firstActiveTransformedBoundingBox >= count) {
this._firstActiveTransformedBoundingBox = 0;
}
this._renderer._onWorldVolumeChanged();
}
}
private _calculateGeneratorBounds(maxLifetime: number, bounds: BoundingBox): void {
const { _tempVector30: directionMax, _tempVector31: directionMin, _tempVector20: speedMinMax } = ParticleGenerator;
const { min, max } = bounds;
const { main } = this;
// StartSpeed's impact
const { shape } = this.emission;
if (shape?.enabled) {
shape._getPositionRange(min, max);
shape._getDirectionRange(directionMin, directionMax);
} else {
min.set(0, 0, 0);
max.set(0, 0, 0);
directionMin.set(0, 0, -1);
directionMax.set(0, 0, 0);
}
this._getExtremeValueFromZero(main.startSpeed, speedMinMax);
const { x: speedMin, y: speedMax } = speedMinMax;
const { x: dirMinX, y: dirMinY, z: dirMinZ } = directionMin;
const { x: dirMaxX, y: dirMaxY, z: dirMaxZ } = directionMax;
min.set(
min.x + Math.min(dirMinX * speedMax, dirMaxX * speedMin) * maxLifetime,
min.y + Math.min(dirMinY * speedMax, dirMaxY * speedMin) * maxLifetime,
min.z + Math.min(dirMinZ * speedMax, dirMaxZ * speedMin) * maxLifetime
);
max.set(
max.x + Math.max(dirMinX * speedMin, dirMaxX * speedMax) * maxLifetime,
max.y + Math.max(dirMinY * speedMin, dirMaxY * speedMax) * maxLifetime,
max.z + Math.max(dirMinZ * speedMin, dirMaxZ * speedMax) * maxLifetime
);
// StartSize's impact
let maxSize = main.startSize._getMax();
if (main.startSize3D) {
const startSizeYMax = main.startSizeY._getMax();
if (
this._renderer.renderMode === ParticleRenderMode.Billboard ||
ParticleRenderMode.StretchBillboard ||
ParticleRenderMode.HorizontalBillboard
) {
maxSize = Math.max(maxSize, startSizeYMax);
} else {
const startSizeZMax = main.startSizeZ._getMax();
maxSize = Math.max(maxSize, startSizeYMax, startSizeZMax);
}
}
// Use diagonal for potential rotation
maxSize *= 1.414;
// SizeOverLifetime impact
const { sizeOverLifetime } = this;
if (sizeOverLifetime.enabled) {
let maxSizeOverLifetime = sizeOverLifetime.size._getMax();
if (sizeOverLifetime.separateAxes) {
const maxSizeOverLifetimeY = sizeOverLifetime.sizeY._getMax();
const maxSizeOverLifetimeZ = sizeOverLifetime.sizeZ._getMax();
maxSizeOverLifetime = Math.max(maxSizeOverLifetime, maxSizeOverLifetimeY, maxSizeOverLifetimeZ);
}
maxSize *= maxSizeOverLifetime;
}
min.set(min.x - maxSize, min.y - maxSize, min.z - maxSize);
max.set(max.x + maxSize, max.y + maxSize, max.z + maxSize);
}
private _mergeTransformedBounds(index: number, bounds: BoundingBox): void {
const { min, max } = bounds;
const boundsArray = this._transformedBoundsArray;
const offset = index * ParticleBufferUtils.boundsFloatStride;
min.set(
Math.min(min.x, boundsArray[offset]),
Math.min(min.y, boundsArray[offset + 1]),
Math.min(min.z, boundsArray[offset + 2])
);
max.set(
Math.max(max.x, boundsArray[offset + 3]),
Math.max(max.y, boundsArray[offset + 4]),
Math.max(max.z, boundsArray[offset + 5])
);
}
private _calculateTransformedBounds(maxLifetime: number, origin: BoundingBox, out: BoundingBox): void {
const {
_tempVector20: velMinMaxX,
_tempVector21: velMinMaxY,
_tempVector22: velMinMaxZ,
_tempMat: rotateMat
} = ParticleGenerator;
const { transform } = this._renderer.entity;
const worldPosition = transform.worldPosition;
Matrix.rotationQuaternion(transform.worldRotationQuaternion, rotateMat);
const { min: originMin, max: originMax } = origin;
const { min, max } = out;
const { velocityOverLifetime } = this;
if (velocityOverLifetime.enabled) {
this._getExtremeValueFromZero(velocityOverLifetime.velocityX, velMinMaxX);
this._getExtremeValueFromZero(velocityOverLifetime.velocityY, velMinMaxY);
this._getExtremeValueFromZero(velocityOverLifetime.velocityZ, velMinMaxZ);
if (velocityOverLifetime.space === ParticleSimulationSpace.Local) {
min.set(
originMin.x + velMinMaxX.x * maxLifetime,
originMin.y + velMinMaxY.x * maxLifetime,
originMin.z + velMinMaxZ.x * maxLifetime
);
max.set(
originMax.x + velMinMaxX.y * maxLifetime,
originMax.y + velMinMaxY.y * maxLifetime,
originMax.z + velMinMaxZ.y * maxLifetime
);
out.transform(rotateMat);
} else {
out.transform(rotateMat);
min.set(
originMin.x + velMinMaxX.x * maxLifetime,
originMin.y + velMinMaxY.x * maxLifetime,
originMin.z + velMinMaxZ.x * maxLifetime
);
max.set(
originMax.x + velMinMaxX.y * maxLifetime,
originMax.y + velMinMaxY.y * maxLifetime,
originMax.z + velMinMaxZ.y * maxLifetime
);
}
} else {
BoundingBox.transform(origin, rotateMat, out);
}
min.add(worldPosition);
max.add(worldPosition);
}
private _addGravityToBounds(maxLifetime: number, origin: BoundingBox, out: BoundingBox): void {
const { min: originMin, max: originMax } = origin;
const modifierMinMax = ParticleGenerator._tempVector20;
// Gravity modifier impact
this._getExtremeValueFromZero(this.main.gravityModifier, modifierMinMax);
const { x, y, z } = this._renderer.scene.physics.gravity;
const coefficient = 0.5 * maxLifetime * maxLifetime;
const minGravityEffect = modifierMinMax.x * coefficient;
const maxGravityEffect = modifierMinMax.y * coefficient;
const gravityEffectMinX = x * minGravityEffect;
const gravityEffectMaxX = x * maxGravityEffect;
const gravityEffectMinY = y * minGravityEffect;
const gravityEffectMaxY = y * maxGravityEffect;
const gravityEffectMinZ = z * minGravityEffect;
const gravityEffectMaxZ = z * maxGravityEffect;
out.min.set(
Math.min(gravityEffectMinX, gravityEffectMaxX) + originMin.x,
Math.min(gravityEffectMinY, gravityEffectMaxY) + originMin.y,
Math.min(gravityEffectMinZ, gravityEffectMaxZ) + originMin.z
);
out.max.set(
Math.max(gravityEffectMinX, gravityEffectMaxX) + originMax.x,
Math.max(gravityEffectMinY, gravityEffectMaxY) + originMax.y,
Math.max(gravityEffectMinZ, gravityEffectMaxZ) + originMax.z
);
}
private _getExtremeValueFromZero(curve: ParticleCompositeCurve, out: Vector2): void {
curve._getMinMax(out);
out.x = Math.min(0, out.x);
out.y = Math.max(0, out.y);
}
}

View File

@@ -1,17 +1,15 @@
import { Vector3, BoundingBox } from "@galacean/engine-math";
import { BoundingBox, Vector3 } from "@galacean/engine-math";
import { Entity } from "../Entity";
import { RenderContext } from "../RenderPipeline/RenderContext";
import { Renderer, RendererUpdateFlags } from "../Renderer";
import { GLCapabilityType } from "../base/Constant";
import { deepClone, ignoreClone, shallowClone } from "../clone/CloneManager";
import { deepClone, shallowClone } from "../clone/CloneManager";
import { ModelMesh } from "../mesh/ModelMesh";
import { ShaderMacro } from "../shader/ShaderMacro";
import { ShaderProperty } from "../shader/ShaderProperty";
import { ParticleGenerator } from "./ParticleGenerator";
import { ParticleRenderMode } from "./enums/ParticleRenderMode";
import { ParticleStopMode } from "./enums/ParticleStopMode";
import { ParticleGenerator } from "./ParticleGenerator";
import { ParticleSimulationSpace } from "./enums/ParticleSimulationSpace";
import { TransformModifyFlags } from "../Transform";
/**
* Particle Renderer Component.
@@ -30,7 +28,7 @@ export class ParticleRenderer extends Renderer {
/** Particle generator. */
@deepClone
readonly generator: ParticleGenerator;
readonly generator = new ParticleGenerator(this);
/** Specifies how much particles stretch depending on their velocity. */
velocityScale = 0;
/** How much are the particles stretched in their direction of motion, defined as the length of the particle compared to its width. */
@@ -39,13 +37,6 @@ export class ParticleRenderer extends Renderer {
@shallowClone
pivot = new Vector3();
/** @internal */
@ignoreClone
_generatorBounds = new BoundingBox();
/** @internal */
@ignoreClone
_transformedBounds = new BoundingBox();
private _renderMode: ParticleRenderMode;
private _currentRenderModeMacro: ShaderMacro;
private _mesh: ModelMesh;
@@ -124,15 +115,12 @@ export class ParticleRenderer extends Renderer {
*/
constructor(entity: Entity) {
super(entity);
this._onGeneratorParamsChanged = this._onGeneratorParamsChanged.bind(this);
this.generator = new ParticleGenerator(this);
this._currentRenderModeMacro = ParticleRenderer._billboardModeMacro;
this.shaderData.enableMacro(ParticleRenderer._billboardModeMacro);
this._supportInstancedArrays = this.engine._hardwareRenderer.canIUse(GLCapabilityType.instancedArrays);
this._onGeneratorParamsChanged();
this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume;
}
/**
@@ -174,26 +162,8 @@ export class ParticleRenderer extends Renderer {
* @internal
*/
protected override _updateBounds(worldBounds: BoundingBox): void {
const { generator } = this;
// Using `isAlive` instead of `firstActiveElement !== firstFreeElement`
// Because `firstActiveElement !== firstFreeElement` will cause bounds is merely a point, and cannot be culled forever
// Must generate bounds even when there is no particle but in play state
if (!generator.isAlive) {
const worldPosition = this.entity.transform.worldPosition;
worldBounds.min.copyFrom(worldPosition);
worldBounds.max.copyFrom(worldPosition);
return;
}
if (generator.main.simulationSpace === ParticleSimulationSpace.Local) {
generator._updateBoundsSimulationLocal(worldBounds);
} else {
if (this._isContainDirtyFlag(ParticleUpdateFlags.TransformVolume)) {
generator._generateTransformedBounds();
this._setDirtyFlagFalse(ParticleUpdateFlags.TransformVolume);
}
generator._updateBoundsSimulationWorld(worldBounds);
}
worldBounds.min.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
worldBounds.max.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
}
/**
@@ -235,51 +205,4 @@ export class ParticleRenderer extends Renderer {
}
this.generator._destroy();
}
/**
* @internal
*/
_isContainDirtyFlag(type: number): boolean {
return (this._dirtyUpdateFlag & type) != 0;
}
/**
* @internal
*/
_setDirtyFlagFalse(type: number): void {
this._dirtyUpdateFlag &= ~type;
}
/**
* @internal
*/
_onWorldVolumeChanged(): void {
this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume;
}
/**
* @internal
*/
@ignoreClone
_onGeneratorParamsChanged(): void {
this._dirtyUpdateFlag |=
ParticleUpdateFlags.GeneratorVolume | ParticleUpdateFlags.TransformVolume | RendererUpdateFlags.WorldVolume;
}
/**
* @internal
*/
override _onTransformChanged(type: TransformModifyFlags): void {
this._dirtyUpdateFlag |= ParticleUpdateFlags.TransformVolume | RendererUpdateFlags.WorldVolume;
}
}
/**
* @internal
*/
export enum ParticleUpdateFlags {
/** On World Transform Changed */
TransformVolume = 0x2,
/** On Generator Bounds Related Params Changed */
GeneratorVolume = 0x4
}

View File

@@ -16,9 +16,10 @@ export class EmissionModule extends ParticleGeneratorModule {
/** The rate at which the emitter spawns new particles over distance. */
@deepClone
rateOverDistance: ParticleCompositeCurve = new ParticleCompositeCurve(0);
/** The shape of the emitter. */
@deepClone
_shape: BaseShape;
shape: BaseShape;
/** @internal */
@ignoreClone
_shapeRand = new Rand(0, ParticleRandomSubSeeds.Shape);
@@ -33,26 +34,6 @@ export class EmissionModule extends ParticleGeneratorModule {
@ignoreClone
private _burstRand: Rand = new Rand(0, ParticleRandomSubSeeds.Burst);
/**
* The shape of the emitter.
*/
get shape() {
return this._shape;
}
set shape(value: BaseShape) {
const lastShape = this._shape;
if (value !== lastShape) {
this._shape = value;
const renderer = this._generator._renderer;
lastShape?._unRegisterOnValueChanged(renderer._onGeneratorParamsChanged);
value?._registerOnValueChanged(renderer._onGeneratorParamsChanged);
renderer._onGeneratorParamsChanged();
}
}
/**
* Gets the burst array.
*/
@@ -121,13 +102,6 @@ export class EmissionModule extends ParticleGeneratorModule {
this._currentBurstIndex = 0;
}
/**
* @internal
*/
_destroy(): void {
this._shape?._unRegisterOnValueChanged(this._generator._renderer._onGeneratorParamsChanged);
}
private _emitByRateOverTime(playTime: number): void {
const ratePerSeconds = this.rateOverTime.evaluate(undefined, undefined);
if (ratePerSeconds > 0) {

View File

@@ -9,7 +9,6 @@ import { ParticleScaleMode } from "../enums/ParticleScaleMode";
import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace";
import { ParticleCompositeCurve } from "./ParticleCompositeCurve";
import { ParticleCompositeGradient } from "./ParticleCompositeGradient";
import { TransformModifyFlags } from "../../Transform";
export class MainModule implements ICustomClone {
private static _tempVector40 = new Vector4();
@@ -32,6 +31,24 @@ export class MainModule implements ICustomClone {
/** Start delay in seconds. */
@deepClone
startDelay = new ParticleCompositeCurve(0);
/** The initial lifetime of particles when emitted. */
@deepClone
startLifetime = new ParticleCompositeCurve(5);
/** The initial speed of particles when the Particle Generator first spawns them. */
@deepClone
startSpeed = new ParticleCompositeCurve(5);
/** A flag to enable specifying particle size individually for each axis. */
startSize3D = false;
/** The initial size of particles along the x-axis when the Particle Generator first spawns them. */
@deepClone
startSizeX = new ParticleCompositeCurve(1);
/** The initial size of particles along the y-axis when the Particle Generator first spawns them. */
@deepClone
startSizeY = new ParticleCompositeCurve(1);
/** The initial size of particles along the z-axis when the Particle Generator first spawns them. */
@deepClone
startSizeZ = new ParticleCompositeCurve(1);
/** A flag to enable 3D particle rotation, when disabled, only `startRotationZ` is used. */
startRotation3D = false;
@@ -51,6 +68,10 @@ export class MainModule implements ICustomClone {
@deepClone
startColor = new ParticleCompositeGradient(new Color(1, 1, 1, 1));
/** A scale that this Particle Generator applies to gravity, defined by Physics.gravity. */
@deepClone
gravityModifier = new ParticleCompositeCurve(0);
/** This selects the space in which to simulate particles. It can be either world or local space. */
simulationSpace = ParticleSimulationSpace.Local;
/** Override the default playback speed of the Particle Generator. */
simulationSpeed = 1.0;
/** Control how the Particle Generator applies its Transform component to the particles it emits. */
@@ -80,149 +101,11 @@ export class MainModule implements ICustomClone {
@ignoreClone
readonly _gravityModifierRand = new Rand(0, ParticleRandomSubSeeds.GravityModifier);
@deepClone
private _startLifetime: ParticleCompositeCurve;
@deepClone
private _startSpeed: ParticleCompositeCurve;
private _startSize3D = false;
@deepClone
private _startSizeX: ParticleCompositeCurve;
@deepClone
private _startSizeY: ParticleCompositeCurve;
@deepClone
private _startSizeZ: ParticleCompositeCurve;
@deepClone
private _gravityModifier: ParticleCompositeCurve;
private _simulationSpace = ParticleSimulationSpace.Local;
@ignoreClone
private _generator: ParticleGenerator;
@ignoreClone
private _gravity = new Vector3();
/**
* The initial lifetime of particles when emitted.
*/
get startLifetime(): ParticleCompositeCurve {
return this._startLifetime;
}
set startLifetime(value: ParticleCompositeCurve) {
const lastValue = this._startLifetime;
if (value !== lastValue) {
this._startLifetime = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* The initial speed of particles when the Particle Generator first spawns them.
*/
get startSpeed(): ParticleCompositeCurve {
return this._startSpeed;
}
set startSpeed(value: ParticleCompositeCurve) {
const lastValue = this._startSpeed;
if (value !== lastValue) {
this._startSpeed = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* A flag to enable specifying particle size individually for each axis.
*/
get startSize3D(): boolean {
return this._startSize3D;
}
set startSize3D(value: boolean) {
if (value !== this._startSize3D) {
this._startSize3D = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}
/**
* The initial size of particles along the x-axis when the Particle Generator first spawns them.
*/
get startSizeX(): ParticleCompositeCurve {
return this._startSizeX;
}
set startSizeX(value: ParticleCompositeCurve) {
const lastValue = this._startSizeX;
if (value !== lastValue) {
this._startSizeX = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* The initial size of particles along the y-axis when the Particle Generator first spawns them.
*/
get startSizeY(): ParticleCompositeCurve {
return this._startSizeY;
}
set startSizeY(value: ParticleCompositeCurve) {
const lastValue = this._startSizeY;
if (value !== lastValue) {
this._startSizeY = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* The initial size of particles along the z-axis when the Particle Generator first spawns them.
*/
get startSizeZ(): ParticleCompositeCurve {
return this._startSizeZ;
}
set startSizeZ(value: ParticleCompositeCurve) {
const lastValue = this._startSizeZ;
if (value !== lastValue) {
this._startSizeZ = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* A scale that this Particle Generator applies to gravity, defined by Physics.gravity.
*/
get gravityModifier(): ParticleCompositeCurve {
return this._gravityModifier;
}
set gravityModifier(value: ParticleCompositeCurve) {
const lastValue = this._gravityModifier;
if (value !== lastValue) {
this._gravityModifier = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* This selects the space in which to simulate particles. It can be either world or local space.
*/
get simulationSpace(): ParticleSimulationSpace {
return this._simulationSpace;
}
set simulationSpace(value: ParticleSimulationSpace) {
if (value !== this._simulationSpace) {
this._simulationSpace = value;
const generator = this._generator;
generator._renderer._onTransformChanged(TransformModifyFlags.WorldMatrix);
if (value === ParticleSimulationSpace.Local) {
generator._freeBoundsArray();
}
}
}
/**
* Max particles count.
*/
@@ -250,13 +133,6 @@ export class MainModule implements ICustomClone {
*/
constructor(generator: ParticleGenerator) {
this._generator = generator;
this.startLifetime = new ParticleCompositeCurve(5);
this.startSpeed = new ParticleCompositeCurve(5);
this.startSizeX = new ParticleCompositeCurve(1);
this.startSizeY = new ParticleCompositeCurve(1);
this.startSizeZ = new ParticleCompositeCurve(1);
this.gravityModifier = new ParticleCompositeCurve(0);
}
/**
@@ -337,16 +213,5 @@ export class MainModule implements ICustomClone {
*/
_cloneTo(target: MainModule): void {
target.maxParticles = this.maxParticles;
if (target._simulationSpace === ParticleSimulationSpace.World) {
target._generator._generateTransformedBounds();
}
}
private _onCompositeCurveChange(lastValue: ParticleCompositeCurve, value: ParticleCompositeCurve): void {
const renderer = this._generator._renderer;
lastValue?._unRegisterOnValueChanged(renderer._onGeneratorParamsChanged);
value?._registerOnValueChanged(renderer._onGeneratorParamsChanged);
renderer._onGeneratorParamsChanged();
}
}

View File

@@ -1,95 +1,23 @@
import { Vector2 } from "@galacean/engine-math";
import { deepClone, ignoreClone } from "../../clone/CloneManager";
import { deepClone } from "../../clone/CloneManager";
import { ParticleCurveMode } from "../enums/ParticleCurveMode";
import { CurveKey, ParticleCurve } from "./ParticleCurve";
import { UpdateFlagManager } from "../../UpdateFlagManager";
import { ParticleCurve } from "./ParticleCurve";
/**
* Particle composite curve.
*/
export class ParticleCompositeCurve {
@ignoreClone
private _updateManager = new UpdateFlagManager();
private _mode = ParticleCurveMode.Constant;
private _constantMin = 0;
private _constantMax = 0;
/** The curve mode. */
mode: ParticleCurveMode = ParticleCurveMode.Constant;
/** The min constant value used by the curve if mode is set to `TwoConstants`.*/
constantMin: number = 0;
/** The max constant value used by the curve if mode is set to `TwoConstants`.*/
constantMax: number = 0;
/** The min curve used by the curve if mode is set to `TwoCurves`. */
@deepClone
private _curveMin: ParticleCurve;
curveMin: ParticleCurve;
/** The max curve used by the curve if mode is set to `TwoCurves`. */
@deepClone
private _curveMax: ParticleCurve;
@ignoreClone
private _updateDispatch: () => void;
/**
* The curve mode.
*/
get mode(): ParticleCurveMode {
return this._mode;
}
set mode(value: ParticleCurveMode) {
if (value !== this._mode) {
this._mode = value;
this._updateDispatch();
}
}
/**
* The min constant value used by the curve if mode is set to `TwoConstants`.
*/
get constantMin(): number {
return this._constantMin;
}
set constantMin(value: number) {
if (value !== this._constantMin) {
this._constantMin = value;
this._updateDispatch();
}
}
/**
* The max constant value used by the curve if mode is set to `TwoConstants`.
*/
get constantMax(): number {
return this._constantMax;
}
set constantMax(value: number) {
if (value !== this._constantMax) {
this._constantMax = value;
this._updateDispatch();
}
}
/**
* The min curve used by the curve if mode is set to `TwoCurves`.
*/
get curveMin(): ParticleCurve {
return this._curveMin;
}
set curveMin(value: ParticleCurve) {
const lastCurve = this._curveMin;
if (value !== lastCurve) {
this._curveMin = value;
this._onCurveChange(lastCurve, value);
}
}
/**
* The max curve used by the curve if mode is set to `TwoCurves`.
*/
get curveMax(): ParticleCurve {
return this._curveMax;
}
set curveMax(value: ParticleCurve) {
const lastCurve = this._curveMax;
if (value !== lastCurve) {
this._curveMax = value;
this._onCurveChange(lastCurve, value);
}
}
curveMax: ParticleCurve;
/**
* The constant value used by the curve if mode is set to `Constant`.
@@ -140,7 +68,6 @@ export class ParticleCompositeCurve {
constructor(curveMin: ParticleCurve, curveMax: ParticleCurve);
constructor(constantOrCurve: number | ParticleCurve, constantMaxOrCurveMax?: number | ParticleCurve) {
this._updateDispatch = this._updateManager.dispatch.bind(this._updateManager);
if (typeof constantOrCurve === "number") {
if (constantMaxOrCurveMax) {
this.constantMin = constantOrCurve;
@@ -179,99 +106,4 @@ export class ParticleCompositeCurve {
break;
}
}
/**
* @internal
*/
_getMax(): number {
switch (this.mode) {
case ParticleCurveMode.Constant:
return this.constantMax;
case ParticleCurveMode.TwoConstants:
return Math.max(this.constantMin, this.constantMax);
case ParticleCurveMode.Curve:
return this._getMaxKeyValue(this.curveMax?.keys);
case ParticleCurveMode.TwoCurves:
const min = this._getMaxKeyValue(this.curveMin?.keys);
const max = this._getMaxKeyValue(this.curveMax?.keys);
return min > max ? min : max;
}
}
/**
* @internal
*/
_getMinMax(out: Vector2): void {
switch (this.mode) {
case ParticleCurveMode.Constant:
out.x = out.y = this.constantMax;
break;
case ParticleCurveMode.TwoConstants:
out.set(Math.min(this.constantMin, this.constantMax), Math.max(this.constantMin, this.constantMax));
break;
case ParticleCurveMode.Curve:
out.set(this._getMinKeyValue(this.curveMax?.keys), this._getMaxKeyValue(this.curveMax?.keys));
break;
case ParticleCurveMode.TwoCurves:
const minCurveMax = this._getMinKeyValue(this.curveMax?.keys);
const minCurveMin = this._getMinKeyValue(this.curveMin?.keys);
const maxCurveMax = this._getMaxKeyValue(this.curveMax?.keys);
const maxCurveMin = this._getMaxKeyValue(this.curveMin?.keys);
const min = minCurveMax < minCurveMin ? minCurveMax : minCurveMin;
const max = maxCurveMax > maxCurveMin ? maxCurveMax : maxCurveMin;
out.set(min, max);
break;
}
}
/**
* @internal
*/
_registerOnValueChanged(listener: () => void): void {
this._updateManager.addListener(listener);
}
/**
* @internal
*/
_unRegisterOnValueChanged(listener: () => void): void {
this._updateManager.removeListener(listener);
}
private _getMaxKeyValue(keys: ReadonlyArray<CurveKey>): number {
let max = undefined;
const count = keys?.length ?? 0;
if (count > 0) {
max = keys[0].value;
for (let i = 1; i < count; i++) {
const value = keys[i].value;
max = Math.max(max, value);
}
}
return max;
}
private _getMinKeyValue(keys: ReadonlyArray<CurveKey>): number {
let min = undefined;
const count = keys?.length ?? 0;
if (count > 0) {
min = keys[0].value;
for (let i = 1; i < count; i++) {
const value = keys[i].value;
min = Math.min(min, value);
}
}
return min;
}
private _onCurveChange(lastValue: ParticleCurve, value: ParticleCurve) {
const dispatch = this._updateDispatch;
lastValue?._unRegisterOnValueChanged(dispatch);
value?._registerOnValueChanged(dispatch);
dispatch();
}
}

View File

@@ -1,19 +1,14 @@
import { UpdateFlagManager } from "../../UpdateFlagManager";
import { deepClone, ignoreClone } from "../../clone/CloneManager";
/**
* Particle curve.
*/
export class ParticleCurve {
@ignoreClone
private _updateManager = new UpdateFlagManager();
@deepClone
private _keys = new Array<CurveKey>();
private _keys: CurveKey[] = [];
@ignoreClone
private _typeArray: Float32Array;
private _typeArrayDirty = false;
@ignoreClone
private _updateDispatch: () => void;
private _typeArrayDirty: boolean = false;
/**
* The keys of the curve.
@@ -27,8 +22,6 @@ export class ParticleCurve {
* @param keys - The keys of the curve
*/
constructor(...keys: CurveKey[]) {
this._updateDispatch = this._updateManager.dispatch.bind(this._updateManager);
for (let i = 0, n = keys.length; i < n; i++) {
const key = keys[i];
this.addKey(key);
@@ -57,8 +50,6 @@ export class ParticleCurve {
const key = typeof timeOrKey === "number" ? new CurveKey(timeOrKey, value) : timeOrKey;
this._addKey(keys, key);
key._registerOnValueChanged(this._updateDispatch);
this._updateDispatch();
this._typeArrayDirty = true;
}
@@ -69,9 +60,6 @@ export class ParticleCurve {
removeKey(index: number): void {
this._keys.splice(index, 1);
this._typeArrayDirty = true;
const removeKey = this._keys[index];
removeKey._unRegisterOnValueChanged(this._updateDispatch);
this._updateDispatch();
}
/**
@@ -104,20 +92,6 @@ export class ParticleCurve {
return typeArray;
}
/**
* @internal
*/
_registerOnValueChanged(listener: () => void): void {
this._updateManager.addListener(listener);
}
/**
* @internal
*/
_unRegisterOnValueChanged(listener: () => void): void {
this._updateManager.removeListener(listener);
}
private _addKey(keys: CurveKey[], key: CurveKey): void {
const count = keys.length;
const time = key.time;
@@ -136,58 +110,13 @@ export class ParticleCurve {
* The key of the curve.
*/
export class CurveKey {
@ignoreClone
private _updateManager = new UpdateFlagManager();
private _time: number;
private _value: number;
/**
* The key time.
*/
get time(): number {
return this._time;
}
set time(value: number) {
if (value !== this._time) {
this._time = value;
this._updateManager.dispatch();
}
}
/**
* The key value.
*/
get value(): number {
return this._value;
}
set value(value: number) {
if (value !== this._value) {
this._value = value;
this._updateManager.dispatch();
}
}
/**
* Create a new key.
*/
constructor(time: number, value: number) {
this._time = time;
this._value = value;
}
/**
* @internal
*/
_registerOnValueChanged(listener: () => void): void {
this._updateManager.addListener(listener);
}
/**
* @internal
*/
_unRegisterOnValueChanged(listener: () => void): void {
this._updateManager.removeListener(listener);
}
constructor(
/** The key time. */
public time: number,
/** The key value. */
public value: number
) {}
}

View File

@@ -6,22 +6,12 @@ import { ParticleGenerator } from "../ParticleGenerator";
* Particle generator module.
*/
export abstract class ParticleGeneratorModule {
/** Specifies whether the module is enabled or not. */
enabled: boolean = false;
@ignoreClone
protected _generator: ParticleGenerator;
protected _enabled: boolean = false;
/**
* Specifies whether the module is enabled or not.
*/
get enabled(): boolean {
return this._enabled;
}
set enabled(value: boolean) {
this._enabled = value;
}
/**
* @internal
*/

View File

@@ -215,12 +215,10 @@ export class ParticleGradient {
keys.splice(index, 1);
}
@ignoreClone
private _setColorTypeArrayDirty(): void {
this._colorTypeArrayDirty = true;
}
@ignoreClone
private _setAlphaTypeArrayDirty(): void {
this._alphaTypeArrayDirty = true;
}

View File

@@ -2,7 +2,6 @@ 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 { ParticleCompositeCurve } from "./ParticleCompositeCurve";
import { CurveKey, ParticleCurve } from "./ParticleCurve";
@@ -23,13 +22,17 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
static readonly _maxCurveYProperty = ShaderProperty.getByName("renderer_SOLMaxCurveY");
static readonly _maxCurveZProperty = ShaderProperty.getByName("renderer_SOLMaxCurveZ");
private _separateAxes = false;
/** Specifies whether the Size is separate on each axis. */
separateAxes = false;
/** Size curve over lifetime for x axis. */
@deepClone
private _sizeX: ParticleCompositeCurve;
sizeX = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
/** Size curve over lifetime for y axis. */
@deepClone
private _sizeY: ParticleCompositeCurve;
sizeY = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
/** Size curve over lifetime for z axis. */
@deepClone
private _sizeZ: ParticleCompositeCurve;
sizeZ = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
@ignoreClone
private _enableSeparateMacro: ShaderMacro;
@@ -38,65 +41,6 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
@ignoreClone
private _isRandomTwoMacro: ShaderMacro;
/**
* Specifies whether the Size is separate on each axis.
*/
set separateAxes(value: boolean) {
if (value !== this._separateAxes) {
this._separateAxes = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}
get separateAxes(): boolean {
return this._separateAxes;
}
/**
* Size curve over lifetime for x axis.
*/
get sizeX(): ParticleCompositeCurve {
return this._sizeX;
}
set sizeX(value: ParticleCompositeCurve) {
const lastValue = this._sizeX;
if (value !== lastValue) {
this._sizeX = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Size curve over lifetime for y axis.
*/
get sizeY(): ParticleCompositeCurve {
return this._sizeY;
}
set sizeY(value: ParticleCompositeCurve) {
const lastValue = this._sizeY;
if (value !== lastValue) {
this._sizeY = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Size curve over lifetime for z axis.
*/
get sizeZ(): ParticleCompositeCurve {
return this._sizeZ;
}
set sizeZ(value: ParticleCompositeCurve) {
const lastValue = this._sizeZ;
if (value !== lastValue) {
this._sizeZ = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Size curve over lifetime.
*/
@@ -108,14 +52,6 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
this.sizeX = value;
}
constructor(generator: ParticleGenerator) {
super(generator);
this.sizeX = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
this.sizeY = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
this.sizeZ = new ParticleCompositeCurve(new ParticleCurve(new CurveKey(0, 0), new CurveKey(1, 1)));
}
/**
* @internal
*/
@@ -169,11 +105,4 @@ export class SizeOverLifetimeModule extends ParticleGeneratorModule {
this._isCurveMacro = this._enableMacro(shaderData, this._isCurveMacro, isCurveMacro);
this._isRandomTwoMacro = this._enableMacro(shaderData, this._isRandomTwoMacro, isRandomTwoMacro);
}
private _onCompositeCurveChange(lastValue: ParticleCompositeCurve, value: ParticleCompositeCurve): void {
const renderer = this._generator._renderer;
lastValue?._unRegisterOnValueChanged(renderer._onGeneratorParamsChanged);
value?._registerOnValueChanged(renderer._onGeneratorParamsChanged);
renderer._onGeneratorParamsChanged();
}
}

View File

@@ -8,7 +8,6 @@ import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds";
import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace";
import { ParticleCompositeCurve } from "./ParticleCompositeCurve";
import { ParticleGeneratorModule } from "./ParticleGeneratorModule";
import { ParticleGenerator } from "../ParticleGenerator";
/**
* Velocity over lifetime module.
@@ -29,6 +28,19 @@ export class VelocityOverLifetimeModule extends ParticleGeneratorModule {
static readonly _maxGradientZProperty = ShaderProperty.getByName("renderer_VOLMaxGradientZ");
static readonly _spaceProperty = ShaderProperty.getByName("renderer_VOLSpace");
/** Velocity over lifetime for x axis. */
@deepClone
velocityX = new ParticleCompositeCurve(0);
/** Velocity over lifetime for z axis. */
@deepClone
velocityY = new ParticleCompositeCurve(0);
/** Velocity over lifetime for z axis. */
@deepClone
velocityZ = new ParticleCompositeCurve(0);
/** Velocity space. */
space = ParticleSimulationSpace.Local;
/** @internal */
@ignoreClone
_velocityRand = new Rand(0, ParticleRandomSubSeeds.VelocityOverLifetime);
@@ -40,92 +52,6 @@ export class VelocityOverLifetimeModule extends ParticleGeneratorModule {
@ignoreClone
private _velocityMacro: ShaderMacro;
@deepClone
private _velocityX: ParticleCompositeCurve;
@deepClone
private _velocityY: ParticleCompositeCurve;
@deepClone
private _velocityZ: ParticleCompositeCurve;
private _space = ParticleSimulationSpace.Local;
/**
* Velocity over lifetime for x axis.
*/
get velocityX(): ParticleCompositeCurve {
return this._velocityX;
}
set velocityX(value: ParticleCompositeCurve) {
const lastValue = this._velocityX;
if (value !== lastValue) {
this._velocityX = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Velocity over lifetime for y axis.
*/
get velocityY(): ParticleCompositeCurve {
return this._velocityY;
}
set velocityY(value: ParticleCompositeCurve) {
const lastValue = this._velocityY;
if (value !== lastValue) {
this._velocityY = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Velocity over lifetime for z axis.
*/
get velocityZ(): ParticleCompositeCurve {
return this._velocityZ;
}
set velocityZ(value: ParticleCompositeCurve) {
const lastValue = this._velocityZ;
if (value !== lastValue) {
this._velocityZ = value;
this._onCompositeCurveChange(lastValue, value);
}
}
/**
* Velocity space.
*/
get space(): ParticleSimulationSpace {
return this._space;
}
set space(value: ParticleSimulationSpace) {
if (value !== this._space) {
this._space = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}
override get enabled(): boolean {
return this._enabled;
}
override set enabled(value: boolean) {
if (value !== this._enabled) {
this._enabled = value;
this._generator._renderer._onGeneratorParamsChanged();
}
}
constructor(generator: ParticleGenerator) {
super(generator);
this.velocityX = new ParticleCompositeCurve(0);
this.velocityY = new ParticleCompositeCurve(0);
this.velocityZ = new ParticleCompositeCurve(0);
}
/**
* @internal
*/
@@ -196,11 +122,4 @@ export class VelocityOverLifetimeModule extends ParticleGeneratorModule {
_resetRandomSeed(seed: number): void {
this._velocityRand.reset(seed, ParticleRandomSubSeeds.VelocityOverLifetime);
}
private _onCompositeCurveChange(lastValue: ParticleCompositeCurve, value: ParticleCompositeCurve): void {
const renderer = this._generator._renderer;
lastValue?._unRegisterOnValueChanged(renderer._onGeneratorParamsChanged);
value?._registerOnValueChanged(renderer._onGeneratorParamsChanged);
renderer._onGeneratorParamsChanged();
}
}

View File

@@ -1,7 +1,5 @@
import { Rand, Vector3 } from "@galacean/engine-math";
import { ParticleShapeType } from "./enums/ParticleShapeType";
import { UpdateFlagManager } from "../../../UpdateFlagManager";
import { ignoreClone } from "../../../clone/CloneManager";
/**
* Base class for all particle shapes.
@@ -9,67 +7,15 @@ import { ignoreClone } from "../../../clone/CloneManager";
export abstract class BaseShape {
/** The type of shape to emit particles from. */
abstract readonly shapeType: ParticleShapeType;
@ignoreClone
protected _updateManager = new UpdateFlagManager();
private _enabled = true;
private _randomDirectionAmount = 0;
/**
* Specifies whether the ShapeModule is enabled or disabled.
*/
get enabled(): boolean {
return this._enabled;
}
set enabled(value: boolean) {
if (value !== this._enabled) {
this._enabled = value;
this._updateManager.dispatch();
}
}
/**
* Randomizes the starting direction of particles.
*/
get randomDirectionAmount(): number {
return this._randomDirectionAmount;
}
set randomDirectionAmount(value: number) {
if (value !== this._randomDirectionAmount) {
this._randomDirectionAmount = value;
this._updateManager.dispatch();
}
}
/** Specifies whether the ShapeModule is enabled or disabled. */
enabled: boolean = true;
/** Randomizes the starting direction of particles. */
randomDirectionAmount: number = 0;
/**
* @internal
*/
_registerOnValueChanged(listener: () => void): void {
this._updateManager.addListener(listener);
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
throw new Error("BaseShape: must override it.");
}
/**
* @internal
*/
_unRegisterOnValueChanged(listener: () => void): void {
this._updateManager.removeListener(listener);
}
/**
* @internal
*/
abstract _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void;
/**
* @internal
*/
abstract _getDirectionRange(outMin: Vector3, outMax: Vector3): void;
/**
* @internal
*/
abstract _getPositionRange(outMin: Vector3, outMax: Vector3): void;
}

View File

@@ -12,32 +12,14 @@ export class BoxShape extends BaseShape {
readonly shapeType = ParticleShapeType.Box;
/** The size of the box. */
@deepClone
private _size = new Vector3(1, 1, 1);
/**
* The size of the box.
*/
get size() {
return this._size;
}
set size(value: Vector3) {
if (value !== this._size) {
this._size.copyFrom(value);
}
}
constructor() {
super();
// @ts-ignore
this._size._onValueChanged = this._updateManager.dispatch.bind(this._updateManager);
}
size = new Vector3(1, 1, 1);
/**
* @internal
*/
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
ShapeUtils._randomPointInsideHalfUnitBox(position, rand);
position.multiply(this.size);
@@ -46,30 +28,4 @@ export class BoxShape extends BaseShape {
ShapeUtils._randomPointUnitSphere(direction, rand);
Vector3.lerp(defaultDirection, direction, this.randomDirectionAmount, direction);
}
/**
* @internal
*/
_getDirectionRange(outMin: Vector3, outMax: Vector3): void {
const radian = Math.PI * this.randomDirectionAmount;
if (this.randomDirectionAmount < 0.5) {
const dirSin = Math.sin(radian);
outMin.set(-dirSin, -dirSin, -1);
outMax.set(dirSin, dirSin, 0);
} else {
const dirCos = Math.cos(radian);
outMin.set(-1, -1, -1);
outMax.set(1, 1, -dirCos);
}
}
/**
* @internal
*/
_getPositionRange(outMin: Vector3, outMax: Vector3): void {
const { x, y, z } = this._size;
outMin.set(-x * 0.5, -y * 0.5, -z * 0.5);
outMax.set(x * 0.5, y * 0.5, z * 0.5);
}
}

View File

@@ -8,75 +8,23 @@ import { ParticleShapeType } from "./enums/ParticleShapeType";
* Particle shape that emits particles from a circle.
*/
export class CircleShape extends BaseShape {
private static _tempPositionPoint = new Vector2();
private static _tempPositionPoint: Vector2 = new Vector2();
readonly shapeType = ParticleShapeType.Circle;
private _radius = 1.0;
private _arc = 360.0;
private _arcMode = ParticleShapeArcMode.Random;
private _arcSpeed = 1.0;
/**
* Radius of the shape to emit particles from.
*/
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value !== this._radius) {
this._radius = value;
this._updateManager.dispatch();
}
}
/**
* Angle of the circle arc to emit particles from.
*/
get arc(): number {
return this._arc;
}
set arc(value: number) {
if (value !== this._arc) {
this._arc = value;
this._updateManager.dispatch();
}
}
/**
* The mode to generate particles around the arc.
*/
get arcMode(): ParticleShapeArcMode {
return this._arcMode;
}
set arcMode(value: ParticleShapeArcMode) {
if (value !== this._arcMode) {
this._arcMode = value;
this._updateManager.dispatch();
}
}
/**
* The speed of complete 360 degree rotation.
*/
get arcSpeed(): number {
return this._arcSpeed;
}
set arcSpeed(value: number) {
if (value !== this._arcSpeed) {
this._arcSpeed = value;
this._updateManager.dispatch();
}
}
/** Radius of the shape to emit particles from. */
radius = 1.0;
/** Angle of the circle arc to emit particles from. */
arc = 360.0;
/** The mode to generate particles around the arc. */
arcMode = ParticleShapeArcMode.Random;
/** The speed of complete 360 degree rotation. */
arcSpeed = 1.0;
/**
* @internal
*/
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
const positionPoint = CircleShape._tempPositionPoint;
switch (this.arcMode) {
@@ -97,49 +45,4 @@ export class CircleShape extends BaseShape {
ShapeUtils._randomPointUnitSphere(direction, rand);
Vector3.lerp(position, direction, this.randomDirectionAmount, direction);
}
/**
* @internal
*/
_getDirectionRange(outMin: Vector3, outMax: Vector3): void {
const randomDirZ = this.randomDirectionAmount > 0.5 ? 1 : Math.sin(this.randomDirectionAmount * Math.PI);
const randomDegreeOnXY = 0.5 * (360 - this._arc) * this.randomDirectionAmount;
const randomDirY = randomDegreeOnXY > 90 ? -1 : -Math.sin(randomDegreeOnXY);
this._getUnitArcRange(this._arc + randomDegreeOnXY, outMin, outMax, randomDirY, randomDirZ);
}
/**
* @internal
*/
_getPositionRange(outMin: Vector3, outMax: Vector3): void {
this._getUnitArcRange(this._arc, outMin, outMax, 0, 0);
outMin.scale(this._radius);
outMax.scale(this._radius);
}
private _getUnitArcRange(
arc: number,
outMin: Vector3,
outMax: Vector3,
randomDirY: number,
randomDirZ: number
): void {
const radian = MathUtil.degreeToRadian(arc);
const dirSin = Math.sin(radian);
const dirCos = Math.cos(radian);
if (arc < 90) {
outMin.set(0, randomDirY, -randomDirZ);
outMax.set(1, dirSin, randomDirZ);
} else if (arc < 180) {
outMin.set(dirCos, randomDirY, -randomDirZ);
outMax.set(1, 1, randomDirZ);
} else if (arc < 270) {
outMin.set(-1, Math.min(dirSin, randomDirY), -randomDirZ);
outMax.set(1, 1, randomDirZ);
} else {
outMin.set(-1, -1, -randomDirZ);
outMax.set(1, 1, randomDirZ);
}
}
}

View File

@@ -14,71 +14,19 @@ export class ConeShape extends BaseShape {
readonly shapeType = ParticleShapeType.Cone;
private _angle = 25.0;
private _radius = 1.0;
private _length = 5.0;
private _emitType = ConeEmitType.Base;
/**
* Angle of the cone to emit particles from.
*/
get angle(): number {
return this._angle;
}
set angle(value: number) {
if (value !== this._angle) {
this._angle = value;
this._updateManager.dispatch();
}
}
/**
* Radius of the shape to emit particles from.
*/
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value !== this._radius) {
this._radius = value;
this._updateManager.dispatch();
}
}
/**
* Length of the cone to emit particles from.
*/
get length(): number {
return this._length;
}
set length(value: number) {
if (value !== this._length) {
this._length = value;
this._updateManager.dispatch();
}
}
/**
* Cone emitter type.
*/
get emitType(): ConeEmitType {
return this._emitType;
}
set emitType(value: ConeEmitType) {
if (value !== this._emitType) {
this._emitType = value;
this._updateManager.dispatch();
}
}
/** Angle of the cone to emit particles from. */
angle = 25.0;
/** Radius of the shape to emit particles from. */
radius = 1.0;
/** Length of the cone to emit particles from. */
length = 5.0;
/** Cone emitter type. */
emitType = ConeEmitType.Base;
/**
* @internal
*/
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
const unitPosition = ConeShape._tempVector20;
const radian = MathUtil.degreeToRadian(this.angle);
const dirSinA = Math.sin(radian);
@@ -111,47 +59,6 @@ export class ConeShape extends BaseShape {
break;
}
}
/**
* @internal
*/
_getDirectionRange(outMin: Vector3, outMax: Vector3): void {
let radian = 0;
switch (this.emitType) {
case ConeEmitType.Base:
radian = MathUtil.degreeToRadian(this._angle);
break;
case ConeEmitType.Volume:
const randomRadian = MathUtil.degreeToRadian((180 - this._angle) * this.randomDirectionAmount + this._angle);
radian = Math.sin(randomRadian);
break;
}
const dirSin = Math.sin(radian);
outMin.set(-dirSin, -dirSin, -1);
outMax.set(dirSin, dirSin, 0);
}
/**
* @internal
*/
_getPositionRange(outMin: Vector3, outMax: Vector3): void {
const { radius } = this;
switch (this.emitType) {
case ConeEmitType.Base:
outMin.set(-radius, -radius, 0);
outMax.set(radius, radius, 0);
break;
case ConeEmitType.Volume:
const { length } = this;
const dirSin = Math.sin(MathUtil.degreeToRadian(this._angle));
outMin.set(-radius - dirSin * length, -radius - dirSin * length, -length);
outMax.set(radius + dirSin * length, radius + dirSin * length, 0);
break;
}
}
}
/**

View File

@@ -9,26 +9,13 @@ import { ParticleShapeType } from "./enums/ParticleShapeType";
export class HemisphereShape extends BaseShape {
readonly shapeType = ParticleShapeType.Hemisphere;
private _radius = 1.0;
/**
* Radius of the shape to emit particles from.
*/
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value !== this._radius) {
this._radius = value;
this._updateManager.dispatch();
}
}
/** Radius of the shape to emit particles from. */
radius = 1.0;
/**
* @internal
*/
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
ShapeUtils._randomPointInsideUnitSphere(position, rand);
position.scale(this.radius);
@@ -38,22 +25,4 @@ export class HemisphereShape extends BaseShape {
ShapeUtils._randomPointUnitSphere(direction, rand);
Vector3.lerp(position, direction, this.randomDirectionAmount, direction);
}
/**
* @internal
*/
_getDirectionRange(outMin: Vector3, outMax: Vector3): void {
const randomDir = Math.sin(0.5 * this.randomDirectionAmount * Math.PI);
outMin.set(-1, -1, -1);
outMax.set(1, 1, randomDir);
}
/**
* @internal
*/
_getPositionRange(outMin: Vector3, outMax: Vector3): void {
const radius = this._radius;
outMin.set(-radius, -radius, -radius);
outMax.set(radius, radius, 0);
}
}

View File

@@ -9,47 +9,17 @@ import { ParticleShapeType } from "./enums/ParticleShapeType";
export class SphereShape extends BaseShape {
readonly shapeType = ParticleShapeType.Sphere;
private _radius = 1.0;
/**
* Radius of the shape to emit particles from.
*/
get radius(): number {
return this._radius;
}
set radius(value: number) {
if (value !== this._radius) {
this._radius = value;
this._updateManager.dispatch();
}
}
/** Radius of the shape to emit particles from. */
radius = 1.0;
/**
* @internal
*/
_generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
override _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void {
ShapeUtils._randomPointInsideUnitSphere(position, rand);
position.scale(this.radius);
ShapeUtils._randomPointUnitSphere(direction, rand);
Vector3.lerp(position, direction, this.randomDirectionAmount, direction);
}
/**
* @internal
*/
_getDirectionRange(outMin: Vector3, outMax: Vector3): void {
outMin.set(-1, -1, -1);
outMax.set(1, 1, 1);
}
/**
* @internal
*/
_getPositionRange(outMin: Vector3, outMax: Vector3): void {
const radius = this._radius;
outMin.set(-radius, -radius, -radius);
outMax.set(radius, radius, radius);
}
}

View File

@@ -1,428 +0,0 @@
import {
ParticleRenderer,
BoxShape,
ParticleMaterial,
Camera,
SphereShape,
HemisphereShape,
CircleShape,
ConeShape,
ParticleShapeArcMode,
ConeEmitType,
Entity,
ParticleCurveMode,
Engine,
ParticleStopMode
} from "@galacean/engine-core";
import { Color, Vector3 } from "@galacean/engine-math";
import { WebGLEngine } from "@galacean/engine-rhi-webgl";
import { LitePhysics } from "@galacean/engine-physics-lite";
import { expect } from "chai";
const delta = 0.2;
function expectObjectToBeCloseTo(actual, expected, delta) {
Object.keys(expected).forEach((key) => {
expect(actual[key]).to.be.closeTo(expected[key], delta);
});
}
export const updateEngine = (engine: Engine, deltaTime = 100) => {
//@ts-ignore
engine._vSyncCount = Infinity;
//@ts-ignore
engine._time._lastSystemTime = 0;
let times = 0;
performance.now = function () {
times++;
return times * deltaTime;
};
for (let i = 0; i < 50; ++i) {
engine.update();
}
};
function testParticleRendererBounds(
engine: Engine,
render: ParticleRenderer,
expectedMinBounds: { x: number; y: number; z: number },
expectedMaxBounds: { x: number; y: number; z: number },
delta: number
) {
render.generator.stop(true, ParticleStopMode.StopEmittingAndClear);
render.generator.play();
updateEngine(engine);
expectObjectToBeCloseTo(render.bounds.min, expectedMinBounds, delta);
expectObjectToBeCloseTo(render.bounds.max, expectedMaxBounds, delta);
}
describe("ParticleBoundingBox", function () {
let engine: Engine;
let particleRenderer: ParticleRenderer;
let entity: Entity;
before(async function () {
engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new LitePhysics() });
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity("root");
const cameraEntity = rootEntity.createChild("camera");
cameraEntity.addComponent(Camera);
cameraEntity.transform.setPosition(0, 0, -10);
cameraEntity.transform.lookAt(new Vector3());
entity = rootEntity.createChild("particle");
particleRenderer = entity.addComponent(ParticleRenderer);
const material = new ParticleMaterial(engine);
material.baseColor = new Color(1.0, 1.0, 1.0, 1.0);
particleRenderer.setMaterial(material);
engine.run();
});
beforeEach(function () {
particleRenderer.generator.stop(true, ParticleStopMode.StopEmittingAndClear);
entity.transform.position.set(0, 0, 0);
entity.transform.rotation.set(0, 0, 0);
entity.transform.scale.set(1, 1, 1);
particleRenderer.generator.main.startSpeed.mode = ParticleCurveMode.Constant;
particleRenderer.generator.main.startSpeed.constant = 5;
particleRenderer.generator.main.gravityModifier.mode = ParticleCurveMode.Constant;
particleRenderer.generator.main.gravityModifier.constant = 0;
particleRenderer.generator.velocityOverLifetime.enabled = false;
particleRenderer.generator.emission.shape = null;
});
it("EmptyShape", function () {
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.414, y: -1.414, z: -26.414 },
{ x: 1.414, y: 1.414, z: 1.414 },
delta
);
});
it("BoxShape", function () {
const shape = new BoxShape();
particleRenderer.generator.emission.shape = shape;
// Test that box shape works correctly on boundingBox
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.914, y: -1.914, z: -26.914 },
{ x: 1.914, y: 1.914, z: 1.914 },
delta
);
// Test that size works correctly on boundingBox
shape.size.set(1, 2, 4);
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.914, y: -2.414, z: -28.414 },
{ x: 1.914, y: 2.414, z: 3.414 },
delta
);
// Test that randomDirectionAmount works correctly on boundingBox
shape.randomDirectionAmount = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -26.914, y: -27.414, z: -28.414 },
{ x: 26.914, y: 27.414, z: 3.414 },
delta
);
});
it("SphereShape", function () {
const shape = new SphereShape();
particleRenderer.generator.emission.shape = shape;
// Test that sphere shape works correctly on boundingBox
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -27.414, y: -27.414, z: -27.414 },
{ x: 27.414, y: 27.414, z: 27.414 },
delta
);
// Test that radius works correctly on boundingBox
shape.radius = 2.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -28.914 },
{ x: 28.914, y: 28.914, z: 28.914 },
delta
);
// Test that randomDirectionAmount works correctly on boundingBox
shape.randomDirectionAmount = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -28.914 },
{ x: 28.914, y: 28.914, z: 28.914 },
delta
);
});
it("HemisphereShape", function () {
const shape = new HemisphereShape();
particleRenderer.generator.emission.shape = shape;
// Test that hemisphere shape works correctly on boundingBox
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -27.414, y: -27.414, z: -27.414 },
{ x: 27.414, y: 27.414, z: 1.414 },
delta
);
// Test that radius works correctly on boundingBox
shape.radius = 2.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -28.914 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
// Test that randomDirectionAmount works correctly on boundingBox
shape.randomDirectionAmount = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -28.914 },
{ x: 28.914, y: 28.914, z: 19.092 },
delta
);
});
it("CircleShape", function () {
const shape = new CircleShape();
particleRenderer.generator.emission.shape = shape;
// Test that circle shape works correctly on boundingBox
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -27.414, y: -27.414, z: -1.414 },
{ x: 27.414, y: 27.414, z: 1.414 },
delta
);
// Test that radius works correctly on boundingBox
shape.radius = 2.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -1.414 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
// Test that arc works correctly on boundingBox
shape.arc = 45;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.414, y: -1.414, z: -1.414 },
{ x: 28.914, y: 20.859, z: 1.414 },
delta
);
shape.arc = 135;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -20.859, y: -1.414, z: -1.414 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
shape.arc = 225;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -20.859, z: -1.414 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
shape.arc = 315;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -1.414 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
// Test that arc mode loop works correctly on boundingBox
shape.arcMode = ParticleShapeArcMode.Loop;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -1.414 },
{ x: 28.914, y: 28.914, z: 1.414 },
delta
);
// Test that randomDirectionAmount works correctly on boundingBox
shape.randomDirectionAmount = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -28.914, y: -28.914, z: -26.414 },
{ x: 28.914, y: 28.914, z: 26.414 },
delta
);
});
it("ConeShape", function () {
const shape = new ConeShape();
particleRenderer.generator.emission.shape = shape;
// Test that cone shape works correctly on boundingBox
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -12.979, y: -12.979, z: -26.414 },
{ x: 12.979, y: 12.979, z: 1.414 },
delta
);
// Test that radius works correctly on boundingBox
shape.radius = 2.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -14.479, y: -14.479, z: -26.414 },
{ x: 14.479, y: 14.479, z: 1.414 },
delta
);
// Test that angle works correctly on boundingBox
shape.angle = 30;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -16.414, y: -16.414, z: -26.414 },
{ x: 16.414, y: 16.414, z: 1.414 },
delta
);
// Test that arc mode loop works correctly on boundingBox
shape.emitType = ConeEmitType.Volume;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -18.4, y: -18.4, z: -31.414 },
{ x: 18.4, y: 18.4, z: 1.414 },
delta
);
// Test that arc mode loop works correctly on boundingBox
shape.length = 10;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -20.9, y: -20.9, z: -36.414 },
{ x: 20.9, y: 20.9, z: 1.414 },
delta
);
// Test that randomDirectionAmount works correctly on boundingBox
shape.randomDirectionAmount = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -29.478, y: -29.478, z: -36.414 },
{ x: 29.478, y: 29.478, z: 1.414 },
delta
);
});
it("StartSpeed", function () {
particleRenderer.generator.main.startSpeed.mode = ParticleCurveMode.TwoConstants;
particleRenderer.generator.main.startSpeed.constantMin = -10;
particleRenderer.generator.main.startSpeed.constantMax = 2;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.414, y: -1.414, z: -11.414 },
{ x: 1.414, y: 1.414, z: 51.414 },
delta
);
});
it("Gravity", function () {
particleRenderer.generator.main.gravityModifier.mode = ParticleCurveMode.TwoConstants;
particleRenderer.generator.main.gravityModifier.constantMin = -1;
particleRenderer.generator.main.gravityModifier.constantMax = 0.2;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.414, y: -25.939, z: -26.414 },
{ x: 1.414, y: 124.039, z: 1.414 },
delta
);
});
it("VelocityOverLifetime", function () {
this.timeout(10000);
particleRenderer.generator.main.startSpeed.mode = ParticleCurveMode.Constant;
particleRenderer.generator.main.startSpeed.constant = 0;
const velocityOverLifetime = particleRenderer.generator.velocityOverLifetime;
const { velocityX, velocityY, velocityZ } = velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityX.constant = 1;
velocityY.constant = 1;
velocityZ.mode = ParticleCurveMode.TwoConstants;
velocityZ.constantMin = -1;
velocityZ.constantMax = 0.5;
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -1.414, y: -1.414, z: -6.414 },
{ x: 6.414, y: 6.414, z: 3.914 },
delta
);
});
it("Transform", function () {
entity.transform.position.set(1, 2, 3);
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -0.414, y: 0.586, z: -23.414 },
{ x: 2.414, y: 3.414, z: 4.414 },
delta
);
entity.transform.rotation.set(30, 60, 120);
testParticleRendererBounds(
engine,
particleRenderer,
{ x: -19.906, y: -0.3798, z: -10.239 },
{ x: 3.156, y: 16.88, z: 5.414 },
delta
);
});
});