/*
* 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
}
}