Skip to content

Commit 4d7a98f

Browse files
[NPE] Particles color (#17467)
- Adds support to migrate color gradients. - Updates gradient functions to handle ColorGradient or FactorGradient values. - Fixes an issue with alpha clamp on gradients. PG to test: #ZRZJ02#2 PG to test: #ZRZJ02#5 <img width="2098" height="1130" alt="image" src="https://github.com/user-attachments/assets/572cfcb9-5afe-462e-8891-fa8fc3c79f6e" />
1 parent c185fd6 commit 4d7a98f

File tree

2 files changed

+98
-139
lines changed

2 files changed

+98
-139
lines changed

packages/dev/core/src/Particles/Node/nodeParticleSystemSet.helper.ts

Lines changed: 94 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ import { UpdateSizeBlock } from "./Blocks/Update/updateSizeBlock";
4646
/** Represents blocks or groups of blocks that can be used in multiple places in the graph, so they are stored in this context to be reused */
4747
type ConversionContext = {
4848
targetStopDurationBlockOutput: NodeParticleConnectionPoint;
49+
// Connections that represent calculated ratios values
4950
timeToStopTimeRatioBlockGroupOutput: NodeParticleConnectionPoint;
50-
sizeGradientValue0Output: NodeParticleConnectionPoint;
5151
ageToLifeTimeRatioBlockGroupOutput: NodeParticleConnectionPoint;
52+
// Connections for the start value of a gradient. These are stored so they can be reused for the Creation phase and the Update phase of the particle
53+
sizeGradientValue0Output: NodeParticleConnectionPoint;
54+
colorGradientValue0Output: NodeParticleConnectionPoint;
5255
};
5356

5457
type RuntimeConversionContext = Partial<ConversionContext>;
@@ -76,23 +79,19 @@ export async function ConvertToNodeParticleSystemSetAsync(name: string, particle
7679
}
7780

7881
async function _ExtractDatafromParticleSystemAsync(newSet: NodeParticleSystemSet, oldSystem: ParticleSystem, context: RuntimeConversionContext): Promise<void> {
79-
// CreateParticle block
80-
const createParticleBlock = _CreateParticleBlockGroup(oldSystem, context);
82+
// CreateParticle block group
83+
const createParticleOutput = _CreateParticleBlockGroup(oldSystem, context);
8184

8285
// Emitter Shape block
83-
const shapeBlock = _EmitterShapeBlock(oldSystem);
84-
createParticleBlock.particle.connectTo(shapeBlock.particle);
85-
86-
// Update the particle position
87-
const positionUpdatedParticle = _UpdateParticleBlockGroup(shapeBlock.output, oldSystem, context);
86+
const shapeOutput = _EmitterShapeBlock(oldSystem);
87+
createParticleOutput.particle.connectTo(shapeOutput.particle);
8888

89-
// Color update
90-
const colorUpdateBlock = _CreateColorUpdateBlock(oldSystem, createParticleBlock);
91-
positionUpdatedParticle.connectTo(colorUpdateBlock.particle);
89+
// UpdateParticle block group
90+
const updateParticleOutput = _UpdateParticleBlockGroup(shapeOutput.output, oldSystem, context);
9291

9392
// System block
9493
const newSystem = _SystemBlockGroup(oldSystem, context);
95-
colorUpdateBlock.output.connectTo(newSystem.particle);
94+
updateParticleOutput.connectTo(newSystem.particle);
9695

9796
// Register
9897
newSet.systemBlocks.push(newSystem);
@@ -145,8 +144,7 @@ function _CreateParticleBlockGroup(oldSystem: ParticleSystem, context: RuntimeCo
145144
_CreateParticleSizeBlockGroup(oldSystem, context).connectTo(createParticleBlock.size);
146145
_CreateParticleScaleBlockGroup(oldSystem, context).connectTo(createParticleBlock.scale);
147146
_CreateParticleAngleBlockGroup(oldSystem).connectTo(createParticleBlock.angle);
148-
149-
// Color is handled when we do the color update block to manage gradients
147+
_CreateParticleColorBlockGroup(oldSystem, context).connectTo(createParticleBlock.color);
150148

151149
// Dead color
152150
_CreateAndConnectInput("Dead Color", oldSystem.colorDead, createParticleBlock.colorDead);
@@ -198,8 +196,8 @@ function _CreateParticleEmitPowerBlockGroup(oldSystem: ParticleSystem): NodePart
198196
*/
199197
function _CreateParticleSizeBlockGroup(oldSystem: ParticleSystem, context: RuntimeConversionContext): NodeParticleConnectionPoint {
200198
if (oldSystem._sizeGradients && oldSystem._sizeGradients.length > 0) {
201-
const sizeGradientBlockGroupOutput = _CreateParticleSizeGradientBlockGroup(oldSystem._sizeGradients, context);
202-
return sizeGradientBlockGroupOutput;
199+
context.sizeGradientValue0Output = _CreateParticleInitialValueFromGradient(oldSystem._sizeGradients);
200+
return context.sizeGradientValue0Output;
203201
} else {
204202
const randomSizeBlock = new ParticleRandomBlock("Random size");
205203
_CreateAndConnectInput("Min size", oldSystem.minSize, randomSizeBlock.min);
@@ -254,28 +252,44 @@ function _CreateParticleAngleBlockGroup(oldSystem: ParticleSystem): NodeParticle
254252
return randomRotationBlock.output;
255253
}
256254

257-
function _CreateParticleSizeGradientBlockGroup(sizeGradients: Array<FactorGradient>, context: RuntimeConversionContext): NodeParticleConnectionPoint {
258-
if (sizeGradients.length === 0) {
259-
throw new Error("No size gradients provided.");
255+
/**
256+
* Creates the group of blocks that represent the particle color
257+
* @param oldSystem The old particle system to convert
258+
* @param context The context of the current conversion
259+
* @returns The output of the group of blocks that represent the particle color
260+
*/
261+
function _CreateParticleColorBlockGroup(oldSystem: ParticleSystem, context: RuntimeConversionContext): NodeParticleConnectionPoint {
262+
if (oldSystem._colorGradients && oldSystem._colorGradients.length > 0) {
263+
context.colorGradientValue0Output = _CreateParticleInitialValueFromGradient(oldSystem._colorGradients);
264+
return context.colorGradientValue0Output;
265+
} else {
266+
const randomColorBlock = new ParticleRandomBlock("Random color");
267+
_CreateAndConnectInput("Color 1", oldSystem.color1, randomColorBlock.min);
268+
_CreateAndConnectInput("Color 2", oldSystem.color2, randomColorBlock.max);
269+
return randomColorBlock.output;
270+
}
271+
}
272+
273+
function _CreateParticleInitialValueFromGradient(gradients: Array<FactorGradient> | Array<ColorGradient>): NodeParticleConnectionPoint {
274+
if (gradients.length === 0) {
275+
throw new Error("No gradients provided.");
260276
}
261277

262-
const initialParticleSize = _CreateSizeFromGradientStep(sizeGradients[0], 0);
263-
context.sizeGradientValue0Output = initialParticleSize;
264-
return initialParticleSize;
265-
}
278+
const gradientStep = gradients[0];
279+
const value1 = (gradientStep as any).factor1 ?? (gradientStep as any).color1;
280+
const value2 = (gradientStep as any).factor2 ?? (gradientStep as any).color2;
266281

267-
function _CreateSizeFromGradientStep(gradientStep: FactorGradient, index: number): NodeParticleConnectionPoint {
268-
if (gradientStep.factor2 !== undefined) {
282+
if (value2 !== undefined) {
269283
// Create a random between value1 and value2
270-
const randomBlock = new ParticleRandomBlock("Random Value " + index);
284+
const randomBlock = new ParticleRandomBlock("Random Value 0");
271285
randomBlock.lockMode = ParticleRandomBlockLocks.OncePerParticle;
272-
_CreateAndConnectInput("Value 1", gradientStep.factor1, randomBlock.min);
273-
_CreateAndConnectInput("Value 2", gradientStep.factor2, randomBlock.max);
286+
_CreateAndConnectInput("Value 1", value1, randomBlock.min);
287+
_CreateAndConnectInput("Value 2", value2, randomBlock.max);
274288
return randomBlock.output;
275289
} else {
276290
// Single value
277291
const sizeBlock = new ParticleInputBlock("Value");
278-
sizeBlock.value = gradientStep.factor1;
292+
sizeBlock.value = value1;
279293
return sizeBlock.output;
280294
}
281295
}
@@ -437,6 +451,7 @@ function _EmitterShapeBlock(oldSystem: IParticleSystem): IShapeBlock {
437451
function _UpdateParticleBlockGroup(inputParticle: NodeParticleConnectionPoint, oldSystem: ParticleSystem, context: RuntimeConversionContext): NodeParticleConnectionPoint {
438452
let updateBlockGroupOutput: NodeParticleConnectionPoint = inputParticle;
439453

454+
updateBlockGroupOutput = _UpdateParticleColorBlockGroup(updateBlockGroupOutput, oldSystem._colorGradients, context);
440455
updateBlockGroupOutput = _UpdateParticleAngleBlockGroup(updateBlockGroupOutput, oldSystem, context);
441456
updateBlockGroupOutput = _UpdateParticlePositionBlockGroup(updateBlockGroupOutput, oldSystem.isLocal);
442457

@@ -451,14 +466,41 @@ function _UpdateParticleBlockGroup(inputParticle: NodeParticleConnectionPoint, o
451466
return updateBlockGroupOutput;
452467
}
453468

469+
function _UpdateParticleColorBlockGroup(
470+
inputParticle: NodeParticleConnectionPoint,
471+
colorGradients: Nullable<Array<ColorGradient>>,
472+
context: RuntimeConversionContext
473+
): NodeParticleConnectionPoint {
474+
let colorCalculation: NodeParticleConnectionPoint | undefined = undefined;
475+
if (colorGradients && colorGradients.length > 0) {
476+
if (context.colorGradientValue0Output === undefined) {
477+
throw new Error("Initial color gradient values not found in context.");
478+
}
479+
480+
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
481+
colorCalculation = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, colorGradients, ParticleRandomBlockLocks.OncePerParticle, "Color", [
482+
context.colorGradientValue0Output,
483+
]);
484+
} else {
485+
colorCalculation = _CreateBasicColorUpdate();
486+
}
487+
488+
// Create the color update block clamping alpha >= 0
489+
const colorUpdateBlock = new UpdateColorBlock("Color update");
490+
inputParticle.connectTo(colorUpdateBlock.particle);
491+
_ClampUpdateColorAlpha(colorCalculation).connectTo(colorUpdateBlock.color);
492+
493+
return colorUpdateBlock.output;
494+
}
495+
454496
function _UpdateParticleAngleBlockGroup(inputParticle: NodeParticleConnectionPoint, oldSystem: ParticleSystem, context: RuntimeConversionContext): NodeParticleConnectionPoint {
455497
// We will try to use gradients if they exist
456498
// If not, we will try to use min/max angular speed
457499
let angularSpeedCalculation = null;
458500
if (oldSystem._angularSpeedGradients && oldSystem._angularSpeedGradients.length > 0) {
459-
angularSpeedCalculation = _UpdateParticleAngularSpeedGradientBlockGroup(inputParticle, oldSystem._angularSpeedGradients, context);
501+
angularSpeedCalculation = _UpdateParticleAngularSpeedGradientBlockGroup(oldSystem._angularSpeedGradients, context);
460502
} else if (oldSystem.minAngularSpeed !== 0 || oldSystem.maxAngularSpeed !== 0) {
461-
angularSpeedCalculation = _UpdateParticleAngularSpeedBlockGroup(inputParticle, oldSystem.minAngularSpeed, oldSystem.maxAngularSpeed);
503+
angularSpeedCalculation = _UpdateParticleAngularSpeedBlockGroup(oldSystem.minAngularSpeed, oldSystem.maxAngularSpeed);
462504
}
463505

464506
// If we have an angular speed calculation, then update the angle
@@ -483,11 +525,7 @@ function _UpdateParticleAngleBlockGroup(inputParticle: NodeParticleConnectionPoi
483525
}
484526
}
485527

486-
function _UpdateParticleAngularSpeedGradientBlockGroup(
487-
inputParticle: NodeParticleConnectionPoint,
488-
angularSpeedGradients: Array<FactorGradient>,
489-
context: RuntimeConversionContext
490-
): NodeParticleConnectionPoint {
528+
function _UpdateParticleAngularSpeedGradientBlockGroup(angularSpeedGradients: Array<FactorGradient>, context: RuntimeConversionContext): NodeParticleConnectionPoint {
491529
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
492530

493531
// Generate the gradient
@@ -500,7 +538,7 @@ function _UpdateParticleAngularSpeedGradientBlockGroup(
500538
return angularSpeedValueOutput;
501539
}
502540

503-
function _UpdateParticleAngularSpeedBlockGroup(inputParticle: NodeParticleConnectionPoint, minAngularSpeed: number, maxAngularSpeed: number): NodeParticleConnectionPoint {
541+
function _UpdateParticleAngularSpeedBlockGroup(minAngularSpeed: number, maxAngularSpeed: number): NodeParticleConnectionPoint {
504542
// Random value between for the angular speed of the particle
505543
const randomAngularSpeedBlock = new ParticleRandomBlock("Random Angular Speed");
506544
randomAngularSpeedBlock.lockMode = ParticleRandomBlockLocks.OncePerParticle;
@@ -570,110 +608,19 @@ function _UpdateParticleGravityBlockGroup(inputParticle: NodeParticleConnectionP
570608
return updateDirection.output;
571609
}
572610

573-
function _CreateColorUpdateBlock(oldSystem: IParticleSystem, createParticleBlock: CreateParticleBlock): UpdateColorBlock {
574-
if (!oldSystem) {
575-
throw new Error("Invalid particle system");
576-
}
577-
578-
// Calculate the color
579-
const colorGradients = oldSystem.getColorGradients();
580-
let colorBlock: Nullable<ParticleGradientBlock | ParticleMathBlock> = null;
581-
if (colorGradients && colorGradients.length > 0) {
582-
colorBlock = _CreateGradientColorUpdate(oldSystem, colorGradients, createParticleBlock);
583-
} else {
584-
colorBlock = _CreateBasicColorUpdate(oldSystem, createParticleBlock);
585-
}
586-
587-
// Clamp alpha >= 0
588-
const clampedColor = _ClampUpdateColorAlpha(colorBlock);
589-
590-
// Create the color update block
591-
const colorUpdateBlock = new UpdateColorBlock("Color update");
592-
clampedColor.colorOut.connectTo(colorUpdateBlock.color);
593-
594-
return colorUpdateBlock;
595-
}
596-
597-
function _CreateGradientColorUpdate(oldSystem: IParticleSystem, gradient: Array<ColorGradient>, createParticleBlock: CreateParticleBlock): ParticleGradientBlock {
598-
const colorGradientBlock = new ParticleGradientBlock("Color Gradient");
599-
_CreateAndConnectContextualSource("gradient", NodeParticleContextualSources.Age, colorGradientBlock.gradient);
600-
601-
let tempColor: Nullable<ParticleInputBlock | ParticleRandomBlock> = null;
602-
let colorStart: Nullable<ParticleInputBlock | ParticleRandomBlock> = null;
603-
let colorEnd: Nullable<ParticleInputBlock | ParticleRandomBlock> = null;
604-
for (let i = 0; i < gradient.length; i++) {
605-
const gradientStep = gradient[i];
606-
const gradientValueBlock = new ParticleGradientValueBlock("Color Gradient Value " + i);
607-
gradientValueBlock.reference = gradientStep.gradient;
608-
609-
if (gradientStep.color2) {
610-
// Create a random between color1 and color2
611-
const randomColorBlock = new ParticleRandomBlock("Random Color for Gradient " + i);
612-
randomColorBlock.lockMode = ParticleRandomBlockLocks.PerSystem;
613-
_CreateAndConnectInput("Color 1", gradientStep.color1, randomColorBlock.min);
614-
_CreateAndConnectInput("Color 2", gradientStep.color2, randomColorBlock.max);
615-
randomColorBlock.output.connectTo(gradientValueBlock.value);
616-
tempColor = randomColorBlock;
617-
} else {
618-
// Single color
619-
const input = new ParticleInputBlock("Color " + i);
620-
input.value = gradientStep.color1;
621-
input.output.connectTo(gradientValueBlock.value);
622-
tempColor = input;
623-
}
624-
625-
if (gradientStep.gradient === 0) {
626-
colorStart = tempColor;
627-
} else if (gradientStep.gradient === 1) {
628-
colorEnd = tempColor;
629-
}
630-
631-
gradientValueBlock.output.connectTo(colorGradientBlock.inputs[i + 1]);
632-
}
633-
634-
_UpdateCreateParticleColor(oldSystem, colorStart, colorEnd, createParticleBlock);
635-
636-
return colorGradientBlock;
637-
}
638-
639-
function _CreateBasicColorUpdate(oldSystem: IParticleSystem, createParticleBlock: CreateParticleBlock): ParticleMathBlock {
611+
function _CreateBasicColorUpdate(): NodeParticleConnectionPoint {
640612
const addColorBlock = new ParticleMathBlock("Add Color");
641613
addColorBlock.operation = ParticleMathBlockOperations.Add;
642614
_CreateAndConnectContextualSource("Color", NodeParticleContextualSources.Color, addColorBlock.left);
643615
_CreateAndConnectContextualSource("Scaled Color Step", NodeParticleContextualSources.ScaledColorStep, addColorBlock.right);
644616

645-
_UpdateCreateParticleColor(oldSystem, null, null, createParticleBlock);
646-
647-
return addColorBlock;
617+
return addColorBlock.output;
648618
}
649619

650-
function _UpdateCreateParticleColor(
651-
oldSystem: IParticleSystem,
652-
colorStart: Nullable<ParticleInputBlock | ParticleRandomBlock>,
653-
colorEnd: Nullable<ParticleInputBlock | ParticleRandomBlock>,
654-
createParticleBlock: CreateParticleBlock
655-
): void {
656-
if (colorStart === null) {
657-
colorStart = new ParticleInputBlock("Color Start");
658-
colorStart.value = oldSystem.color1;
659-
}
660-
661-
if (colorEnd === null) {
662-
colorEnd = new ParticleInputBlock("Color End");
663-
colorEnd.value = oldSystem.color2;
664-
}
665-
666-
const randomColorBlock = new ParticleRandomBlock("Random color");
667-
randomColorBlock.lockMode = ParticleRandomBlockLocks.PerParticle;
668-
colorStart.output.connectTo(randomColorBlock.min);
669-
colorEnd.output.connectTo(randomColorBlock.max);
670-
randomColorBlock.output.connectTo(createParticleBlock.color);
671-
}
672-
673-
function _ClampUpdateColorAlpha(colorBlock: ParticleMathBlock | ParticleGradientBlock): ParticleConverterBlock {
620+
function _ClampUpdateColorAlpha(colorCalculationOutput: NodeParticleConnectionPoint): NodeParticleConnectionPoint {
674621
// Decompose color to clamp alpha
675622
const decomposeColorBlock = new ParticleConverterBlock("Decompose Color");
676-
colorBlock.outputs[0].connectTo(decomposeColorBlock.colorIn);
623+
colorCalculationOutput.connectTo(decomposeColorBlock.colorIn);
677624

678625
// Clamp alpha to be >= 0
679626
const maxAlphaBlock = new ParticleMathBlock("Alpha >= 0");
@@ -686,7 +633,7 @@ function _ClampUpdateColorAlpha(colorBlock: ParticleMathBlock | ParticleGradient
686633
decomposeColorBlock.xyzOut.connectTo(composeColorBlock.xyzIn);
687634
maxAlphaBlock.output.connectTo(composeColorBlock.wIn);
688635

689-
return composeColorBlock;
636+
return composeColorBlock.colorOut;
690637
}
691638

692639
// ------------- UTILITY FUNCTIONS -------------
@@ -816,7 +763,7 @@ function _CreateAgeToLifeTimeRatioBlockGroup(context: RuntimeConversionContext):
816763
*/
817764
function _CreateGradientBlockGroup(
818765
gradientSelector: NodeParticleConnectionPoint,
819-
gradientValues: Array<FactorGradient>,
766+
gradientValues: Array<FactorGradient> | Array<ColorGradient>,
820767
randomLockMode: ParticleRandomBlockLocks,
821768
prefix: string,
822769
initialValues: NodeParticleConnectionPoint[] = []
@@ -853,20 +800,28 @@ function _CreateGradientBlockGroup(
853800
* @param index The index of the gradient step
854801
* @returns The output connection point of the gradient value block
855802
*/
856-
function _CreateGradientValueBlockGroup(gradientStep: FactorGradient, randomLockMode: ParticleRandomBlockLocks, prefix: string, index: number): NodeParticleConnectionPoint {
803+
function _CreateGradientValueBlockGroup(
804+
gradientStep: FactorGradient | ColorGradient,
805+
randomLockMode: ParticleRandomBlockLocks,
806+
prefix: string,
807+
index: number
808+
): NodeParticleConnectionPoint {
857809
const gradientValueBlock = new ParticleGradientValueBlock(prefix + " Gradient Value " + index);
858810
gradientValueBlock.reference = gradientStep.gradient;
859811

860-
if (gradientStep.factor2 !== undefined) {
812+
const value1 = (gradientStep as any).factor1 ?? (gradientStep as any).color1;
813+
const value2 = (gradientStep as any).factor2 ?? (gradientStep as any).color2;
814+
815+
if (value2 !== undefined) {
861816
// Create a random between value1 and value2
862817
const randomBlock = new ParticleRandomBlock("Random Value " + index);
863818
randomBlock.lockMode = randomLockMode;
864-
_CreateAndConnectInput("Value 1", gradientStep.factor1, randomBlock.min);
865-
_CreateAndConnectInput("Value 2", gradientStep.factor2, randomBlock.max);
819+
_CreateAndConnectInput("Value 1", value1, randomBlock.min);
820+
_CreateAndConnectInput("Value 2", value2, randomBlock.max);
866821
randomBlock.output.connectTo(gradientValueBlock.value);
867822
} else {
868823
// Single value
869-
_CreateAndConnectInput("Value", gradientStep.factor1, gradientValueBlock.value);
824+
_CreateAndConnectInput("Value", value1, gradientValueBlock.value);
870825
}
871826

872827
return gradientValueBlock.output;

packages/dev/core/src/Particles/thinParticleSystem.function.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export function _ProcessColorGradients(particle: Particle, system: ThinParticleS
4444
particle._currentColorGradient = <ColorGradient>currentGradient;
4545
}
4646
Color4.LerpToRef(particle._currentColor1, particle._currentColor2, scale, particle.color);
47+
48+
if (particle.color.a < 0) {
49+
particle.color.a = 0;
50+
}
4751
});
4852
}
4953

0 commit comments

Comments
 (0)