// 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 { /// /// The radius of the circular container. /// public float Radius; /// /// 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 = 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 = 1; /// /// Updates all particles by constraining them to the circular boundary. /// /// public override unsafe void Update(float elapsedSeconds, ParticleIterator iterator) { if (!Enabled) { return; } float radiusSq = Radius * Radius; while (iterator.HasNext) { 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; } }