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 { 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 { SystemBlock } from "./Blocks/systemBlock.js";
import { ParticleVectorLengthBlock } from "./Blocks/particleVectorLengthBlock.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 { 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);
}
// ------------- SYSTEM FUNCTIONS -------------
function _SystemBlockGroup(oldSystem, context) {
    const newSystem = new SystemBlock(oldSystem.name);
    _CreateAndConnectInput("Translation pivot", oldSystem.translationPivot, newSystem.translationPivot);
    _CreateAndConnectInput("Texture mask", oldSystem.textureMask, newSystem.textureMask);
    _CreateTargetStopDurationInputBlock(oldSystem, context).connectTo(newSystem.targetStopDuration);
    newSystem.emitRate = oldSystem.emitRate;
    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;
    // Texture
    const textureBlock = new ParticleTextureSourceBlock("Texture");
    const url = oldSystem.particleTexture.url || "";
    if (url) {
        textureBlock.url = url;
    }
    else {
        textureBlock.sourceTexture = oldSystem.particleTexture;
    }
    textureBlock.texture.connectTo(newSystem.texture);
    return 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 updateBlockGroupOutput = inputParticle;
    updateBlockGroupOutput = _UpdateParticleColorBlockGroup(updateBlockGroupOutput, oldSystem._colorGradients, context);
    updateBlockGroupOutput = _UpdateParticleAngleBlockGroup(updateBlockGroupOutput, 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);
    }
    updateBlockGroupOutput = _UpdateParticlePositionBlockGroup(updateBlockGroupOutput, oldSystem.isLocal, context);
    if (oldSystem._limitVelocityGradients && oldSystem._limitVelocityGradients.length > 0 && oldSystem.limitVelocityDamping !== 0) {
        updateBlockGroupOutput = _UpdateParticleVelocityLimitGradientBlockGroup(updateBlockGroupOutput, oldSystem._limitVelocityGradients, oldSystem.limitVelocityDamping, context);
    }
    if (oldSystem._sizeGradients && oldSystem._sizeGradients.length > 0) {
        updateBlockGroupOutput = _UpdateParticleSizeGradientBlockGroup(updateBlockGroupOutput, oldSystem._sizeGradients, context);
    }
    if (oldSystem.gravity.equalsToFloats(0, 0, 0) === false) {
        updateBlockGroupOutput = _UpdateParticleGravityBlockGroup(updateBlockGroupOutput, oldSystem.gravity);
    }
    return updateBlockGroupOutput;
}
/**
 * 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 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;
}
// ------------- 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;
}
//# sourceMappingURL=nodeParticleSystemSet.helper.js.map