Editor/ShaderGraphPlus/Nodes Core/SubgraphOutput.cs
using Sandbox.Resources;
using System.Text;
namespace ShaderGraphPlus;
public enum SubgraphOutputPreviewType
{
[Icon( "clear" )]
None,
[Icon( "palette" )]
Albedo,
[Icon( "brightness_5" )]
Emission,
[Icon( "opacity" )]
Opacity,
[Icon( "texture" )]
Normal,
[Icon( "terrain" )]
Roughness,
[Icon( "auto_awesome" )]
Metalness,
[Icon( "tonality" )]
AmbientOcclusion,
[Icon( "arrow_forward" )]
PositionOffset
}
/// <summary>
/// Output of a subgraph.
/// </summary>
[Title( "Subgraph Output" ), Icon( "output" ), SubgraphOnly]
[InternalNode]
public sealed class SubgraphOutput : BaseResult, BaseNodePlus.IInitializeNode, IBlackboardNode, IErroringNode
{
[JsonIgnore, Hide, Browsable( false )]
public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.SubgraphNode;
[JsonIgnore, Hide, Browsable( false )]
public override bool AutoSize => true;
[JsonIgnore, Hide, Browsable( false )]
public override bool CanRemove => true;
//[Hide, Browsable( false )]
//public Guid OutputIdentifier { get; set; }
[Hide, Browsable( false )]
public Guid ParameterIdentifier { get; set; }
[JsonIgnore, Hide, Browsable( false )]
public string OutputName => GetParameter().Name;
[JsonIgnore, Hide, Browsable( false )]
public string OutputDescription => GetParameter().Description;
[JsonIgnore, Hide, Browsable( false )]
public SubgraphPortType PortType => GetParameter().PortType;
[JsonIgnore, Hide, Browsable( false )]
public SubgraphOutputPreviewType Preview => GetParameter().Preview;
[JsonIgnore, Hide, Browsable( false )]
public int PortOrder => GetParameter().PortOrder;
[Hide]
private List<IPlugIn> InternalInputs = new();
[Hide]
public override IEnumerable<IPlugIn> Inputs => InternalInputs;
public SubgraphOutput() : base()
{
}
[JsonIgnore, Hide, Browsable( false )]
int _lastHashCode = 0;
public override void OnFrame()
{
var hashCodeInput = 0;
hashCodeInput = System.HashCode.Combine( ParameterIdentifier, OutputName, OutputDescription, PortType, PortOrder );
if ( hashCodeInput != _lastHashCode )
{
_lastHashCode = hashCodeInput;
InitializeNode();
}
}
private IBlackboardSubgraphOutputParameter GetParameter()
{
if ( Graph is ShaderGraphPlus graph )
{
var parameter = graph.FindParameter( ParameterIdentifier );
if ( parameter is IBlackboardSubgraphOutputParameter subgraphOutputParameter )
{
return subgraphOutputParameter;
}
return new FloatSubgraphOutputParameter();
}
return new FloatSubgraphOutputParameter();
}
public void InitializeNode()
{
CreateInput();
Update();
}
private void CreateInput()
{
var plugs = new List<IPlugIn>();
var parameter = GetParameter();
if ( !parameter.IsValid )
return;
var type = PortType switch
{
SubgraphPortType.Bool => typeof( bool ),
SubgraphPortType.Int => typeof( int ),
SubgraphPortType.Float => typeof( float ),
SubgraphPortType.Vector2 => typeof( Vector2 ),
SubgraphPortType.Vector3 => typeof( Vector3 ),
SubgraphPortType.Vector4 => typeof( Color ),
SubgraphPortType.Color => typeof( Color ),
SubgraphPortType.Float2x2 => typeof( Float2x2 ),
SubgraphPortType.Float3x3 => typeof( Float3x3 ),
SubgraphPortType.Float4x4 => typeof( Float4x4 ),
SubgraphPortType.Gradient => typeof( Gradient ),
SubgraphPortType.SamplerState => typeof( Sampler ),
SubgraphPortType.Texture2DObject => typeof( Texture ),
SubgraphPortType.TextureCubeObject => typeof( Texture ),
_ => throw new Exception( $"Unknown PortType \"{PortType}\"" )
};
var info = new PlugInfo()
{
Id = parameter.Identifier,
Name = parameter.Name,
Type = type,
DisplayInfo = new()
{
Name = parameter.Name,
Fullname = type.FullName,
Description = parameter.Description,
}
};
var plug = new BasePlugIn( this, info, info.Type );
//var oldPlug = InternalInputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Name == info.Name ) as BasePlugIn;
var oldPlug = InternalInputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Id == info.Id ) as BasePlugIn;
if ( oldPlug is not null )
{
oldPlug.Info.Name = info.Name;
oldPlug.Info.Type = type;
oldPlug.Info.DisplayInfo = info.DisplayInfo;
if ( oldPlug.Type != plug.Type )
{
plugs.Add( plug );
}
else
{
plugs.Add( oldPlug );
}
}
else
{
plugs.Add( plug );
}
InternalInputs = plugs;
}
public void AddMaterialOutput( GraphCompiler compiler, StringBuilder sb, SubgraphOutputPreviewType previewType, out List<string> errors )
{
errors = new List<string>();
// Make sure we dont try to preview outputs that we cant.
var parameter = GetParameter();
var graph = Graph as ShaderGraphPlus;
if ( GetParameter().CannotPreviewOutputType )
{
parameter.Preview = SubgraphOutputPreviewType.None;
graph.UpdateParameter( parameter );
return;
}
if ( previewType == SubgraphOutputPreviewType.Albedo )
{
var albedoResult = GetAlbedoResult( compiler );
if ( !albedoResult.IsValid )
return;
sb.AppendLine( $"m.Albedo = {albedoResult.Cast( 3 )};" );
}
if ( previewType == SubgraphOutputPreviewType.Emission )
{
var emissionResult = GetEmissionResult( compiler );
if ( !emissionResult.IsValid )
return;
sb.AppendLine( $"m.Emission = {emissionResult.Cast( 3 )};" );
}
if ( previewType == SubgraphOutputPreviewType.Opacity )
{
var opacityResult = GetOpacityResult( compiler );
if ( !opacityResult.IsValid )
return;
sb.AppendLine( $"m.Opacity = {opacityResult.Cast( 1 )};" );
}
if ( previewType == SubgraphOutputPreviewType.Normal )
{
var normalResult = GetNormalResult( compiler );
if ( !normalResult.IsValid )
return;
sb.AppendLine( $"m.Normal = {normalResult.Cast( 3 )};" );
}
if ( previewType == SubgraphOutputPreviewType.Roughness )
{
var roughnessResult = GetRoughnessResult( compiler );
if ( !roughnessResult.IsValid )
return;
sb.AppendLine( $"m.Roughness = {roughnessResult.Cast( 1 )};" );
}
if ( previewType == SubgraphOutputPreviewType.Metalness )
{
var metalnessResult = GetMetalnessResult( compiler );
if ( !metalnessResult.IsValid )
return;
sb.AppendLine( $"m.Metalness = {metalnessResult.Cast( 1 )};" );
}
if ( previewType == SubgraphOutputPreviewType.AmbientOcclusion )
{
var ambientOcclusionResult = GetAmbientOcclusionResult( compiler );
if ( !ambientOcclusionResult.IsValid )
return;
sb.AppendLine( $"m.AmbientOcclusion = {ambientOcclusionResult.Cast( 1 )};" );
}
}
public NodeInput? GetInputFromPreview( SubgraphOutputPreviewType previewType )
{
// Make sure we dont try to preview outputs that we cant.
var parameter = GetParameter();
var graph = Graph as ShaderGraphPlus;
if ( parameter.CannotPreviewOutputType )
{
parameter.Preview = SubgraphOutputPreviewType.None;
graph.UpdateParameter( parameter );
}
if ( Preview == previewType )
{
var input = Inputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Id == ParameterIdentifier );
if ( input is BasePlugIn plugIn )
{
if ( plugIn.Info.ConnectedPlug is not null )
{
return new NodeInput
{
Identifier = plugIn.Info.ConnectedPlug.Node.Identifier,
Output = plugIn.Info.ConnectedPlug.Identifier
};
}
else
{
SGPLogger.Warning( $"Missing Inputernal Input : {plugIn.DisplayInfo.Name}" );
}
return plugIn.Info.GetInput( plugIn.Node );
}
}
return null;
}
public override NodeInput GetAlbedo()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Albedo );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetEmission()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Emission );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetOpacity()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Opacity );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetNormal()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Normal );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetRoughness()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Roughness );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetMetalness()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.Metalness );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetAmbientOcclusion()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.AmbientOcclusion );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override NodeInput GetPositionOffset()
{
var input = GetInputFromPreview( SubgraphOutputPreviewType.PositionOffset );
if ( input is not null )
{
return input.Value;
}
return default;
}
public override Color GetDefaultAlbedo()
{
return base.GetDefaultAlbedo();
}
public List<string> GetErrors()
{
var errors = new List<string>();
if ( Graph is ShaderGraphPlus shaderGraphPlus && shaderGraphPlus.IsSubgraph )
{
//if ( string.IsNullOrWhiteSpace( OutputName ) )
//{
// errors.Add( $"Subgraph output must have a name!" );
//
// return errors;
//}
foreach ( var node in Graph.Nodes )
{
if ( node == this )
continue;
if ( node is SubgraphOutput otherOutput && otherOutput.OutputName == OutputName )
{
errors.Add( $"Duplicate subgraph output node \"{OutputName}\"" );
break;
}
}
}
return errors;
}
}