/// <summary> /// Orientates the Particle to face the camera, but constrains the particle to always be perpendicular to the /// X-Y plane. /// </summary> /// <param name="cParticle">The Particle to update.</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update.</param> protected void UpdateParticleToBeConstrainedAroundZAxis(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { Vector3 newNormal = CameraPosition - cParticle.Position; newNormal.Z = 0; cParticle.Normal = newNormal; }
//=========================================================== // Vertex Update and Overridden Particle System Functions //=========================================================== /// <summary> /// Function to update the Vertex properties according to the Particle properties /// </summary> /// <param name="sVertexBuffer">The array containing the Vertices to be drawn</param> /// <param name="iIndex">The Index in the array where the Particle's Vertex info should be placed</param> /// <param name="Particle">The Particle to copy the information from</param> protected virtual void UpdateVertexProperties(ref DefaultQuadParticleVertex[] sVertexBuffer, int iIndex, DPSFParticle Particle) { // Cast the Particle to the type it really is DefaultQuadParticle cParticle = (DefaultQuadParticle)Particle; // Calculate what half of the Quads Width and Height are float fHalfWidth = cParticle.Width / 2.0f; float fHalfHeight = cParticle.Height / 2.0f; // Calculate the Positions of the Quads corners around the origin Vector3 sTopLeft = new Vector3(-fHalfWidth, -fHalfHeight, 0); Vector3 sTopRight = new Vector3(fHalfWidth, -fHalfHeight, 0); Vector3 sBottomLeft = new Vector3(-fHalfWidth, fHalfHeight, 0); Vector3 sBottomRight = new Vector3(fHalfWidth, fHalfHeight, 0); // Rotate the Quad corners around the origin according to its Orientation, // then calculate their final Positions sTopLeft = Vector3.Transform(sTopLeft, cParticle.Orientation) + cParticle.Position; sTopRight = Vector3.Transform(sTopRight, cParticle.Orientation) + cParticle.Position; sBottomLeft = Vector3.Transform(sBottomLeft, cParticle.Orientation) + cParticle.Position; sBottomRight = Vector3.Transform(sBottomRight, cParticle.Orientation) + cParticle.Position; // Copy this Particle's renderable Properties to the Vertex Buffer // This is a Quad so we must copy all 4 Vertices over sVertexBuffer[iIndex].Position = sBottomLeft; sVertexBuffer[iIndex].Color = cParticle.Color; sVertexBuffer[iIndex + 1].Position = sTopLeft; sVertexBuffer[iIndex + 1].Color = cParticle.Color; sVertexBuffer[iIndex + 2].Position = sBottomRight; sVertexBuffer[iIndex + 2].Color = cParticle.Color; sVertexBuffer[iIndex + 3].Position = sTopRight; sVertexBuffer[iIndex + 3].Color = cParticle.Color; // Fill in the Index Buffer for the newly added Vertices. // Specify the Vertices in Clockwise order. // It takes 6 Indices to represent a quad (2 triangles = 6 corners). // If we're using the HiDef profile, fill in the regular Index Buffer. if (this.GraphicsDevice.GraphicsProfile == GraphicsProfile.HiDef) { IndexBuffer[IndexBufferIndex++] = iIndex + 1; IndexBuffer[IndexBufferIndex++] = iIndex + 2; IndexBuffer[IndexBufferIndex++] = iIndex; IndexBuffer[IndexBufferIndex++] = iIndex + 1; IndexBuffer[IndexBufferIndex++] = iIndex + 3; IndexBuffer[IndexBufferIndex++] = iIndex + 2; } // Else we're using the Reach profile, so fill the Reach Index Buffer instead. else { IndexBufferReach[IndexBufferIndex++] = (short)(iIndex + 1); IndexBufferReach[IndexBufferIndex++] = (short)(iIndex + 2); IndexBufferReach[IndexBufferIndex++] = (short)(iIndex); IndexBufferReach[IndexBufferIndex++] = (short)(iIndex + 1); IndexBufferReach[IndexBufferIndex++] = (short)(iIndex + 3); IndexBufferReach[IndexBufferIndex++] = (short)(iIndex + 2); } }
/// <summary> /// Update a Particle's Rotational Velocity according to its Rotational Acceleration /// </summary> /// <param name="cParticle">The Particle to update</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update</param> protected void UpdateParticleRotationalVelocityUsingRotationalAcceleration(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { // If Rotational Acceleration is being used if (cParticle.RotationalAcceleration != Vector3.Zero) { // Update the Particle's Rotational Velocity according to its Rotational Acceleration cParticle.RotationalVelocity += cParticle.RotationalAcceleration * fElapsedTimeInSeconds; } }
//=========================================================== // Initialization Function //=========================================================== /// <summary> /// Function to Initialize a Default Particle with default settings /// </summary> /// <param name="Particle">The Particle to be Initialized</param> public override void InitializeParticleUsingInitialProperties(DPSFParticle Particle) { // Cast the Particle to the type it really is DefaultQuadParticle cParticle = (DefaultQuadParticle)Particle; // Initialize the Particle according to the values specified in the Initial Settings base.InitializeParticleUsingInitialProperties(cParticle, mcInitialProperties); // If the Rotation should be interpolated between the Min and Max Rotation if (mcInitialProperties.InterpolateBetweenMinAndMaxRotation) { // Calculate the Particle's initial Rotational values Vector3 sRotation = Vector3.Lerp(mcInitialProperties.RotationMin, mcInitialProperties.RotationMax, RandomNumber.NextFloat()); cParticle.Orientation = Quaternion.CreateFromYawPitchRoll(sRotation.Y, sRotation.X, sRotation.Z); } // Else the Rotation XYZ values should each be calculated individually else { // Calculate the Particle's initial Rotational values Vector3 sRotation = DPSFHelper.RandomVectorBetweenTwoVectors(mcInitialProperties.RotationMin, mcInitialProperties.RotationMax); cParticle.Orientation = Quaternion.CreateFromYawPitchRoll(sRotation.Y, sRotation.X, sRotation.Z); } // If the Rotational Velocity should be interpolated between the Min and Max Rotational Velocities if (mcInitialProperties.InterpolateBetweenMinAndMaxRotationalVelocity) { cParticle.RotationalVelocity = Vector3.Lerp(mcInitialProperties.RotationalVelocityMin, mcInitialProperties.RotationalVelocityMax, RandomNumber.NextFloat()); } // Else the Rotational Velocity XYZ values should each be calculated individually else { cParticle.RotationalVelocity = DPSFHelper.RandomVectorBetweenTwoVectors(mcInitialProperties.RotationalVelocityMin, mcInitialProperties.RotationalVelocityMax); } // If the Rotational Acceleration should be interpolated between the Min and Max Rotational Acceleration if (mcInitialProperties.InterpolateBetweenMinAndMaxRotationalAcceleration) { cParticle.RotationalAcceleration = Vector3.Lerp(mcInitialProperties.RotationalAccelerationMin, mcInitialProperties.RotationalAccelerationMax, RandomNumber.NextFloat()); } // Else the Rotational Acceleration XYZ values should each be calculated individually else { cParticle.RotationalAcceleration = DPSFHelper.RandomVectorBetweenTwoVectors(mcInitialProperties.RotationalAccelerationMin, mcInitialProperties.RotationalAccelerationMax); } // Calculate the Particle's Width and Height values cParticle.StartWidth = DPSFHelper.RandomNumberBetween(mcInitialProperties.StartSizeMin > 0 ? mcInitialProperties.StartSizeMin : mcInitialProperties.StartWidthMin, mcInitialProperties.StartSizeMax > 0 ? mcInitialProperties.StartSizeMax : mcInitialProperties.StartWidthMax); cParticle.EndWidth = DPSFHelper.RandomNumberBetween(mcInitialProperties.EndSizeMin > 0 ? mcInitialProperties.EndSizeMin : mcInitialProperties.EndWidthMin, mcInitialProperties.EndSizeMax > 0 ? mcInitialProperties.EndSizeMax : mcInitialProperties.EndWidthMax); cParticle.StartHeight = DPSFHelper.RandomNumberBetween(mcInitialProperties.StartSizeMin > 0 ? mcInitialProperties.StartSizeMin : mcInitialProperties.StartHeightMin, mcInitialProperties.StartSizeMax > 0 ? mcInitialProperties.StartSizeMax : mcInitialProperties.StartHeightMax); cParticle.EndHeight = DPSFHelper.RandomNumberBetween(mcInitialProperties.EndSizeMin > 0 ? mcInitialProperties.EndSizeMin : mcInitialProperties.EndHeightMin, mcInitialProperties.EndSizeMax > 0 ? mcInitialProperties.EndSizeMax : mcInitialProperties.EndHeightMax); cParticle.Width = cParticle.StartWidth; cParticle.Height = cParticle.StartHeight; }
//=========================================================== // Particle Update Functions //=========================================================== /// <summary> /// Update a Particle's Rotation according to its Rotational Velocity /// </summary> /// <param name="cParticle">The Particle to update</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update</param> protected void UpdateParticleRotationUsingRotationalVelocity(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { // Update the Particle's Rotation according to its Rotational Velocity // If Rotational Velocity is being used if (cParticle.RotationalVelocity != Vector3.Zero) { // Quaternion Rotation Formula: NewOrientation = OldNormalizedOrientation * Rotation(v*t) * 0.5, // where v is the angular(rotational) velocity vector, and t is the amount of time elapsed. // The 0.5 is used to scale the rotation so that Pi = 180 degree rotation cParticle.Orientation.Normalize(); Quaternion sRotation = new Quaternion(cParticle.RotationalVelocity * (fElapsedTimeInSeconds * 0.5f), 0); cParticle.Orientation += cParticle.Orientation * sRotation; } }
//=========================================================== // Particle System Update Functions //=========================================================== /// <summary> /// Sorts the particles to draw particles furthest from the camera first, in order to achieve proper depth perspective. /// /// <para>NOTE: This operation is very expensive and should only be used when you are /// drawing particles with both opaque and semi-transparent portions, and not using additive blending.</para> /// <para>Merge Sort is the sorting algorithm used, as it tends to be best for linked lists. /// TODO - WHILE MERGE SORT SHOULD BE USED, DUE TO TIME CONSTRAINTS A (PROBABLY) SLOWER METHOD (QUICK-SORT) /// IS BEING USED INSTEAD. THIS FUNCTION NEEDS TO BE UPDATED TO USE MERGE SORT STILL. /// THE LINKED LIST MERGE SORT ALGORITHM CAN BE FOUND AT http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html</para> /// </summary> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update</param> protected void UpdateParticleSystemToSortParticlesByDistanceFromCamera(float fElapsedTimeInSeconds) { // Store the Number of Active Particles to sort int iNumberOfActiveParticles = ActiveParticles.Count; // If there is nothing to sort if (iNumberOfActiveParticles <= 1) { // Exit without sorting return; } // Create a List to put the Active Particles in to be sorted List <Particle> cActiveParticleList = new List <Particle>(iNumberOfActiveParticles); // Add all of the Particles to the List LinkedListNode <Particle> cNode = ActiveParticles.First; while (cNode != null) { // Copy this Particle into the Array cActiveParticleList.Add(cNode.Value); // Move to the next Active Particle cNode = cNode.Next; } // Now that the List is full, sort it cActiveParticleList.Sort(delegate(Particle Particle1, Particle Particle2) { DefaultQuadParticle cParticle1 = (DefaultQuadParticle)(DPSFParticle)Particle1; DefaultQuadParticle cParticle2 = (DefaultQuadParticle)(DPSFParticle)Particle2; return(cParticle1.DistanceFromCameraSquared.CompareTo(cParticle2.DistanceFromCameraSquared)); }); // Now that the List is sorted, add the Particles into the Active Particles Linked List in sorted order ActiveParticles.Clear(); for (int iIndex = 0; iIndex < iNumberOfActiveParticles; iIndex++) { // Add this Particle to the Active Particles Linked List. // List is sorted from smallest to largest, but we want // our Linked List sorted from largest to smallest, since // the Particles at the end of the Linked List are drawn last. ActiveParticles.AddFirst(cActiveParticleList[iIndex]); } }
/// <summary> /// Deep copy all of the Particle properties /// </summary> /// <param name="ParticleToCopy">The Particle to Copy the properties from</param> public override void CopyFrom(DPSFParticle ParticleToCopy) { // Cast the Particle to the type it really is DefaultQuadParticle cParticleToCopy = (DefaultQuadParticle)ParticleToCopy; base.CopyFrom(cParticleToCopy); this.Orientation = cParticleToCopy.Orientation; this.RotationalVelocity = cParticleToCopy.RotationalVelocity; this.RotationalAcceleration = cParticleToCopy.RotationalAcceleration; this.Width = cParticleToCopy.Width; this.Height = cParticleToCopy.Height; this.StartHeight = cParticleToCopy.StartHeight; this.StartWidth = cParticleToCopy.StartWidth; this.EndHeight = cParticleToCopy.EndHeight; this.EndWidth = cParticleToCopy.EndWidth; this.DistanceFromCameraSquared = cParticleToCopy.DistanceFromCameraSquared; }
/// <summary> /// Updates the Particle's DistanceFromCameraSquared property to reflect how far this Particle is from the Camera. /// </summary> /// <param name="cParticle">The Particle to update.</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update.</param> protected void UpdateParticleDistanceFromCameraSquared(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { cParticle.DistanceFromCameraSquared = Vector3.DistanceSquared(this.CameraPosition, cParticle.Position); }
/// <summary> /// Turns the Particle into a Billboard Particle (i.e. The Particle always faces the Camera). /// <para>NOTE: This Update function should be called after all other Update functions to ensure that /// the Particle is orientated correctly.</para> /// <para>NOTE: Update the Particle System's Camera Position every frame to ensure that this works correctly.</para> /// <para>NOTE: Only Roll Rotations (i.e. around the Z axis) will be visible when this is used.</para> /// </summary> /// <param name="cParticle">The Particle to update.</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update.</param> protected void UpdateParticleToFaceTheCamera(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { // Make the Particle face the Camera cParticle.Normal = CameraPosition - cParticle.Position; }
/// <summary> /// Linearly interpolate the Particle's Width and Height between the Start and End values according /// to the Particle's Normalized Lifetime /// </summary> /// <param name="cParticle">The Particle to update</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update</param> protected void UpdateParticleWidthAndHeightUsingLerp(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { // Calculate the Particle's new Width and Height cParticle.Width = MathHelper.Lerp(cParticle.StartWidth, cParticle.EndWidth, cParticle.NormalizedElapsedTime); cParticle.Height = MathHelper.Lerp(cParticle.StartHeight, cParticle.EndHeight, cParticle.NormalizedElapsedTime); }
/// <summary> /// Update a Particle's Rotation and Rotational Velocity according to its Rotational Acceleration /// </summary> /// <param name="cParticle">The Particle to update</param> /// <param name="fElapsedTimeInSeconds">How long it has been since the last update</param> protected void UpdateParticleRotationAndRotationalVelocityUsingRotationalAcceleration(DefaultQuadParticle cParticle, float fElapsedTimeInSeconds) { // Update the Particle's Rotational Velocity and Rotation according to its Rotational Acceleration UpdateParticleRotationalVelocityUsingRotationalAcceleration(cParticle, fElapsedTimeInSeconds); UpdateParticleRotationUsingRotationalVelocity(cParticle, fElapsedTimeInSeconds); }
//=========================================================== // Vertex Update and Overridden Particle System Functions //=========================================================== /// <summary> /// Function to update the Vertex properties according to the Particle properties /// </summary> /// <param name="sVertexBuffer">The array containing the Vertices to be drawn</param> /// <param name="iIndex">The Index in the array where the Particle's Vertex info should be placed</param> /// <param name="Particle">The Particle to copy the information from</param> protected virtual void UpdateVertexProperties(ref DefaultQuadParticleVertex[] sVertexBuffer, int iIndex, DPSFParticle Particle) { // Cast the Particle to the type it really is DefaultQuadParticle cParticle = (DefaultQuadParticle)Particle; // Calculate what half of the Quads Width and Height are float fHalfWidth = cParticle.Width / 2.0f; float fHalfHeight = cParticle.Height / 2.0f; // Calculate the Positions of the Quads corners around the origin Vector3 sTopLeft = new Vector3(-fHalfWidth, -fHalfHeight, 0); Vector3 sTopRight = new Vector3(fHalfWidth, -fHalfHeight, 0); Vector3 sBottomLeft = new Vector3(-fHalfWidth, fHalfHeight, 0); Vector3 sBottomRight = new Vector3(fHalfWidth, fHalfHeight, 0); // Rotate the Quad corners around the origin according to its Orientation, // then calculate their final Positions sTopLeft = Vector3.Transform(sTopLeft, cParticle.Orientation) + cParticle.Position; sTopRight = Vector3.Transform(sTopRight, cParticle.Orientation) + cParticle.Position; sBottomLeft = Vector3.Transform(sBottomLeft, cParticle.Orientation) + cParticle.Position; sBottomRight = Vector3.Transform(sBottomRight, cParticle.Orientation) + cParticle.Position; // Effect expects a premultiplied color, so get the actual color to use. Color premultipliedColor = cParticle.ColorAsPremultiplied; // Copy this Particle's renderable Properties to the Vertex Buffer // This is a Quad so we must copy all 4 Vertices over sVertexBuffer[iIndex].Position = sBottomLeft; sVertexBuffer[iIndex].Color = premultipliedColor; sVertexBuffer[iIndex + 1].Position = sTopLeft; sVertexBuffer[iIndex + 1].Color = premultipliedColor; sVertexBuffer[iIndex + 2].Position = sBottomRight; sVertexBuffer[iIndex + 2].Color = premultipliedColor; sVertexBuffer[iIndex + 3].Position = sTopRight; sVertexBuffer[iIndex + 3].Color = premultipliedColor; // Fill in the Index Buffer for the newly added Vertices. // Specify the Vertices in Clockwise order. // It takes 6 Indices to represent a quad (2 triangles = 6 corners). // If we should be using the 32-bit Integer Index Buffer, fill it. if (this.IsUsingIntegerIndexBuffer) { IndexBuffer[IndexBufferIndex++] = iIndex + 1; IndexBuffer[IndexBufferIndex++] = iIndex + 2; IndexBuffer[IndexBufferIndex++] = iIndex; IndexBuffer[IndexBufferIndex++] = iIndex + 1; IndexBuffer[IndexBufferIndex++] = iIndex + 3; IndexBuffer[IndexBufferIndex++] = iIndex + 2; } // Else we should be using the 16-bit Short Index Buffer. else { IndexBufferShort[IndexBufferIndex++] = (short)(iIndex + 1); IndexBufferShort[IndexBufferIndex++] = (short)(iIndex + 2); IndexBufferShort[IndexBufferIndex++] = (short)(iIndex); IndexBufferShort[IndexBufferIndex++] = (short)(iIndex + 1); IndexBufferShort[IndexBufferIndex++] = (short)(iIndex + 3); IndexBufferShort[IndexBufferIndex++] = (short)(iIndex + 2); } }