VortexModifier.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. using System;
  2. using Microsoft.Xna.Framework;
  3. using MonoGame.Extended.Particles.Data;
  4. namespace MonoGame.Extended.Particles.Modifiers;
  5. /// <summary>
  6. /// A modifier that creates vortex effects by applying rotated gravitational forces to particles.
  7. /// </summary>
  8. /// <remarks>
  9. /// <para>
  10. /// The <see cref="VortexModifier"/> generates spiral motion by rotating gravitational attraction vectors
  11. /// around a central point. Unlike pure gravitational attraction, this creates swirling, orbital, and
  12. /// spiral-out effects depending on the rotation angle applied to the force vectors.
  13. /// </para>
  14. /// <para>
  15. /// Forces are strongest at the <see cref="InnerRadius"/> and weakest at the <see cref="OuterRadius"/>,
  16. /// following a linear inverse relationship with distance.
  17. /// </para>
  18. /// </remarks>
  19. public unsafe class VortexModifier : Modifier
  20. {
  21. private float _rotationAngle;
  22. private float _cosAngle;
  23. private float _sinAngle;
  24. /// <summary>
  25. /// Gets or sets the position of the vortex center relative to particle emission points.
  26. /// </summary>
  27. public Vector2 Position { get; set; } = Vector2.Zero;
  28. /// <summary>
  29. /// Gets or sets the force strength applied to particles at the outer radius.
  30. /// </summary>
  31. /// <value>The acceleration in units per second squared applied at the <see cref="OuterRadius"/>.</value>
  32. /// <remarks>
  33. /// This value represents the acceleration magnitude applied to particles at the vortex edge.
  34. /// Particles closer to the center experience proportionally stronger forces. The scaling
  35. /// follows the formula: <c>actualForce = Strength × (OuterRadius / particleDistance)</c>.
  36. /// </remarks>
  37. public float Strength { get; set; }
  38. /// <summary>
  39. /// Gets or sets the maximum distance from the vortex center where forces are applied.
  40. /// </summary>
  41. /// <remarks>
  42. /// Particles beyond this radius are unaffected by the vortex. This distance also serves
  43. /// as the reference point for force strength calculations, where particles at this exact
  44. /// distance experience the base <see cref="Strength"/> value.
  45. /// </remarks>
  46. public float OuterRadius { get; set; }
  47. /// <summary>
  48. /// Gets or sets the minimum distance from the vortex center where forces are applied.
  49. /// </summary>
  50. /// <remarks>
  51. /// Creates a dead zone around the vortex center where particles are unaffected.
  52. /// Prevents extreme force magnitudes and simulation instability when particles
  53. /// get very close to the center point.
  54. /// </remarks>
  55. public float InnerRadius { get; set; }
  56. /// <summary>
  57. /// Gets or sets the maximum velocity magnitude that particles can reach under vortex influence.
  58. /// </summary>
  59. /// <value>The speed limit in units per second.</value>
  60. /// <remarks>
  61. /// Particle velocities are clamped to this magnitude after vortex forces are applied.
  62. /// Prevents runaway acceleration and maintains visual stability when particles
  63. /// accumulate high velocities through repeated vortex acceleration.
  64. /// </remarks>
  65. public float MaxVelocity { get; set; }
  66. /// <summary>
  67. /// Gets or sets the rotation angle, in radians, applied to gravitational force vectors.
  68. /// </summary>
  69. /// <remarks>
  70. /// This angle determines the motion pattern created by the vortex
  71. /// <list type="table">
  72. /// <listheader><term>Angle</term><description>description</description></listheader>
  73. /// <item><term>0°</term><description>Pure gravitational attraction (particles pulled straight inward)</description></item>
  74. /// <item><term>Small angles (5-20°)</term><description>Inward spirals and temporary orbital motion</description></item>
  75. /// <item><term>Medium angles (30-60°)</term><description>Wide deflection arcs around the vortex</description></item>
  76. /// <item><term>Large angles (90°+)</term><description>Particles to deflect around the vortex perimeter without entering</description></item>
  77. /// </list>
  78. /// Positive values create counterclockwise rotation, where as negative values create clockwise rotation.
  79. /// </remarks>
  80. public float RotationAngle
  81. {
  82. get => _rotationAngle;
  83. set
  84. {
  85. if (_rotationAngle == value)
  86. {
  87. return;
  88. }
  89. _rotationAngle = value;
  90. // Precalculate cos and sin angle so we're not doing it each
  91. // modifier loop.
  92. _cosAngle = MathF.Cos(_rotationAngle);
  93. _sinAngle = MathF.Sin(_rotationAngle);
  94. }
  95. }
  96. /// <summary>
  97. /// Initializes a new instance of the <see cref="VortexModifier"/> class.
  98. /// </summary>
  99. public VortexModifier()
  100. {
  101. RotationAngle = 0.0f;
  102. }
  103. /// <inheritdoc />
  104. protected internal override unsafe void Update(float elapsedSeconds, ParticleIterator iterator, int particleCount)
  105. {
  106. if (!Enabled) return;
  107. for (int i = 0; i < particleCount && iterator.HasNext; i++)
  108. {
  109. Particle* particle = iterator.Next();
  110. float vortexX = particle->TriggeredPos[0] + Position.X;
  111. float vortexY = particle->TriggeredPos[1] + Position.Y;
  112. float dx = particle->Position[0] - vortexX;
  113. float dy = particle->Position[1] - vortexY;
  114. float distance = MathF.Sqrt(dx * dx + dy * dy);
  115. if (distance < InnerRadius || distance > OuterRadius)
  116. {
  117. continue;
  118. }
  119. float gravityX = -dx / distance;
  120. float gravityY = -dy / distance;
  121. float rotatedX = gravityX * _cosAngle - gravityY * _sinAngle;
  122. float rotatedY = gravityX * _sinAngle + gravityY * _cosAngle;
  123. // Strength is the force applied at the outer edge
  124. // Closer particles get proportionally stronger force
  125. float distanceRatio = OuterRadius / distance;
  126. float forceStrength = Strength * distanceRatio;
  127. particle->Velocity[0] += rotatedX * forceStrength * elapsedSeconds;
  128. particle->Velocity[1] += rotatedY * forceStrength * elapsedSeconds;
  129. // Clamp total velocity to max speed
  130. float velocityMagnitude = MathF.Sqrt(particle->Velocity[0] * particle->Velocity[0] +
  131. particle->Velocity[1] * particle->Velocity[1]);
  132. if (velocityMagnitude > MaxVelocity)
  133. {
  134. float scale = MaxVelocity / velocityMagnitude;
  135. particle->Velocity[0] *= scale;
  136. particle->Velocity[1] *= scale;
  137. }
  138. }
  139. }
  140. }