Exemple #1
0
        private void AddJob(ParticleSystemNode node, ParticleSystemData particleSystemData, bool sortByDistance, bool backToFront)
        {
            if (particleSystemData.Particles.Count == 0)
            {
                return;
            }

            float distance = 0;

            if (sortByDistance)
            {
                // Position relative to ParticleSystemNode (root particle system).
                Vector3 position = particleSystemData.Pose.Position;

                // Position in world space.
                position = node.PoseWorld.ToWorldPosition(position);

                // Determine distance to camera.
                Vector3 cameraToNode = position - _cameraPose.Position;

                // Planar distance: Project vector onto look direction.
                distance = Vector3.Dot(cameraToNode, _cameraForward);

                // Use linear distance for viewpoint-oriented and world-oriented billboards.
                if (particleSystemData.BillboardOrientation.Normal != BillboardNormal.ViewPlaneAligned)
                {
                    distance = cameraToNode.LengthSquared() * Math.Sign(distance);
                }

                if (backToFront)
                {
                    distance = -distance;
                }
            }

            // Add draw job to list.
            ushort drawOrder = (ushort)particleSystemData.DrawOrder;
            var    textureId = GetTextureId(particleSystemData.Texture);
            var    job       = new Job
            {
                SortKey            = GetSortKey(distance, drawOrder, textureId),
                Node               = node,
                ParticleSystemData = particleSystemData,
            };

            _jobs.Add(ref job);
        }
Exemple #2
0
        private void DrawParticlesOldToNew(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3 scale, ref Pose pose, ref Vector3 color, float alpha, float angleOffset)
        {
            var b = new BillboardArgs
            {
                Orientation    = particleSystemData.BillboardOrientation,
                Softness       = particleSystemData.Softness,
                ReferenceAlpha = particleSystemData.AlphaTest,
            };

            int  numberOfParticles  = particleSystemData.Particles.Count;
            var  particles          = particleSystemData.Particles.Array;
            bool isViewPlaneAligned = (particleSystemData.BillboardOrientation.Normal == BillboardNormal.ViewPlaneAligned);
            bool isAxisInViewSpace  = particleSystemData.BillboardOrientation.IsAxisInViewSpace;

            for (int i = 0; i < numberOfParticles; i++)
            {
                if (particles[i].IsAlive) // Skip dead particles.
                {
                    if (requiresTransformation)
                    {
                        b.Position = pose.ToWorldPosition(particles[i].Position * scale);
                        b.Normal   = isViewPlaneAligned ? _defaultNormal : pose.ToWorldDirection(particles[i].Normal);
                        b.Axis     = isAxisInViewSpace ? particles[i].Axis : pose.ToWorldDirection(particles[i].Axis);
                        b.Size     = particles[i].Size * scale.Y; // Assume uniform scale for size.
                    }
                    else
                    {
                        b.Position = particles[i].Position;
                        b.Normal   = isViewPlaneAligned ? _defaultNormal : particles[i].Normal;
                        b.Axis     = particles[i].Axis;
                        b.Size     = particles[i].Size;
                    }

                    b.Angle         = particles[i].Angle + angleOffset;
                    b.Color         = particles[i].Color * color;
                    b.Alpha         = particles[i].Alpha * alpha;
                    b.AnimationTime = particles[i].AnimationTime;
                    b.BlendMode     = particles[i].BlendMode;

                    var texture = particleSystemData.Texture ?? _debugTexture;
                    _billboardBatch.DrawBillboard(ref b, texture);
                }
            }
        }
Exemple #3
0
        private void Draw(ParticleSystemNode node, ParticleSystemData particleSystemData)
        {
            // Scale and pose.
            Vector3 scale = Vector3.One;
            Pose    pose  = Pose.Identity;
            bool    requiresTransformation = (particleSystemData.ReferenceFrame == ParticleReferenceFrame.Local);

            if (requiresTransformation)
            {
                scale = node.ScaleWorld;
                pose  = node.PoseWorld * particleSystemData.Pose;
            }

            // Tint color and alpha.
            Vector3 color       = node.Color;
            float   alpha       = node.Alpha;
            float   angleOffset = node.AngleOffset;

            if (particleSystemData.IsRibbon)
            {
                if (particleSystemData.AxisParameter == null)
                {
                    // Ribbons with automatic axis.
                    DrawParticleRibbonsAuto(particleSystemData, requiresTransformation, ref scale, ref pose, ref color, alpha);
                }
                else
                {
                    // Ribbons with fixed axis.
                    DrawParticleRibbonsFixed(particleSystemData, requiresTransformation, ref scale, ref pose, ref color, alpha);
                }
            }
            else if (particleSystemData.IsDepthSorted)
            {
                // Particles sorted by depth.
                DrawParticlesBackToFront(particleSystemData, requiresTransformation, ref scale, ref pose, ref color, alpha, angleOffset);
            }
            else
            {
                // Particles sorted by age.
                DrawParticlesOldToNew(particleSystemData, requiresTransformation, ref scale, ref pose, ref color, alpha, angleOffset);
            }
        }
