using System.Runtime.CompilerServices;
namespace Sandbox;
[Expose]
public partial class Particle : IDynamicFloatContext
{
[ActionGraphInclude]
public Vector3 Position;
[ActionGraphInclude]
public Vector3 Size;
[ActionGraphInclude]
public Vector3 Velocity;
public Color Color;
public Color OverlayColor;
public float Alpha;
public float BornTime;
public float Age;
public float Radius;
public Angles Angles;
public int Sequence;
public Vector3 SequenceTime;
public int Frame;
int RandomSeed;
internal bool hasUpdated;
[System.Obsolete]
public float Random01;
[System.Obsolete]
public float Random02;
[System.Obsolete]
public float Random03;
[System.Obsolete]
public float Random04;
[System.Obsolete]
public float Random05;
[System.Obsolete]
public float Random06;
[System.Obsolete]
public float Random07;
[ActionGraphInclude]
public Vector3 HitPos;
[ActionGraphInclude]
public Vector3 HitNormal;
public float HitTime;
public float LastHitTime;
public Vector3 StartPosition;
///
/// A range from 0 to 1 descriving how long this particle has been alive
///
public float LifeDelta;
///
/// The time that this particle is scheduled to die
///
public float DeathTime;
public float TimeScale;
///
/// A GameObject that is following us. Might be emitting other particles or something.
///
internal GameObject Follower;
public float LifeTimeRemaining => DeathTime - BornTime;
float IDynamicFloatContext.LifetimeDelta => LifeDelta;
int IDynamicFloatContext.RandomSeed => RandomSeed;
private Dictionary _data;
public static Queue Pool = new( 512 );
public static Particle Create()
{
if ( !Pool.TryDequeue( out Particle p ) )
{
p = new Particle();
}
p.RandomSeed = Random.Shared.Int( 0, 10000 );
p.BornTime = Time.Now;
p.Age = 0;
p.Angles = Angles.Zero;
p.Frame = 0;
p.Velocity = 0;
p.Color = Color.White;
p.OverlayColor = Color.White.WithAlpha( 0 );
p.Alpha = 1;
p.Sequence = 0;
p.SequenceTime = 0;
p.Size = 5;
p.HitTime = -1000;
p.LastHitTime = -1000;
p.TimeScale = 1;
p._data?.Clear();
p._controllers?.Clear();
p.hasUpdated = false;
return p;
}
///
/// Get an arbituary data value
///
public T Get( string key )
{
if ( _data is null ) return default;
if ( !_data.TryGetValue( key, out var val ) )
return default;
return (T)val;
}
///
/// Set an arbituary data value
///
public void Set( string key, T tvalue )
{
_data ??= new Dictionary( 4, StringComparer.OrdinalIgnoreCase );
_data[key] = tvalue;
}
public void ApplyDamping( in float amount )
{
Velocity = Velocity.WithFriction( amount, 100.0f );
}
public bool MoveWithCollision( in float bounce, in float friction, in float bumpiness, in float push, in bool die, in float dt, float radius, in SceneTrace trace )
{
const float surfaceOffset = 0.1f;
// We previously hit something.
// Keep the surface normal out of our velocity
// Periodically check whether it's still there.
if ( HitTime > 0 )
{
// if time passed, or we moved too far, see if it's still there
bool recheck = HitTime < Time.Now - 0.1f || HitPos.Distance( Position ) > 16;
if ( recheck )
{
var checkTrace = trace.Ray( Position, Position + HitNormal * surfaceOffset * -2.0f )
.Radius( radius * Radius )
.Run();
if ( checkTrace.Hit )
{
HitPos = checkTrace.HitPosition;
HitNormal = checkTrace.Normal;
HitTime = Time.Now;
}
else
{
HitTime = 0;
HitPos = 0;
HitNormal = 0;
}
}
if ( HitTime > 0 )
{
LastHitTime = Time.Now;
// Keep removing the ground velocity
Velocity = Velocity.SubtractDirection( HitNormal );
}
}
if ( LastHitTime > Time.Now - 0.03f )
{
ApplyDamping( friction * dt * 5.0f );
}
var targetPosition = Position + Velocity * dt;
var tr = trace.Ray( Position, targetPosition )
.Radius( radius * Radius )
.Run();
if ( !tr.Hit )
{
Position = targetPosition;
return false;
}
//
// If we want to die on collision then set its age to max
//
if ( die )
{
Age = float.MaxValue;
}
//
// If we have push, then push the physics object we hit
//
if ( push != 0 )
{
tr.Body.ApplyForceAt( tr.HitPosition, Velocity * tr.Body.Mass * push );
}
HitPos = tr.HitPosition;
HitNormal = tr.Normal;
HitTime = Time.Now;
var velocity = Velocity;
var speed = Velocity.Length;
var surfaceNormal = tr.Normal;
// make the hit normal bumpy if we have bumpiness
if ( speed > 10f && bumpiness > 0 )
{
surfaceNormal += Vector3.Random * bumpiness * 0.5f;
}
var surfaceVelocityNormal = velocity.SubtractDirection( surfaceNormal, 1 + bounce ).Normal;
targetPosition = tr.EndPosition;// + tr.Normal * surfaceOffset;
Velocity = surfaceVelocityNormal * speed;
if ( bounce > 0 && Velocity.Dot( tr.Normal ) > 5.0f )
{
HitTime = 0;
}
Position = targetPosition;
return true;
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public float Rand( int seed = 0, [CallerLineNumber] int line = 0 )
{
int i = RandomSeed + (line * 20) + seed;
return Game.Random.FloatDeterministic( i );
}
}