Skip to content

Commit cff1427

Browse files
Adding cone shape and other fixes (#17380)
More support for converting to NPE: - Adding support for cone shape emitter, which is required to support ParticleHelper.CreateDefault. - Adding size/scale on the CreateParticleBlock - Adding updateSpeed on system block. - Wiring other properties for the system block. PG to test: #7J0NXA#5 PG capture <img width="1863" height="561" alt="image" src="https://github.com/user-attachments/assets/d1a9d7ab-8119-4dfd-a454-14b1622aa95e" /> NPE capture <img width="807" height="898" alt="image" src="https://github.com/user-attachments/assets/9900155a-b48b-4002-899a-1e4f9057facd" />
1 parent 5695bd3 commit cff1427

File tree

8 files changed

+407
-137
lines changed

8 files changed

+407
-137
lines changed

packages/dev/core/src/Particles/Node/Blocks/Emitters/boxShapeBlock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export class BoxShapeBlock extends NodeParticleBlock implements IShapeBlock {
107107
system._positionCreation.process = (particle: Particle) => {
108108
state.particleContext = particle;
109109
state.systemContext = system;
110+
110111
const minEmitBox = this.minEmitBox.getConnectedValue(state) as Vector3;
111112
const maxEmitBox = this.maxEmitBox.getConnectedValue(state) as Vector3;
112113

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import type { IShapeBlock } from "./IShapeBlock";
2+
import type { NodeParticleConnectionPoint } from "core/Particles/Node/nodeParticleBlockConnectionPoint";
3+
import type { NodeParticleBuildState } from "core/Particles/Node/nodeParticleBuildState";
4+
import type { Particle } from "core/Particles/particle";
5+
6+
import { RegisterClass } from "core/Misc/typeStore";
7+
import { NodeParticleBlockConnectionPointTypes } from "core/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes";
8+
import { NodeParticleBlock } from "core/Particles/Node/nodeParticleBlock";
9+
import { RandomRange } from "core/Maths/math.scalar.functions";
10+
import { TmpVectors, Vector3 } from "core/Maths/math.vector";
11+
12+
/**
13+
* Block used to provide a flow of particles emitted from a cone shape.
14+
*/
15+
export class ConeShapeBlock extends NodeParticleBlock implements IShapeBlock {
16+
/**
17+
* Create a new ConeShapeBlock
18+
* @param name defines the block name
19+
*/
20+
public constructor(name: string) {
21+
super(name);
22+
23+
this.registerInput("particle", NodeParticleBlockConnectionPointTypes.Particle);
24+
this.registerInput("radius", NodeParticleBlockConnectionPointTypes.Float, true, 1);
25+
this.registerInput("angle", NodeParticleBlockConnectionPointTypes.Float, true, Math.PI);
26+
this.registerInput("radiusRange", NodeParticleBlockConnectionPointTypes.Float, true, 1);
27+
this.registerInput("heightRange", NodeParticleBlockConnectionPointTypes.Float, true, 1);
28+
this.registerInput("emitFromSpawnPointOnly", NodeParticleBlockConnectionPointTypes.Int, true, 0);
29+
this.registerInput("directionRandomizer", NodeParticleBlockConnectionPointTypes.Float, true, 0);
30+
this.registerOutput("output", NodeParticleBlockConnectionPointTypes.Particle);
31+
}
32+
33+
/**
34+
* Gets the current class name
35+
* @returns the class name
36+
*/
37+
public override getClassName() {
38+
return "ConeShapeBlock";
39+
}
40+
41+
/**
42+
* Gets the particle input component
43+
*/
44+
public get particle(): NodeParticleConnectionPoint {
45+
return this._inputs[0];
46+
}
47+
48+
/**
49+
* Gets the radius input component
50+
*/
51+
public get radius(): NodeParticleConnectionPoint {
52+
return this._inputs[1];
53+
}
54+
55+
/**
56+
* Gets the angle input component
57+
*/
58+
public get angle(): NodeParticleConnectionPoint {
59+
return this._inputs[2];
60+
}
61+
62+
/**
63+
* Gets the radiusRange input component
64+
*/
65+
public get radiusRange(): NodeParticleConnectionPoint {
66+
return this._inputs[3];
67+
}
68+
69+
/**
70+
* Gets the heightRange input component
71+
*/
72+
public get heightRange(): NodeParticleConnectionPoint {
73+
return this._inputs[4];
74+
}
75+
76+
/**
77+
* Gets the emitFromSpawnPointOnly input component
78+
*/
79+
public get emitFromSpawnPointOnly(): NodeParticleConnectionPoint {
80+
return this._inputs[5];
81+
}
82+
83+
/**
84+
* Gets the directionRandomizer input component
85+
*/
86+
public get directionRandomizer(): NodeParticleConnectionPoint {
87+
return this._inputs[6];
88+
}
89+
90+
/**
91+
* Gets the output component
92+
*/
93+
public get output(): NodeParticleConnectionPoint {
94+
return this._outputs[0];
95+
}
96+
97+
/**
98+
* Builds the block
99+
* @param state defines the build state
100+
*/
101+
public override _build(state: NodeParticleBuildState) {
102+
const system = this.particle.getConnectedValue(state);
103+
104+
system._directionCreation.process = (particle: Particle) => {
105+
state.particleContext = particle;
106+
state.systemContext = system;
107+
108+
// Connected values
109+
let directionRandomizer = this.directionRandomizer.getConnectedValue(state) as number;
110+
directionRandomizer = Math.max(0, Math.min(directionRandomizer, 1));
111+
112+
// Calculate create direction logic
113+
if (system.isLocal) {
114+
TmpVectors.Vector3[0].copyFrom(particle.position).normalize();
115+
} else {
116+
particle.position.subtractToRef(state.emitterWorldMatrix!.getTranslation(), TmpVectors.Vector3[0]).normalize();
117+
}
118+
119+
const randX = RandomRange(0, directionRandomizer);
120+
const randY = RandomRange(0, directionRandomizer);
121+
const randZ = RandomRange(0, directionRandomizer);
122+
const directionToUpdate = new Vector3();
123+
directionToUpdate.x = TmpVectors.Vector3[0].x + randX;
124+
directionToUpdate.y = TmpVectors.Vector3[0].y + randY;
125+
directionToUpdate.z = TmpVectors.Vector3[0].z + randZ;
126+
directionToUpdate.normalize();
127+
128+
if (system.isLocal) {
129+
particle.direction.copyFromFloats(directionToUpdate.x, directionToUpdate.y, directionToUpdate.z);
130+
} else {
131+
Vector3.TransformNormalFromFloatsToRef(directionToUpdate.x, directionToUpdate.y, directionToUpdate.z, state.emitterWorldMatrix!, particle.direction);
132+
}
133+
134+
particle._initialDirection = particle.direction.clone();
135+
};
136+
137+
system._positionCreation.process = (particle: Particle) => {
138+
state.particleContext = particle;
139+
state.systemContext = system;
140+
141+
// Connected values
142+
const radius = this.radius.getConnectedValue(state) as number;
143+
const angle = this.angle.getConnectedValue(state) as number;
144+
let radiusRange = this.radiusRange.getConnectedValue(state) as number;
145+
radiusRange = Math.max(0, Math.min(radiusRange, 1));
146+
let heightRange = this.heightRange.getConnectedValue(state) as number;
147+
heightRange = Math.max(0, Math.min(heightRange, 1));
148+
const emitFromSpawnPointOnly = (this.emitFromSpawnPointOnly.getConnectedValue(state) as number) !== 0;
149+
150+
// Calculate position creation logic
151+
let h: number;
152+
if (!emitFromSpawnPointOnly) {
153+
h = RandomRange(0, heightRange);
154+
// Better distribution in a cone at normal angles.
155+
h = 1 - h * h;
156+
} else {
157+
h = 0.0001;
158+
}
159+
160+
let newRadius = radius - RandomRange(0, radius * radiusRange);
161+
newRadius = newRadius * h;
162+
163+
const s = RandomRange(0, Math.PI * 2);
164+
const height = this._calculateHeight(angle, radius);
165+
166+
const randX = newRadius * Math.sin(s);
167+
const randZ = newRadius * Math.cos(s);
168+
const randY = h * height;
169+
170+
if (system.isLocal) {
171+
particle.position.copyFromFloats(randX, randY, randZ);
172+
particle.position.addInPlace(state.emitterPosition!);
173+
} else {
174+
Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, state.emitterWorldMatrix!, particle.position);
175+
}
176+
};
177+
178+
this.output._storedValue = system;
179+
}
180+
181+
private _calculateHeight(angle: number, radius: number): number {
182+
if (angle !== 0) {
183+
return radius / Math.tan(angle / 2);
184+
} else {
185+
return 1;
186+
}
187+
}
188+
}
189+
190+
RegisterClass("BABYLON.ConeShapeBlock", ConeShapeBlock);
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
export * from "./createParticleBlock";
12
export * from "./boxShapeBlock";
2-
export * from "./sphereShapeBlock";
3-
export * from "./pointShapeBlock";
4-
export * from "./customShapeBlock";
3+
export * from "./coneShapeBlock";
54
export * from "./cylinderShapeBlock";
5+
export * from "./customShapeBlock";
66
export * from "./meshShapeBlock";
7+
export * from "./pointShapeBlock";
78
export * from "./setupSpriteSheetBlock";
8-
export * from "./createParticleBlock";
9+
export * from "./sphereShapeBlock";

packages/dev/core/src/Particles/Node/Blocks/systemBlock.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export class SystemBlock extends NodeParticleBlock {
5454
@editableInPropertyPage("Delay start(ms)", PropertyTypeForEdition.Float, "ADVANCED", { embedded: true, notifiers: { rebuild: true }, min: 0 })
5555
public startDelay = 0;
5656

57+
/**
58+
* Gets or sets the target stop duration for the particle system
59+
*/
60+
@editableInPropertyPage("updateSpeed", PropertyTypeForEdition.Float, "ADVANCED", { embedded: true, notifiers: { rebuild: true }, min: 0, max: 0.1 })
61+
public updateSpeed = 0.0167;
62+
5763
/**
5864
* Gets or sets a boolean indicating if the system should not start automatically
5965
*/
@@ -136,6 +142,7 @@ export class SystemBlock extends NodeParticleBlock {
136142
const particleSystem = this.particle.getConnectedValue(state) as ParticleSystem;
137143
particleSystem.particleTexture = this.texture.getConnectedValue(state);
138144
particleSystem.emitRate = this.emitRate;
145+
particleSystem.updateSpeed = this.updateSpeed;
139146
particleSystem.blendMode = this.blendMode;
140147
particleSystem.name = this.name;
141148
particleSystem._targetStopDuration = this.targetStopDuration;

0 commit comments

Comments
 (0)