Component that applies shader/render attributes to a ModelRenderer's SceneObject. It stores dictionaries of floats, vectors, colors, textures and dynamic combos, reads shader attributes from a material, and stamps them each frame (with optional duration/looping and batching control).
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sandbox;
[Title("ModelShaderAttributes")]
[Category("Shaders")]
[Description("Let's you set the render attributes of the scene object of a ModelRenderer")]
public sealed class ModelShaderAttributes : Component, Component.ExecuteInEditor, IResetable
{
[Property] private ModelRenderer ModelRenderer { get; set; }
[Property, Group( "Floats1" )] public Dictionary<String, ParticleFloat> Floats { get; set; }
[Property, Group("Floats2")] public Dictionary<String, Vector2> Floats2 { get; set; }
[Property, Group("Colors")] public Dictionary<String, Color> Colors {get; set;}
[Property, Group("Textures")] public Dictionary<String, Texture> Textures { get; set; }
[Property, Group("Floats4")] public Dictionary<String, Vector4> Floats4 { get; set; }
[Property, Group("DynamicCombos")] public Dictionary<String, int> DynamicCombos { get; set; }
[Property] public bool Batchable { get; set; } = true;
[Property] public float Duration { get; set; } = 1.0f;
[Property] public bool Loop { get; set; } = false;
public float Delta { get; set; }
private TimeSince _elapsed;
public void ResetTime()
{
_elapsed = 0;
}
protected override void OnStart()
{
Floats = Floats == null ? new Dictionary<string, ParticleFloat>() : Floats;
Floats2 = Floats2 == null ? new Dictionary<string, Vector2>() : Floats2;
Floats4 = Floats4 == null ? new Dictionary<string, Vector4>() : Floats4;
Textures = Textures == null ? new Dictionary<string, Texture>() : Textures;
Colors = Colors == null ? new Dictionary<String, Color>() : Colors;
DynamicCombos = DynamicCombos == null ? new Dictionary<string, int>() : DynamicCombos;
_elapsed = 0;
ApplyAttributes();
}
[Button]
private void ReadAttributes()
{
if ( ModelRenderer.MaterialOverride == null || !FileSystem.Mounted.FileExists( ModelRenderer.MaterialOverride.Shader.ResourcePath )) return;
var parsedFloats = new Dictionary<string, float>();
AttributesParser<float,Color> parser = new AttributesParser<float,Color>(new NativeAttributeTypeSet());
parser.Floats = parsedFloats;
parser.Floats2 = Floats2;
parser.Floats4 = Floats4;
parser.Textures = Textures;
parser.DynamicCombos = DynamicCombos;
parser.Colors = Colors;
parser.ParseAttributes( ModelRenderer.MaterialOverride.Shader.ResourcePath );
foreach (var pair in parsedFloats)
{
if (Floats.ContainsKey(pair.Key))
continue;
Floats.Add(pair.Key, new ParticleFloat { ConstantValue = pair.Value });
}
}
protected override void OnUpdate()
{
// _elapsed is a TimeSince, it already advances with real time on its own.
// Don't manually add Time.Delta or it runs at 2x speed.
ApplyAttributes();
}
private void ApplyAttributes()
{
if ( ModelRenderer == null || !ModelRenderer.SceneObject.IsValid() )
return;
// ModelRenderer.OnEnabled constructs a brand new SceneObject on every enable
// cycle, and a fresh SceneObject defaults to Batchable = true. Setting this
// once in OnStart loses it on the first disable/enable (and races
// ModelRenderer.OnEnabled on the very first spawn).
//
// It has to hold for as long as we're stamping per-object attributes: the
// engine batches by material, and an instanced draw has exactly one
// attribute binding and one shader permutation. Two layers sharing a model
// and material would collapse into one draw and a single layer's Colors /
// DynamicCombos would win for all of them.
ModelRenderer.SceneObject.Batchable = Batchable;
SetColorAttributes();
SetFloatAttributes();
SetFloat2Attributes();
SetTexturesAttributes();
SetFloat4Attributes();
SetDynamicCombos();
}
private void SetDynamicCombos()
{
foreach ( var dynamicCombo in DynamicCombos )
{
ModelRenderer.SceneObject.Attributes.SetCombo( dynamicCombo.Key, dynamicCombo.Value );
}
}
private void SetFloat4Attributes()
{
foreach ( var float4Attribute in Floats4 )
{
ModelRenderer.SceneObject.Attributes.Set( float4Attribute.Key, float4Attribute.Value );
}
}
private void SetTexturesAttributes()
{
foreach ( var textureAttribute in Textures )
{
ModelRenderer.SceneObject.Attributes.Set( textureAttribute.Key, textureAttribute.Value );
}
}
private void SetColorAttributes()
{
foreach ( var colorAttribute in Colors )
{
ModelRenderer.SceneObject.Attributes.Set( colorAttribute.Key, colorAttribute.Value );
}
}
private void SetFloatAttributes()
{
float t = 0f;
if ( Duration > 0f )
{
var raw = (float)_elapsed / Duration;
t = Loop ? raw % 1f : raw.Clamp( 0f, 1f );
}
foreach ( var float1 in Floats )
{
ModelRenderer.SceneObject.Attributes.Set( float1.Key, float1.Value.Evaluate( t, 6211 ) );
}
}
private void SetFloat2Attributes()
{
foreach ( var float2 in Floats2 )
{
ModelRenderer.SceneObject.Attributes.Set( float2.Key, float2.Value );
}
}
}