@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
844 lines • 46.9 kB
JavaScript
import { Vector2, Vector3 } from "../../Maths/math.vector.js";
import { NodeParticleSystemSet } from "./nodeParticleSystemSet.js";
import { NodeParticleContextualSources } from "./Enums/nodeParticleContextualSources.js";
import { NodeParticleSystemSources } from "./Enums/nodeParticleSystemSources.js";
import { ParticleConverterBlock } from "./Blocks/particleConverterBlock.js";
import { ParticleFloatToIntBlock, ParticleFloatToIntBlockOperations } from "./Blocks/particleFloatToIntBlock.js";
import { ParticleGradientBlock } from "./Blocks/particleGradientBlock.js";
import { ParticleGradientValueBlock } from "./Blocks/particleGradientValueBlock.js";
import { ParticleInputBlock } from "./Blocks/particleInputBlock.js";
import { ParticleMathBlock, ParticleMathBlockOperations } from "./Blocks/particleMathBlock.js";
import { ParticleRandomBlock, ParticleRandomBlockLocks } from "./Blocks/particleRandomBlock.js";
import { ParticleTextureSourceBlock } from "./Blocks/particleSourceTextureBlock.js";
import { ParticleVectorLengthBlock } from "./Blocks/particleVectorLengthBlock.js";
import { SystemBlock } from "./Blocks/systemBlock.js";
import { ParticleConditionBlock, ParticleConditionBlockTests } from "./Blocks/Conditions/particleConditionBlock.js";
import { CreateParticleBlock } from "./Blocks/Emitters/createParticleBlock.js";
import { BoxShapeBlock } from "./Blocks/Emitters/boxShapeBlock.js";
import { ConeShapeBlock } from "./Blocks/Emitters/coneShapeBlock.js";
import { CylinderShapeBlock } from "./Blocks/Emitters/cylinderShapeBlock.js";
import { CustomShapeBlock } from "./Blocks/Emitters/customShapeBlock.js";
import { MeshShapeBlock } from "./Blocks/Emitters/meshShapeBlock.js";
import { PointShapeBlock } from "./Blocks/Emitters/pointShapeBlock.js";
import { SphereShapeBlock } from "./Blocks/Emitters/sphereShapeBlock.js";
import { UpdateAngleBlock } from "./Blocks/Update/updateAngleBlock.js";
import { UpdateColorBlock } from "./Blocks/Update/updateColorBlock.js";
import { UpdateDirectionBlock } from "./Blocks/Update/updateDirectionBlock.js";
import { UpdateNoiseBlock } from "./Blocks/Update/updateNoiseBlock.js";
import { UpdatePositionBlock } from "./Blocks/Update/updatePositionBlock.js";
import { UpdateSizeBlock } from "./Blocks/Update/updateSizeBlock.js";
/**
* Converts a ParticleSystem to a NodeParticleSystemSet.
* @param name The name of the node particle system set.
* @param particleSystemsList The particle systems to convert.
* @returns The converted node particle system set or null if conversion failed.
*/
export async function ConvertToNodeParticleSystemSetAsync(name, particleSystemsList) {
if (!particleSystemsList || !particleSystemsList.length) {
return null;
}
const nodeParticleSystemSet = new NodeParticleSystemSet(name);
const promises = [];
for (const particleSystem of particleSystemsList) {
promises.push(_ExtractDatafromParticleSystemAsync(nodeParticleSystemSet, particleSystem, {}));
}
await Promise.all(promises);
return nodeParticleSystemSet;
}
async function _ExtractDatafromParticleSystemAsync(newSet, oldSystem, context) {
// CreateParticle block group
const createParticleOutput = _CreateParticleBlockGroup(oldSystem, context);
// Emitter Shape block
const shapeOutput = _EmitterShapeBlock(oldSystem);
createParticleOutput.particle.connectTo(shapeOutput.particle);
// UpdateParticle block group
const updateParticleOutput = _UpdateParticleBlockGroup(shapeOutput.output, oldSystem, context);
// System block
const newSystem = _SystemBlockGroup(oldSystem, context);
updateParticleOutput.connectTo(newSystem.particle);
// Register
newSet.systemBlocks.push(newSystem);
}
// ------------- CREATE PARTICLE FUNCTIONS -------------
// The creation of the different properties follows the order they are added to the CreationQueue in ThinParticleSystem:
// Lifetime, Emit Power, Size, Scale/StartSize, Angle, Color, Noise, ColorDead, Ramp, Sheet
function _CreateParticleBlockGroup(oldSystem, context) {
// Create particle block
const createParticleBlock = new CreateParticleBlock("Create Particle");
_CreateParticleLifetimeBlockGroup(oldSystem, context).connectTo(createParticleBlock.lifeTime);
_CreateParticleEmitPowerBlockGroup(oldSystem).connectTo(createParticleBlock.emitPower);
_CreateParticleSizeBlockGroup(oldSystem, context).connectTo(createParticleBlock.size);
_CreateParticleScaleBlockGroup(oldSystem, context).connectTo(createParticleBlock.scale);
_CreateParticleAngleBlockGroup(oldSystem).connectTo(createParticleBlock.angle);
_CreateParticleColorBlockGroup(oldSystem, context).connectTo(createParticleBlock.color);
// Dead color
_CreateAndConnectInput("Dead Color", oldSystem.colorDead, createParticleBlock.colorDead);
return createParticleBlock;
}
/**
* Creates the group of blocks that represent the particle lifetime
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle lifetime
*/
function _CreateParticleLifetimeBlockGroup(oldSystem, context) {
if (oldSystem.targetStopDuration && oldSystem._lifeTimeGradients && oldSystem._lifeTimeGradients.length > 0) {
context.timeToStopTimeRatioBlockGroupOutput = _CreateTimeToStopTimeRatioBlockGroup(oldSystem, context);
const gradientBlockGroupOutput = _CreateGradientBlockGroup(context.timeToStopTimeRatioBlockGroupOutput, oldSystem._lifeTimeGradients, ParticleRandomBlockLocks.PerParticle, "Lifetime");
return gradientBlockGroupOutput;
}
else {
const randomLifetimeBlock = new ParticleRandomBlock("Random Lifetime");
_CreateAndConnectInput("Min Lifetime", oldSystem.minLifeTime, randomLifetimeBlock.min);
_CreateAndConnectInput("Max Lifetime", oldSystem.maxLifeTime, randomLifetimeBlock.max);
return randomLifetimeBlock.output;
}
}
/**
* Creates the group of blocks that represent the particle emit power
* @param oldSystem The old particle system to convert
* @returns The output of the group of blocks that represent the particle emit power
*/
function _CreateParticleEmitPowerBlockGroup(oldSystem) {
const randomEmitPowerBlock = new ParticleRandomBlock("Random Emit Power");
_CreateAndConnectInput("Min Emit Power", oldSystem.minEmitPower, randomEmitPowerBlock.min);
_CreateAndConnectInput("Max Emit Power", oldSystem.maxEmitPower, randomEmitPowerBlock.max);
return randomEmitPowerBlock.output;
}
/**
* Creates the group of blocks that represent the particle size
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle size
*/
function _CreateParticleSizeBlockGroup(oldSystem, context) {
if (oldSystem._sizeGradients && oldSystem._sizeGradients.length > 0) {
context.sizeGradientValue0Output = _CreateParticleInitialValueFromGradient(oldSystem._sizeGradients);
return context.sizeGradientValue0Output;
}
else {
const randomSizeBlock = new ParticleRandomBlock("Random size");
_CreateAndConnectInput("Min size", oldSystem.minSize, randomSizeBlock.min);
_CreateAndConnectInput("Max size", oldSystem.maxSize, randomSizeBlock.max);
return randomSizeBlock.output;
}
}
/**
* Creates the group of blocks that represent the particle scale
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle scale
*/
function _CreateParticleScaleBlockGroup(oldSystem, context) {
// Create the random scale
const randomScaleBlock = new ParticleRandomBlock("Random Scale");
_CreateAndConnectInput("Min Scale", new Vector2(oldSystem.minScaleX, oldSystem.minScaleY), randomScaleBlock.min);
_CreateAndConnectInput("Max Scale", new Vector2(oldSystem.maxScaleX, oldSystem.maxScaleY), randomScaleBlock.max);
if (oldSystem.targetStopDuration && oldSystem._startSizeGradients && oldSystem._startSizeGradients.length > 0) {
// Create the start size gradient
context.timeToStopTimeRatioBlockGroupOutput = _CreateTimeToStopTimeRatioBlockGroup(oldSystem, context);
const gradientBlockGroupOutput = _CreateGradientBlockGroup(context.timeToStopTimeRatioBlockGroupOutput, oldSystem._startSizeGradients, ParticleRandomBlockLocks.PerParticle, "Start Size");
// Multiply the initial random scale by the start size gradient
const multiplyScaleBlock = new ParticleMathBlock("Multiply Scale by Start Size Gradient");
multiplyScaleBlock.operation = ParticleMathBlockOperations.Multiply;
randomScaleBlock.output.connectTo(multiplyScaleBlock.left);
gradientBlockGroupOutput.connectTo(multiplyScaleBlock.right);
return multiplyScaleBlock.output;
}
else {
return randomScaleBlock.output;
}
}
/**
* Creates the group of blocks that represent the particle angle (rotation)
* @param oldSystem The old particle system to convert
* @returns The output of the group of blocks that represent the particle angle (rotation)
*/
function _CreateParticleAngleBlockGroup(oldSystem) {
const randomRotationBlock = new ParticleRandomBlock("Random Rotation");
_CreateAndConnectInput("Min Rotation", oldSystem.minInitialRotation, randomRotationBlock.min);
_CreateAndConnectInput("Max Rotation", oldSystem.maxInitialRotation, randomRotationBlock.max);
return randomRotationBlock.output;
}
/**
* Creates the group of blocks that represent the particle color
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle color
*/
function _CreateParticleColorBlockGroup(oldSystem, context) {
if (oldSystem._colorGradients && oldSystem._colorGradients.length > 0) {
context.colorGradientValue0Output = _CreateParticleInitialValueFromGradient(oldSystem._colorGradients);
return context.colorGradientValue0Output;
}
else {
const randomColorBlock = new ParticleRandomBlock("Random color");
_CreateAndConnectInput("Color 1", oldSystem.color1, randomColorBlock.min);
_CreateAndConnectInput("Color 2", oldSystem.color2, randomColorBlock.max);
return randomColorBlock.output;
}
}
function _CreateParticleInitialValueFromGradient(gradients) {
if (gradients.length === 0) {
throw new Error("No gradients provided.");
}
const gradientStep = gradients[0];
const value1 = gradientStep.factor1 ?? gradientStep.color1;
const value2 = gradientStep.factor2 ?? gradientStep.color2;
if (value2 !== undefined) {
// Create a random between value1 and value2
const randomBlock = new ParticleRandomBlock("Random Value 0");
randomBlock.lockMode = ParticleRandomBlockLocks.OncePerParticle;
_CreateAndConnectInput("Value 1", value1, randomBlock.min);
_CreateAndConnectInput("Value 2", value2, randomBlock.max);
return randomBlock.output;
}
else {
// Single value
const sizeBlock = new ParticleInputBlock("Value");
sizeBlock.value = value1;
return sizeBlock.output;
}
}
// ------------- EMITTER SHAPE FUNCTIONS -------------
function _EmitterShapeBlock(oldSystem) {
const emitter = oldSystem.particleEmitterType;
if (!emitter) {
throw new Error("Particle system has no emitter type.");
}
let shapeBlock = null;
switch (emitter.getClassName()) {
case "BoxParticleEmitter": {
const source = emitter;
shapeBlock = new BoxShapeBlock("Box Shape");
const target = shapeBlock;
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
_CreateAndConnectInput("Min Emit Box", source.minEmitBox, target.minEmitBox);
_CreateAndConnectInput("Max Emit Box", source.maxEmitBox, target.maxEmitBox);
break;
}
case "ConeParticleEmitter": {
const source = emitter;
shapeBlock = new ConeShapeBlock("Cone Shape");
const target = shapeBlock;
target.emitFromSpawnPointOnly = source.emitFromSpawnPointOnly;
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Angle", source.angle, target.angle);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Height Range", source.heightRange, target.heightRange);
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
break;
}
case "ConeDirectedParticleEmitter": {
const source = emitter;
shapeBlock = new ConeShapeBlock("Cone Shape");
const target = shapeBlock;
target.emitFromSpawnPointOnly = source.emitFromSpawnPointOnly;
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Angle", source.angle, target.angle);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Height Range", source.heightRange, target.heightRange);
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
break;
}
case "CustomParticleEmitter": {
const source = emitter;
shapeBlock = new CustomShapeBlock("Custom Shape");
const target = shapeBlock;
target.particlePositionGenerator = source.particlePositionGenerator;
target.particleDestinationGenerator = source.particleDestinationGenerator;
target.particleDirectionGenerator = source.particleDirectionGenerator;
break;
}
case "CylinderParticleEmitter": {
const source = emitter;
shapeBlock = new CylinderShapeBlock("Cylinder Shape");
const target = shapeBlock;
_CreateAndConnectInput("Height", source.height, target.height);
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
break;
}
case "CylinderDirectedParticleEmitter": {
const source = emitter;
shapeBlock = new CylinderShapeBlock("Cylinder Shape");
const target = shapeBlock;
_CreateAndConnectInput("Height", source.height, target.height);
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
break;
}
case "HemisphericParticleEmitter": {
const source = emitter;
shapeBlock = new SphereShapeBlock("Sphere Shape");
const target = shapeBlock;
target.isHemispheric = true;
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
break;
}
case "MeshParticleEmitter": {
const source = emitter;
shapeBlock = new MeshShapeBlock("Mesh Shape");
const target = shapeBlock;
target.useMeshNormalsForDirection = source.useMeshNormalsForDirection;
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
target.mesh = source.mesh;
break;
}
case "PointParticleEmitter": {
const source = emitter;
shapeBlock = new PointShapeBlock("Point Shape");
const target = shapeBlock;
_CreateAndConnectInput("Direction 1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction 2", source.direction2, target.direction2);
break;
}
case "SphereParticleEmitter": {
const source = emitter;
shapeBlock = new SphereShapeBlock("Sphere Shape");
const target = shapeBlock;
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Direction Randomizer", source.directionRandomizer, target.directionRandomizer);
break;
}
case "SphereDirectedParticleEmitter": {
const source = emitter;
shapeBlock = new SphereShapeBlock("Sphere Shape");
const target = shapeBlock;
_CreateAndConnectInput("Radius", source.radius, target.radius);
_CreateAndConnectInput("Radius Range", source.radiusRange, target.radiusRange);
_CreateAndConnectInput("Direction1", source.direction1, target.direction1);
_CreateAndConnectInput("Direction2", source.direction2, target.direction2);
break;
}
}
if (!shapeBlock) {
throw new Error(`Unsupported particle emitter type: ${emitter.getClassName()}`);
}
return shapeBlock;
}
// ------------- UPDATE PARTICLE FUNCTIONS -------------
/**
* Creates the group of blocks that represent the particle system update
* The creation of the different properties follows the order they are added to the ProcessQueue in ThinParticleSystem:
* Color, AngularSpeedGradients, AngularSpeed, VelocityGradients, Direction, LimitVelocityGradients, DragGradients, Position, Noise, SizeGradients, Gravity, RemapGradients
* @param inputParticle The particle input connection point
* @param oldSystem The old particle system to convert
* @param context The runtime conversion context
* @returns The output connection point after all updates have been applied
*/
function _UpdateParticleBlockGroup(inputParticle, oldSystem, context) {
let updatedParticle = inputParticle;
updatedParticle = _UpdateParticleColorBlockGroup(updatedParticle, oldSystem._colorGradients, context);
updatedParticle = _UpdateParticleAngleBlockGroup(updatedParticle, oldSystem, context);
if (oldSystem._velocityGradients && oldSystem._velocityGradients.length > 0) {
context.scaledDirection = _UpdateParticleVelocityGradientBlockGroup(oldSystem._velocityGradients, context);
}
if (oldSystem._dragGradients && oldSystem._dragGradients.length > 0) {
context.scaledDirection = _UpdateParticleDragGradientBlockGroup(oldSystem._dragGradients, context);
}
updatedParticle = _UpdateParticlePositionBlockGroup(updatedParticle, oldSystem.isLocal, context);
if (oldSystem._limitVelocityGradients && oldSystem._limitVelocityGradients.length > 0 && oldSystem.limitVelocityDamping !== 0) {
updatedParticle = _UpdateParticleVelocityLimitGradientBlockGroup(updatedParticle, oldSystem._limitVelocityGradients, oldSystem.limitVelocityDamping, context);
}
if (oldSystem.noiseTexture && oldSystem.noiseStrength) {
updatedParticle = _UpdateParticleNoiseBlockGroup(updatedParticle, oldSystem.noiseTexture, oldSystem.noiseStrength);
}
if (oldSystem._sizeGradients && oldSystem._sizeGradients.length > 0) {
updatedParticle = _UpdateParticleSizeGradientBlockGroup(updatedParticle, oldSystem._sizeGradients, context);
}
if (oldSystem.gravity.equalsToFloats(0, 0, 0) === false) {
updatedParticle = _UpdateParticleGravityBlockGroup(updatedParticle, oldSystem.gravity);
}
return updatedParticle;
}
/**
* Creates the group of blocks that represent the particle color update
* @param inputParticle The input particle to update
* @param colorGradients The color gradients (if any)
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle color update
*/
function _UpdateParticleColorBlockGroup(inputParticle, colorGradients, context) {
let colorCalculation = undefined;
if (colorGradients && colorGradients.length > 0) {
if (context.colorGradientValue0Output === undefined) {
throw new Error("Initial color gradient values not found in context.");
}
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
colorCalculation = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, colorGradients, ParticleRandomBlockLocks.OncePerParticle, "Color", [
context.colorGradientValue0Output,
]);
}
else {
colorCalculation = _BasicColorUpdateBlockGroup();
}
// Create the color update block clamping alpha >= 0
const colorUpdateBlock = new UpdateColorBlock("Color update");
inputParticle.connectTo(colorUpdateBlock.particle);
_ClampUpdateColorAlpha(colorCalculation).connectTo(colorUpdateBlock.color);
return colorUpdateBlock.output;
}
/**
* Creates the group of blocks that represent the particle angle update
* @param inputParticle The input particle to update
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle color update
*/
function _UpdateParticleAngleBlockGroup(inputParticle, oldSystem, context) {
// We will try to use gradients if they exist
// If not, we will try to use min/max angular speed
let angularSpeedCalculation = null;
if (oldSystem._angularSpeedGradients && oldSystem._angularSpeedGradients.length > 0) {
angularSpeedCalculation = _UpdateParticleAngularSpeedGradientBlockGroup(oldSystem._angularSpeedGradients, context);
}
else if (oldSystem.minAngularSpeed !== 0 || oldSystem.maxAngularSpeed !== 0) {
angularSpeedCalculation = _UpdateParticleAngularSpeedBlockGroup(oldSystem.minAngularSpeed, oldSystem.maxAngularSpeed);
}
// If we have an angular speed calculation, then update the angle
if (angularSpeedCalculation) {
// Create the angular speed delta
const angleSpeedDeltaOutput = _CreateDeltaModifiedInput("Angular Speed", angularSpeedCalculation);
// Add it to the angle
const addAngle = new ParticleMathBlock("Add Angular Speed to Angle");
addAngle.operation = ParticleMathBlockOperations.Add;
_CreateAndConnectContextualSource("Angle", NodeParticleContextualSources.Angle, addAngle.left);
angleSpeedDeltaOutput.connectTo(addAngle.right);
// Update the particle angle
const updateAngle = new UpdateAngleBlock("Angle Update with Angular Speed");
inputParticle.connectTo(updateAngle.particle);
addAngle.output.connectTo(updateAngle.angle);
return updateAngle.output;
}
else {
return inputParticle;
}
}
/**
* Creates the group of blocks that represent the particle velocity update
* @param velocityGradients The velocity gradients
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle velocity update
*/
function _UpdateParticleVelocityGradientBlockGroup(velocityGradients, context) {
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
// Generate the gradient
const velocityValueOutput = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, velocityGradients, ParticleRandomBlockLocks.OncePerParticle, "Velocity");
// Update the direction scale based on the velocity
const multiplyScaleByVelocity = new ParticleMathBlock("Multiply Direction Scale by Velocity");
multiplyScaleByVelocity.operation = ParticleMathBlockOperations.Multiply;
velocityValueOutput.connectTo(multiplyScaleByVelocity.left);
_CreateAndConnectContextualSource("Direction Scale", NodeParticleContextualSources.DirectionScale, multiplyScaleByVelocity.right);
// Update the particle direction scale
const multiplyDirection = new ParticleMathBlock("Scaled Direction");
multiplyDirection.operation = ParticleMathBlockOperations.Multiply;
multiplyScaleByVelocity.output.connectTo(multiplyDirection.left);
_CreateAndConnectContextualSource("Direction", NodeParticleContextualSources.Direction, multiplyDirection.right);
// Store the new calculation of the scaled direction in the context
context.scaledDirection = multiplyDirection.output;
return multiplyDirection.output;
}
/**
* Creates the group of blocks that represent the particle velocity limit update
* @param inputParticle The input particle to update
* @param velocityLimitGradients The velocity limit gradients
* @param limitVelocityDamping The limit velocity damping factor
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle velocity limit update
*/
function _UpdateParticleVelocityLimitGradientBlockGroup(inputParticle, velocityLimitGradients, limitVelocityDamping, context) {
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
// Calculate the current speed
const currentSpeedBlock = new ParticleVectorLengthBlock("Current Speed");
_CreateAndConnectContextualSource("Direction", NodeParticleContextualSources.Direction, currentSpeedBlock.input);
// Calculate the velocity limit from the gradient
const velocityLimitValueOutput = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, velocityLimitGradients, ParticleRandomBlockLocks.OncePerParticle, "Velocity Limit");
// Blocks that will calculate the new velocity if over the limit
const damped = new ParticleMathBlock("Damped Speed");
damped.operation = ParticleMathBlockOperations.Multiply;
_CreateAndConnectContextualSource("Direction", NodeParticleContextualSources.Direction, damped.left);
_CreateAndConnectInput("Limit Velocity Damping", limitVelocityDamping, damped.right);
// Compare current speed and limit
const compareSpeed = new ParticleConditionBlock("Compare Speed to Limit");
compareSpeed.test = ParticleConditionBlockTests.GreaterThan;
currentSpeedBlock.output.connectTo(compareSpeed.left);
velocityLimitValueOutput.connectTo(compareSpeed.right);
damped.output.connectTo(compareSpeed.ifTrue);
_CreateAndConnectContextualSource("Direction", NodeParticleContextualSources.Direction, compareSpeed.ifFalse);
// Update the direction based on the calculted value
const updateDirection = new UpdateDirectionBlock("Direction Update");
inputParticle.connectTo(updateDirection.particle);
compareSpeed.output.connectTo(updateDirection.direction);
return updateDirection.output;
}
/**
* Creates the group of blocks that represent the particle noise update
* @param inputParticle The particle to update
* @param noiseTexture The noise texture
* @param noiseStrength The strength of the noise
* @returns The output of the group of blocks that represent the particle noise update
*/
function _UpdateParticleNoiseBlockGroup(inputParticle, noiseTexture, noiseStrength) {
const noiseUpdate = new UpdateNoiseBlock("Noise Update");
inputParticle.connectTo(noiseUpdate.particle);
_CreateTextureBlock(noiseTexture).connectTo(noiseUpdate.noiseTexture);
_CreateAndConnectInput("Noise Strength", noiseStrength, noiseUpdate.strength);
return noiseUpdate.output;
}
/**
* Creates the group of blocks that represent the particle drag update
* @param dragGradients The drag gradients
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle drag update
*/
function _UpdateParticleDragGradientBlockGroup(dragGradients, context) {
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
// Generate the gradient
const dragValueOutput = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, dragGradients, ParticleRandomBlockLocks.OncePerParticle, "Drag");
// Calculate drag factor
const oneMinusDragBlock = new ParticleMathBlock("1 - Drag");
oneMinusDragBlock.operation = ParticleMathBlockOperations.Subtract;
_CreateAndConnectInput("One", 1, oneMinusDragBlock.left);
dragValueOutput.connectTo(oneMinusDragBlock.right);
// Multiply the scaled direction by drag factor
const multiplyDirection = new ParticleMathBlock("Scaled Direction with Drag");
multiplyDirection.operation = ParticleMathBlockOperations.Multiply;
oneMinusDragBlock.output.connectTo(multiplyDirection.left);
if (context.scaledDirection === undefined) {
_CreateAndConnectContextualSource("Scaled Direction", NodeParticleContextualSources.ScaledDirection, multiplyDirection.right);
}
else {
context.scaledDirection.connectTo(multiplyDirection.right);
}
// Store the new calculation of the scaled direction in the context
context.scaledDirection = multiplyDirection.output;
return multiplyDirection.output;
}
/**
* Creates the group of blocks that represent the particle position update
* @param inputParticle The input particle to update
* @param isLocal Whether the particle coordinate system is local or not
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle position update
*/
function _UpdateParticlePositionBlockGroup(inputParticle, isLocal, context) {
// Update the particle position
const updatePosition = new UpdatePositionBlock("Position Update");
inputParticle.connectTo(updatePosition.particle);
if (isLocal) {
_CreateAndConnectContextualSource("Local Position Updated", NodeParticleContextualSources.LocalPositionUpdated, updatePosition.position);
}
else {
// Calculate the new position
const addPositionBlock = new ParticleMathBlock("Add Position");
addPositionBlock.operation = ParticleMathBlockOperations.Add;
_CreateAndConnectContextualSource("Position", NodeParticleContextualSources.Position, addPositionBlock.left);
if (context.scaledDirection === undefined) {
_CreateAndConnectContextualSource("Scaled Direction", NodeParticleContextualSources.ScaledDirection, addPositionBlock.right);
}
else {
context.scaledDirection.connectTo(addPositionBlock.right);
}
addPositionBlock.output.connectTo(updatePosition.position);
}
return updatePosition.output;
}
/**
* Creates the group of blocks that represent the particle size update
* @param inputParticle The input particle to update
* @param sizeGradients The size gradients (if any)
* @param context The context of the current conversion
* @returns The output of the group of blocks that represent the particle size update
*/
function _UpdateParticleSizeGradientBlockGroup(inputParticle, sizeGradients, context) {
if (context.sizeGradientValue0Output === undefined) {
throw new Error("Initial size gradient values not found in context.");
}
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
// Generate the gradient
const sizeValueOutput = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, sizeGradients, ParticleRandomBlockLocks.OncePerParticle, "Size", [
context.sizeGradientValue0Output,
]);
// Create the update size
const updateSizeBlock = new UpdateSizeBlock("Size Update");
inputParticle.connectTo(updateSizeBlock.particle);
sizeValueOutput.connectTo(updateSizeBlock.size);
return updateSizeBlock.output;
}
/**
* Creates the group of blocks that represent the particle gravity update
* @param inputParticle The input particle to update
* @param gravity The gravity vector to apply
* @returns The output of the group of blocks that represent the particle gravity update
*/
function _UpdateParticleGravityBlockGroup(inputParticle, gravity) {
// Create the gravity delta
const gravityDeltaOutput = _CreateDeltaModifiedInput("Gravity", gravity);
// Add it to the direction
const addDirectionBlock = new ParticleMathBlock("Add Gravity to Direction");
addDirectionBlock.operation = ParticleMathBlockOperations.Add;
_CreateAndConnectContextualSource("Direction", NodeParticleContextualSources.Direction, addDirectionBlock.left);
gravityDeltaOutput.connectTo(addDirectionBlock.right);
// Update the particle direction
const updateDirection = new UpdateDirectionBlock("Direction Update with Gravity");
inputParticle.connectTo(updateDirection.particle);
addDirectionBlock.output.connectTo(updateDirection.direction);
return updateDirection.output;
}
function _UpdateParticleAngularSpeedGradientBlockGroup(angularSpeedGradients, context) {
context.ageToLifeTimeRatioBlockGroupOutput = _CreateAgeToLifeTimeRatioBlockGroup(context);
// Generate the gradient
const angularSpeedValueOutput = _CreateGradientBlockGroup(context.ageToLifeTimeRatioBlockGroupOutput, angularSpeedGradients, ParticleRandomBlockLocks.OncePerParticle, "Angular Speed");
return angularSpeedValueOutput;
}
function _UpdateParticleAngularSpeedBlockGroup(minAngularSpeed, maxAngularSpeed) {
// Random value between for the angular speed of the particle
const randomAngularSpeedBlock = new ParticleRandomBlock("Random Angular Speed");
randomAngularSpeedBlock.lockMode = ParticleRandomBlockLocks.OncePerParticle;
_CreateAndConnectInput("Min Angular Speed", minAngularSpeed, randomAngularSpeedBlock.min);
_CreateAndConnectInput("Max Angular Speed", maxAngularSpeed, randomAngularSpeedBlock.max);
return randomAngularSpeedBlock.output;
}
function _BasicColorUpdateBlockGroup() {
const addColorBlock = new ParticleMathBlock("Add Color");
addColorBlock.operation = ParticleMathBlockOperations.Add;
_CreateAndConnectContextualSource("Color", NodeParticleContextualSources.Color, addColorBlock.left);
_CreateAndConnectContextualSource("Scaled Color Step", NodeParticleContextualSources.ScaledColorStep, addColorBlock.right);
return addColorBlock.output;
}
function _ClampUpdateColorAlpha(colorCalculationOutput) {
// Decompose color to clamp alpha
const decomposeColorBlock = new ParticleConverterBlock("Decompose Color");
colorCalculationOutput.connectTo(decomposeColorBlock.colorIn);
// Clamp alpha to be >= 0
const maxAlphaBlock = new ParticleMathBlock("Alpha >= 0");
maxAlphaBlock.operation = ParticleMathBlockOperations.Max;
decomposeColorBlock.wOut.connectTo(maxAlphaBlock.left);
_CreateAndConnectInput("Zero", 0, maxAlphaBlock.right);
// Recompose color
const composeColorBlock = new ParticleConverterBlock("Compose Color");
decomposeColorBlock.xyzOut.connectTo(composeColorBlock.xyzIn);
maxAlphaBlock.output.connectTo(composeColorBlock.wIn);
return composeColorBlock.colorOut;
}
// ------------- SYSTEM FUNCTIONS -------------
function _SystemBlockGroup(oldSystem, context) {
const newSystem = new SystemBlock(oldSystem.name);
newSystem.translationPivot.value = oldSystem.translationPivot;
newSystem.textureMask.value = oldSystem.textureMask;
newSystem.manualEmitCount = oldSystem.manualEmitCount;
newSystem.blendMode = oldSystem.blendMode;
newSystem.capacity = oldSystem.getCapacity();
newSystem.startDelay = oldSystem.startDelay;
newSystem.updateSpeed = oldSystem.updateSpeed;
newSystem.preWarmCycles = oldSystem.preWarmCycles;
newSystem.preWarmStepOffset = oldSystem.preWarmStepOffset;
newSystem.isBillboardBased = oldSystem.isBillboardBased;
newSystem.isLocal = oldSystem.isLocal;
newSystem.disposeOnStop = oldSystem.disposeOnStop;
_SystemEmitRateValue(oldSystem, newSystem, context);
_CreateTextureBlock(oldSystem.particleTexture).connectTo(newSystem.texture);
_SystemTargetStopDuration(oldSystem, newSystem, context);
return newSystem;
}
function _SystemEmitRateValue(oldSystem, newSystem, context) {
const emitGradients = oldSystem.getEmitRateGradients();
if (emitGradients && emitGradients.length > 0 && oldSystem.targetStopDuration > 0) {
// Create the emit gradients
context.timeToStopTimeRatioBlockGroupOutput = _CreateTimeToStopTimeRatioBlockGroup(oldSystem, context);
const gradientValue = _CreateGradientBlockGroup(context.timeToStopTimeRatioBlockGroupOutput, emitGradients, ParticleRandomBlockLocks.PerSystem, "Emit Rate");
// Round the value to an int
const roundBlock = new ParticleFloatToIntBlock("Round to Int");
roundBlock.operation = ParticleFloatToIntBlockOperations.Round;
gradientValue.connectTo(roundBlock.input);
roundBlock.output.connectTo(newSystem.emitRate);
}
else {
newSystem.emitRate.value = oldSystem.emitRate;
}
}
function _SystemTargetStopDuration(oldSystem, newSystem, context) {
// If something else uses the target stop duration (like a gradient),
// then the block is already created and stored in the context
if (context.targetStopDurationBlockOutput) {
context.targetStopDurationBlockOutput.connectTo(newSystem.targetStopDuration);
}
else {
// If no one used it, do not create a block just set the value
newSystem.targetStopDuration.value = oldSystem.targetStopDuration;
}
}
// ------------- UTILITY FUNCTIONS -------------
function _CreateDeltaModifiedInput(name, value) {
const multiplyBlock = new ParticleMathBlock("Multiply by Delta");
multiplyBlock.operation = ParticleMathBlockOperations.Multiply;
if (value instanceof Vector3) {
_CreateAndConnectInput(name, value, multiplyBlock.left);
}
else {
value.connectTo(multiplyBlock.left);
}
_CreateAndConnectSystemSource("Delta", NodeParticleSystemSources.Delta, multiplyBlock.right);
return multiplyBlock.output;
}
function _CreateAndConnectInput(inputBlockName, value, targetToConnectTo, inputType) {
const input = new ParticleInputBlock(inputBlockName, inputType);
input.value = value;
input.output.connectTo(targetToConnectTo);
}
function _CreateAndConnectContextualSource(contextualBlockName, contextSource, targetToConnectTo) {
const input = new ParticleInputBlock(contextualBlockName);
input.contextualValue = contextSource;
input.output.connectTo(targetToConnectTo);
}
function _CreateAndConnectSystemSource(systemBlockName, systemSource, targetToConnectTo) {
const input = new ParticleInputBlock(systemBlockName);
input.systemSource = systemSource;
input.output.connectTo(targetToConnectTo);
}
/**
* Creates the target stop duration input block, as it can be shared in multiple places
* This block is stored in the context so the same block is shared in the graph
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns
*/
function _CreateTargetStopDurationInputBlock(oldSystem, context) {
// If we have already created the target stop duration input block, return it
if (context.targetStopDurationBlockOutput) {
return context.targetStopDurationBlockOutput;
}
// Create the target stop duration input block if not already created
const targetStopDurationInputBlock = new ParticleInputBlock("Target Stop Duration");
targetStopDurationInputBlock.value = oldSystem.targetStopDuration;
// Save the output in our context to avoid regenerating it again
context.targetStopDurationBlockOutput = targetStopDurationInputBlock.output;
return context.targetStopDurationBlockOutput;
}
/**
* Create a group of blocks that calculates the ratio between the actual frame and the target stop duration, clamped between 0 and 1.
* This is used to simulate the behavior of the old particle system where several particle gradient values are affected by the target stop duration.
* This block group is stored in the context so the same group is shared in the graph
* @param oldSystem The old particle system to convert
* @param context The context of the current conversion
* @returns The ratio block output connection point
*/
function _CreateTimeToStopTimeRatioBlockGroup(oldSystem, context) {
// If we have already generated this group, return it
if (context.timeToStopTimeRatioBlockGroupOutput) {
return context.timeToStopTimeRatioBlockGroupOutput;
}
context.targetStopDurationBlockOutput = _CreateTargetStopDurationInputBlock(oldSystem, context);
// Find the ratio between the actual frame and the target stop duration
const ratio = new ParticleMathBlock("Frame/Stop Ratio");
ratio.operation = ParticleMathBlockOperations.Divide;
_CreateAndConnectSystemSource("Actual Frame", NodeParticleSystemSources.Time, ratio.left);
context.targetStopDurationBlockOutput.connectTo(ratio.right);
// Make sure values is >=0
const clampMin = new ParticleMathBlock("Clamp Min 0");
clampMin.operation = ParticleMathBlockOperations.Max;
_CreateAndConnectInput("Zero", 0, clampMin.left);
ratio.output.connectTo(clampMin.right);
// Make sure values is <=1
const clampMax = new ParticleMathBlock("Clamp Max 1");
clampMax.operation = ParticleMathBlockOperations.Min;
_CreateAndConnectInput("One", 1, clampMax.left);
clampMin.output.connectTo(clampMax.right);
// Save the group output in our context to avoid regenerating it again
context.timeToStopTimeRatioBlockGroupOutput = clampMax.output;
return context.timeToStopTimeRatioBlockGroupOutput;
}
function _CreateAgeToLifeTimeRatioBlockGroup(context) {
// If we have already generated this group, return it
if (context.ageToLifeTimeRatioBlockGroupOutput) {
return context.ageToLifeTimeRatioBlockGroupOutput;
}
// Find the ratio between the age and the lifetime
const ratio = new ParticleMathBlock("Age/LifeTime Ratio");
ratio.operation = ParticleMathBlockOperations.Divide;
_CreateAndConnectContextualSource("Age", NodeParticleContextualSources.Age, ratio.left);
_CreateAndConnectContextualSource("LifeTime", NodeParticleContextualSources.Lifetime, ratio.right);
// Save the group output in our context to avoid regenerating it again
context.ageToLifeTimeRatioBlockGroupOutput = ratio.output;
return ratio.output;
}
/**
* Creates the blocks that represent a gradient
* @param gradientSelector The value that determines which gradient to use
* @param gradientValues The list of gradient values
* @param randomLockMode The type of random to use for the gradient values
* @param prefix The prefix to use for naming the blocks
* @param initialValues Optional initial values to connect to the gradient inputs that were calculated during other steps of the conversion
* @returns The output connection point of the gradient block
*/
function _CreateGradientBlockGroup(gradientSelector, gradientValues, randomLockMode, prefix, initialValues = []) {
// Create the gradient block and connect the value that controls the gradient selection
const gradientBlock = new ParticleGradientBlock(prefix + " Gradient Block");
gradientSelector.connectTo(gradientBlock.gradient);
// If initial values are provided, we use them instead of the values in the gradientValues array
// These means this values were already transformed into blocks on a previous step of the conversion and we must reuse them
for (let i = 0; i < initialValues.length; i++) {
const reference = i < gradientValues.length ? gradientValues[i].gradient : 1;
const gradientValueBlock = new ParticleGradientValueBlock(prefix + " Gradient Value " + i);
gradientValueBlock.reference = reference;
initialValues[i].connectTo(gradientValueBlock.value);
gradientValueBlock.output.connectTo(gradientBlock.inputs[i + 1]);
}
// Create the gradient values
for (let i = 0 + initialValues.length; i < gradientValues.length; i++) {
const gradientValueBlockGroupOutput = _CreateGradientValueBlockGroup(gradientValues[i], randomLockMode, prefix, i);
gradientValueBlockGroupOutput.connectTo(gradientBlock.inputs[i + 1]);
}
return gradientBlock.output;
}
/**
* Creates the blocks that represent a gradient value
* This can be either a single value or a random between two values
* @param gradientStep The gradient step data
* @param randomLockMode The lock mode to use for random values
* @param prefix The prefix to use for naming the blocks
* @param index The index of the gradient step
* @returns The output connection point of the gradient value block
*/
function _CreateGradientValueBlockGroup(gradientStep, randomLockMode, prefix, index) {
const gradientValueBlock = new ParticleGradientValueBlock(prefix + " Gradient Value " + index);
gradientValueBlock.reference = gradientStep.gradient;
const value1 = gradientStep.factor1 ?? gradientStep.color1;
const value2 = gradientStep.factor2 ?? gradientStep.color2;
if (value2 !== undefined) {
// Create a random between value1 and value2
const randomBlock = new ParticleRandomBlock("Random Value " + index);
randomBlock.lockMode = randomLockMode;
_CreateAndConnectInput("Value 1", value1, randomBlock.min);
_CreateAndConnectInput("Value 2", value2, randomBlock.max);
randomBlock.output.connectTo(gradientValueBlock.value);
}
else {
// Single value
_CreateAndConnectInput("Value", value1, gradientValueBlock.value);
}
return gradientValueBlock.output;
}
function _CreateTextureBlock(texture) {
// Texture
const textureBlock = new ParticleTextureSourceBlock("Texture");
const url = texture.url || "";
if (url) {
textureBlock.url = url;
}
else {
textureBlock.sourceTexture = texture;
}
return textureBlock.texture;
}
//# sourceMappingURL=nodeParticleSystemSet.helper.js.map