Skip to content

Commit 2849d39

Browse files
[NPE] New blocks and support for size gradients (#17455)
This PR adds: - A new updateSize block to update the size of a particle. - A new contextual value to expose the current particle size. - A new mode for RandomBlock: OncePerParticle that evaluates a random value once per particle in a system. This is required for gradients that have two factors, as the random value between the two factors needs to be used in multiple updates. - Support for migrating _sizeGradients, both during the creation phase and the update phase. PR to test: #0K3AQ2#3730 <img width="2056" height="1204" alt="image" src="https://github.com/user-attachments/assets/a8ad7751-7a6f-4d34-b29e-1a02cbb43dcd" />
1 parent 43d0ac9 commit 2849d39

File tree

16 files changed

+348
-73
lines changed

16 files changed

+348
-73
lines changed

packages/dev/core/src/Particles/Node/Blocks/Update/basicColorUpdateBlock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { _ConnectAtTheEnd } from "core/Particles/Queue/executionQueue";
1212
*/
1313
export class BasicColorUpdateBlock extends NodeParticleBlock {
1414
/**
15-
* Create a new UpdateScaleBlock
15+
* Create a new BasicColorUpdateBlock
1616
* @param name defines the block name
1717
*/
1818
public constructor(name: string) {

packages/dev/core/src/Particles/Node/Blocks/Update/basicPositionUpdateBlock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { _ConnectAtTheEnd } from "core/Particles/Queue/executionQueue";
1212
*/
1313
export class BasicPositionUpdateBlock extends NodeParticleBlock {
1414
/**
15-
* Create a new UpdateScaleBlock
15+
* Create a new BasicPositionUpdateBlock
1616
* @param name defines the block name
1717
*/
1818
public constructor(name: string) {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { Particle } from "core/Particles/particle";
2+
import type { ThinParticleSystem } from "core/Particles/thinParticleSystem";
3+
import type { NodeParticleConnectionPoint } from "core/Particles/Node/nodeParticleBlockConnectionPoint";
4+
import type { NodeParticleBuildState } from "core/Particles/Node/nodeParticleBuildState";
5+
6+
import { RegisterClass } from "core/Misc/typeStore";
7+
import { NodeParticleBlock } from "core/Particles/Node/nodeParticleBlock";
8+
import { NodeParticleBlockConnectionPointTypes } from "core/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes";
9+
import { _ConnectAtTheEnd } from "core/Particles/Queue/executionQueue";
10+
11+
/**
12+
* Block used to update the size of a particle
13+
*/
14+
export class UpdateSizeBlock extends NodeParticleBlock {
15+
/**
16+
* Create a new UpdateSizeBlock
17+
* @param name defines the block name
18+
*/
19+
public constructor(name: string) {
20+
super(name);
21+
22+
this.registerInput("particle", NodeParticleBlockConnectionPointTypes.Particle);
23+
this.registerInput("size", NodeParticleBlockConnectionPointTypes.Float);
24+
this.registerOutput("output", NodeParticleBlockConnectionPointTypes.Particle);
25+
}
26+
27+
/**
28+
* Gets the particle component
29+
*/
30+
public get particle(): NodeParticleConnectionPoint {
31+
return this._inputs[0];
32+
}
33+
34+
/**
35+
* Gets the size input component
36+
*/
37+
public get size(): NodeParticleConnectionPoint {
38+
return this._inputs[1];
39+
}
40+
41+
/**
42+
* Gets the output component
43+
*/
44+
public get output(): NodeParticleConnectionPoint {
45+
return this._outputs[0];
46+
}
47+
48+
/**
49+
* Gets the current class name
50+
* @returns the class name
51+
*/
52+
public override getClassName() {
53+
return "UpdateSizeBlock";
54+
}
55+
56+
/**
57+
* Builds the block
58+
* @param state defines the current build state
59+
*/
60+
public override _build(state: NodeParticleBuildState) {
61+
const system = this.particle.getConnectedValue(state) as ThinParticleSystem;
62+
63+
this.output._storedValue = system;
64+
65+
if (!this.size.isConnected) {
66+
return;
67+
}
68+
69+
const processSize = (particle: Particle) => {
70+
state.particleContext = particle;
71+
state.systemContext = system;
72+
particle.size = this.size.getConnectedValue(state) as number;
73+
};
74+
75+
const sizeProcessing = {
76+
process: processSize,
77+
previousItem: null,
78+
nextItem: null,
79+
};
80+
81+
if (system._updateQueueStart) {
82+
_ConnectAtTheEnd(sizeProcessing, system._updateQueueStart);
83+
} else {
84+
system._updateQueueStart = sizeProcessing;
85+
}
86+
}
87+
}
88+
89+
RegisterClass("BABYLON.UpdateSizeBlock", UpdateSizeBlock);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from "./Update/updateDirectionBlock";
88
export * from "./Update/updatePositionBlock";
99
export * from "./Update/updateColorBlock";
1010
export * from "./Update/updateScaleBlock";
11+
export * from "./Update/updateSizeBlock";
1112
export * from "./Update/updateAngleBlock";
1213
export * from "./Update/updateAgeBlock";
1314
export * from "./Update/basicPositionUpdateBlock";

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export class ParticleInputBlock extends NodeParticleBlock {
149149
case NodeParticleContextualSources.Lifetime:
150150
case NodeParticleContextualSources.Angle:
151151
case NodeParticleContextualSources.AgeGradient:
152+
case NodeParticleContextualSources.Size:
152153
this._type = NodeParticleBlockConnectionPointTypes.Float;
153154
break;
154155
case NodeParticleContextualSources.SpriteCellEnd:

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

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { RegisterClass } from "../../../Misc/typeStore";
2-
import { Vector2, Vector3 } from "../../../Maths/math.vector";
3-
import type { Nullable } from "../../../types";
1+
import type { Nullable } from "core/types";
2+
import type { NodeParticleConnectionPoint } from "core/Particles/Node/nodeParticleBlockConnectionPoint";
3+
import type { NodeParticleBuildState } from "core/Particles/Node/nodeParticleBuildState";
4+
45
import { PropertyTypeForEdition, editableInPropertyPage } from "core/Decorators/nodeDecorator";
5-
import { NodeParticleBlock } from "../nodeParticleBlock";
6-
import { NodeParticleBlockConnectionPointTypes } from "../Enums/nodeParticleBlockConnectionPointTypes";
7-
import type { NodeParticleConnectionPoint } from "../nodeParticleBlockConnectionPoint";
8-
import type { NodeParticleBuildState } from "../nodeParticleBuildState";
6+
import { RegisterClass } from "core/Misc/typeStore";
97
import { Color4 } from "core/Maths/math.color";
8+
import { Vector2, Vector3 } from "core/Maths/math.vector";
9+
import { NodeParticleBlock } from "core/Particles/Node/nodeParticleBlock";
10+
import { NodeParticleBlockConnectionPointTypes } from "core/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes";
1011

1112
/**
1213
* Locks supported by the random block
@@ -18,13 +19,17 @@ export enum ParticleRandomBlockLocks {
1819
PerParticle = 1,
1920
/** PerSystem */
2021
PerSystem = 2,
22+
/** OncePerParticle */
23+
OncePerParticle = 3,
2124
}
2225

2326
/**
2427
* Block used to get a random number
2528
*/
2629
export class ParticleRandomBlock extends NodeParticleBlock {
2730
private _currentLockId = -2;
31+
private _oncePerParticleMap: Map<number, any> = new Map();
32+
2833
/**
2934
* Gets or sets a value indicating if that block will lock its value for a specific event
3035
*/
@@ -35,6 +40,7 @@ export class ParticleRandomBlock extends NodeParticleBlock {
3540
{ label: "None", value: ParticleRandomBlockLocks.None },
3641
{ label: "Per particle", value: ParticleRandomBlockLocks.PerParticle },
3742
{ label: "Per system", value: ParticleRandomBlockLocks.PerSystem },
43+
{ label: "Once per particle", value: ParticleRandomBlockLocks.OncePerParticle },
3844
],
3945
})
4046
public lockMode = ParticleRandomBlockLocks.PerParticle;
@@ -103,6 +109,7 @@ export class ParticleRandomBlock extends NodeParticleBlock {
103109
public override _build() {
104110
let func: Nullable<(state: NodeParticleBuildState) => any> = null;
105111
this._currentLockId = -2;
112+
this._oncePerParticleMap.clear();
106113

107114
switch (this.min.type) {
108115
case NodeParticleBlockConnectionPointTypes.AutoDetect:
@@ -147,23 +154,39 @@ export class ParticleRandomBlock extends NodeParticleBlock {
147154
}
148155

149156
this.output._storedFunction = (state) => {
150-
let lockId = 0;
151-
152-
switch (this.lockMode) {
153-
case ParticleRandomBlockLocks.PerParticle:
154-
lockId = state.particleContext?.id ?? -1;
155-
break;
156-
case ParticleRandomBlockLocks.PerSystem:
157-
lockId = state.buildId ?? 0;
158-
break;
159-
}
157+
if (this.lockMode === ParticleRandomBlockLocks.OncePerParticle) {
158+
const particleId = state.particleContext?.id ?? -1;
159+
let cachedValue = this._oncePerParticleMap.get(particleId);
160+
161+
if (!cachedValue) {
162+
cachedValue = func!(state);
163+
this._oncePerParticleMap.set(particleId, cachedValue);
164+
}
160165

161-
if (this._currentLockId !== lockId) {
162-
if (this.lockMode !== ParticleRandomBlockLocks.None) {
163-
this._currentLockId = lockId;
166+
this.output._storedValue = cachedValue;
167+
} else {
168+
let lockId = -2;
169+
170+
switch (this.lockMode) {
171+
case ParticleRandomBlockLocks.PerParticle:
172+
lockId = state.particleContext?.id ?? -1;
173+
break;
174+
case ParticleRandomBlockLocks.PerSystem:
175+
lockId = state.buildId ?? 0;
176+
break;
177+
default:
178+
break;
179+
}
180+
181+
if (this.lockMode === ParticleRandomBlockLocks.None || this._currentLockId !== lockId) {
182+
if (this.lockMode !== ParticleRandomBlockLocks.None) {
183+
this._currentLockId = lockId;
184+
}
185+
186+
this.output._storedValue = func!(state);
164187
}
165-
this.output._storedValue = func!(state);
166188
}
189+
167190
return this.output._storedValue;
168191
};
169192
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ export class SystemBlock extends NodeParticleBlock {
256256
return particleSystem;
257257
}
258258

259+
/**
260+
* Serializes the system block
261+
* @returns The serialized object
262+
*/
259263
public override serialize(): any {
260264
const serializationObject = super.serialize();
261265

@@ -275,6 +279,10 @@ export class SystemBlock extends NodeParticleBlock {
275279
return serializationObject;
276280
}
277281

282+
/**
283+
* Deserializes the system block
284+
* @param serializationObject The serialized system
285+
*/
278286
public override _deserialize(serializationObject: any) {
279287
super._deserialize(serializationObject);
280288

packages/dev/core/src/Particles/Node/Enums/nodeParticleContextualSources.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ export enum NodeParticleContextualSources {
4141
ScaledColorStep = 0x0017,
4242
/** Local Position Updated */
4343
LocalPositionUpdated = 0x0018,
44+
/** Size */
45+
Size = 0x0019,
4446
}

packages/dev/core/src/Particles/Node/nodeParticleBuildState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ export class NodeParticleBuildState {
131131
return this.particleContext.angle;
132132
case NodeParticleContextualSources.Scale:
133133
return this.particleContext.scale;
134+
case NodeParticleContextualSources.Size:
135+
return this.particleContext.size;
134136
case NodeParticleContextualSources.AgeGradient:
135137
return this.particleContext.age / this.particleContext.lifeTime;
136138
case NodeParticleContextualSources.SpriteCellEnd:

0 commit comments

Comments
 (0)