/// <summary>
            /// Constructs the ribbon by outputting vertex stream based on the positions and sizes specified previously
            /// </summary>
            /// <param name="bufferState">Target <see cref="ParticleBufferState"/></param> to use
            /// <param name="invViewX">Unit vector X in clip space as calculated from the inverse view matrix</param>
            /// <param name="invViewY">Unit vector Y in clip space as calculated from the inverse view matrix</param>
            /// <param name="quadsPerParticle">The required number of quads per each particle</param>
            public unsafe void Ribbonize(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, int quadsPerParticle)
            {
                if (lastParticle <= 0)
                    return;

                var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
                var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

                if (lastParticle <= sections)
                {
                    // Optional - connect first particle to the origin/emitter

                    // Draw a dummy quad for the first particle
                    var particlePos = new Vector3(0, 0, 0);
                    var uvCoord = new Vector2(0, 0);

                    for (var particleIdx = 0; particleIdx < lastParticle; particleIdx++)
                    {
                        for (var vtxIdx = 0; vtxIdx < 4; vtxIdx++)
                        {
                            bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                            bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                            bufferState.NextVertex();
                        }
                    }

                    return;
                }

                if (sections > 1)
                {
                    if (SmoothingPolicy == SmoothingPolicy.Best)
                        ExpandVertices_Circular();
                    else // if (SmoothingPolicy == SmoothingPolicy.Fast)
                        ExpandVertices_CatmullRom();
                }

                bufferState.SetVerticesPerSegment(quadsPerParticle * 6, quadsPerParticle * 4, quadsPerParticle * 2);

                var positions = (Vector3*)positionData;
                var sizes = (float*)sizeData;

                // Step 1 - Determine the origin of the ribbon
                var invViewZ = Vector3.Cross(invViewX, invViewY);
                invViewZ.Normalize();

                var axis0 = positions[0] - positions[1];
                axis0.Normalize();

                var oldPoint = positions[0];
                var oldUnitX = GetWidthVector(sizes[0], ref invViewZ, ref axis0, ref axis0);

                // Step 2 - Draw each particle, connecting it to the previous (front) position

                var vCoordOld = 0f;

                for (int i = 0; i < lastParticle; i++)
                {
                    var centralPos = positions[i];

                    var particleSize = sizes[i];

                    // Directions for smoothing
                    var axis1 = (i + 1 < lastParticle) ? positions[i] - positions[i + 1] : positions[lastParticle - 2] - positions[lastParticle - 1];
                    axis1.Normalize();

                    var unitX = GetWidthVector(particleSize, ref invViewZ, ref axis0, ref axis1);

                    axis0 = axis1;

                    // Particle rotation - intentionally IGNORED for ribbon

                    var particlePos = oldPoint - oldUnitX;
                    var uvCoord = new Vector2(0, 0);
                    var rotatedCoord = uvCoord;


                    // Top Left - 0f 0f
                    uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 0 : vCoordOld;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Top Right - 1f 0f
                    particlePos += oldUnitX * 2;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    uvCoord.X = 1;
                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Move the position to the next particle in the ribbon
                    particlePos += centralPos - oldPoint;
                    particlePos += unitX - oldUnitX;
                    vCoordOld = (TextureCoordinatePolicy == TextureCoordinatePolicy.Stretched) ? 
                        ((i + 1)/(float)(lastParticle) * TexCoordsFactor) : ((centralPos - oldPoint).Length() * TexCoordsFactor) + vCoordOld;


                    // Bottom Left - 1f 1f
                    uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 1 : vCoordOld;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Bottom Right - 0f 1f
                    particlePos -= unitX * 2;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    uvCoord.X = 0;
                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Preserve the old attributes for the next cycle
                    oldUnitX = unitX;
                    oldPoint = centralPos;
                }

            }
        /// <inheritdoc />
        public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, 
            ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter)
        {
            // Update the curve samplers if required
            base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter);

            // Get all required particle fields
            var positionField = sorter.GetField(ParticleFields.Position);
            if (!positionField.IsValid())
                return 0;
            var sizeField = sorter.GetField(ParticleFields.Size);
            var lifeField = sorter.GetField(ParticleFields.Life);
            var angleField = sorter.GetField(ParticleFields.Angle);
            var hasAngle = angleField.IsValid() || (SamplerRotation != null);


            // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors
            var trsIdentity = (spaceScale == 1f);
            trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0)));
            trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity));


            var renderedParticles = 0;

            var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
            var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

            foreach (var particle in sorter)
            {
                var centralPos = GetParticlePosition(particle, positionField, lifeField);

                var particleSize = GetParticleSize(particle, sizeField, lifeField);

                if (!trsIdentity)
                {
                    spaceRotation.Rotate(ref centralPos);
                    centralPos = centralPos * spaceScale + spaceTranslation;
                    particleSize *= spaceScale;
                }

                // Use half size to make a Size = 1 result in a Billboard of 1m x 1m
                var unitX = invViewX * (particleSize * 0.5f); 
                var unitY = invViewY * (particleSize * 0.5f); 

                // Particle rotation. Positive value means clockwise rotation.
                if (hasAngle)
                {
                    var rotationAngle = GetParticleRotation(particle, angleField, lifeField);
                    var cosA = (float)Math.Cos(rotationAngle);
                    var sinA = (float)Math.Sin(rotationAngle);
                    var tempX = unitX * cosA - unitY * sinA;
                    unitY = unitY * cosA + unitX * sinA;
                    unitX = tempX;
                }

                // vertex.Size = particleSize;

                const float Sqrt3Half = 0.86602540378f;
                unitY *= Sqrt3Half;
                var halfX = unitX * 0.5f;

                var particlePos = centralPos - halfX + unitY;
                var uvCoord = new Vector2(0.25f, 0.5f - Sqrt3Half * 0.5f);


                // Upper half

                // 0f 0f
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 0f
                particlePos += unitX;
                uvCoord.X = 0.75f;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 1f
                particlePos += halfX;
                particlePos -= unitY;
                uvCoord.X = 1;
                uvCoord.Y = 0.5f;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 0f 1f
                particlePos -= unitX * 2;
                uvCoord.X = 0;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();

                // Upper half

                // 0f 0f
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 0f
                particlePos += unitX * 2;
                uvCoord.X = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 1f
                particlePos -= halfX;
                particlePos -= unitY;
                uvCoord.X = 0.75f;
                uvCoord.Y = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 0f 1f
                particlePos -= unitX;
                uvCoord.X = 0.25f;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();

                renderedParticles++;
            }

            var vtxPerShape = 4 * QuadsPerParticle;
            return renderedParticles * vtxPerShape;
        }
