public QuadEmitterRenderState(RenderingDevice device, PartSysEmitter emitter) : base(device, emitter, false)
    {
        bufferBinding = new BufferBinding(device, material.Resource.VertexShader).Ref();

        var maxCount = emitter.GetSpec().GetMaxParticles();

        vertexBuffer =
            device.CreateEmptyVertexBuffer(SpriteVertex.Size * 4 * maxCount, debugName:"ParticlesQuadEmitter");

        bufferBinding.Resource
            .AddBuffer<SpriteVertex>(vertexBuffer, 0)
            .AddElement(VertexElementType.Float4, VertexElementSemantic.Position)
            .AddElement(VertexElementType.Color, VertexElementSemantic.Color)
            .AddElement(VertexElementType.Float2, VertexElementSemantic.TexCoord);
    }
示例#2
0
    public static Material CreateMaterial(RenderingDevice device,
                                          PartSysEmitter emitter,
                                          bool pointSprites)
    {
        var blendState = new BlendSpec();

        blendState.blendEnable = true;

        switch (emitter.GetSpec().GetBlendMode())
        {
        case PartSysBlendMode.Add:
            blendState.srcBlend  = BlendOperand.SrcAlpha;
            blendState.destBlend = BlendOperand.One;
            break;

        case PartSysBlendMode.Subtract:
            blendState.srcBlend  = BlendOperand.Zero;
            blendState.destBlend = BlendOperand.InvSrcAlpha;
            break;

        case PartSysBlendMode.Blend:
            blendState.srcBlend  = BlendOperand.SrcAlpha;
            blendState.destBlend = BlendOperand.InvSrcAlpha;
            break;

        case PartSysBlendMode.Multiply:
            blendState.srcBlend  = BlendOperand.Zero;
            blendState.destBlend = BlendOperand.SrcColor;
            break;

        default:
            break;
        }

        // Particles respect the depth buffer, but do not modify it
        DepthStencilSpec depthStencilState = new DepthStencilSpec();

        depthStencilState.depthEnable = true;
        depthStencilState.depthWrite  = false;
        RasterizerSpec rasterizerState = new RasterizerSpec();

        rasterizerState.cullMode = CullMode.None;


        var samplers = new List <MaterialSamplerSpec>();

        var shaderName  = "diffuse_only_ps";
        var textureName = emitter.GetSpec().GetTextureName();

        if (textureName.Length > 0)
        {
            var samplerState = new SamplerSpec
            {
                addressU  = TextureAddress.Clamp,
                addressV  = TextureAddress.Clamp,
                minFilter = TextureFilterType.Linear,
                magFilter = TextureFilterType.Linear,
                mipFilter = TextureFilterType.Linear
            };
            var texture = device.GetTextures().Resolve(textureName, true);
            samplers.Add(new MaterialSamplerSpec(texture, samplerState));
            shaderName = "textured_simple_ps";
        }

        using var pixelShader = device.GetShaders().LoadPixelShader(shaderName);

        var vsName = pointSprites ? "particles_points_vs" : "particles_quads_vs";

        using var vertexShader = device.GetShaders().LoadVertexShader(vsName);

        return(device.CreateMaterial(blendState, depthStencilState, rasterizerState,
                                     samplers.ToArray(), vertexShader, pixelShader));
    }