Exemple #4
0
        private void DrawParticleRibbonsAuto(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3 scale, ref Pose pose, ref Vector3 color, float alpha)
        {
            // At least two particles are required to create a ribbon.
            int numberOfParticles = particleSystemData.Particles.Count;

            if (numberOfParticles < 2)
            {
                return;
            }

            // The up axis is not defined and needs to be derived automatically:
            // - Compute tangents along the ribbon curve.
            // - Build cross-products of normal and tangent vectors.

            // Is normal uniform across all particles?
            Vector3?uniformNormal;

            switch (particleSystemData.BillboardOrientation.Normal)
            {
            case BillboardNormal.ViewPlaneAligned:
                uniformNormal = _defaultNormal;
                break;

            case BillboardNormal.ViewpointOriented:
                uniformNormal = _cameraPose.Position - pose.Position;
                if (!uniformNormal.Value.TryNormalize())
                {
                    uniformNormal = _defaultNormal;
                }
                break;

            default:
                var normalParameter = particleSystemData.NormalParameter;
                if (normalParameter == null)
                {
                    uniformNormal = _defaultNormal;
                }
                else if (normalParameter.IsUniform)
                {
                    uniformNormal = normalParameter.DefaultValue;
                    if (requiresTransformation)
                    {
                        uniformNormal = pose.ToWorldDirection(uniformNormal.Value);
                    }
                }
                else
                {
                    // Normal is set in particle data.
                    uniformNormal = null;
                }
                break;
            }

            var texture   = particleSystemData.Texture ?? _debugTexture;
            var particles = particleSystemData.Particles.Array;
            int index     = 0;

            do
            {
                // ----- Skip dead particles.
                while (index < numberOfParticles && !particles[index].IsAlive)
                {
                    index++;
                }

                // ----- Start of new ribbon.
                int endIndex = index + 1;
                while (endIndex < numberOfParticles && particles[endIndex].IsAlive)
                {
                    endIndex++;
                }

                int numberOfSegments = endIndex - index - 1;

                var p0 = new RibbonArgs
                {
                    // Uniform parameters
                    Softness       = particleSystemData.Softness,
                    ReferenceAlpha = particleSystemData.AlphaTest
                };

                var p1 = new RibbonArgs
                {
                    // Uniform parameters
                    Softness       = particleSystemData.Softness,
                    ReferenceAlpha = particleSystemData.AlphaTest
                };

                // Compute axes and render ribbon.
                // First particle.
                if (requiresTransformation)
                {
                    p0.Position = pose.ToWorldPosition(particles[index].Position * scale);
                    p0.Size     = particles[index].Size.Y * scale.Y;
                }
                else
                {
                    p0.Position = particles[index].Position;
                    p0.Size     = particles[index].Size.Y;
                }

                p0.Color              = particles[index].Color * color;
                p0.Alpha              = particles[index].Alpha * alpha;
                p0.AnimationTime      = particles[index].AnimationTime;
                p0.BlendMode          = particles[index].BlendMode;
                p0.TextureCoordinateU = 0;

                index++;
                Vector3 nextPosition;
                if (requiresTransformation)
                {
                    nextPosition = pose.ToWorldPosition(particles[index].Position * scale);
                }
                else
                {
                    nextPosition = particles[index].Position;
                }

                Vector3 normal;
                if (uniformNormal.HasValue)
                {
                    // Uniform normal.
                    normal = uniformNormal.Value;
                }
                else
                {
                    // Varying normal.
                    normal = particles[index].Normal;
                    if (requiresTransformation)
                    {
                        normal = pose.ToWorldDirection(normal);
                    }
                }

                Vector3 previousDelta = nextPosition - p0.Position;
                p0.Axis = Vector3.Cross(normal, previousDelta);
                p0.Axis.TryNormalize();

                // Intermediate particles.
                while (index < endIndex - 1)
                {
                    p1.Position = nextPosition;

                    if (requiresTransformation)
                    {
                        nextPosition = pose.ToWorldPosition(particles[index + 1].Position * scale);
                        p1.Size      = particles[index].Size.Y * scale.Y;
                    }
                    else
                    {
                        nextPosition = particles[index + 1].Position;
                        p1.Size      = particles[index].Size.Y;
                    }

                    if (uniformNormal.HasValue)
                    {
                        // Uniform normal.
                        normal = uniformNormal.Value;
                    }
                    else
                    {
                        // Varying normal.
                        normal = particles[index].Normal;
                        if (requiresTransformation)
                        {
                            normal = pose.ToWorldDirection(normal);
                        }
                    }

                    Vector3 delta   = nextPosition - p1.Position;
                    Vector3 tangent = delta + previousDelta; // Note: Should we normalize vectors for better average?
                    p1.Axis = Vector3.Cross(normal, tangent);
                    p1.Axis.TryNormalize();

                    p1.Color              = particles[index].Color * color;
                    p1.Alpha              = particles[index].Alpha * alpha;
                    p1.AnimationTime      = particles[index].AnimationTime;
                    p1.BlendMode          = particles[index].BlendMode;
                    p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling);

                    // Draw ribbon segment.
                    _billboardBatch.DrawRibbon(ref p0, ref p1, texture);

                    p0 = p1;
                    p0.TextureCoordinateU = GetTextureCoordinateU0(index, numberOfSegments, particleSystemData.TextureTiling);
                    previousDelta         = delta;
                    index++;
                }

                // Last particle.
                p1.Position = nextPosition;

                if (uniformNormal.HasValue)
                {
                    // Uniform normal.
                    normal = uniformNormal.Value;
                }
                else
                {
                    // Varying normal.
                    normal = particles[index].Normal;
                    if (requiresTransformation)
                    {
                        normal = pose.ToWorldDirection(normal);
                    }
                }

                p1.Axis = Vector3.Cross(normal, previousDelta);
                p1.Axis.TryNormalize();

                if (requiresTransformation)
                {
                    p1.Size = particles[index].Size.Y * scale.Y;
                }
                else
                {
                    p1.Size = particles[index].Size.Y;
                }

                p1.Color              = particles[index].Color * color;
                p1.Alpha              = particles[index].Alpha * alpha;
                p1.AnimationTime      = particles[index].AnimationTime;
                p1.BlendMode          = particles[index].BlendMode;
                p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling);

                // Draw last ribbon segment.
                _billboardBatch.DrawRibbon(ref p0, ref p1, texture);
                index++;
            } while (index < numberOfParticles);
        }
