Controllers/VortexForce.Cs

A particle controller intended to apply a vortex force to particles, pulling them into circular motion around an axis and optional local-space center. It computes the radial plane, finds a tangent direction, evaluates strength and responsiveness via ParticleFloat, and adjusts particle velocity each step.

Rough CodeNative Interop
using Sandbox;

public class VortexForce() : ParticleController
{
    [Property]
    public Vector3 VortexCenterOffset { get; set; }

    [Property]
    public Vector3 Axis { get; set; } = Vector3.Up;

    /// <summary>
    /// When true the Axis and center offset follow the controller's rotation/position,
    /// so the vortex tilts and moves with the emitter. When false they are world-space.
    /// </summary>
    [Property]
    public bool LocalSpace { get; set; } = false;

    [Property]
    public ParticleFloat Strength { get; set; } = 100f;

    [Property]
    public ParticleFloat Responsiveness { get; set; } = 1f;

    protected override void OnParticleStep(Particle particle, float delta)
    {
        var vortexCenter = LocalSpace
            ? WorldTransform.PointToWorld(VortexCenterOffset)
            : WorldPosition + VortexCenterOffset;
        var axis = LocalSpace ? WorldTransform.NormalToWorld(Axis) : Axis;

        var r = particle.Position - vortexCenter;

        var rPlane = r - Vector3.Dot(r, axis) / Vector3.Dot(axis, axis) * axis;

        if (rPlane.LengthSquared < 0.0001f)
            return;

        var tangent = Vector3.Cross(axis, rPlane.Normal);

        var strength = Strength.Evaluate(delta, 6555f);
        var responsiveness = Responsiveness.Evaluate(delta, 6555f);

        var currentTangentialSpeed = Vector3.Dot(particle.Velocity, tangent);
        var targetDelta = (strength - currentTangentialSpeed) * responsiveness;
        particle.Velocity += tangent * targetDelta * delta;
    }
}