示例#3
0
    public override void Render(IGameViewport viewport, PartSysEmitter emitter)
    {
        var it = emitter.NewIterator();

        var animParams = AnimatedModelParams.Default;

        animParams.rotation3d = true;

        // Lazily initialize render state
        if (!emitter.HasRenderState())
        {
            // Resolve the mesh filename
            var baseName = ResolveBasename(emitter.GetSpec().GetMeshName());
            var skmName  = baseName + ".skm";
            var skaName  = baseName + ".ska";

            try
            {
                var animId = new EncodedAnimId(NormalAnimType.ItemIdle); // This seems to be item_idle
                var model  = _modelFactory.FromFilenames(skmName, skaName, animId, animParams);

                emitter.SetRenderState(
                    new ModelEmitterRenderState(model)
                    );
            }
            catch (Exception e)
            {
                Logger.Error("Unable to load model {0} for particle system {1}: {2}",
                             baseName, emitter.GetSpec().GetParent().GetName(), e);

                emitter.SetRenderState(new ModelEmitterRenderState(null));
            }
        }

        var renderState = (ModelEmitterRenderState)emitter.GetRenderState();

        if (renderState.Model == null)
        {
            return; // The loader above was unable to load the model for this emitter
        }

        var overrides = new MdfRenderOverrides
        {
            ignoreLighting  = true,
            overrideDiffuse = true
        };

        var yaw   = emitter.GetParamState(PartSysParamId.part_yaw);
        var pitch = emitter.GetParamState(PartSysParamId.part_pitch);
        var roll  = emitter.GetParamState(PartSysParamId.part_roll);

        while (it.HasNext())
        {
            var particleIdx = it.Next();
            var age         = emitter.GetParticleAge(particleIdx);

            overrides.overrideColor = GeneralEmitterRenderState.GetParticleColor(emitter, particleIdx);

            // Yes, this is *actually* swapped for Y / Z
            var particleState = emitter.GetParticleState();
            animParams.offsetX = particleState.GetState(ParticleStateField.PSF_POS_VAR_X, particleIdx);
            animParams.offsetY = particleState.GetState(ParticleStateField.PSF_POS_VAR_Z, particleIdx);
            animParams.offsetZ = particleState.GetState(ParticleStateField.PSF_POS_VAR_Y, particleIdx);

            if (yaw != null)
            {
                animParams.rotationYaw = Angles.ToRadians(yaw.GetValue(emitter, particleIdx, age));
            }

            if (pitch != null)
            {
                animParams.rotationPitch = Angles.ToRadians(pitch.GetValue(emitter, particleIdx, age));
            }

            if (roll != null)
            {
                animParams.rotationRoll = Angles.ToRadians(roll.GetValue(emitter, particleIdx, age));
            }

            renderState.Model.SetTime(animParams, age);

            _modelRenderer.Render(viewport, renderState.Model, animParams, new List <Light3d>(), overrides);
        }
    }
    protected bool GetEmitterWorldMatrix(IGameViewport viewport, PartSysEmitter emitter, out Matrix4x4 worldMatrix)
    {
        worldMatrix = Matrix4x4.Identity;

        var spec          = emitter.GetSpec();
        var particleSpace = spec.GetParticleSpace();
        var emitterSpace  = spec.GetSpace();

        if (particleSpace == PartSysParticleSpace.SameAsEmitter)
        {
            if (emitterSpace == PartSysEmitterSpace.ObjectPos || emitterSpace == PartSysEmitterSpace.ObjectYpr)
            {
                Matrix4x4 localMat;
                if (emitterSpace == PartSysEmitterSpace.ObjectYpr)
                {
                    var angle = emitter.GetObjRotation() + MathF.PI;
                    localMat = Matrix4x4.CreateRotationY(angle);
                }
                else
                {
                    localMat = Matrix4x4.Identity;
                }

                // Set the translation component of the transformation matrix
                localMat.Translation = emitter.GetObjPos();

                worldMatrix = localMat * viewport.Camera.GetViewProj();
                ExtractScreenSpaceUnitVectors(worldMatrix);
                return(true);
            }

            if (emitterSpace == PartSysEmitterSpace.NodePos || emitterSpace == PartSysEmitterSpace.NodeYpr)
            {
                var external = emitter.External;

                Matrix4x4 boneMatrix;
                if (emitterSpace == PartSysEmitterSpace.NodeYpr)
                {
                    if (!external.GetBoneWorldMatrix(emitter.GetAttachedTo(), spec.GetNodeName(), out boneMatrix))
                    {
                        // This effectively acts as a fallback if the bone doesn't exist
                        boneMatrix = Matrix4x4.CreateTranslation(emitter.GetObjPos());
                    }

                    worldMatrix = boneMatrix * viewport.Camera.GetViewProj();
                    ExtractScreenSpaceUnitVectors(worldMatrix);
                    return(true);
                }

                if (external.GetBoneWorldMatrix(emitter.GetAttachedTo(), spec.GetNodeName(), out boneMatrix))
                {
                    worldMatrix = Matrix4x4.CreateTranslation(boneMatrix.Translation) *
                                  viewport.Camera.GetViewProj();
                    ExtractScreenSpaceUnitVectors(worldMatrix);
                    return(true);
                }

                return(false);
            }

            worldMatrix = viewport.Camera.GetViewProj();
            ExtractScreenSpaceUnitVectors(worldMatrix);
            return(true);
        }

        if (particleSpace == PartSysParticleSpace.World)
        {
            worldMatrix = viewport.Camera.GetViewProj();
            ExtractScreenSpaceUnitVectors(worldMatrix);
            return(true);
        }

        if (emitterSpace != PartSysEmitterSpace.ObjectPos && emitterSpace != PartSysEmitterSpace.ObjectYpr)
        {
            if (emitterSpace != PartSysEmitterSpace.NodePos && emitterSpace != PartSysEmitterSpace.NodeYpr)
            {
                return(true);
            }

            var external = emitter.External;

            Matrix4x4 boneMatrix;
            if (emitterSpace == PartSysEmitterSpace.NodeYpr)
            {
                // Use the entire bone matrix if possible
                external.GetBoneWorldMatrix(emitter.GetAttachedTo(), spec.GetNodeName(), out boneMatrix);
            }
            else
            {
                // Only use the bone translation part
                if (!external.GetBoneWorldMatrix(emitter.GetAttachedTo(), spec.GetNodeName(), out boneMatrix))
                {
                    return(false);
                }
                boneMatrix =
                    Matrix4x4.CreateTranslation(boneMatrix.Translation); // TODO: This might not be needed...
            }

            worldMatrix = viewport.Camera.GetViewProj();
            ExtractScreenSpaceUnitVectors2(boneMatrix);
            return(true);
        }

        Matrix4x4 matrix;

        if (emitterSpace == PartSysEmitterSpace.ObjectYpr)
        {
            var angle = emitter.GetObjRotation() + MathF.PI;
            matrix = Matrix4x4.CreateRotationY(angle);
        }
        else
        {
            matrix = Matrix4x4.Identity;
        }

        worldMatrix = viewport.Camera.GetViewProj();
        ExtractScreenSpaceUnitVectors2(matrix);
        return(true);
    }
    public static void SimulateParticleSpawn(IPartSysExternal external, PartSysEmitter emitter, int particleIdx,
                                             float timeToSimulate)
    {
        var spec          = emitter.GetSpec();
        var partSpawnTime = emitter.GetParticleSpawnTime(particleIdx);

        var worldPosVar = emitter.GetWorldPosVar();
        var particleX   = worldPosVar.X;
        var particleY   = worldPosVar.Y;
        var particleZ   = worldPosVar.Z;

        var emitOffsetX = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_offset_X, partSpawnTime);
        var emitOffsetY = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_offset_Y, partSpawnTime);
        var emitOffsetZ = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_offset_Z, partSpawnTime);

        if (spec.GetOffsetCoordSys() == PartSysCoordSys.Polar)
        {
            var coords = SphericalDegToCartesian(emitOffsetX, emitOffsetY, emitOffsetZ);
            particleX += coords.X;
            particleY += coords.Y;
            particleZ += coords.Z;
        }
        else
        {
            particleX += emitOffsetX;
            particleY += emitOffsetY;
            particleZ += emitOffsetZ;
        }

        switch (spec.GetSpace())
        {
        case PartSysEmitterSpace.ObjectPos:
        case PartSysEmitterSpace.ObjectYpr:
        {
            if (spec.GetParticleSpace() != PartSysParticleSpace.SameAsEmitter)
            {
                // TODO: Figure out this formula...
                var scale      = 1.0f - emitter.GetParticleAge(particleIdx) / timeToSimulate;
                var prevObjPos = emitter.GetPrevObjPos();
                var objPos     = emitter.GetObjPos();
                var dX         = prevObjPos.X + (objPos.X - prevObjPos.X) * scale;
                var dY         = prevObjPos.Y + (objPos.Y - prevObjPos.Y) * scale;
                var dZ         = prevObjPos.Z + (objPos.Z - prevObjPos.Z) * scale;
                var rotation   = 0.0f;
                if (spec.GetSpace() == PartSysEmitterSpace.ObjectYpr)
                {
                    rotation = emitter.GetPrevObjRotation() +
                               (emitter.GetObjRotation() - emitter.GetPrevObjRotation()) * scale;
                }

                RotateAndMove(dX, dY, dZ, rotation, ref particleX, ref particleY, ref particleZ);
            }
        }
        break;

        case PartSysEmitterSpace.Bones:
        {
            var scale = 1.0f - emitter.GetParticleAge(particleIdx) / timeToSimulate;
            if (emitter.GetBoneState() == null)
            {
                break;
            }

            if (emitter.GetBoneState().GetRandomPos(scale, out var bonePos))
            {
                particleX = bonePos.X;
                particleY = bonePos.Y;
                particleZ = bonePos.Z;
            }
        }
        break;

        case PartSysEmitterSpace.NodePos:
        case PartSysEmitterSpace.NodeYpr:
        {
            if (spec.GetParticleSpace() != PartSysParticleSpace.SameAsEmitter)
            {
                var scale = 1.0f - emitter.GetParticleAge(particleIdx) / timeToSimulate;
                if (spec.GetSpace() == PartSysEmitterSpace.NodeYpr)
                {
                    if (!external.GetBoneWorldMatrix(emitter.GetAttachedTo(), spec.GetNodeName(),
                                                     out var boneM))
                    {
                        boneM = Matrix4x4.Identity;
                    }

                    var ppos   = new Vector3(particleX, particleY, particleZ);
                    var newpos = Vector3.Transform(ppos, boneM);
                    particleX = newpos.X;
                    particleY = newpos.Y;
                    particleZ = newpos.Z;
                }
                else
                {
                    var prevObjPos = emitter.GetPrevObjPos();
                    var objPos     = emitter.GetObjPos();
                    var dX         = prevObjPos.X + (objPos.X - prevObjPos.X) * scale;
                    var dY         = prevObjPos.Y + (objPos.Y - prevObjPos.Y) * scale;
                    var dZ         = prevObjPos.Z + (objPos.Z - prevObjPos.Z) * scale;
                    RotateAndMove(dX, dY, dZ, 0.0f, ref particleX, ref particleY, ref particleZ);
                }
            }
        }
        break;

        default:
            break;
        }

        var state = emitter.GetParticleState();

        state.SetState(ParticleStateField.PSF_X, particleIdx, particleX);
        state.SetState(ParticleStateField.PSF_Y, particleIdx, particleY);
        state.SetState(ParticleStateField.PSF_Z, particleIdx, particleZ);

        // Initialize particle color
        SetParticleParam(emitter, particleIdx, PartSysParamId.emit_init_red, partSpawnTime,
                         ParticleStateField.PSF_RED, 255.0f);
        SetParticleParam(emitter, particleIdx, PartSysParamId.emit_init_green, partSpawnTime,
                         ParticleStateField.PSF_GREEN, 255.0f);
        SetParticleParam(emitter, particleIdx, PartSysParamId.emit_init_blue, partSpawnTime,
                         ParticleStateField.PSF_BLUE, 255.0f);
        SetParticleParam(emitter, particleIdx, PartSysParamId.emit_init_alpha, partSpawnTime,
                         ParticleStateField.PSF_ALPHA, 255.0f);

        // Initialize particle velocity
        var partVelX = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_init_vel_X, partSpawnTime);
        var partVelY = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_init_vel_Y, partSpawnTime);
        var partVelZ = GetParticleValue(emitter, particleIdx, PartSysParamId.emit_init_vel_Z, partSpawnTime);

        if (spec.GetSpace() == PartSysEmitterSpace.ObjectYpr)
        {
            if (spec.GetParticleSpace() != PartSysParticleSpace.SameAsEmitter)
            {
                var scale = 1.0f - emitter.GetParticleAge(particleIdx) / timeToSimulate;

                var rotation = 0.0f;
                if (spec.GetSpace() == PartSysEmitterSpace.ObjectYpr)
                {
                    rotation = (emitter.GetObjRotation() - emitter.GetPrevObjRotation()) * scale +
                               emitter.GetPrevObjRotation();
                }

                // Rotate the velocity vector according to the current object rotation in the world
                // TODO: Even for rotation == 0, this will flip the velocity vector
                Rotate2D(rotation, ref partVelX, ref partVelZ);
            }
        }
        else if (spec.GetSpace() == PartSysEmitterSpace.NodeYpr)
        {
            if (spec.GetParticleSpace() != PartSysParticleSpace.SameAsEmitter)
            {
                var objId = emitter.GetAttachedTo();

                if (!external.GetBoneWorldMatrix(objId, spec.GetNodeName(), out var boneMatrix))
                {
                    boneMatrix = Matrix4x4.Identity;
                }

                // Construct a directional vector (not a positional one, w=0 here) for the velocity
                var dirVec = new Vector3(partVelX, partVelY, partVelZ);
                dirVec = Vector3.TransformNormal(dirVec, boneMatrix);

                partVelX = dirVec.X;
                partVelY = dirVec.Y;
                partVelZ = dirVec.Z;
            }
        }

        // Are particle coordinates defined as polar coordinates? Convert them to cartesian here
        if (spec.GetParticleVelocityCoordSys() == PartSysCoordSys.Polar)
        {
            var cartesianVel = SphericalDegToCartesian(partVelX, partVelY, partVelZ);
            partVelX = cartesianVel.X;
            partVelY = cartesianVel.Y;
            partVelZ = cartesianVel.Z;
        }

        state.SetState(ParticleStateField.PSF_VEL_X, particleIdx, partVelX);
        state.SetState(ParticleStateField.PSF_VEL_Y, particleIdx, partVelY);
        state.SetState(ParticleStateField.PSF_VEL_Z, particleIdx, partVelZ);

        // I don't know why it's taken at lifetime 0.
        // TODO: Figure out if this actually *never* changes?
        var posVarX = GetParticleValue(emitter, particleIdx, PartSysParamId.part_posVariation_X, 0);
        var posVarY = GetParticleValue(emitter, particleIdx, PartSysParamId.part_posVariation_Y, 0);
        var posVarZ = GetParticleValue(emitter, particleIdx, PartSysParamId.part_posVariation_Z, 0);

        // For a polar system, convert these positions to cartesian to apply them to the
        // rendering position
        if (spec.GetParticlePosCoordSys() == PartSysCoordSys.Polar)
        {
            state.SetState(ParticleStateField.PSF_POS_AZIMUTH, particleIdx, 0);
            state.SetState(ParticleStateField.PSF_POS_INCLINATION, particleIdx, 0);
            state.SetState(ParticleStateField.PSF_POS_RADIUS, particleIdx, 0);

            // Convert to cartesian and add to the actual current particle position
            // As usual, x, y, z here are (azimuth, inclination, radius)
            var cartesianPos = SphericalDegToCartesian(posVarX, posVarY, posVarZ);
            posVarX = cartesianPos.X;
            posVarY = cartesianPos.Y;
            posVarZ = cartesianPos.Z;
        }

        // Apply the position variation to the initial position and store it
        posVarX += particleX;
        posVarY += particleY;
        posVarZ += particleZ;
        state.SetState(ParticleStateField.PSF_POS_VAR_X, particleIdx, posVarX);
        state.SetState(ParticleStateField.PSF_POS_VAR_Y, particleIdx, posVarY);
        state.SetState(ParticleStateField.PSF_POS_VAR_Z, particleIdx, posVarZ);

        /*
         * The following code will apply particle movement after
         * spawning a particle retroactively. This should only happen
         * for high frequency particle systems that spawn multiple particles
         * per frame.
         * Also note how particle age instead of particle lifetime is used here
         * to access parameters of the emitter.
         */
        var partAge = emitter.GetParticleAge(particleIdx);

        if (partAge != 0)
        {
            var partAgeSquared = partAge * partAge;

            particleX += partVelX * partAge;
            particleY += partVelY * partAge;
            particleZ += partVelZ * partAge;

            var param = emitter.GetParamState(PartSysParamId.part_accel_X);
            if (param != null)
            {
                var accelX = param.GetValue(emitter, particleIdx, partAge);
                particleX += accelX * partAgeSquared * 0.5f;
                partVelX  += accelX * partAge;
            }

            param = emitter.GetParamState(PartSysParamId.part_accel_Y);
            if (param != null)
            {
                var accelY = param.GetValue(emitter, particleIdx, partAge);
                particleY += accelY * partAgeSquared * 0.5f;
                partVelY  += accelY * partAge;
            }

            param = emitter.GetParamState(PartSysParamId.part_accel_Z);
            if (param != null)
            {
                var accelZ = param.GetValue(emitter, particleIdx, partAge);
                particleZ += accelZ * partAgeSquared * 0.5f;
                partVelZ  += accelZ * partAge;
            }

            state.SetState(ParticleStateField.PSF_VEL_X, particleIdx, partVelX);
            state.SetState(ParticleStateField.PSF_VEL_Y, particleIdx, partVelY);
            state.SetState(ParticleStateField.PSF_VEL_Z, particleIdx, partVelZ);

            param = emitter.GetParamState(PartSysParamId.part_velVariation_X);
            if (param != null)
            {
                particleX += param.GetValue(emitter, particleIdx, partAge) * partAge;
            }

            param = emitter.GetParamState(PartSysParamId.part_velVariation_Y);
            if (param != null)
            {
                particleY += param.GetValue(emitter, particleIdx, partAge) * partAge;
            }

            param = emitter.GetParamState(PartSysParamId.part_velVariation_Z);
            if (param != null)
            {
                particleZ += param.GetValue(emitter, particleIdx, partAge) * partAge;
            }

            state.SetState(ParticleStateField.PSF_X, particleIdx, particleX);
            state.SetState(ParticleStateField.PSF_Y, particleIdx, particleY);
            state.SetState(ParticleStateField.PSF_Z, particleIdx, particleZ);
        }

        // Simulate rotation for anything other than a point particle
        if (spec.GetParticleType() != PartSysParticleType.Point)
        {
            var emitterAge = emitter.GetParticleSpawnTime(particleIdx);
            var rotation   = emitter.GetParamValue(PartSysParamId.emit_yaw, particleIdx, emitterAge, 0.0f);
            emitter.GetParticleState().SetState(ParticleStateField.PSF_ROTATION, particleIdx, rotation);
        }
    }
    public static void SimulateParticleMovement(PartSysEmitter emitter, float timeToSimulateSecs)
    {
        var spec = emitter.GetSpec();

        // Used as a factor in integrating the acceleration to retroactively calculate
        // its influence on the particle position
        var accelIntegrationFactor = timeToSimulateSecs * timeToSimulateSecs * 0.5f;

        var state = emitter.GetParticleState();
        var it    = emitter.NewIterator();

        while (it.HasNext())
        {
            var particleIdx = it.Next();
            var particleAge = emitter.GetParticleAge(particleIdx);

            var valueSource = new ParticleValueSource(emitter, particleIdx, particleAge);

            var x = state.GetState(ParticleStateField.PSF_X, particleIdx);
            var y = state.GetState(ParticleStateField.PSF_Y, particleIdx);
            var z = state.GetState(ParticleStateField.PSF_Z, particleIdx);

            var velX = state.GetState(ParticleStateField.PSF_VEL_X, particleIdx);
            var velY = state.GetState(ParticleStateField.PSF_VEL_Y, particleIdx);
            var velZ = state.GetState(ParticleStateField.PSF_VEL_Z, particleIdx);

            // Calculate new position of particle based on velocity
            x += timeToSimulateSecs * velX;
            y += timeToSimulateSecs * velY;
            z += timeToSimulateSecs * velZ;

            // Apply acceleration to velocity (retroactively to position as well)
            float value;
            if (valueSource.GetValue(PartSysParamId.part_accel_X, out value))
            {
                x    += accelIntegrationFactor * value;
                velX += timeToSimulateSecs * value;
            }

            if (valueSource.GetValue(PartSysParamId.part_accel_Y, out value))
            {
                y    += accelIntegrationFactor * value;
                velY += timeToSimulateSecs * value;
            }

            if (valueSource.GetValue(PartSysParamId.part_accel_Z, out value))
            {
                z    += accelIntegrationFactor * value;
                velZ += timeToSimulateSecs * value;
            }

            /*
             *  Apply Velocity Var
             */
            if (spec.GetParticleVelocityCoordSys() == PartSysCoordSys.Polar)
            {
                if (spec.GetParticlePosCoordSys() != PartSysCoordSys.Polar)
                {
                    // Velocity is polar, positions are not . convert velocity
                    var azimuth = emitter.GetParamValue(PartSysParamId.part_velVariation_X, particleIdx,
                                                        particleAge);
                    var inclination = emitter.GetParamValue(PartSysParamId.part_velVariation_Y, particleIdx,
                                                            particleAge);
                    var radius = emitter.GetParamValue(PartSysParamId.part_velVariation_Z, particleIdx,
                                                       particleAge);

                    var cartesianVel = SphericalDegToCartesian(azimuth, inclination, radius);
                    x += cartesianVel.X * timeToSimulateSecs;
                    y += cartesianVel.Y * timeToSimulateSecs;
                    z += cartesianVel.Z * timeToSimulateSecs;
                }
                else
                {
                    // Modify the spherical coordinates of the particle directly
                    if (valueSource.GetValue(PartSysParamId.part_velVariation_X, out value))
                    {
                        ref var azimuth = ref state.GetStatePtr(ParticleStateField.PSF_POS_AZIMUTH, particleIdx);
                        azimuth += value * timeToSimulateSecs;
                    }

                    if (valueSource.GetValue(PartSysParamId.part_velVariation_Y, out value))
                    {
                        ref var inclination =
                            ref state.GetStatePtr(ParticleStateField.PSF_POS_INCLINATION, particleIdx);
                        inclination += value * timeToSimulateSecs;
                    }

                    if (valueSource.GetValue(PartSysParamId.part_velVariation_Z, out value))
                    {
                        ref var radius = ref state.GetStatePtr(ParticleStateField.PSF_POS_RADIUS, particleIdx);
                        radius += value * timeToSimulateSecs;
                    }
                }