using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.Graphics;
using MonoGame.Extended.Particles.Data;
namespace MonoGame.Extended.Particles;
///
/// Provides extension methods for to draw particle effects and emitters.
///
public static class SpriteBatchExtensions
{
///
/// Draws a particle effect by rendering all of its active emitters.
///
/// The used for drawing.
/// The to draw.
/// Thrown when is null.
/// Thrown when is disposed.
public static void Draw(this SpriteBatch spriteBatch, ParticleEffect effect)
{
ArgumentNullException.ThrowIfNull(effect);
ObjectDisposedException.ThrowIf(effect.IsDisposed, effect);
for (int i = 0; i < effect.Emitters.Count; i++)
{
UnsafeDraw(spriteBatch, effect.Emitters[i]);
}
}
///
/// Draws a particle emitter by rendering all of its active particles.
///
/// The used for drawing.
/// The to draw.
/// Thrown when is null.
/// Thrown when is disposed.
public static void Draw(this SpriteBatch spriteBatch, ParticleEmitter emitter)
{
ArgumentNullException.ThrowIfNull(emitter);
ObjectDisposedException.ThrowIf(emitter.IsDisposed, emitter);
UnsafeDraw(spriteBatch, emitter);
}
private static unsafe void UnsafeDraw(SpriteBatch spriteBatch, ParticleEmitter emitter)
{
ArgumentNullException.ThrowIfNull(spriteBatch);
// Early exit if no texture region assigned
if (emitter.TextureRegion == null)
{
return;
}
// Early exit if there are no active particles
if (emitter.ActiveParticles == 0)
{
return;
}
// Early exit if the emitter is not visible
if (!emitter.Visible)
{
return;
}
Texture2DRegion region = emitter.TextureRegion;
Texture2D texture = region.Texture;
Rectangle sourceRect = region.Bounds;
Vector2 origin = new Vector2(region.Width, region.Height) * 0.5f;
if (emitter.RenderingOrder == ParticleRenderingOrder.FrontToBack)
{
int count = emitter.ActiveParticles;
Span particlePtrs = count <= 1024 ?
stackalloc IntPtr[count] :
new IntPtr[count];
ParticleIterator iterator = emitter.Buffer.Iterator;
int index = 0;
while (iterator.HasNext)
{
particlePtrs[index++] = (IntPtr)iterator.Next();
}
for (int i = count - 1; i >= 0; i--)
{
RenderParticle(spriteBatch, (Particle*)particlePtrs[i], texture, sourceRect, origin, emitter.Offset);
}
}
else
{
ParticleIterator iterator = emitter.Buffer.Iterator;
while (iterator.HasNext)
{
Particle* particle = iterator.Next();
RenderParticle(spriteBatch, particle, texture, sourceRect, origin, emitter.Offset);
}
}
}
private static unsafe void RenderParticle(SpriteBatch spriteBatch, Particle* particle, Texture2D texture, Rectangle sourceRect, Vector2 origin, Vector2 offset)
{
HslColor hsl = new HslColor(particle->Color[0], particle->Color[1], particle->Color[2]);
Color color = HslColor.ToRgb(hsl);
if (spriteBatch.GraphicsDevice.BlendState == BlendState.AlphaBlend)
{
color *= particle->Opacity;
}
else
{
color.A = (byte)MathHelper.Clamp(particle->Opacity * 255, 0, 255);
}
Vector2 position = new Vector2(particle->Position[0], particle->Position[1]) + offset;
Vector2 scale = new Vector2(particle->Scale[0], particle->Scale[1]);
float rotation = particle->Rotation;
float layerDepth = particle->LayerDepth;
spriteBatch.Draw(
texture,
position,
sourceRect,
color,
rotation,
origin,
scale,
SpriteEffects.None,
layerDepth
);
}
}