Editor/ShaderGraphPlus/ShaderGraphPlus.cs
using Editor;
using ShaderGraphPlus.Nodes;
using static Editor.SceneViewportWidget;
using static ShaderGraphPlus.ShaderGraphPlusGlobals;
using static ShaderGraphPlus.ShaderTemplate;
namespace ShaderGraphPlus;
public enum BlendMode
{
[Icon( "circle" )]
Opaque,
[Icon( "radio_button_unchecked" )]
Masked,
[Icon( "blur_on" )]
Translucent,
/*
[Icon( "tune" )]
Dynamic,
*/
}
public enum ShadingModel
{
/// <summary>
/// Default Valve lighting model
/// </summary>
[Icon( "tungsten" )]
Lit,
/// <summary>
/// No Lighting model
/// </summary>
[Icon( "brightness_3" )]
Unlit,
}
public enum ShaderDomain
{
[Icon( "view_in_ar" )]
Surface,
[Icon( "nights_stay" )]
Sky,
[Icon( "desktop_windows" )]
PostProcess,
}
public enum RenderFace
{
/// <summary>
/// Render only the front faces
/// </summary>
[Icon( "visibility" )]
Front,
/// <summary>
/// Render only the back faces
/// </summary>
[Icon( "visibility_off" )]
Back,
/// <summary>
/// Render both the front and back faces
/// </summary>
[Icon( "visibility_off" )]
Both,
}
public class PreviewSettings
{
/// <summary>
/// Current viewmode of the preview veiwport
/// </summary>
public ViewMode ViewMode { get; set; } = ViewMode.Perspective;
public bool RenderBackfaces { get; set; } = false;
/// <summary>
/// If true we'll render shadows
/// </summary>
public bool EnableShadows { get; set; } = true;
/// <summary>
/// If true we'll show the ground plane
/// </summary>
public bool ShowGround { get; set; } = false;
/// <summary>
/// If true we'll show the shybox
/// </summary>
public bool ShowSkybox { get; set; } = true;
/// <summary>
/// Color of the background when the skybox is not being drawn
/// </summary>
public Color BackgroundColor { get; set; } = Color.Black;
/// <summary>
/// Color tint of the model in the preview
/// </summary>
public Color Tint { get; set; } = Color.White;
}
[AssetType( Name = ShaderGraphPlusGlobals.AssetTypeName, Extension = ShaderGraphPlusGlobals.AssetTypeExtension, Flags = AssetTypeFlags.NoEmbedding ), Icon( "account_tree" )]
public partial class ShaderGraphPlus : IBlackboardNodeGraph
{
[Hide]
public int Version => 11;
[Hide, JsonIgnore]
public IEnumerable<BaseNodePlus> Nodes => _nodes.Values;
[Hide, JsonIgnore]
private readonly Dictionary<string, BaseNodePlus> _nodes = new();
[Hide, JsonIgnore]
IEnumerable<IGraphNode> INodeGraph.Nodes => Nodes;
[Hide, JsonIgnore]
public IEnumerable<BlackboardParameter> Parameters => _parameters.Values;
[Hide, JsonIgnore]
private readonly OrderedDictionary<Guid, BlackboardParameter> _parameters = new();
[Hide, JsonIgnore]
IEnumerable<IBlackboardParameter> IBlackboardNodeGraph.Parameters => Parameters;
[Hide, JsonIgnore]
public IEnumerable<CategoryData> CategoryData => _categoryData.Values;
[Hide, JsonIgnore]
private readonly OrderedDictionary<Guid, CategoryData> _categoryData = new();
/// <summary>
/// Custom key-value storage for this project.
/// </summary>
[Hide]
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
[Hide]
public bool IsSubgraph { get; set; }
[Hide]
public string Path { get; set; }
[Hide]
public string Model { get; set; }
/// <summary>
/// The name of the Node when used in ShaderGraph or when graph is not a subgraph the asset name
/// </summary>
[ShowIf( nameof( IsSubgraph ), true )]
public string Title { get; set; }
public string Description { get; set; }
/// <summary>
/// The category of the Node when browsing the Node Library (optional)
/// </summary>
[ShowIf( nameof( AddToNodeLibrary ), true )]
public string Category { get; set; }
[IconName, ShowIf( nameof( IsSubgraph ), true )]
public string Icon { get; set; }
/// <summary>
/// Whether or not this Node should appear when browsing the Node Library.
/// Otherwise can only be referenced by dragging the Subgraph asset into the graph.
/// </summary>
[ShowIf( nameof( IsSubgraph ), true )]
public bool AddToNodeLibrary { get; set; }
[Hide]
public bool HasTemplate => !string.IsNullOrWhiteSpace( ShaderType ) && ShaderType.EndsWith( ShaderGraphPlusGlobals.ShaderTemplateAssetTypeExtension );
[Hide]
public bool HasNoTemplate => !HasTemplate;
[Hide]
private bool ShowShadingModel => Domain == ShaderDomain.Surface && HasNoTemplate;
[Hide]
private bool ShowRenderFace => Domain == ShaderDomain.Surface && (ShaderTypeInfo.HasSupport( SupportsAllRenderFace ));
[Hide]
private bool ShowBlendMode => Domain == ShaderDomain.Surface;
[Editor( ControlWidgetCustomEditors.ShaderTypeDropdownEditor )]
public string ShaderType { get; set; } = ShaderTemplateSurface.ShaderTypeInfo.Title;
[Hide]
public ShaderDomain Domain => ShaderTypeInfo.IsValid ? ShaderTypeInfo.Domian : ShaderDomain.Surface;
[ShowIf( nameof( ShowBlendMode ), true )]
public BlendMode BlendMode { get; set; }
[ShowIf( nameof( ShowShadingModel ), true )]
public ShadingModel ShadingModel { get; set; }
[ShowIf( nameof( ShowRenderFace ), true )]
public RenderFace RenderFace { get; set; }
[Hide]
public PreviewSettings PreviewSettings { get; set; } = new();
[Hide, JsonIgnore]
public ShaderTypeInfo ShaderTypeInfo { get; set; } = new();
public ShaderGraphPlus()
{
}
/// <summary>
/// Validates settings to what is supported by the current <see cref="ShaderTypeInfo"/>
/// </summary>
public void ValidateShaderTypeInfo()
{
// Auto-correct BlendMode if current is not supported
bool currentBlendModeSupported = BlendMode switch
{
BlendMode.Opaque => ShaderTypeInfo.HasSupport( SupportsOpaqueBlend ),
BlendMode.Masked => ShaderTypeInfo.HasSupport( SupportsMaskedBlend ),
BlendMode.Translucent => ShaderTypeInfo.HasSupport( SupportsTranslucentBlend ),
_ => false
};
if ( !currentBlendModeSupported )
{
if ( !ShaderTypeInfo.HasSupport( SupportsOpaqueBlend ) && !ShaderTypeInfo.HasSupport( SupportsMaskedBlend ) && !ShaderTypeInfo.HasSupport( SupportsTranslucentBlend ) )
{
BlendMode = BlendMode.Opaque; // Fallback to Opaque blend mode.
}
else
{
// Find the first supported blend mode
if ( ShaderTypeInfo.HasSupport( SupportsOpaqueBlend ) ) BlendMode = BlendMode.Opaque;
else if ( ShaderTypeInfo.HasSupport( SupportsMaskedBlend ) ) BlendMode = BlendMode.Masked;
else if ( ShaderTypeInfo.HasSupport( SupportsTranslucentBlend ) ) BlendMode = BlendMode.Translucent;
}
}
// Auto-correct ShadingModel if current is not supported
bool currentShadingModelSupported = ShadingModel switch
{
ShadingModel.Lit => ShaderTypeInfo.HasSupport( SupportsLitShading ),
ShadingModel.Unlit => ShaderTypeInfo.HasSupport( SupportsUnlitShading ),
_ => false
};
if ( !currentShadingModelSupported )
{
// Find the first supported shading model
if ( ShaderTypeInfo.HasSupport( SupportsLitShading ) ) ShadingModel = ShadingModel.Lit;
else if ( ShaderTypeInfo.HasSupport( SupportsUnlitShading ) ) ShadingModel = ShadingModel.Unlit;
}
if ( HasTemplate )
{
if ( !ShaderTypeInfo.HasSupport( SupportsAllRenderFace ) )
{
// Ensure template culling mode
if ( ShaderTypeInfo.HasSupport( SupportsRenderFaceFront ) )
{
RenderFace = RenderFace.Front;
}
else if ( ShaderTypeInfo.HasSupport( SupportsRenderFaceBack ) )
{
RenderFace = RenderFace.Back;
}
else if ( ShaderTypeInfo.HasSupport( SupportsRenderFaceBoth ) )
{
RenderFace = RenderFace.Both;
}
else
{
RenderFace = RenderFace.Front; // Fallback to backface culling.
}
}
}
}
public bool ContainsNode( string id )
{
if ( _nodes.ContainsKey( id ) ) return true;
return false;
}
public bool ContainsParameter( Guid id )
{
if ( _parameters.ContainsKey( id ) ) return true;
return false;
}
public void AddNode( BaseNodePlus node )
{
node.Graph = this;
_nodes.Add( node.Identifier, node );
}
public void AddParameter( IBlackboardParameter parameter )
{
AddParameter( (BlackboardParameter)parameter );
}
public void RemoveNode( BaseNodePlus node )
{
if ( node.Graph != this )
return;
//SGPLog.Info( $"Removing node with id : {node.Identifier}");
_nodes.Remove( node.Identifier );
}
public BaseNodePlus FindNode( string name )
{
_nodes.TryGetValue( name, out var node );
return node;
}
public int GetParameterIndex( BlackboardParameter parameter )
{
var index = _parameters.IndexOf( parameter.Identifier );
if ( index != -1 )
{
return index;
}
return 0;
}
public BlackboardParameter FindParameter( Guid identifier )
{
_parameters.TryGetValue( identifier, out var parameter );
return parameter;
}
public BlackboardParameter FindParameter( string name )
{
var parameter = _parameters.Values.FirstOrDefault( x => x.Name == name );
return parameter;
}
public bool TryFindParameter( Guid identifier, out BlackboardParameter parameter )
{
return _parameters.TryGetValue( identifier, out parameter );
}
public bool TryFindParameter( string name, out BlackboardParameter parameter )
{
parameter = _parameters.Values.FirstOrDefault( x => x.Name == name );
return parameter != null;
}
public T FindParameter<T>( Guid identifier ) where T : BlackboardParameter
{
_parameters.TryGetValue( identifier, out var parameter );
return (T)parameter;
}
public T FindParameter<T>( string name ) where T : BlackboardParameter
{
var parameter = _parameters.Values.OfType<T>().FirstOrDefault( x => x.Name == name );
return parameter;
}
public bool TryFindParameter<T>( Guid identifier, out T parameter ) where T : BlackboardParameter
{
parameter = null;
if ( _parameters.TryGetValue( identifier, out var foundParameter ) )
{
parameter = (T)foundParameter;
return true;
}
return false;
}
public bool TryFindParameter<T>( string name, out T parameter ) where T : BlackboardParameter
{
parameter = (T)_parameters.Values.FirstOrDefault( x => x.Name == name );
return parameter != null;
}
public bool TryFindCategoryData( Guid identifier, out CategoryData categoryData )
{
categoryData = null;
if ( _categoryData.TryGetValue( identifier, out var foundCategoryData ) )
{
categoryData = foundCategoryData;
return true;
}
return false;
}
public bool HasParameterWithName( string name )
{
return _parameters.Any( x => string.Equals( x.Value.Name, name, StringComparison.CurrentCultureIgnoreCase ) );
}
public bool HasCategoryDataWithName( string name )
{
return _categoryData.Any( x => x.Value.Name == name );
}
public void AddParameter( BlackboardParameter parameter, int index = -1 )
{
parameter.Graph = this;
if ( index <= -1 )
{
_parameters.Add( parameter.Identifier, parameter );
}
else
{
_parameters.Insert( index, parameter.Identifier, parameter );
}
}
public void AddCategoryData( CategoryData categoryData )
{
categoryData.Graph = this;
_categoryData.Add( categoryData.Identifier, categoryData );
}
public bool ReOrderParameter( BlackboardParameter parameter, int newIndex )
{
if ( parameter.Graph != this )
return false;
if ( newIndex <= -1 )
{
//throw new IndexOutOfRangeException( $"New Index Invalid '{newIndex}'" );
SGPLogger.Error( $"New Index Invalid '{newIndex}'" );
return false;
}
_parameters.Remove( parameter.Identifier );
if ( newIndex > _parameters.Count )
{
_parameters.Add( parameter.Identifier, parameter );
}
else
{
_parameters.Insert( newIndex, parameter.Identifier, parameter );
}
return true;
}
public void UpdateParameter( IBlackboardParameter parameter )
{
var blackboardParameter = parameter as BlackboardParameter;
if ( blackboardParameter.Graph != this )
return;
_parameters[parameter.Identifier] = blackboardParameter;
}
public void UpdateParameterValue( Guid identifier, object value )
{
if ( !_parameters.ContainsKey( identifier ) )
throw new Exception( $"There is no parameter with the identifier : {identifier}" );
_parameters[identifier].SetValue( value );
}
public void RemoveParameter( BlackboardParameter parameter )
{
if ( parameter.Graph != this )
return;
RemoveParameter( parameter.Identifier );
}
public void RemoveParameter( Guid identifier )
{
_parameters.Remove( identifier );
}
public void RemoveCategoryData( CategoryData categoryData )
{
if ( categoryData.Graph != this )
return;
_categoryData.Remove( categoryData.Identifier );
}
public void UpdateCategoryData( CategoryData categoryData )
{
if ( categoryData.Graph != this )
return;
_categoryData[categoryData.Identifier] = categoryData;
}
internal NamedRerouteDeclarationNode FindNamedRerouteDeclarationNode( string name )
{
var node = Nodes.OfType<NamedRerouteDeclarationNode>().Where( x => x.Name == name ).FirstOrDefault();
if ( node != null )
{
return node;
}
SGPLogger.Error( $"Could not find NamedReroute \"{name}\"" );
return null;
}
public void ClearNodes()
{
_nodes.Clear();
}
public void ClearParameters()
{
_parameters.Clear();
}
public void ClearCategoryData()
{
_categoryData.Clear();
}
string INodeGraph.SerializeNodes( IEnumerable<IGraphNode> nodes )
{
return SerializeNodes( nodes.Cast<BaseNodePlus>() );
}
IEnumerable<IGraphNode> INodeGraph.DeserializeNodes( string serialized )
{
return DeserializeNodes( serialized );
}
string IBlackboardNodeGraph.SerializeParameters( IEnumerable<IBlackboardParameter> parameters )
{
return SerializeParameters( parameters.Cast<BlackboardParameter>() );
}
IEnumerable<IBlackboardParameter> IBlackboardNodeGraph.DeserializeParameters( string serialized )
{
return DeserializeParameters( serialized );
}
void INodeGraph.AddNode( IGraphNode node )
{
AddNode( (BaseNodePlus)node );
}
void INodeGraph.RemoveNode( IGraphNode node )
{
RemoveNode( (BaseNodePlus)node );
}
void IBlackboardNodeGraph.AddParameter( IBlackboardParameter parameter )
{
AddParameter( (BlackboardParameter)parameter );
}
void IBlackboardNodeGraph.RemoveParameter( IBlackboardParameter parameter )
{
RemoveParameter( (BlackboardParameter)parameter );
}
IBlackboardParameter IBlackboardNodeGraph.FindParameter( Guid identifier )
{
return FindParameter( identifier );
}
internal void UpdateCategoryPriority( CategoryData target, int newPriority )
{
var oldPriority = target.Priority;
target.Priority = newPriority;
if ( newPriority > oldPriority ) // Category moved down the list
{
foreach ( var kvp in _categoryData )
{
if ( kvp.Value != target &&
kvp.Value.Priority > oldPriority &&
kvp.Value.Priority <= newPriority )
{
kvp.Value.Priority--;
}
}
}
else if ( newPriority < oldPriority ) // Category moved up the list
{
foreach ( var kvp in _categoryData )
{
if ( kvp.Value != target &&
kvp.Value.Priority >= newPriority &&
kvp.Value.Priority < oldPriority )
{
kvp.Value.Priority++;
}
}
}
}
}
[AssetType( Name = ShaderGraphPlusGlobals.SubgraphAssetTypeName, Extension = ShaderGraphPlusGlobals.SubgraphAssetTypeExtension, Flags = AssetTypeFlags.NoEmbedding )]
public sealed partial class ShaderGraphPlusSubgraph : ShaderGraphPlus
{
}