/* * ParticleRenderer.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 Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using tAC_Engine.Graphics.Cameras; using TACParticleEngine.Emitters; namespace TACParticleEngine.Particles { /// /// This class handles the rendering process for particle effects using GPU /// public class ParticleRenderer { #region Fields // Size of the particle for when reserving space in vertex buffer private readonly int SIZE_IN_BYTES; // Effect used for displaying particles private Effect mParticleEffect; // A vertex buffer holding our particles. This contains the same data as // the particles array, but copied across to where the GPU can access it. private DynamicVertexBuffer mVertexBuffer, mTempVertexBuffer; // Vertex declaration describes the format of our ParticleVertex structure. private VertexDeclaration mVertexDeclaration; // Reference to the current game private Game mGame; #endregion #region Creation /// /// Constructor /// /// The reference to the current game /// The current content manager used for loading particle XNA effect file /// The emitter which this will draw for public ParticleRenderer(Game game, ContentManager Content, Emitter emitter) { mGame = game; mParticleEffect = Content.Load("ParticleEffect"); SIZE_IN_BYTES = System.Runtime.InteropServices.Marshal.SizeOf(typeof(VertexParticle)); mVertexDeclaration = new VertexDeclaration(mGame.GraphicsDevice, VertexParticle.VertexElements); mVertexBuffer = new DynamicVertexBuffer(mGame.GraphicsDevice, SIZE_IN_BYTES * emitter.mParticles.Length, BufferUsage.WriteOnly | BufferUsage.Points); mTempVertexBuffer = new DynamicVertexBuffer(mGame.GraphicsDevice, SIZE_IN_BYTES * emitter.mParticles.Length, BufferUsage.WriteOnly | BufferUsage.Points); } #endregion #region Render /// /// Sets up rendering of all particles for the given emitter /// /// The emitter whose particles will be rendered /// The current camera in use for rendering public void Render(Emitter emitter, Camera activeCamera) { if (emitter.ParticlesDone) { return; } // Set render state information switch (emitter.mBlendEffect) { case SpriteBlendMode.Additive: mGame.GraphicsDevice.RenderState.DestinationBlend = Blend.One; break; case SpriteBlendMode.AlphaBlend: mGame.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha; break; } // If there are any particles waiting in the newly added queue, // we'd better upload them to the GPU ready for drawing. if (emitter.mParticlesChanged) { mGame.GraphicsDevice.Vertices[0].SetSource(mTempVertexBuffer, 0, SIZE_IN_BYTES); mVertexBuffer.SetData(0, emitter.mVertexParticles, 0, emitter.mVertexParticles.Length, SIZE_IN_BYTES, SetDataOptions.NoOverwrite); emitter.mParticlesChanged = false; } // Draw particles DrawParticles(emitter, activeCamera); } /// /// Draws all of the particles in the given emitter /// /// The emitter whose particles will be rendered /// The current camera in use for rendering private void DrawParticles(Emitter emitter, Camera activeCamera) { // Restore the vertex buffer contents if the graphics device was lost. if (mVertexBuffer.IsContentLost) { mVertexBuffer.SetData(emitter.mVertexParticles); } mParticleEffect.Parameters["View"].SetValue(activeCamera.View); mParticleEffect.Parameters["Projection"].SetValue(activeCamera.Projection); Texture2D texture = emitter.Texture; if (texture == null) { return; } mParticleEffect.Parameters["Texture"].SetValue(texture); mParticleEffect.CommitChanges(); // Set the particle vertex buffer and vertex declaration. mGame.GraphicsDevice.Vertices[0].SetSource(mVertexBuffer, 0, SIZE_IN_BYTES); mGame.GraphicsDevice.VertexDeclaration = mVertexDeclaration; // Activate the particle effect. mParticleEffect.Begin(); #if MULTIPLE_PASSES foreach (EffectPass pass in mParticleEffect.CurrentTechnique.Passes) { #else EffectPass pass = mParticleEffect.CurrentTechnique.Passes[0]; #endif pass.Begin(); if (emitter.mFirstActiveIndex < emitter.mFirstInactiveIndex) { mGame.GraphicsDevice.DrawPrimitives(PrimitiveType.PointList, emitter.mFirstActiveIndex, emitter.mFirstInactiveIndex - emitter.mFirstActiveIndex); } else { // If the active particle range wraps past the end of the queue // back to the start, we must split them over two draw calls. mGame.GraphicsDevice.DrawPrimitives(PrimitiveType.PointList, emitter.mFirstActiveIndex, emitter.mVertexParticles.Length - emitter.mFirstActiveIndex); mGame.GraphicsDevice.DrawPrimitives(PrimitiveType.PointList, 0, emitter.mFirstInactiveIndex); } pass.End(); #if MULTIPLE_PASSES } #endif mParticleEffect.End(); } #endregion #region Management /// /// Cleans up all the graphical components used that need management /// public void CleanUp() { mVertexBuffer.Dispose(); mVertexDeclaration.Dispose(); mTempVertexBuffer.Dispose(); } #endregion } }