/* * Emitter.cs * Authors: Brian Murphy * Copyright (c) 2007-2008 Cornell University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using TACParticleEngine.Particles; using tAC_Engine.Graphics.Cameras; using Random=Util.Random; using System.ComponentModel; namespace TACParticleEngine.Emitters { /// /// Class that handles the spawning, updating, and rendering of all the particles associated with it /// public abstract class Emitter : ICloneable, IDisposable { #region Fields // Used for drawing particles private ParticleRenderer mGPURenderer; // Loads in textures used for displaying particles private ContentManager mContent; /// /// The texture that is associated with every particle created by this emitter /// protected Texture2D mTexture; /// /// Array of particles that are reused to save on memory and processing /// Stores lifetime and parameter range infomation /// protected internal Particle[] mParticles; /// /// Array of vertex particles that are reused to save on memory and processing /// Stores drawing infomation /// protected internal VertexParticle[] mVertexParticles; /// /// Whether or not the emitter is currently active /// *Note* active implies that the emitter will continue to spawn particles while it is alive /// protected Boolean mIsActive; /// /// Whether or not a particle has been recently added. /// internal bool mParticlesChanged; /// /// Whether or not all particles should move in a random direction rather than a uniform one /// protected bool mRandomDir; /// /// Stores the current index of particles so as to always draw the most recently created particle /// last to avoid inconsistancies in the particle effect /// internal int mFirstActiveIndex = 0; /// /// Stores the index of the first unused particle so as to not clog the GPU with unused particles /// internal int mFirstInactiveIndex = 1; /// /// The current lifetime of the emitter in seconds /// protected float mCurrentEmitLife = 0; #endregion #region Form Fields /// /// Blend mode used for drawing of particles in this emitter. Usually either Additive or AlphaBlend /// protected internal SpriteBlendMode mBlendEffect; /// /// The path name for the texture to render particles with /// protected string mTextureName; /// /// The amount of particles that are created every second /// protected float mParticlesPerSec; /// /// The length of time in which a particle lives in seconds /// protected float mParticleLifetime; /// /// The maximum amount of particles that can be stored in this emitter /// *Note* this size should not be smaller than mParticlesPerSec * mParticleLifetime /// protected int mParticleAmount; /// /// The maximum amount of time the emitter lives for in seconds /// protected float mEmitLifetime; /// /// Values for the starting and ending speed magnitude ranges /// protected float mMinStartSpeed, mMaxStartSpeed, mMinEndSpeed, mMaxEndSpeed; /// /// Values used for starting and ending size ranges in two-dimensional form /// protected Vector2 mMinStartSize, mMaxStartSize, mMinEndSize, mMaxEndSize; /// /// Values used for starting and ending color ranges in RGBA form /// protected Vector4 mMinStartColor, mMaxStartColor, mMinEndColor, mMaxEndColor; #endregion #region Properties /// /// Sets the content manager for loading in textures /// public ContentManager Content { set { mContent = value; } } /// /// Gets the texture used for drawing all particles contained within this emitter /// public Texture2D Texture { get { return mTexture; } } /// /// Sets the starting color of the emitter in RGBA format /// public Vector4 StartColor { set { mMinStartColor = value; mMaxStartColor = value; } } /// /// Sets the ending color of the emitter in RGBA format /// public Vector4 EndColor { set { mMinEndColor = value; mMaxEndColor = value; } } /// /// Sets the size of the emitter to a constant value /// public Vector2 Size { set { mMinStartSize = value; mMaxStartSize = value; mMinEndSize = value; mMaxEndSize = value; } } /// /// Sets the starting speed of the emitter /// public float StartSpeed { set { mMinStartSpeed = value; mMaxStartSpeed = value; } } /// /// Sets the ending speed of the emitter /// public float EndSpeed { set { mMinEndSpeed = value; mMaxEndSpeed = value; } } /// /// Sets if the direction of an emitter is random /// public bool RandomDirection { set { mRandomDir = value; } } /// /// Gets or sets whether the emitter is active or not. When set to be active, resets lifetime /// public virtual Boolean IsActive { get { return mIsActive; } set { mIsActive = value; if (mIsActive) { mParticlesChanged = true; mCurrentEmitLife = 0; } else { mCurrentEmitLife = mEmitLifetime; } } } /// /// Returns whether or not all particles are dead /// public bool ParticlesDone { get { return !mParticles[mFirstActiveIndex].alive; } } /// /// Returns whether or not the emitter is alive or not based on lifetime /// public bool Alive { get { return (mEmitLifetime < 0) || (mCurrentEmitLife < mEmitLifetime); } } #endregion #region Form Properties /// /// Gets/Sets the position of the emitter /// public abstract Vector3 EmitterPosition { get; set;} /// /// Sets the direction of the emitter /// public abstract Vector3 Direction { get; set; } /// /// Gets/Sets the sprite blend mode to use for rendering /// public SpriteBlendMode BlendEffect { get { return mBlendEffect; } set { mBlendEffect = value; } } /// /// Gets/Sets the path name for the texture to render particles with /// [TypeConverter(typeof(TypeConverterTexture))] public string TextureName { get { return mTextureName; } set { mTextureName = value; LoadContent(); } } /// /// Gets/Sets the amount of particles per second the emitter creates. /// public float ParticlesPerSec { get { return mParticlesPerSec; } set { if (value <= 0f) { mParticlesPerSec = 1f; } else { mParticlesPerSec = value; } } } /// /// Gets/Sets the lifetime of all particles in seconds /// public float ParticleLifetime { get { return mParticleLifetime; } set { if (value <= 0f) { mParticleLifetime = 0.001f; } else { mParticleLifetime = value; } for (int i = mParticleAmount - 1; i >= 0; --i) { mParticles[i].lifetime = mParticleLifetime; } } } /// /// Gets/Sets the maximum amount of particles that can be stored in this emitter /// *Note* this size should not be smaller than mParticlesPerSec * mParticleLifetime /// public int PaticleAmount { get { return mParticleAmount; } set { for (int i = mParticleAmount - 1; i >= 0; --i) { mParticles[i].currentLife = 0; mParticles[i].alive = false; } if (value <= 0) { mParticleAmount = 1; } else { mParticleAmount = value; } mParticleAmount = value; mParticles = new Particle[value]; mVertexParticles = new VertexParticle[value]; mFirstActiveIndex = 0; mFirstInactiveIndex = 1; mParticlesChanged = true; } } /// /// Gets/Sets the maximum amount of time the emitter lives for in seconds /// public float EmitLifetime { get { return mEmitLifetime; } set { mEmitLifetime = value; } } /// /// Gets/Sets the minimum starting speed for the speed magnitude range /// public float MinStartSpeed { get { return mMinStartSpeed; } set { mMinStartSpeed = value; } } /// /// Gets/Sets the maximum starting speed for the speed magnitude range /// public float MaxStartSpeed { get { return mMaxStartSpeed; } set { mMaxStartSpeed = value; } } /// /// Gets/Sets the minimum ending speed for the speed magnitude range /// public float MinEndSpeed { get { return mMinEndSpeed; } set { mMinEndSpeed = value; } } /// /// Gets/Sets the maximum ending speed for the speed magnitude range /// public float MaxEndSpeed { get { return mMaxEndSpeed; } set { mMaxEndSpeed = value; } } /// /// Gets/Sets the minimum starting size in two-dimensional form for the size ranges /// public Vector2 MinStartSize { get { return mMinStartSize; } set { mMinStartSize = value; } } /// /// Gets/Sets the maximum starting size in two-dimensional form for the size ranges /// public Vector2 MaxStartSize { get { return mMaxStartSize; } set { mMaxStartSize = value; } } /// /// Gets/Sets the minimum ending size in two-dimensional form for the size ranges /// public Vector2 MinEndSize { get { return mMinEndSize; } set { mMinEndSize = value; } } /// /// Gets/Sets the maximum ending size in two-dimensional form for the size ranges /// public Vector2 MaxEndSize { get { return mMaxEndSize; } set { mMaxEndSize = value; } } /// /// Gets/Sets the minimum starting color in RGBA form for the color ranges /// public Vector4 MinStartColor { get { return mMinStartColor; } set { mMinStartColor = value; } } /// /// Gets/Sets the maximum starting color in RGBA form for the color ranges /// public Vector4 MaxStartColor { get { return mMaxStartColor; } set { mMaxStartColor = value; } } /// /// Gets/Sets the minimum ending color in RGBA form for the color ranges /// public Vector4 MinEndColor { get { return mMinEndColor; } set { mMinEndColor = value; } } /// /// Gets/Sets the maximum ending color in RGBA form for the color ranges /// public Vector4 MaxEndColor { get { return mMaxEndColor; } set { mMaxEndColor = value; } } #endregion #region Creation /// /// Constructor /// /// SpriteBlendMode to use /// Name of the texture to draw with /// How many particles per second are made /// The max amount of particles available at one time /// How long the emitter stays active for /// How long the patrticle stays active for /// How small a particle can start /// How large a particle can start /// How small a particle can end /// How large a particle can end /// How slow a particle can start /// How fast a particle can start /// How slow a particle can end /// How fast a particle can end /// Minimum values for color to start with /// Maximum values for color to start with /// Minimum values for color to end with /// Maximum values for color to end with public Emitter(SpriteBlendMode blendEffect, string textureName, float particlesPerSec, int particleAmount, float emitLifetime, float particleLifetime, Vector2 minStartSize, Vector2 maxStartSize, Vector2 minEndSize, Vector2 maxEndSize, float minStartSpeed, float maxStartSpeed, float minEndSpeed, float maxEndSpeed, Vector4 minStartColor, Vector4 maxStartColor, Vector4 minEndColor, Vector4 maxEndColor) { mParticles = new Particle[particleAmount]; mVertexParticles = new VertexParticle[particleAmount]; mIsActive = false; mBlendEffect = blendEffect; mTextureName = textureName; mParticlesPerSec = particlesPerSec; mParticleAmount = particleAmount; mEmitLifetime = emitLifetime; mParticleLifetime = particleLifetime; mMinStartSpeed = minStartSpeed; mMaxStartSpeed = maxStartSpeed; mMinEndSpeed = minEndSpeed; mMaxEndSpeed = maxEndSpeed; mMinStartSize = minStartSize; mMaxStartSize = maxStartSize; mMinEndSize = minEndSize; mMaxEndSize = maxEndSize; mMinStartColor = minStartColor; mMaxStartColor = maxStartColor; mMinEndColor = minEndColor; mMaxEndColor = maxEndColor; mRandomDir = false; } /// /// Provides a copy of this emitter so that there are objects that won't reference the same emitter /// /// A copy of the current emitter public abstract object Clone(); #endregion #region Management /// /// Reloads in content that we are using whenever the graphics device loses focus so as to not crash program /// public void LoadContent() { if (mTextureName != null && !mTextureName.Equals("")) { mTexture = mContent.Load(mTextureName); } } /// /// Unloads content so as to not take up too much memory. Primarily used for minigames. /// public void UnloadContent() { mTexture.Dispose(); } /// /// Disposes necessary objects. /// public void Dispose() { if (mGPURenderer != null) { mGPURenderer.CleanUp(); } mParticles = null; mVertexParticles = null; } #endregion #region Update /// /// Updates the emitter and all particles inside of it /// /// Time passed since last call cycle public virtual void Update(float elapsedTime) { mCurrentEmitLife += elapsedTime; if (mFirstActiveIndex > mFirstInactiveIndex) { for (int i = mFirstActiveIndex; i < mParticles.Length; ++i) { if (mParticles[i].alive) { UpdateParticle(i, elapsedTime); } } for (int i = 0; i < mFirstInactiveIndex; ++i) { if (mParticles[i].alive) { UpdateParticle(i, elapsedTime); } } } else { for (int i = mFirstActiveIndex; i <= mFirstInactiveIndex; ++i) { if (mParticles[i].alive) { UpdateParticle(i, elapsedTime); } } } } #endregion #region ParticleManagement /// /// Initializes particles using preset ranges /// /// Starting position for particle /// Starting velocity of particle /// Array index of particle public void InitializeParticle(Vector3 position, Vector3 velocity, int index) { mParticles[index].alive = true; mParticles[index].currentLife = 0; mParticles[index].lifetime = mParticleLifetime; mParticles[index].startSize.X = Random.NextFloat(mMinStartSize.X, mMaxStartSize.X); mParticles[index].startSize.Y = Random.NextFloat(mMinStartSize.Y, mMaxStartSize.Y); mParticles[index].endSize.X = Random.NextFloat(mMinEndSize.X, mMaxEndSize.X); mParticles[index].endSize.Y = Random.NextFloat(mMinEndSize.Y, mMaxEndSize.Y); mVertexParticles[index].size = mParticles[index].startSize; float TempVel = Random.NextFloat(mMinStartSpeed, mMaxStartSpeed); mParticles[index].startVelocity.X = TempVel * velocity.X; mParticles[index].startVelocity.Y = TempVel * velocity.Y; mParticles[index].startVelocity.Z = TempVel * velocity.Z; float TempVel2 = Random.NextFloat(mMinEndSpeed, mMaxEndSpeed); mParticles[index].endVelocity.X = TempVel2 * velocity.X; mParticles[index].endVelocity.Y = TempVel2 * velocity.Y; mParticles[index].endVelocity.Z = TempVel2 * velocity.Z; mVertexParticles[index].position = position; mParticles[index].startColor.X = Random.NextFloat(mMinStartColor.X, mMaxStartColor.X); mParticles[index].startColor.Y = Random.NextFloat(mMinStartColor.Y, mMaxStartColor.Y); mParticles[index].startColor.Z = Random.NextFloat(mMinStartColor.Z, mMaxStartColor.Z); mParticles[index].startColor.W = Random.NextFloat(mMinStartColor.W, mMaxStartColor.W); mParticles[index].endColor.X = Random.NextFloat(mMinEndColor.X, mMaxEndColor.X); mParticles[index].endColor.Y = Random.NextFloat(mMinEndColor.Y, mMaxEndColor.Y); mParticles[index].endColor.Z = Random.NextFloat(mMinEndColor.Z, mMaxEndColor.Z); mParticles[index].endColor.W = Random.NextFloat(mMinEndColor.W, mMaxEndColor.W); mVertexParticles[index].color = mParticles[index].startColor; mFirstInactiveIndex = (index + 1) % mParticleAmount; mParticlesChanged = true; } /// /// Updates an individual particle based on its parameters /// /// The index inside of the particle arrays /// Time that has passed since last update private void UpdateParticle(int i, float elapsedTime) { mParticlesChanged = true; Particle myParticle = mParticles[i]; Vector3 myStartVelocity = myParticle.startVelocity; Vector3 myEndVelocity = myParticle.endVelocity; Vector2 myStartSize = myParticle.startSize; Vector2 myEndSize = myParticle.endSize; Vector4 myStartColor = myParticle.startColor; Vector4 myEndColor = myParticle.endColor; VertexParticle myVertexParticle = mVertexParticles[i]; Vector3 myPosition = myVertexParticle.position; Vector2 mySize = myVertexParticle.size; Vector4 myColor = myVertexParticle.color; float Life = myParticle.currentLife + elapsedTime; float TimeRatio = Life / myParticle.lifetime; float InvTimeRatio = 1f - TimeRatio; myParticle.currentLife = Life; if (InvTimeRatio <= 0f) { myParticle.alive = false; mFirstActiveIndex = (mFirstActiveIndex + 1) % mParticleAmount; myPosition.X += myEndVelocity.X * elapsedTime; myPosition.Y += myEndVelocity.Y * elapsedTime; myPosition.Z += myEndVelocity.Z * elapsedTime; mySize.X = myEndSize.X; mySize.Y = myEndSize.Y; myColor.X = myEndColor.X; myColor.Y = myEndColor.Y; myColor.Z = myEndColor.Z; myColor.W = myEndColor.W; } else { myPosition.X += ((myStartVelocity.X * InvTimeRatio) + (myEndVelocity.X * TimeRatio)) * elapsedTime; myPosition.Y += ((myStartVelocity.Y * InvTimeRatio) + (myEndVelocity.Y * TimeRatio)) * elapsedTime; myPosition.Z += ((myStartVelocity.Z * InvTimeRatio) + (myEndVelocity.Z * TimeRatio)) * elapsedTime; mySize.X = (myStartSize.X * InvTimeRatio) + (myEndSize.X * TimeRatio); mySize.Y = (myStartSize.Y * InvTimeRatio) + (myEndSize.Y * TimeRatio); myColor.X = (myStartColor.X * InvTimeRatio) + (myEndColor.X * TimeRatio); myColor.Y = (myStartColor.Y * InvTimeRatio) + (myEndColor.Y * TimeRatio); myColor.Z = (myStartColor.Z * InvTimeRatio) + (myEndColor.Z * TimeRatio); myColor.W = (myStartColor.W * InvTimeRatio) + (myEndColor.W * TimeRatio); } // Because they are structs, save the data back: myVertexParticle.position = myPosition; myVertexParticle.size = mySize; myVertexParticle.color = myColor; mVertexParticles[i] = myVertexParticle; mParticles[i] = myParticle; } #endregion #region Renderers /// /// Initializes the Particle Renderer /// /// The reference to the current game /// The reference to the current content manager public void InitializeRenderer(Game game, ContentManager Content) { mGPURenderer = new ParticleRenderer(game, Content, this); mContent = Content; } /// /// Renders all particles contained in this emitter via CPU /// /// The sprite batch to draw with /// The current camera in use for rendering /// The viewport in which the particles are drawn within public void RenderCPU(SpriteBatch spriteBatch, Camera activeCamera, Viewport viewport) { ParticleCPURenderer.Render(spriteBatch, this, activeCamera, viewport); } /// /// Rneders all particles contained in this emitter via GPU /// /// The current camera in use for rendering public void RenderGPU(Camera activeCamera) { mGPURenderer.Render(this, activeCamera); } #endregion } }