Пример #3
0
        /// <inheritdoc />
        public unsafe override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, 
            ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter)
        {
            // Update the curve samplers if required
            base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter);

            // Get all required particle fields
            var positionField = sorter.GetField(ParticleFields.Position);
            if (!positionField.IsValid())
                return 0;
            var lifeField = sorter.GetField(ParticleFields.Life);
            var sizeField = sorter.GetField(ParticleFields.Size);
            var rotField = sorter.GetField(ParticleFields.Quaternion);
            var hasRotation = rotField.IsValid() || (SamplerRotation != null);


            // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors
            var trsIdentity = (spaceScale == 1f);
            trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0)));
            trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity));


            var renderedParticles = 0;

            var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
            var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

            foreach (var particle in sorter)
            {
                var centralPos = GetParticlePosition(particle, positionField, lifeField);

                var particleSize = GetParticleSize(particle, sizeField, lifeField);

                var unitX = new Vector3(1, 0, 0); 
                var unitY = new Vector3(0, 0, 1); 

                if (hasRotation)
                {
                    var particleRotation = GetParticleRotation(particle, rotField, lifeField);
                    particleRotation.Rotate(ref unitX);
                    particleRotation.Rotate(ref unitY);
                }

                // The TRS matrix is not an identity, so we need to transform the quad
                if (!trsIdentity)
                {
                    spaceRotation.Rotate(ref centralPos);
                    centralPos = centralPos * spaceScale + spaceTranslation;
                    particleSize *= spaceScale;

                    spaceRotation.Rotate(ref unitX);
                    spaceRotation.Rotate(ref unitY);
                }

                // Use half size to make a Size = 1 result in a Billboard of 1m x 1m
                unitX *= (particleSize * 0.5f);
                unitY *= (particleSize * 0.5f);

                var particlePos = centralPos - unitX + unitY;
                var uvCoord = new Vector2(0, 0);
                // 0f 0f
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 0f
                particlePos += unitX * 2;
                uvCoord.X = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 1f
                particlePos -= unitY * 2;
                uvCoord.Y = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 0f 1f
                particlePos -= unitX * 2;
                uvCoord.X = 0;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();

                renderedParticles++;
            }

            var vtxPerShape = 4 * QuadsPerParticle;
            return renderedParticles * vtxPerShape;
        }
        /// <inheritdoc />
        public unsafe override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY,
            ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter)
        {
            // Update the curve samplers if required
            base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter);

            // Get all the required particle fields
            var positionField = sorter.GetField(ParticleFields.Position);
            if (!positionField.IsValid())
                return 0;
            var lifeField = sorter.GetField(ParticleFields.Life);
            var sizeField = sorter.GetField(ParticleFields.Size);
            var directionField = sorter.GetField(ParticleFields.Direction);

            // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors
            var trsIdentity = (spaceScale == 1f);
            trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0)));
            trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity));


            var renderedParticles = 0;

            var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
            var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

            Vector3 invViewZ;
            Vector3.Cross(ref invViewX, ref invViewY, out invViewZ);
            invViewZ.Normalize();

            foreach (var particle in sorter)
            {
                var centralPos = GetParticlePosition(particle, positionField, lifeField);

                var centralOffset = (directionField.IsValid()) ? particle.Get(directionField) : new Vector3(0, 1, 0);

                var particleSize = GetParticleSize(particle, sizeField, lifeField);

                if (!trsIdentity)
                {
                    spaceRotation.Rotate(ref centralPos);
                    centralPos = centralPos * spaceScale + spaceTranslation;

                    spaceRotation.Rotate(ref centralOffset);
                    centralOffset = centralOffset * spaceScale;

                    particleSize *= spaceScale;
                }

                var unitX = invViewX;
                var unitY = invViewY;

                {
                    var centralAxis = centralOffset;

                    float dotZ;
                    Vector3.Dot(ref centralAxis, ref invViewZ, out dotZ);
                    centralAxis -= invViewZ*dotZ;
                    centralAxis.Normalize();

                    float dotX;
                    Vector3.Dot(ref centralAxis, ref unitX, out dotX);

                    float dotY;
                    Vector3.Dot(ref centralAxis, ref unitY, out dotY);

                    unitX = unitX*dotY - unitY*dotX;
                    unitX.Normalize();

                    unitY = centralOffset;
                }

                // Use half size to make a Size = 1 result in a Billboard of 1m x 1m
                unitX *= (particleSize * 0.5f);
                if (ScaleLength)
                {
                    unitY *= (LengthFactor * particleSize * 0.5f);
                }
                else
                {
                    unitY *= (LengthFactor * 0.5f);
                }

                var particlePos = centralPos - unitX + unitY;
                var uvCoord = new Vector2(0, 0);
                // 0f 0f
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 0f
                particlePos += unitX * 2;
                uvCoord.X = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 1f
                particlePos -= unitY * 2;
                uvCoord.Y = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 0f 1f
                particlePos -= unitX * 2;
                uvCoord.X = 0;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();

                renderedParticles++;
            }

            var vtxPerShape = 4 * QuadsPerParticle;
            return renderedParticles * vtxPerShape;
        }
        /// <inheritdoc />
        public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY,
            ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter)
        {
            // Step 1 - get all required fields to build the particle shapes. Some fields may not exist if no initializer or updater operates on them
            //  In that case we just decide on a default value for that field and skip the update

            var positionField = sorter.GetField(ParticleFields.Position);
            if (!positionField.IsValid())
                return 0; // We can't display the particles without position. All other fields are optional

            var sizeField = sorter.GetField(ParticleFields.Size);
            var angleField = sorter.GetField(ParticleFields.Angle);
            var hasAngle = angleField.IsValid();

            var rectField = sorter.GetField(CustomParticleFields.RectangleXY);
            var isRectangle = rectField.IsValid();

            // In case of Local space particles they are simulated in local emitter space, but drawn in world space
            //  If the draw space is identity (i.e. simulation space = draw space) skip transforming the particle's location later
            var trsIdentity = (spaceScale == 1f);
            trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0)));
            trsIdentity = trsIdentity && (spaceRotation.Equals(new Quaternion(0, 0, 0, 1)));

            // Custom feature - fix the Y axis to always point up in world space rather than screen space
            if (FixYAxis)
            {
                invViewY = new Vector3(0, 1, 0);
                invViewX.Y = 0;
                invViewX.Normalize();
            }

            var renderedParticles = 0;

            var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
            var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

            foreach (var particle in sorter)
            {
                var centralPos = particle.Get(positionField);

                var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f;

                if (!trsIdentity)
                {
                    spaceRotation.Rotate(ref centralPos);
                    centralPos = centralPos * spaceScale + spaceTranslation;
                    particleSize *= spaceScale;
                }

                var unitX = invViewX * particleSize;
                var unitY = invViewY * particleSize;
                if (isRectangle)
                {
                    var rectSize = particle.Get(rectField);

                    unitX *= rectSize.X;
                    unitY *= rectSize.Y;
                }

                // Particle rotation. Positive value means clockwise rotation.
                if (hasAngle)
                {
                    var rotationAngle = particle.Get(angleField);
                    var cosA = (float)Math.Cos(rotationAngle);
                    var sinA = (float)Math.Sin(rotationAngle);
                    var tempX = unitX * cosA - unitY * sinA;
                    unitY = unitY * cosA + unitX * sinA;
                    unitX = tempX;
                }


                var particlePos = centralPos - unitX + unitY;
                var uvCoord = new Vector2(0, 0);
                // 0f 0f
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 0f
                particlePos += unitX * 2;
                uvCoord.X = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 1f 1f
                particlePos -= unitY * 2;
                uvCoord.Y = 1;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();


                // 0f 1f
                particlePos -= unitX * 2;
                uvCoord.X = 0;
                bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                bufferState.NextVertex();

                renderedParticles++;
            }

            // Return the number of updated vertices
            var vtxPerShape = 4 * QuadsPerParticle;
            return renderedParticles * vtxPerShape;
        }