Exemple #5
0
        // Particle ribbons:
        // Particles can be rendered as ribbons (a.k.a. trails, lines). Subsequent living
        // particles are connected using rectangles.
        //   +--------------+--------------+
        //   |              |              |
        //   p0             p1             p2
        //   |              |              |
        //   +--------------+--------------+
        // At least two living particles are required to create a ribbon. Dead particles
        // ("NormalizedAge" ≥ 1) can be used as delimiters to terminate one ribbon and
        // start the next ribbon.
        //
        // p0 and p1 can have different colors and alpha values to create color gradients
        // or a ribbon that fades in/out.

        private void DrawParticleRibbonsFixed(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3 scale, ref Pose pose, ref Vector3 color, float alpha)
        {
            // At least two particles are required to create a ribbon.
            int numberOfParticles = particleSystemData.Particles.Count;

            if (numberOfParticles < 2)
            {
                return;
            }

            var  particles         = particleSystemData.Particles.Array;
            bool isAxisInViewSpace = particleSystemData.BillboardOrientation.IsAxisInViewSpace;
            int  index             = 0;

            do
            {
                // ----- Skip dead particles.
                while (index < numberOfParticles && !particles[index].IsAlive)
                {
                    index++;
                }

                // ----- Start of new ribbon.
                int endIndex = index + 1;
                while (endIndex < numberOfParticles && particles[endIndex].IsAlive)
                {
                    endIndex++;
                }

                int numberOfSegments = endIndex - index - 1;

                var p0 = new RibbonArgs
                {
                    // Uniform parameters
                    Softness       = particleSystemData.Softness,
                    ReferenceAlpha = particleSystemData.AlphaTest
                };

                var p1 = new RibbonArgs
                {
                    // Uniform parameters
                    Softness       = particleSystemData.Softness,
                    ReferenceAlpha = particleSystemData.AlphaTest
                };

                p0.Axis = particles[index].Axis;
                if (requiresTransformation)
                {
                    p0.Position = pose.ToWorldPosition(particles[index].Position * scale);
                    if (!isAxisInViewSpace)
                    {
                        p0.Axis = pose.ToWorldDirection(p0.Axis);
                    }

                    p0.Size = particles[index].Size.Y * scale.Y;
                }
                else
                {
                    p0.Position = particles[index].Position;
                    p0.Size     = particles[index].Size.Y;
                }

                p0.Color              = particles[index].Color * color;
                p0.Alpha              = particles[index].Alpha * alpha;
                p0.AnimationTime      = particles[index].AnimationTime;
                p0.BlendMode          = particles[index].BlendMode;
                p0.TextureCoordinateU = 0;

                index++;
                while (index < endIndex)
                {
                    p1.Axis = particles[index].Axis;
                    if (requiresTransformation)
                    {
                        p1.Position = pose.ToWorldPosition(particles[index].Position * scale);
                        if (!isAxisInViewSpace)
                        {
                            p1.Axis = pose.ToWorldDirection(p1.Axis);
                        }

                        p1.Size = particles[index].Size.Y * scale.Y;
                    }
                    else
                    {
                        p1.Position = particles[index].Position;
                        p1.Size     = particles[index].Size.Y;
                    }

                    p1.Color              = particles[index].Color * color;
                    p1.Alpha              = particles[index].Alpha * alpha;
                    p1.AnimationTime      = particles[index].AnimationTime;
                    p1.BlendMode          = particles[index].BlendMode;
                    p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling);

                    // Draw ribbon segment.
                    var texture = particleSystemData.Texture ?? _debugTexture;
                    _billboardBatch.DrawRibbon(ref p0, ref p1, texture);

                    p0 = p1;
                    p0.TextureCoordinateU = GetTextureCoordinateU0(index, numberOfSegments, particleSystemData.TextureTiling);
                    index++;
                }
            } while (index < numberOfParticles);
        }
