Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ParticleRenderer support bounds and frustum culling #1963

Merged
merged 113 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
e2a5cdb
feat: calculate particleRenderer boundingBox
JujieX Jan 11, 2024
b732e7a
fix: shape calculate move into each type
JujieX Jan 17, 2024
85113fe
fix: get local and world bounds
JujieX Jan 17, 2024
07f9af9
fix: use private static vector3
JujieX Jan 18, 2024
917f655
Merge branch 'dev/1.2' of github.com:galacean/runtime into feat/parti…
JujieX Jan 29, 2024
9583852
fix: opt code
JujieX Jan 30, 2024
8bc6974
fix: get bounds
JujieX Jan 30, 2024
c80b5bf
feat: add unit test
JujieX Jan 30, 2024
5b31b26
feat: simulation space world
JujieX Feb 4, 2024
05e50a7
feat: add rotation into consideration
JujieX Feb 5, 2024
5bc60d6
fix: add timeout for test
JujieX Feb 5, 2024
759123a
fix: modify test
JujieX Feb 5, 2024
2725263
fix: add timeout for test
JujieX Feb 5, 2024
c20d5ab
fix: opt test
JujieX Feb 5, 2024
4905762
fix: opt test
JujieX Feb 5, 2024
3df7be4
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
JujieX Apr 2, 2024
ed35435
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
JujieX Apr 9, 2024
05817c2
feat: separately update world space and local space
JujieX Apr 11, 2024
cd5c8d0
fix: opt code
JujieX Apr 11, 2024
1989500
fix: opt code
JujieX Apr 11, 2024
4271bd6
fix: opt code
JujieX Apr 16, 2024
4819992
feat: use dirtyflag updates bounds
JujieX Apr 16, 2024
a6af680
fix: opt code
JujieX Apr 16, 2024
4af19b2
fix: opt code
JujieX Apr 16, 2024
aeb9ea4
fix: opt code
JujieX Apr 16, 2024
df359a4
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
JujieX Apr 16, 2024
04ea065
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
JujieX Apr 16, 2024
6cde202
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
JujieX Apr 19, 2024
a0f37f1
fix: opt code
JujieX Apr 19, 2024
98d3de7
fix: opt code
JujieX Apr 19, 2024
2b6fa87
fix: opt code
JujieX Apr 19, 2024
3d75752
fix: opt code
JujieX Apr 19, 2024
7149891
fix: opt code
JujieX Apr 19, 2024
0e5e9e7
fix: onValueChanged
JujieX Apr 24, 2024
9545be9
fix: opt code
JujieX Apr 24, 2024
3109472
fix: opt code
JujieX Apr 24, 2024
865a95e
fix: opt code
JujieX Apr 24, 2024
4021680
fix: opt code
JujieX Apr 24, 2024
097100c
feat: simplify localSpace bounds update
JujieX May 11, 2024
b5350d2
fix: opt code
JujieX May 11, 2024
b10a872
fix: opt code
JujieX May 13, 2024
9d03b16
fix: opt code
JujieX May 14, 2024
7adb094
fix: opt code
JujieX May 14, 2024
5a7f351
feat: use three flags
JujieX May 17, 2024
c3e7029
fix: opt code
JujieX May 17, 2024
a9a5fa9
fix: opt code
JujieX May 23, 2024
8c1ee51
fix: opt code
JujieX May 30, 2024
a9ec0cb
feat: add e2e test
JujieX May 30, 2024
b2a6b68
feat: add e2e test
JujieX May 30, 2024
b817cb4
feat: opt code
JujieX May 31, 2024
9b6d6b5
fix: opt code
JujieX May 31, 2024
a05ad3d
fix: opt code
Jun 3, 2024
7844b75
fix: opt code
Jun 3, 2024
ca3af1b
feat: circular queue
Jun 3, 2024
b57273d
fix: minmax of composite curve
Jun 3, 2024
9f9f680
fix: opt code
Jun 3, 2024
1b183a5
fix: opt code
Jun 3, 2024
34451da
fix: opt circular queue
Jun 3, 2024
6e09011
fix: opt isNotAlive
Jun 3, 2024
a297e7c
feat: nextFreeElement for resize buffer
Jun 3, 2024
ac11fd2
fix: opt code
Jun 3, 2024
6007a62
fix: opt code
Jun 4, 2024
5e13b4c
fix: opt code
Jun 4, 2024
7938f6f
fix: opt code
Jun 4, 2024
27d3a5c
fix: opt code
Jun 4, 2024
b46fa70
fix: opt code
Jun 4, 2024
070c35e
fix: opt code
Jun 4, 2024
ffc86e8
fix: opt code
Jun 4, 2024
c7bbef2
fix: opt code
Jun 4, 2024
ad576a4
fix: opt code
Jun 4, 2024
4c71e95
fix: opt code
Jun 4, 2024
072464c
fix: opt code
Jun 4, 2024
c856783
fix: opt code
Jun 4, 2024
2e6abc4
fix: opt code
Jun 4, 2024
49539b2
fix: opt code
Jun 4, 2024
2f2bbd5
fix: opt code
Jun 4, 2024
c24c4b8
fix: opt code
Jun 4, 2024
35560e7
fix: opt code
Jun 4, 2024
bf02842
fix: opt code
Jun 5, 2024
82d6e24
feat: use flag update manager for base shape
Jun 5, 2024
68cd513
fix: opt code
Jun 5, 2024
35c3ef8
fix: opt code
Jun 5, 2024
07dd580
fix: opt code
Jun 5, 2024
c2564ad
fix: opt code
Jun 5, 2024
e5b030a
fix: opt code
Jun 5, 2024
5108c6c
fix: opt code
Jun 6, 2024
730bcfd
fix: opt cdoe
Jun 6, 2024
1f56b52
fix: opt code
Jun 6, 2024
31f068b
fix: circle random direction algo
Jun 6, 2024
e0343a9
fix: opt code
Jun 6, 2024
04b7386
fix: opt code
Jun 6, 2024
413a2c3
fix: opt code
Jun 6, 2024
1614ee8
fix: opt code
Jun 6, 2024
a5d0b7a
fix: opt code
Jun 6, 2024
9cbc519
fix: opt code
Jun 6, 2024
395df1b
fix: opt code
Jun 6, 2024
a5f7171
fix: opt code
Jun 6, 2024
ba37308
fix: opt code
Jun 6, 2024
c59bbfa
fix: hemisphere algo
Jun 6, 2024
1f14bd5
fix: opt code
Jun 6, 2024
3b2954c
fix: opt code
Jun 6, 2024
b33a9cf
fix: opt shape algo
Jun 6, 2024
52b116b
fix: opt code
Jun 6, 2024
9903a49
fix: onCurveChange bind
Jun 6, 2024
4de0d1c
fix: opt code
Jun 6, 2024
b73dba1
fix: opt code
Jun 6, 2024
787647f
fix: opt code
Jun 6, 2024
2a8efb8
fix: opt code
Jun 7, 2024
9fe522a
Merge branch 'main' of github.com:galacean/runtime into feat/particle…
Jun 7, 2024
bf5d166
fix: opt code
Jun 7, 2024
46d250e
fix: opt code
Jun 7, 2024
89d5fb7
fix: opt code
Jun 7, 2024
de600a6
fix: opt code
Jun 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions packages/core/src/particle/ParticleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ import { RotationOverLifetimeModule } from "./modules/RotationOverLifetimeModule
import { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule";
import { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule";
import { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule";
import { ParticleShapeType } from "./modules/shape/enums/ParticleShapeType";
import {
BoxShape,
CircleShape,
ConeEmitType,
ConeShape,
HemisphereShape,
ParticleCompositeCurve,
SphereShape
} from "..";
import { BaseShape } from "./modules/shape/BaseShape";

/**
* Particle Generator.
Expand All @@ -37,8 +48,12 @@ export class ParticleGenerator {
/** @internal */
private static _tempVector31 = new Vector3();
/** @internal */
private static _tempVector32 = new Vector3();
/** @internal */
private static _tempColor0 = new Color();
/** @internal */
private static _tempQuat0 = new Quaternion();
/** @internal */
private static _tempParticleRenderers = new Array<ParticleRenderer>();
private static readonly _particleIncreaseCount = 128;

Expand Down Expand Up @@ -110,6 +125,10 @@ export class ParticleGenerator {
private _instanceVertices: Float32Array;
private _randomSeed = 0;

@ignoreClone
private _directionMin = new Vector3();
@ignoreClone
private _directionMax = new Vector3();
/**
* Whether the particle generator is contain alive or is still creating particles.
*/
Expand Down Expand Up @@ -237,6 +256,7 @@ export class ParticleGenerator {
}
this._addNewParticle(position, direction, transform, time);
}
this._calculateBoundingBox();
}
}

Expand Down Expand Up @@ -274,6 +294,9 @@ export class ParticleGenerator {
const discardTime = Math.min(emission._frameRateTime, Math.floor(this._playTime / duration) * duration);
this._playTime -= discardTime;
emission._frameRateTime -= discardTime;

this._renderer._bounds.max.set(0, 0, 0);
this._renderer._bounds.min.set(0, 0, 0);
}

// Add new particles to vertex buffer when has wait process retired element or new particle
Expand Down Expand Up @@ -764,4 +787,261 @@ export class ParticleGenerator {
out.push(vertexBufferBinding);
return index;
}

private _calculateBoundingBox(): void {
const { min, max } = this._renderer._bounds;
const { _directionMax: directionMax, _directionMin: directionMin } = this;

const lifetime = this.main.startLifetime.maxValue;

// StartSpeed's impact
if (this.emission.shape) {
this._calculateShapeBasedBoundingBoxAndStartDirection(this.emission.shape);
} else {
min.set(0, 0, 0);
max.set(0, 0, 0);
directionMin.set(0, 0, -1);
directionMax.set(0, 0, 0);
}
this._adjustBoundingBoxFromDirectionalVelocity(directionMax, directionMin, lifetime, this.main.startSpeed);

// GravityModifier's impact
const { _tempQuat0: worldInvQuat, _tempVector32: direction } = ParticleGenerator;
Quaternion.invert(this._renderer.entity.transform.worldRotationQuaternion, worldInvQuat);
// Transform gravity direction into local space
direction.copyFrom(this._renderer.scene.physics.gravity);
Vector3.transformByQuat(direction, worldInvQuat, direction);

this._adjustBoundingBoxFromDirectionalVelocity(
direction,
direction,
0.5 * lifetime * lifetime,
this.main.gravityModifier
);

// StartSize's impact
const maxSize = this.main.startSize3D
? Math.max(this.main.startSizeX.maxValue, this.main.startSizeY.maxValue, this.main.startSizeZ.maxValue)
: this.main.startSize.maxValue;

min.x -= maxSize;
max.x += maxSize;

min.y -= maxSize;
max.y += maxSize;

min.z -= maxSize;
max.z += maxSize;

// VelocityOverLifetime Module's impact
if (this.velocityOverLifetime.enabled) {
const { velocityX, velocityY, velocityZ } = this.velocityOverLifetime;
directionMin.set(velocityX.minValue, velocityY.minValue, velocityZ.minValue);
directionMax.set(velocityX.maxValue, velocityY.maxValue, velocityZ.maxValue);

if ((this.velocityOverLifetime.space = ParticleSimulationSpace.World)) {
Vector3.transformByQuat(directionMin, worldInvQuat, directionMin);
Vector3.transformByQuat(directionMax, worldInvQuat, directionMax);
}

this._adjustBoundingBoxFromDirectionalVelocity(directionMin, directionMax, lifetime);
}

// SimulationSpace's Impact
if (this.main.simulationSpace === ParticleSimulationSpace.World) {
const worldPosition = this._renderer.entity.transform.worldPosition;
min.x -= Math.max(0, worldPosition.x);
max.x -= Math.min(0, worldPosition.x);

min.y -= Math.max(0, worldPosition.y);
max.y -= Math.min(0, worldPosition.y);

min.z -= Math.max(0, worldPosition.z);
max.z -= Math.min(0, worldPosition.z);
}
}

private _calculateShapeBasedBoundingBoxAndStartDirection(shape: BaseShape): void {
const { min, max } = this._renderer._bounds;
const { _directionMax: directionMax, _directionMin: directionMin } = this;

if (shape.randomDirectionAmount > 0) {
directionMin.set(-1, -1, -1);
directionMax.set(1, 1, 1);

switch (shape.shapeType) {
case ParticleShapeType.Box: {
const size = (shape as BoxShape).size;
min.set(-size.x / 2, -size.y / 2, -size.z / 2);
max.set(size.x / 2, size.y / 2, size.z / 2);
break;
}
case ParticleShapeType.Sphere:
case ParticleShapeType.Hemisphere:
case ParticleShapeType.Circle: {
const radius = (shape as SphereShape | HemisphereShape | CircleShape).radius;

min.set(-radius, -radius, -radius);
max.set(radius, radius, radius);
break;
}
case ParticleShapeType.Cone: {
const radian = MathUtil.degreeToRadian((shape as ConeShape).angle);
const radius = (shape as ConeShape).radius;
const length = (shape as ConeShape).length;
const dirSinA = Math.sin(radian);

switch ((shape as ConeShape).emitType) {
case ConeEmitType.Base:
directionMin.set(-dirSinA, -dirSinA, -1);
directionMax.set(dirSinA, dirSinA, 0);

min.set(-radius, -radius, -radius);
max.set(radius, radius, 0);
break;
case ConeEmitType.Volume:
min.set(-radius - dirSinA * length, -radius - dirSinA * length, -length);
max.set(radius + dirSinA * length, radius + dirSinA * length, 0);
break;
}
break;
}
}
} else {
switch (shape.shapeType) {
case ParticleShapeType.Box: {
const size = (shape as BoxShape).size;

directionMin.set(0, 0, -1);
directionMax.set(0, 0, 0);

min.set(-size.x / 2, -size.y / 2, -size.z / 2);
max.set(size.x / 2, size.y / 2, size.z / 2);
break;
}
case ParticleShapeType.Sphere:
{
const radius = (shape as SphereShape).radius;
directionMin.set(-1, -1, -1);
directionMax.set(1, 1, 1);

min.set(-radius, -radius, -radius);
max.set(radius, radius, radius);
}
break;
case ParticleShapeType.Hemisphere: {
const radius = (shape as HemisphereShape).radius;
directionMin.set(-1, -1, -1);
directionMax.set(1, 1, 0);

min.set(-radius, -radius, -radius);
max.set(radius, radius, 0);
break;
}
case ParticleShapeType.Circle: {
const radius = (shape as CircleShape).radius;
const arc = (shape as CircleShape).arc;
const radian = MathUtil.degreeToRadian(arc);
const dirSinA = Math.sin(radian);
const dirCosA = Math.cos(radian);

if (arc < 90) {
directionMin.set(0, 0, 0);
directionMax.set(1, dirSinA, 0);
} else if (arc <= 180) {
directionMin.set(dirCosA, 0, 0);
directionMax.set(1, 1, 0);
} else if (arc <= 270) {
directionMin.set(-1, dirSinA, 0);
directionMax.set(1, 1, 0);
} else if (arc <= 360) {
directionMin.set(-1, -1, 0);
directionMax.set(1, 1, 0);
}
min.set(-radius, -radius, -radius);
max.set(radius, radius, radius);
break;
}
case ParticleShapeType.Cone: {
const radian = MathUtil.degreeToRadian((shape as ConeShape).angle);
const dirSinA = Math.sin(radian);
const radius = (shape as ConeShape).radius;
const length = (shape as ConeShape).length;

directionMin.set(-dirSinA, -dirSinA, -1);
directionMax.set(dirSinA, dirSinA, 0);

switch ((shape as ConeShape).emitType) {
case ConeEmitType.Base:
min.set(-radius, -radius, -radius);
max.set(radius, radius, 0);
break;
case ConeEmitType.Volume: {
min.set(-radius - dirSinA * length, -radius - dirSinA * length, -length);
max.set(radius + dirSinA * length, radius + dirSinA * length, 0);
break;
}
}
break;
}
}
}
}

private _adjustBoundingBoxFromDirectionalVelocity(
directionMax: Vector3,
directionMin: Vector3,
factor: number,
velocity?: ParticleCompositeCurve
): void {
const { min, max } = this._renderer._bounds;

const velocityMin = velocity ? factor * velocity.minValue : factor;
const velocityMax = velocity ? factor * velocity.maxValue : factor;

min.x += Math.min(
0,
directionMin.x * velocityMin,
directionMin.x * velocityMax,
directionMax.x * velocityMin,
directionMax.x * velocityMax
);
max.x += Math.max(
0,
directionMin.x * velocityMin,
directionMin.x * velocityMax,
directionMax.x * velocityMin,
directionMax.x * velocityMax
);

min.y += Math.min(
0,
directionMin.y * velocityMin,
directionMin.y * velocityMax,
directionMax.y * velocityMin,
directionMax.y * velocityMax
);
max.y += Math.max(
0,
directionMin.y * velocityMin,
directionMin.y * velocityMax,
directionMax.y * velocityMin,
directionMax.y * velocityMax
);

min.z += Math.min(
0,
directionMin.z * velocityMin,
directionMin.z * velocityMax,
directionMax.z * velocityMin,
directionMax.z * velocityMax
);
max.z += Math.max(
0,
directionMin.z * velocityMin,
directionMin.z * velocityMax,
directionMax.z * velocityMin,
directionMax.z * velocityMax
);
}
}
8 changes: 0 additions & 8 deletions packages/core/src/particle/ParticleRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,6 @@ export class ParticleRenderer extends Renderer {
super._prepareRender(context);
}

