using System; using Microsoft.Xna.Framework; using MonoGame.Extended.Particles.Data; namespace MonoGame.Extended.Particles.Modifiers; /// /// A modifier that creates vortex effects by applying rotated gravitational forces to particles. /// /// /// /// The generates spiral motion by rotating gravitational attraction vectors /// around a central point. Unlike pure gravitational attraction, this creates swirling, orbital, and /// spiral-out effects depending on the rotation angle applied to the force vectors. /// /// /// Forces are strongest at the and weakest at the , /// following a linear inverse relationship with distance. /// /// public unsafe class VortexModifier : Modifier { private float _rotationAngle; private float _cosAngle; private float _sinAngle; /// /// Gets or sets the position of the vortex center relative to particle emission points. /// public Vector2 Position { get; set; } = Vector2.Zero; /// /// Gets or sets the force strength applied to particles at the outer radius. /// /// The acceleration in units per second squared applied at the . /// /// This value represents the acceleration magnitude applied to particles at the vortex edge. /// Particles closer to the center experience proportionally stronger forces. The scaling /// follows the formula: actualForce = Strength × (OuterRadius / particleDistance). /// public float Strength { get; set; } /// /// Gets or sets the maximum distance from the vortex center where forces are applied. /// /// /// Particles beyond this radius are unaffected by the vortex. This distance also serves /// as the reference point for force strength calculations, where particles at this exact /// distance experience the base value. /// public float OuterRadius { get; set; } /// /// Gets or sets the minimum distance from the vortex center where forces are applied. /// /// /// Creates a dead zone around the vortex center where particles are unaffected. /// Prevents extreme force magnitudes and simulation instability when particles /// get very close to the center point. /// public float InnerRadius { get; set; } /// /// Gets or sets the maximum velocity magnitude that particles can reach under vortex influence. /// /// The speed limit in units per second. /// /// Particle velocities are clamped to this magnitude after vortex forces are applied. /// Prevents runaway acceleration and maintains visual stability when particles /// accumulate high velocities through repeated vortex acceleration. /// public float MaxVelocity { get; set; } /// /// Gets or sets the rotation angle, in radians, applied to gravitational force vectors. /// /// /// This angle determines the motion pattern created by the vortex /// /// Angledescription /// Pure gravitational attraction (particles pulled straight inward) /// Small angles (5-20°)Inward spirals and temporary orbital motion /// Medium angles (30-60°)Wide deflection arcs around the vortex /// Large angles (90°+)Particles to deflect around the vortex perimeter without entering /// /// Positive values create counterclockwise rotation, where as negative values create clockwise rotation. /// public float RotationAngle { get => _rotationAngle; set { if (_rotationAngle == value) { return; } _rotationAngle = value; // Precalculate cos and sin angle so we're not doing it each // modifier loop. _cosAngle = MathF.Cos(_rotationAngle); _sinAngle = MathF.Sin(_rotationAngle); } } /// /// Initializes a new instance of the class. /// public VortexModifier() { RotationAngle = 0.0f; } /// protected internal override unsafe void Update(float elapsedSeconds, ParticleIterator iterator, int particleCount) { if (!Enabled) return; for (int i = 0; i < particleCount && iterator.HasNext; i++) { Particle* particle = iterator.Next(); float vortexX = particle->TriggeredPos[0] + Position.X; float vortexY = particle->TriggeredPos[1] + Position.Y; float dx = particle->Position[0] - vortexX; float dy = particle->Position[1] - vortexY; float distance = MathF.Sqrt(dx * dx + dy * dy); if (distance < InnerRadius || distance > OuterRadius) { continue; } float gravityX = -dx / distance; float gravityY = -dy / distance; float rotatedX = gravityX * _cosAngle - gravityY * _sinAngle; float rotatedY = gravityX * _sinAngle + gravityY * _cosAngle; // Strength is the force applied at the outer edge // Closer particles get proportionally stronger force float distanceRatio = OuterRadius / distance; float forceStrength = Strength * distanceRatio; particle->Velocity[0] += rotatedX * forceStrength * elapsedSeconds; particle->Velocity[1] += rotatedY * forceStrength * elapsedSeconds; // Clamp total velocity to max speed float velocityMagnitude = MathF.Sqrt(particle->Velocity[0] * particle->Velocity[0] + particle->Velocity[1] * particle->Velocity[1]); if (velocityMagnitude > MaxVelocity) { float scale = MaxVelocity / velocityMagnitude; particle->Velocity[0] *= scale; particle->Velocity[1] *= scale; } } } }