Пример #6
0
            /// <summary>
            /// Constructs the ribbon by outputting vertex stream based on the positions and sizes specified previously
            /// </summary>
            /// <param name="vtxBuilder">Target <see cref="ParticleVertexBuilder"/></param> to use
            /// <param name="quadsPerParticle">The required number of quads per each particle</param>
            public unsafe void Ribbonize(ref ParticleBufferState bufferState, int quadsPerParticle)
            {
                if (lastParticle <= 0)
                {
                    return;
                }

                var posAttribute = bufferState.GetAccessor(VertexAttributes.Position);
                var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords);

                if (lastParticle <= sections)
                {
                    // Optional - connect first particle to the origin/emitter

                    // Draw a dummy quad for the first particle
                    var particlePos = new Vector3(0, 0, 0);
                    var uvCoord     = new Vector2(0, 0);

                    for (var particleIdx = 0; particleIdx < lastParticle; particleIdx++)
                    {
                        for (var vtxIdx = 0; vtxIdx < 4; vtxIdx++)
                        {
                            bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));
                            bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord));
                            bufferState.NextVertex();
                        }
                    }

                    return;
                }

                if (sections > 1)
                {
                    if (SmoothingPolicy == SmoothingPolicy.Best)
                    {
                        ExpandVertices_Circular();
                    }
                    else // if (SmoothingPolicy == SmoothingPolicy.Fast)
                    {
                        ExpandVertices_CatmullRom();
                    }
                }

                var positions  = (Vector3 *)positionData;
                var directions = (Vector3 *)directionData;

                bufferState.SetVerticesPerSegment(quadsPerParticle * 6, quadsPerParticle * 4, quadsPerParticle * 2);

                var axis0 = positions[0] - positions[1];

                axis0.Normalize();

                var oldPoint = positions[0];
                var oldUnitX = directions[0];

                // Step 2 - Draw each particle, connecting it to the previous (front) position

                var vCoordOld = 0f;

                for (int i = 0; i < lastParticle; i++)
                {
                    var centralPos = positions[i];

                    // Directions for smoothing
                    var axis1 = (i + 1 < lastParticle) ? positions[i] - positions[i + 1] : positions[lastParticle - 2] - positions[lastParticle - 1];
                    axis1.Normalize();

                    var unitX = directions[i];

                    // Particle rotation - intentionally IGNORED for ribbon

                    var particlePos  = (EdgePolicy == EdgePolicy.Center) ? oldPoint - oldUnitX : oldPoint;
                    var uvCoord      = new Vector2(0, 0);
                    var rotatedCoord = uvCoord;


                    // Top Left - 0f 0f
                    uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 0 : vCoordOld;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Top Right - 1f 0f
                    particlePos += (EdgePolicy == EdgePolicy.Center) ? oldUnitX * 2 : oldUnitX;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    uvCoord.X    = 1;
                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Move the position to the next particle in the ribbon
                    particlePos += centralPos - oldPoint;
                    particlePos += unitX - oldUnitX;
                    vCoordOld    = (TextureCoordinatePolicy == TextureCoordinatePolicy.Stretched) ?
                                   ((i + 1) / (float)(lastParticle) * TexCoordsFactor) : ((centralPos - oldPoint).Length() * TexCoordsFactor) + vCoordOld;


                    // Bottom Left - 1f 1f
                    uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 1 : vCoordOld;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Bottom Right - 0f 1f
                    particlePos -= (EdgePolicy == EdgePolicy.Center) ? unitX * 2 : unitX;
                    bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos));

                    uvCoord.X    = 0;
                    rotatedCoord = UVRotate.GetCoords(uvCoord);
                    bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord));

                    bufferState.NextVertex();


                    // Preserve the old attributes for the next cycle
                    oldUnitX = unitX;
                    oldPoint = centralPos;
                }
            }