/**
* @internal
*/
protected override _updateBounds(worldBounds: BoundingBox): void {
worldBounds.min.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
worldBounds.max.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
}

/**
* @internal
*/
Expand Down
41 changes: 41 additions & 0 deletions packages/core/src/particle/modules/ParticleCompositeCurve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,47 @@ export class ParticleCompositeCurve {
set curve(value: ParticleCurve) {
this.curveMax = value;
}
/**
* The max positive value of the curve. Would be 0 if no positive value exists.
*/
get maxValue(): number {
switch (this.mode) {
case ParticleCurveMode.Constant:
return Math.max(0, this.constantMax);
case ParticleCurveMode.TwoConstants:
return Math.max(0, this.constantMin, this.constantMax);
case ParticleCurveMode.Curve:
const values = this.curveMax && this.curveMax.keys ? this.curveMax.keys.map((key) => key.value) : [0];
return Math.max(0, ...values);
case ParticleCurveMode.TwoCurves:
const maxValues = this.curveMax && this.curveMax.keys ? this.curveMax.keys.map((key) => key.value) : [0];
const minValues = this.curveMin && this.curveMin.keys ? this.curveMin.keys.map((key) => key.value) : [0];
return Math.max(0, ...maxValues, ...minValues);
default:
return 0;
}
}

/**
* The max negative value of the curve. Would be 0 if no negative value exists.
*/
get minValue(): number {
switch (this.mode) {
case ParticleCurveMode.Constant:
return Math.min(0, this.constantMax);
case ParticleCurveMode.TwoConstants:
return Math.min(0, this.constantMin, this.constantMax);
case ParticleCurveMode.Curve:
const values = this.curveMax && this.curveMax.keys ? this.curveMax.keys.map((key) => key.value) : [0];
return Math.min(0, ...values);
case ParticleCurveMode.TwoCurves:
const maxValues = this.curveMax && this.curveMax.keys ? this.curveMax.keys.map((key) => key.value) : [0];
const minValues = this.curveMin && this.curveMin.keys ? this.curveMin.keys.map((key) => key.value) : [0];
return Math.min(0, ...maxValues, ...minValues);
default:
return 0;
}
}

/**
* Create a particle curve that generates a constant value.
Expand Down