Skip to content

Commit d8d976a

Browse files
Replacing basic update blocks on Particles to NPE converter (#17375)
With this change, when converting from particles to NPE, several nodes change: - Dead Color is now an input block. - BasicPositionUpdate is represented with all its nodes instead of the simplification - BasicColorUpdate is also represented with all its nodes instead of the simplification PG to test: #0K3AQ2#3670 Before: <img width="1225" height="727" alt="image" src="https://github.com/user-attachments/assets/7a35868c-b870-4415-998c-5179094ee301" /> After: <img width="1073" height="878" alt="image" src="https://github.com/user-attachments/assets/08fbd3cf-5aaa-4803-a0dd-9ad8491e02a2" />
1 parent 591ecff commit d8d976a

File tree

7 files changed

+135
-85
lines changed

7 files changed

+135
-85
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ export class ParticleInputBlock extends NodeParticleBlock {
140140
case NodeParticleContextualSources.Color:
141141
case NodeParticleContextualSources.InitialColor:
142142
case NodeParticleContextualSources.ColorDead:
143+
case NodeParticleContextualSources.ColorStep:
144+
case NodeParticleContextualSources.ScaledColorStep:
143145
this._type = NodeParticleBlockConnectionPointTypes.Color4;
144146
break;
145147
case NodeParticleContextualSources.Age:

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ export enum NodeParticleContextualSources {
3535
ColorDead = 0x0014,
3636
/** Initial Direction */
3737
InitialDirection = 0x0015,
38+
/** Color Step */
39+
ColorStep = 0x0016,
40+
/** Scaled Color Step */
41+
ScaledColorStep = 0x0017,
3842
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ export class NodeParticleBuildState {
141141
return this.systemContext.startSpriteCellID;
142142
case NodeParticleContextualSources.InitialDirection:
143143
return this.particleContext._initialDirection;
144+
case NodeParticleContextualSources.ColorStep:
145+
return this.particleContext.colorStep;
146+
case NodeParticleContextualSources.ScaledColorStep:
147+
this.particleContext.colorStep.scaleToRef(this.systemContext._scaledUpdateSpeed, this.systemContext._scaledColorStep);
148+
return this.systemContext._scaledColorStep;
144149
}
145150

146151
return null;
Lines changed: 104 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,54 @@
11
import type { Nullable } from "core/types";
22
import type { ParticleSystem } from "../particleSystem";
3-
import { NodeParticleSystemSet } from "./nodeParticleSystemSet";
4-
import { SystemBlock } from "./Blocks/systemBlock";
5-
import { CreateParticleBlock } from "./Blocks/Emitters/createParticleBlock";
6-
import { BoxShapeBlock } from "./Blocks/Emitters/boxShapeBlock";
73
import type { IShapeBlock } from "./Blocks/Emitters/IShapeBlock";
84
import type { Vector3 } from "core/Maths/math.vector";
5+
import type { Color4 } from "core/Maths/math.color";
96
import type { NodeParticleConnectionPoint } from "./nodeParticleBlockConnectionPoint";
107
import type { BoxParticleEmitter } from "../EmitterTypes/boxParticleEmitter";
118
import type { PointParticleEmitter } from "../EmitterTypes/pointParticleEmitter";
129
import type { SphereParticleEmitter } from "../EmitterTypes/sphereParticleEmitter";
1310
import type { CylinderParticleEmitter, MeshParticleEmitter } from "../EmitterTypes";
1411
import type { Mesh } from "core/Meshes/mesh";
12+
import type { Texture } from "core/Materials/Textures/texture";
13+
14+
import { NodeParticleSystemSet } from "./nodeParticleSystemSet";
15+
import { SystemBlock } from "./Blocks/systemBlock";
16+
import { CreateParticleBlock } from "./Blocks/Emitters/createParticleBlock";
17+
import { BoxShapeBlock } from "./Blocks/Emitters/boxShapeBlock";
1518
import { ParticleInputBlock } from "./Blocks/particleInputBlock";
1619
import { PointShapeBlock } from "./Blocks/Emitters/pointShapeBlock";
1720
import { SphereShapeBlock } from "./Blocks/Emitters/sphereShapeBlock";
1821
import { CylinderShapeBlock } from "./Blocks/Emitters/cylinderShapeBlock";
1922
import { MeshShapeBlock } from "./Blocks/Emitters/meshShapeBlock";
2023
import { ParticleTextureSourceBlock } from "./Blocks/particleSourceTextureBlock";
21-
import type { Texture } from "../../Materials/Textures/texture";
22-
import { BasicPositionUpdateBlock } from "./Blocks/Update/basicPositionUpdateBlock";
23-
import { BasicColorUpdateBlock } from "./Blocks/Update/basicColorUpdateBlock";
2424
import { ParticleRandomBlock } from "./Blocks/particleRandomBlock";
25+
import { ParticleConverterBlock } from "./Blocks/particleConverterBlock";
26+
import { ParticleMathBlock, ParticleMathBlockOperations } from "./Blocks/particleMathBlock";
27+
import { UpdateColorBlock } from "./Blocks/Update/updateColorBlock";
28+
import { UpdatePositionBlock } from "./Blocks/Update/updatePositionBlock";
29+
import { NodeParticleContextualSources } from "./Enums/nodeParticleContextualSources";
2530

26-
function _CreateAndConnectInput(connectionPoint: NodeParticleConnectionPoint, name: string, defaultValue: Vector3 | number) {
27-
const input = new ParticleInputBlock(name);
28-
input.value = defaultValue;
29-
input.output.connectTo(connectionPoint);
31+
/**
32+
* Converts a ParticleSystem to a NodeParticleSystemSet.
33+
* @param name The name of the node particle system set.
34+
* @param particleSystems The particle systems to convert.
35+
* @returns The converted node particle system set or null if conversion failed.
36+
* #0K3AQ2#3670
37+
*/
38+
export async function ConvertToNodeParticleSystemSetAsync(name: string, particleSystems: ParticleSystem[]): Promise<Nullable<NodeParticleSystemSet>> {
39+
if (!particleSystems || !particleSystems.length) {
40+
return null;
41+
}
42+
43+
const nodeParticleSystemSet = new NodeParticleSystemSet(name);
44+
const promises: Promise<void>[] = [];
45+
46+
for (const particleSystem of particleSystems) {
47+
promises.push(_ExtractDatafromParticleSystemAsync(particleSystem, nodeParticleSystemSet));
48+
}
49+
50+
await Promise.all(promises);
51+
return nodeParticleSystemSet;
3052
}
3153

3254
async function _ExtractDatafromParticleSystemAsync(particleSystem: ParticleSystem, target: NodeParticleSystemSet) {
@@ -36,59 +58,59 @@ async function _ExtractDatafromParticleSystemAsync(particleSystem: ParticleSyste
3658
system.emitRate = particleSystem.emitRate;
3759

3860
// Create particle
39-
const createParticleBlock = new CreateParticleBlock("Create particle");
61+
const createParticleBlock = new CreateParticleBlock("Create Particle");
4062

4163
// Shape
4264
let shapeBlock: Nullable<IShapeBlock> = null;
4365
switch (particleSystem.particleEmitterType.getClassName()) {
4466
case "BoxParticleEmitter": {
4567
const source = particleSystem.particleEmitterType as BoxParticleEmitter;
46-
shapeBlock = new BoxShapeBlock("Box shape");
68+
shapeBlock = new BoxShapeBlock("Box Shape");
4769

4870
const target = shapeBlock as BoxShapeBlock;
49-
_CreateAndConnectInput(target.direction1, "Direction 1", source.direction1);
50-
_CreateAndConnectInput(target.direction2, "Direction 2", source.direction2);
51-
_CreateAndConnectInput(target.minEmitBox, "Min Emit Box", source.minEmitBox);
52-
_CreateAndConnectInput(target.maxEmitBox, "Max Emit Box", source.maxEmitBox);
71+
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
72+
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
73+
_CreateAndConnectInput("Min Emit Box", source.minEmitBox, target.minEmitBox);
74+
_CreateAndConnectInput("Max Emit Box", source.maxEmitBox, target.maxEmitBox);
5375
break;
5476
}
5577
case "PointParticleEmitter": {
5678
const source = particleSystem.particleEmitterType as PointParticleEmitter;
57-
shapeBlock = new PointShapeBlock("Point shape");
79+
shapeBlock = new PointShapeBlock("Point Shape");
5880

5981
const target = shapeBlock as PointShapeBlock;
60-
_CreateAndConnectInput(target.direction1, "Direction 1", source.direction1);
61-
_CreateAndConnectInput(target.direction2, "Direction 2", source.direction2);
82+
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
83+
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
6284
break;
6385
}
6486
case "SphereParticleEmitter": {
6587
const source = particleSystem.particleEmitterType as SphereParticleEmitter;
66-
shapeBlock = new SphereShapeBlock("Sphere shape");
88+
shapeBlock = new SphereShapeBlock("Sphere Shape");
6789

6890
const target = shapeBlock as SphereShapeBlock;
69-
_CreateAndConnectInput(target.radius, "Radius", source.radius);
70-
_CreateAndConnectInput(target.radiusRange, "Radius Range", source.radiusRange);
71-
_CreateAndConnectInput(target.directionRandomizer, "Direction Randomizer", source.directionRandomizer);
91+
_CreateAndConnectInput("Radius", source.radius, target.radius);
92+
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
93+
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
7294
break;
7395
}
7496
case "CylinderParticleEmitter": {
7597
const source = particleSystem.particleEmitterType as CylinderParticleEmitter;
76-
shapeBlock = new CylinderShapeBlock("Cylinder shape");
98+
shapeBlock = new CylinderShapeBlock("Cylinder Shape");
7799

78100
const target = shapeBlock as CylinderShapeBlock;
79-
_CreateAndConnectInput(target.height, "Height", source.height);
80-
_CreateAndConnectInput(target.radius, "Radius", source.radius);
81-
_CreateAndConnectInput(target.radiusRange, "Radius Range", source.radiusRange);
82-
_CreateAndConnectInput(target.directionRandomizer, "Direction Randomizer", source.directionRandomizer);
101+
_CreateAndConnectInput("Height", source.height, target.height);
102+
_CreateAndConnectInput("Radius", source.radius, target.radius);
103+
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
104+
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
83105
break;
84106
}
85107
case "MeshParticleEmitter": {
86108
const source = particleSystem.particleEmitterType as MeshParticleEmitter;
87-
shapeBlock = new MeshShapeBlock("Mesh shape");
109+
shapeBlock = new MeshShapeBlock("Mesh Shape");
88110

89111
const target = shapeBlock as MeshShapeBlock;
90-
_CreateAndConnectInput(target.direction1, "Direction 1", source.direction1);
91-
_CreateAndConnectInput(target.direction2, "Direction 2", source.direction2);
112+
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
113+
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
92114

93115
target.mesh = source.mesh as Mesh;
94116
break;
@@ -100,45 +122,26 @@ async function _ExtractDatafromParticleSystemAsync(particleSystem: ParticleSyste
100122
}
101123

102124
createParticleBlock.particle.connectTo(shapeBlock.particle);
103-
createParticleBlock.colorDead.value = particleSystem.colorDead;
104125

105-
// Color
106-
const color0Block = new ParticleInputBlock("Color0");
107-
color0Block.value = particleSystem.color1;
108-
109-
const color1Block = new ParticleInputBlock("Color1");
110-
color1Block.value = particleSystem.color2;
126+
// Dead color
127+
_CreateAndConnectInput("Dead Color", particleSystem.colorDead, createParticleBlock.colorDead);
111128

129+
// Color
112130
const randomColorBlock = new ParticleRandomBlock("Random Color");
113-
color0Block.output.connectTo(randomColorBlock.min);
114-
color1Block.output.connectTo(randomColorBlock.max);
115-
131+
_CreateAndConnectInput("Color 1", particleSystem.color1, randomColorBlock.min);
132+
_CreateAndConnectInput("Color 2", particleSystem.color2, randomColorBlock.max);
116133
randomColorBlock.output.connectTo(createParticleBlock.color);
117134

118135
// Emit power
119-
const minEmitPowerBlock = new ParticleInputBlock("Min Emit Power");
120-
minEmitPowerBlock.value = particleSystem.minEmitPower;
121-
122-
const maxEmitPowerBlock = new ParticleInputBlock("Max Emit Power");
123-
maxEmitPowerBlock.value = particleSystem.maxEmitPower;
124-
125136
const randomEmitPowerBlock = new ParticleRandomBlock("Random Emit Power");
126-
minEmitPowerBlock.output.connectTo(randomEmitPowerBlock.min);
127-
maxEmitPowerBlock.output.connectTo(randomEmitPowerBlock.max);
128-
137+
_CreateAndConnectInput("Min Emit Power", particleSystem.minEmitPower, randomEmitPowerBlock.min);
138+
_CreateAndConnectInput("Max Emit Power", particleSystem.maxEmitPower, randomEmitPowerBlock.max);
129139
randomEmitPowerBlock.output.connectTo(createParticleBlock.emitPower);
130140

131141
// Lifetime
132-
const minLifetimeBlock = new ParticleInputBlock("Min Lifetime");
133-
minLifetimeBlock.value = particleSystem.minLifeTime;
134-
135-
const maxLifetimeBlock = new ParticleInputBlock("Max Lifetime");
136-
maxLifetimeBlock.value = particleSystem.maxLifeTime;
137-
138142
const randomLifetimeBlock = new ParticleRandomBlock("Random Lifetime");
139-
minLifetimeBlock.output.connectTo(randomLifetimeBlock.min);
140-
maxLifetimeBlock.output.connectTo(randomLifetimeBlock.max);
141-
143+
_CreateAndConnectInput("Min Lifetime", particleSystem.minLifeTime, randomLifetimeBlock.min);
144+
_CreateAndConnectInput("Max Lifetime", particleSystem.maxLifeTime, randomLifetimeBlock.max);
142145
randomLifetimeBlock.output.connectTo(createParticleBlock.lifeTime);
143146

144147
// Texture
@@ -152,37 +155,53 @@ async function _ExtractDatafromParticleSystemAsync(particleSystem: ParticleSyste
152155
textureBlock.texture.connectTo(system.texture);
153156

154157
// Default position update
155-
const basicPositionUpdateBlock = new BasicPositionUpdateBlock("Position update");
156-
shapeBlock.output.connectTo(basicPositionUpdateBlock.particle);
158+
const positionUpdateblock = new UpdatePositionBlock("Position update");
159+
shapeBlock.output.connectTo(positionUpdateblock.particle);
160+
161+
const addPositionBlock = new ParticleMathBlock("Add Position");
162+
addPositionBlock.operation = ParticleMathBlockOperations.Add;
163+
_CreateAndConnectContextual("Position", NodeParticleContextualSources.Position, addPositionBlock.left);
164+
_CreateAndConnectContextual("Scaled Direction", NodeParticleContextualSources.ScaledDirection, addPositionBlock.right);
165+
addPositionBlock.output.connectTo(positionUpdateblock.position);
157166

158167
// Default color update
159-
const basicColorUpdateBlock = new BasicColorUpdateBlock("Color update");
160-
basicPositionUpdateBlock.output.connectTo(basicColorUpdateBlock.particle);
161-
basicColorUpdateBlock.output.connectTo(system.particle);
168+
const colorUpdateblock = new UpdateColorBlock("Color update");
169+
positionUpdateblock.output.connectTo(colorUpdateblock.particle);
170+
171+
const addColorBlock = new ParticleMathBlock("Add Color");
172+
addColorBlock.operation = ParticleMathBlockOperations.Add;
173+
_CreateAndConnectContextual("Color", NodeParticleContextualSources.Color, addColorBlock.left);
174+
_CreateAndConnectContextual("Scaled Color Step", NodeParticleContextualSources.ScaledColorStep, addColorBlock.right);
175+
addColorBlock.output.connectTo(colorUpdateblock.color);
176+
177+
const decomposeColorBlock = new ParticleConverterBlock("Decompose Color");
178+
addColorBlock.output.connectTo(decomposeColorBlock.colorIn);
179+
180+
// Clamp alpha to be >= 0
181+
const maxAlphaBlock = new ParticleMathBlock("Alpha >= 0");
182+
maxAlphaBlock.operation = ParticleMathBlockOperations.Max;
183+
decomposeColorBlock.wOut.connectTo(maxAlphaBlock.left);
184+
_CreateAndConnectInput("Zero", 0, maxAlphaBlock.right);
185+
186+
const composeColorBlock = new ParticleConverterBlock("Compose Color");
187+
decomposeColorBlock.xyzOut.connectTo(composeColorBlock.xyzIn);
188+
maxAlphaBlock.output.connectTo(composeColorBlock.wIn);
189+
composeColorBlock.colorOut.connectTo(colorUpdateblock.color);
190+
191+
colorUpdateblock.output.connectTo(system.particle);
162192

163193
// Register
164194
target.systemBlocks.push(system);
165195
}
166196

167-
/**
168-
* Converts a ParticleSystem to a NodeParticleSystemSet.
169-
* @param name The name of the node particle system set.
170-
* @param particleSystems The particle systems to convert.
171-
* @returns The converted node particle system set or null if conversion failed.
172-
* #0K3AQ2#3627
173-
*/
174-
export async function ConvertToNodeParticleSystemSetAsync(name: string, particleSystems: ParticleSystem[]): Promise<Nullable<NodeParticleSystemSet>> {
175-
if (!particleSystems || !particleSystems.length) {
176-
return null;
177-
}
178-
179-
const nodeParticleSystemSet = new NodeParticleSystemSet(name);
180-
const promises: Promise<void>[] = [];
181-
182-
for (const particleSystem of particleSystems) {
183-
promises.push(_ExtractDatafromParticleSystemAsync(particleSystem, nodeParticleSystemSet));
184-
}
197+
function _CreateAndConnectInput(inputBlockName: string, value: number | Vector3 | Color4, outputToConnectTo: NodeParticleConnectionPoint) {
198+
const input = new ParticleInputBlock(inputBlockName);
199+
input.value = value;
200+
input.output.connectTo(outputToConnectTo);
201+
}
185202

186-
await Promise.all(promises);
187-
return nodeParticleSystemSet;
203+
function _CreateAndConnectContextual(contextualBlockName: string, contextValue: NodeParticleContextualSources, outputToConnectTo: NodeParticleConnectionPoint): void {
204+
const input = new ParticleInputBlock(contextualBlockName);
205+
input.contextualValue = contextValue;
206+
input.output.connectTo(outputToConnectTo);
188207
}

packages/tools/nodeParticleEditor/src/blockTools.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,16 @@ export class BlockTools {
238238
block.contextualValue = NodeParticleContextualSources.SpriteCellStart;
239239
return block;
240240
}
241+
case "ColorStepBlock": {
242+
const block = new ParticleInputBlock("Color Step");
243+
block.contextualValue = NodeParticleContextualSources.ColorStep;
244+
return block;
245+
}
246+
case "ScaledColorStepBlock": {
247+
const block = new ParticleInputBlock("Scaled Color Step");
248+
block.contextualValue = NodeParticleContextualSources.ScaledColorStep;
249+
return block;
250+
}
241251
case "TimeBlock": {
242252
const block = new ParticleInputBlock("Time");
243253
block.systemSource = NodeParticleSystemSources.Time;

packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
120120
VectorLengthBlock: "Block used to get the length of a vector",
121121
LocalVariableBlock: "Block used to store local values (eg. within a loop)",
122122
FresnelBlock: "Block used to compute the Fresnel term",
123+
ColorStepBlock: "Contextual block to get the expected color step of a particle",
124+
ScaledColorStepBlock: "Contextual block to get the expected scaled color step of a particle",
123125
};
124126

125127
private _customFrameList: { [key: string]: string };
@@ -275,6 +277,8 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
275277
"SpriteCellStartBlock",
276278
"SpriteCellIndexBlock",
277279
"InitialDirectionBlock",
280+
"ColorStepBlock",
281+
"ScaledColorStepBlock",
278282
],
279283
};
280284

packages/tools/nodeParticleEditor/src/graphSystem/display/inputDisplayManager.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ export class InputDisplayManager implements IDisplayManager {
119119
case NodeParticleContextualSources.SpriteCellStart:
120120
value = "Sprite Cell Start";
121121
break;
122+
case NodeParticleContextualSources.ColorStep:
123+
value = "Color Step";
124+
break;
125+
case NodeParticleContextualSources.ScaledColorStep:
126+
value = "Scaled Color Step";
127+
break;
122128
}
123129
} else {
124130
switch (inputBlock.type) {

0 commit comments

Comments
 (0)