// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Particles.Data;
namespace MonoGame.Extended.Particles.Modifiers.Containers;
///
/// A modifier that constrains particles within or outside of a circular boundary.
///
///
/// The keeps particles either inside or outside a circular area, reflecting them
/// at the boundary based on a configurable restitution coefficient. The circle is centered at each particle's trigger
/// position (where it was emitted), creating local containment areas.
///
public class CircleContainerModifier : Modifier
{
///
/// Gets or sets the radius of the circular container.
///
public float Radius { get; set; }
///
/// Gets or sets a value that indicates whether particles should be contained inside the circle.
///
///
///
/// -
/// When , particles are kept inside the circle (bouncing inward at the boundary).
///
/// -
/// When , particles are kept outside the circle (bouncing outward at the boundary).
///
///
///
/// The default value is .
///
public bool Inside { get; set; } = true;
///
/// Gets or sets the coefficient of restitution (bounciness) for particle collisions with the boundary.
///
///
///
/// -
/// A value of 1.0 creates a perfectly elastic collision where particles maintain their energy.
///
/// -
/// Values less than 1.0 create inelastic collisions where particles lose energy with each bounce.
///
/// -
/// Values greater than 1.0 create super-elastic collisions where particles gain energy.
///
///
///
/// The default value is 1.0.
///
public float RestitutionCoefficient { get; set; } = 1;
///
/// Updates all particles by constraining them to the circular boundary.
///
///
protected internal override unsafe void Update(float elapsedSeconds, ParticleIterator iterator, int particleCount)
{
if (!Enabled) { return; }
float radiusSq = Radius * Radius;
for (int i = 0; i < particleCount && iterator.HasNext; i++)
{
Particle* particle = iterator.Next();
Vector2 localPos;
localPos.X = particle->Position[0] - particle->TriggeredPos[0];
localPos.Y = particle->Position[1] - particle->TriggeredPos[1];
float distSq = localPos.LengthSquared();
Vector2 normal = Vector2.Normalize(localPos);
if (Inside)
{
if (distSq < radiusSq) { continue; }
SetReflected(distSq, particle, normal);
}
else
{
if (distSq > radiusSq) { continue; }
SetReflected(distSq, particle, -normal);
}
}
}
private unsafe void SetReflected(float distSq, Particle* particle, Vector2 normal)
{
float dist = MathF.Sqrt(distSq);
float d = dist - Radius;
float twoRestDot = 2 * RestitutionCoefficient *
Vector2.Dot(new Vector2(particle->Velocity[0], particle->Velocity[1]), normal);
particle->Velocity[0] -= twoRestDot * normal.X;
particle->Velocity[1] -= twoRestDot * normal.Y;
// exact computation requires sqrt or goniometrics
particle->Position[0] -= normal.X * d;
particle->Position[1] -= normal.Y * d;
}
}