Utility that sets renderer attributes for particles. ParticleAttributesSetter evaluates various typed particle parameters (floats, vectors, colors, textures, combos) for a given Particle and writes them into a RenderAttributes instance; AnimatedAttributes records which attributes vary over a particle's lifetime and builds a compact list of entries for per-frame updates.
using System;
using System.Collections.Generic;
namespace Sandbox;
/**
* Simple class designed to set render attributes accounting for particule timings
*/
public class ParticleAttributesSetter
{
private RenderAttributes _renderAttributes;
private Particle _particle;
public Dictionary<String, ParticleGradient> Colors { get; set; }
public Dictionary<String, ParticleFloat> Floats { get; set; }
public Dictionary<String, ParticleVector2> Floats2 { get; set; }
public Dictionary<String, ParticleVector3> Floats3 { get; set; }
public Dictionary<String, Vector4> Floats4 { get; set; }
public Dictionary<String, Texture> Textures { get; set; }
public Dictionary<String, int> DynamicCombos { get; set; }
/// <summary>
/// The renderer's attributes that change over a particle's life, shared by all of its particles.
/// </summary>
public AnimatedAttributes Animated { get; set; }
public ParticleAttributesSetter(RenderAttributes renderAttributes, Particle particle)
{
_renderAttributes = renderAttributes;
_particle = particle;
}
/// <summary>
/// Sets every attribute. Call once when the particle spawns.
/// </summary>
public void SetAttributes()
{
foreach ( var dynamicCombo in DynamicCombos )
_renderAttributes.SetCombo( dynamicCombo.Key, dynamicCombo.Value );
foreach ( var pair in Floats )
_renderAttributes.Set( pair.Key, EvaluateFloat( pair.Value ) );
foreach ( var pair in Colors )
_renderAttributes.Set( pair.Key, EvaluateColor( pair.Value ) );
foreach ( var pair in Floats2 )
_renderAttributes.Set( pair.Key, EvaluateFloat2( pair.Value ) );
foreach ( var pair in Floats3 )
_renderAttributes.Set( pair.Key, EvaluateFloat3( pair.Value ) );
foreach ( var float4 in Floats4 )
_renderAttributes.Set( float4.Key, float4.Value );
foreach ( var texture in Textures )
_renderAttributes.Set( texture.Key, texture.Value );
}
/// <summary>
/// Sets only the attributes whose value changes over the particle's life. The scene object keeps the
/// others from <see cref="SetAttributes"/>.
/// </summary>
public void SetAnimatedAttributes()
{
if ( Animated is null ) return;
var entries = Animated.Entries;
for ( int i = 0; i < entries.Count; i++ )
{
var a = entries[i];
switch ( a.Kind )
{
case AnimatedAttributes.Kind.Float: _renderAttributes.Set( a.Token, EvaluateFloat( a.Float ) ); break;
case AnimatedAttributes.Kind.Float2: _renderAttributes.Set( a.Token, EvaluateFloat2( a.Float2 ) ); break;
case AnimatedAttributes.Kind.Float3: _renderAttributes.Set( a.Token, EvaluateFloat3( a.Float3 ) ); break;
case AnimatedAttributes.Kind.Color: _renderAttributes.Set( a.Token, EvaluateColor( a.Color ) ); break;
}
}
}
// One call site per type: the per-particle random seed depends on the calling line, so spawn and
// per-frame evaluation have to go through the same one to agree.
private float EvaluateFloat( in ParticleFloat value ) => value.Evaluate( _particle, 6211 );
private Vector2 EvaluateFloat2( ParticleVector2 value ) => value.Evaluate( _particle, 6211 );
private Vector3 EvaluateFloat3( in ParticleVector3 value ) => value.Evaluate( _particle, 6211 );
private Color EvaluateColor( in ParticleGradient value ) => value.Evaluate( _particle, 6211 );
}
/// <summary>
/// The attributes of a renderer that change over a particle's life, with their names already converted to
/// tokens. Rebuilt by the renderer on the main thread before each particle step (so inspector edits apply
/// straight away), then only read by the particles during the parallel step.
/// </summary>
public sealed class AnimatedAttributes
{
public enum Kind : byte { Float, Float2, Float3, Color }
public struct Entry
{
public Kind Kind;
public StringToken Token;
public ParticleFloat Float;
public ParticleVector2 Float2;
public ParticleVector3 Float3;
public ParticleGradient Color;
}
public List<Entry> Entries { get; } = new();
public void Rebuild( Dictionary<String, ParticleFloat> floats, Dictionary<String, ParticleVector2> floats2,
Dictionary<String, ParticleVector3> floats3, Dictionary<String, ParticleGradient> colors )
{
Entries.Clear();
if ( floats is not null )
foreach ( var pair in floats )
if ( Varies( pair.Value ) ) Entries.Add( new Entry { Kind = Kind.Float, Token = pair.Key, Float = pair.Value } );
if ( colors is not null )
foreach ( var pair in colors )
if ( Varies( pair.Value ) ) Entries.Add( new Entry { Kind = Kind.Color, Token = pair.Key, Color = pair.Value } );
if ( floats2 is not null )
foreach ( var pair in floats2 )
if ( Varies( pair.Value.X ) || Varies( pair.Value.Y ) ) Entries.Add( new Entry { Kind = Kind.Float2, Token = pair.Key, Float2 = pair.Value } );
if ( floats3 is not null )
foreach ( var pair in floats3 )
if ( Varies( pair.Value.X ) || Varies( pair.Value.Y ) || Varies( pair.Value.Z ) ) Entries.Add( new Entry { Kind = Kind.Float3, Token = pair.Key, Float3 = pair.Value } );
}
// Constant values, and values picked once per particle from its seed, don't change after spawn
private static bool Varies( in ParticleFloat value ) =>
value.Type != ParticleFloat.ValueType.Constant && value.Evaluation != ParticleFloat.EvaluationType.Seed;
private static bool Varies( in ParticleGradient value ) =>
value.Type != ParticleGradient.ValueType.Constant && value.Evaluation != ParticleGradient.EvaluationType.Particle;
}