mirror of
https://github.com/galacean/engine.git
synced 2026-06-20 20:06:02 +08:00
This reverts commit 6d0fdd8658.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user