Code/EmittersShapes/ConchplexDonutEmitter.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConchplexEmitters;
public sealed class ConchplexDonutEmitter : ConchplexEmitter
{
/// <summary>
/// 1 = spawns on edge, 0 = spawns in centre
/// </summary>
[Property, Group("Spawn Bias"), Order(0)]
[Range(0f, 1f), Step(0.01f)]
public float DistanceBias { get; set; } = 0.5f;
/// <summary>
/// Direction + intensity to bias particles to spawn towards
/// </summary>
[Property, Group("Spawn Bias"), Order(0)]
[Range(-1, 1)]
public Vector3 PositionBias { get; set; } = 0f;
/// <summary>
/// Bias partcles inwards or outwards on a each axis
/// </summary>
[Property, Group("Spawn Bias"), Order(0)]
[Range(-1, 1)]
public Vector3 AxisBias { get; set; } = 0f;
/// <summary>
/// Particles only spawn on the axes marked
/// </summary>
[Property, Group("Spawn Bias"), Order(0)]
public AxisClampFlags AxisClamp { get; set; }
[Property]
public float MainRadius { get; set; } = 20f;
[Property]
public float RingRadius { get; set; } = 7.5f;
[Property]
[Range(0f, 1f), Step(0.01f)]
public float Thickness { get; set; } = 0f;
[Property, Group("Initial Velocity")]
public ParticleFloat VelocityFromDonut { get; set; } = 0f;
protected override void DrawGizmos()
{
if (!Gizmo.IsSelected) return;
Gizmo.Draw.Color = Color.White.WithAlpha(0.5f);
Gizmo.GizmoDraw draw = Gizmo.Draw;
// draw shape
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius + RingRadius, 16);
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius - RingRadius, 16);
draw.LineCircle(Vector3.Zero.WithZ(RingRadius), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithZ(-RingRadius), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithX(MainRadius), Vector3.Left, RingRadius, 8);
draw.LineCircle(Vector3.Zero.WithX(-MainRadius), Vector3.Left, RingRadius, 8);
draw.LineCircle(Vector3.Zero.WithY(MainRadius), Vector3.Forward, RingRadius, 8);
draw.LineCircle(Vector3.Zero.WithY(-MainRadius), Vector3.Forward, RingRadius, 8);
// draw thickness
if (Thickness > 0f && Thickness < 1f)
{
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius + (RingRadius * Thickness), 16);
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius - (RingRadius * Thickness), 16);
draw.LineCircle(Vector3.Zero.WithZ(RingRadius), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithZ(-RingRadius * Thickness), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithX(MainRadius), Vector3.Left, RingRadius * Thickness, 8);
draw.LineCircle(Vector3.Zero.WithX(-MainRadius), Vector3.Left, RingRadius * Thickness, 8);
draw.LineCircle(Vector3.Zero.WithY(MainRadius), Vector3.Forward, RingRadius * Thickness, 8);
draw.LineCircle(Vector3.Zero.WithY(-MainRadius), Vector3.Forward, RingRadius * Thickness, 8);
}
// draw Distance Bias
if (DistanceBias > 0f && DistanceBias < 1f)
{
Gizmo.Draw.Color = Color.Red.WithAlpha(0.4f);
var biasRadius = MathX.Lerp(RingRadius * Thickness, RingRadius, DistanceBias);
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius + biasRadius, 16);
draw.LineCircle(Vector3.Zero, Vector3.Up, MainRadius - biasRadius, 16);
draw.LineCircle(Vector3.Zero.WithZ(biasRadius), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithZ(-biasRadius), Vector3.Up, MainRadius, 16);
draw.LineCircle(Vector3.Zero.WithX(MainRadius), Vector3.Left, biasRadius, 8);
draw.LineCircle(Vector3.Zero.WithX(-MainRadius), Vector3.Left, biasRadius, 8);
draw.LineCircle(Vector3.Zero.WithY(MainRadius), Vector3.Forward, biasRadius, 8);
draw.LineCircle(Vector3.Zero.WithY(-MainRadius), Vector3.Forward, biasRadius, 8);
}
if (PositionBias.IsNearlyZero()) return;
Gizmo.Draw.Color = Color.Red.WithAlpha(0.8f);
Vector3 biasClamped = Vector3.Clamp(PositionBias, new Vector3(-1f), new Vector3(1f)) * (MainRadius + RingRadius);
draw.Arrow(Vector3.Zero, biasClamped, biasClamped.Length * 0.1f, biasClamped.Length * 0.1f);
}
public override Particle Emit(ParticleEffect target)
{
// radius point.
var mainRadius = Random.Shared.VectorInCircle();
// radius pos bias
mainRadius += new Vector2(PositionBias.x, PositionBias.y);
mainRadius = mainRadius.Normal;
// Random point around the edge of a unit circle
Vector3 random = new Vector3(mainRadius.x, mainRadius.y, 0f);
random *= MainRadius;
// get random point on the ring
var randomRing = Random.Shared.VectorInCircle();
var biasedRing = randomRing;
biasedRing.y += PositionBias.z;
var ringPosBias = new Vector2(PositionBias.x, PositionBias.y).Length;
biasedRing.x += ringPosBias;
var outterRing = biasedRing.Normal * RingRadius;
var innerRing = outterRing * Thickness;
var betweenRandomRing = Vector2.Lerp(innerRing, outterRing, randomRing.Length);
var ringAdjustment = Vector2.Zero;
// apply distance bias
if (DistanceBias <= 0.5f)
{
float scaledBias = DistanceBias / 0.5f;
ringAdjustment = Vector2.Lerp(innerRing, betweenRandomRing, scaledBias);
}
else
{
float scaledBias = (DistanceBias - 0.5f) / 0.5f;
ringAdjustment = Vector2.Lerp(betweenRandomRing, outterRing, scaledBias);
}
var randomXY = new Vector2(random.x, random.y);
random.x += randomXY.Normal.x * ringAdjustment.x;
random.y += randomXY.Normal.y * ringAdjustment.x;
random.z += ringAdjustment.y;
// AXIS BIAS
random = ApplyAxisBias(random, AxisBias);
// TODO - we should clamp position and velocity before calculating world position/rotation position/velocity
var donutVelocityDir = new Vector3(randomXY.Normal.x * ringAdjustment.x, randomXY.Normal.y * ringAdjustment.x, ringAdjustment.y).Normal;
var clampedParticlePoint = ProcessClampPosition(random, donutVelocityDir, AxisClamp);
var finalPos = (clampedParticlePoint.Position * LocalScale * LocalRotation) + WorldPosition;
var finalParticleVel = clampedParticlePoint.Velocity * LocalRotation;
Particle particle = target.Emit(finalPos, base.Delta);
particle.Velocity += finalParticleVel * VelocityFromDonut.Evaluate(Delta,Random.Shared.Float());
return particle;
}
}