Exemple #6
0
        private void DrawParticlesBackToFront(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3 scale, ref Pose pose, ref Vector3 color, float alpha, float angleOffset)
        {
            var b = new BillboardArgs
            {
                Orientation    = particleSystemData.BillboardOrientation,
                Softness       = particleSystemData.Softness,
                ReferenceAlpha = particleSystemData.AlphaTest,
            };

            int numberOfParticles = particleSystemData.Particles.Count;
            var particles         = particleSystemData.Particles.Array;

            if (_particleIndices == null)
            {
                _particleIndices = new ArrayList <ParticleIndex>(numberOfParticles);
            }
            else
            {
                _particleIndices.Clear();
                _particleIndices.EnsureCapacity(numberOfParticles);
            }

            // Use linear distance for viewpoint-oriented and world-oriented billboards.
            bool useLinearDistance = (particleSystemData.BillboardOrientation.Normal != BillboardNormal.ViewPlaneAligned);

            // Compute positions and distance to camera.
            for (int i = 0; i < numberOfParticles; i++)
            {
                if (particles[i].IsAlive) // Skip dead particles.
                {
                    var particleIndex = new ParticleIndex();
                    particleIndex.Index = i;
                    if (requiresTransformation)
                    {
                        particleIndex.Position = pose.ToWorldPosition(particles[i].Position * scale);
                    }
                    else
                    {
                        particleIndex.Position = particles[i].Position;
                    }

                    // Planar distance: Project vector onto look direction.
                    Vector3 cameraToParticle = particleIndex.Position - _cameraPose.Position;
                    particleIndex.Distance = Vector3.Dot(cameraToParticle, _cameraForward);
                    if (useLinearDistance)
                    {
                        particleIndex.Distance = cameraToParticle.Length * Math.Sign(particleIndex.Distance);
                    }

                    _particleIndices.Add(ref particleIndex);
                }
            }

            // Sort particles back-to-front.
            _particleIndices.Sort(ParticleIndexComparer.Instance);

            bool isViewPlaneAligned = (particleSystemData.BillboardOrientation.Normal == BillboardNormal.ViewPlaneAligned);
            bool isAxisInViewSpace  = particleSystemData.BillboardOrientation.IsAxisInViewSpace;

            // Draw sorted particles.
            var indices = _particleIndices.Array;

            numberOfParticles = _particleIndices.Count; // Dead particles have been removed.
            for (int i = 0; i < numberOfParticles; i++)
            {
                int index = indices[i].Index;
                b.Position = indices[i].Position;
                if (requiresTransformation)
                {
                    b.Normal = isViewPlaneAligned ? _defaultNormal : pose.ToWorldDirection(particles[index].Normal);
                    b.Axis   = isAxisInViewSpace ? particles[index].Axis : pose.ToWorldDirection(particles[index].Axis);
                    b.Size   = particles[index].Size * scale.Y; // Assume uniform scale for size.
                }
                else
                {
                    b.Normal = isViewPlaneAligned ? _defaultNormal : particles[index].Normal;
                    b.Axis   = particles[index].Axis;
                    b.Size   = particles[index].Size;
                }

                b.Angle         = particles[index].Angle + angleOffset;
                b.Color         = particles[index].Color * color;
                b.Alpha         = particles[index].Alpha * alpha;
                b.AnimationTime = particles[index].AnimationTime;
                b.BlendMode     = particles[index].BlendMode;

                var texture = particleSystemData.Texture ?? _debugTexture;
                _billboardBatch.DrawBillboard(ref b, texture);
            }
        }