// Amplify Shader Editor - Visual Shader Editing Tool // Copyright (c) Amplify Creations, Lda using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; namespace AmplifyShaderEditor { [Serializable] public class DynamicTypeNode : ParentNode { protected string m_inputA = string.Empty; protected string m_inputB = string.Empty; protected List m_extensibleInputResults; protected bool m_dynamicOutputType = true; protected bool m_extensibleInputPorts = false; protected bool m_allowMatrixCheck = false; protected bool m_vectorMatrixOps = false; //[SerializeField] private int m_inputCount = 2; //[SerializeField] private int m_lastInputCount = 2; private bool m_previouslyDragging = false; private int m_beforePreviewCount = 0; [UnityEngine.SerializeField] protected WirePortDataType m_mainDataType = WirePortDataType.FLOAT; protected WirePortDataType[] m_dynamicRestrictions = { WirePortDataType.OBJECT, WirePortDataType.FLOAT, WirePortDataType.FLOAT2, WirePortDataType.FLOAT3, WirePortDataType.FLOAT4, WirePortDataType.COLOR, WirePortDataType.INT }; protected override void CommonInit( int uniqueId ) { base.CommonInit( uniqueId ); m_useInternalPortData = true; m_textLabelWidth = 35; AddPorts(); } protected virtual void AddPorts() { AddInputPort( WirePortDataType.FLOAT, false, "A" ); AddInputPort( WirePortDataType.FLOAT, false, "B" ); AddOutputPort( WirePortDataType.FLOAT, Constants.EmptyPortValue ); m_inputPorts[ 0 ].CreatePortRestrictions( m_dynamicRestrictions ); m_inputPorts[ 1 ].CreatePortRestrictions( m_dynamicRestrictions ); } public override void OnConnectedOutputNodeChanges( int inputPortId, int otherNodeId, int otherPortId, string name, WirePortDataType type ) { UpdateConnection( inputPortId ); } public override void OnInputPortConnected( int portId, int otherNodeId, int otherPortId, bool activateNode = true ) { base.OnInputPortConnected( portId, otherNodeId, otherPortId, activateNode ); UpdateConnection( portId ); } public override void OnInputPortDisconnected( int portId ) { base.OnInputPortDisconnected( portId ); UpdateDisconnectedConnection( portId ); UpdateConnection( portId ); UpdateEmptyInputPorts( true ); } void UpdateDisconnectedConnection( int portId ) { if( m_extensibleInputPorts || m_allowMatrixCheck ) { int higher = 0; int groupOneType = 0; int groupTwoType = 0; for( int i = 0; i < m_inputPorts.Count; i++ ) { if( m_inputPorts[ i ].IsConnected ) { int currentPriority = UIUtils.GetPriority( m_inputPorts[ i ].DataType ); if( !m_vectorMatrixOps && currentPriority < 3 ) currentPriority += 7; if( currentPriority > higher && currentPriority > 2 ) { higher = currentPriority; m_mainDataType = m_inputPorts[ i ].DataType; } switch( m_inputPorts[ i ].DataType ) { case WirePortDataType.FLOAT2: case WirePortDataType.FLOAT3: case WirePortDataType.FLOAT4: case WirePortDataType.COLOR: { groupOneType++; groupTwoType++; } break; case WirePortDataType.FLOAT3x3: { groupOneType++; } break; case WirePortDataType.FLOAT4x4: { groupTwoType++; } break; } } } for( int i = 0; i < m_inputPorts.Count; i++ ) { if( !m_inputPorts[ i ].IsConnected ) { m_inputPorts[ i ].ChangeType( m_mainDataType, false ); } } if( groupOneType > 0 && m_mainDataType == WirePortDataType.FLOAT4x4 ) { m_errorMessageTooltip = "Doing this operation with FLOAT4x4 value only works against other FLOAT4x4 or FLOAT values"; m_showErrorMessage = true; } else if( groupTwoType > 0 && m_mainDataType == WirePortDataType.FLOAT3x3 ) { m_errorMessageTooltip = "Doing this operation with FLOAT3x3 value only works against other FLOAT3x3 or FLOAT values"; m_showErrorMessage = true; } else { m_showErrorMessage = false; } if( m_dynamicOutputType ) m_outputPorts[ 0 ].ChangeType( m_mainDataType, false ); } else if( m_inputPorts[ 0 ].DataType != m_inputPorts[ 1 ].DataType ) { int otherPortId = ( portId + 1 ) % 2; if( m_inputPorts[ otherPortId ].IsConnected ) { m_mainDataType = m_inputPorts[ otherPortId ].DataType; m_inputPorts[ portId ].ChangeType( m_mainDataType, false ); if( m_dynamicOutputType ) m_outputPorts[ 0 ].ChangeType( m_mainDataType, false ); } else { if( UIUtils.GetPriority( m_inputPorts[ 0 ].DataType ) > UIUtils.GetPriority( m_inputPorts[ 1 ].DataType ) ) { m_mainDataType = m_inputPorts[ 0 ].DataType; m_inputPorts[ 1 ].ChangeType( m_mainDataType, false ); } else { m_mainDataType = m_inputPorts[ 1 ].DataType; m_inputPorts[ 0 ].ChangeType( m_mainDataType, false ); } if( m_dynamicOutputType ) { if( m_mainDataType != m_outputPorts[ 0 ].DataType ) { m_outputPorts[ 0 ].ChangeType( m_mainDataType, false ); } } } } } void UpdateConnection( int portId ) { if( m_extensibleInputPorts || m_allowMatrixCheck ) { m_inputPorts[ portId ].MatchPortToConnection(); int higher = 0; int groupOneType = 0; int groupTwoType = 0; for( int i = 0; i < m_inputPorts.Count; i++ ) { if( m_inputPorts[ i ].IsConnected ) { int currentPriority = UIUtils.GetPriority( m_inputPorts[ i ].DataType ); if( !m_vectorMatrixOps && currentPriority < 3 ) currentPriority += 7; if( currentPriority > higher ) { higher = currentPriority; m_mainDataType = m_inputPorts[ i ].DataType; } switch( m_inputPorts[ i ].DataType ) { case WirePortDataType.FLOAT2: case WirePortDataType.FLOAT3: case WirePortDataType.FLOAT4: case WirePortDataType.COLOR: { groupOneType++; groupTwoType++; } break; case WirePortDataType.FLOAT3x3: { groupOneType++; } break; case WirePortDataType.FLOAT4x4: { groupTwoType++; } break; } } } for( int i = 0; i < m_inputPorts.Count; i++ ) { if( !m_inputPorts[ i ].IsConnected ) { m_inputPorts[ i ].ChangeType( m_mainDataType, false ); } } if( groupOneType > 0 && m_mainDataType == WirePortDataType.FLOAT4x4 ) { m_errorMessageTooltip = "Doing this operation with FLOAT4x4 value only works against other FLOAT4x4 or FLOAT values"; m_showErrorMessage = true; } else if( groupTwoType > 0 && m_mainDataType == WirePortDataType.FLOAT3x3 ) { m_errorMessageTooltip = "Doing this operation with FLOAT3x3 value only works against other FLOAT3x3 or FLOAT values"; m_showErrorMessage = true; } else { m_showErrorMessage = false; } if( m_dynamicOutputType ) m_outputPorts[ 0 ].ChangeType( m_mainDataType, false ); } else { m_inputPorts[ portId ].MatchPortToConnection(); int otherPortId = ( portId + 1 ) % 2; if( !m_inputPorts[ otherPortId ].IsConnected ) { m_inputPorts[ otherPortId ].ChangeType( m_inputPorts[ portId ].DataType, false ); } if( m_inputPorts[ 0 ].DataType == m_inputPorts[ 1 ].DataType ) { m_mainDataType = m_inputPorts[ 0 ].DataType; if( m_dynamicOutputType ) m_outputPorts[ 0 ].ChangeType( InputPorts[ 0 ].DataType, false ); } else { if( UIUtils.GetPriority( m_inputPorts[ 0 ].DataType ) > UIUtils.GetPriority( m_inputPorts[ 1 ].DataType ) ) { m_mainDataType = m_inputPorts[ 0 ].DataType; } else { m_mainDataType = m_inputPorts[ 1 ].DataType; } if( m_dynamicOutputType ) { if( m_mainDataType != m_outputPorts[ 0 ].DataType ) { m_outputPorts[ 0 ].ChangeType( m_mainDataType, false ); } } } } } public override void OnNodeLayout( DrawInfo drawInfo ) { base.OnNodeLayout( drawInfo ); if( !m_extensibleInputPorts ) return; if( m_previouslyDragging != m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid && m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.NodeId != UniqueId ) { if( m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid ) { m_beforePreviewCount = 2; for( int i = 2; i < m_inputPorts.Count; i++ ) { if( m_inputPorts[ i ].IsConnected ) { m_beforePreviewCount++; } } m_inputCount = m_beforePreviewCount + 1; if( m_inputCount <= 10 ) { if( m_inputCount > m_lastInputCount ) { Undo.RegisterCompleteObjectUndo( m_containerGraph.ParentWindow, Constants.UndoCreateDynamicPortId ); RecordObject( Constants.UndoCreateDynamicPortId ); AddInputPort( m_mainDataType, false, ( ( char ) ( 'A' + m_inputCount - 1 ) ).ToString() ); m_inputPorts[ m_inputCount - 1 ].CreatePortRestrictions( m_dynamicRestrictions ); if( Selected && ContainerGraph.ParentWindow.ParametersWindow.IsMaximized ) Event.current.type = EventType.Used; } m_lastInputCount = m_inputCount; m_sizeIsDirty = true; m_isDirty = true; SetSaveIsDirty(); } } else { bool hasEmpty = CheckValidConnections(); if( hasEmpty ) UpdateEmptyInputPorts( false ); } m_previouslyDragging = m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid; } UpdateEmptyInputPorts( false ); } private bool CheckValidConnections() { if( !m_extensibleInputPorts ) return false; bool hasEmptyConnections = false; bool hasMatrix = m_inputPorts[ 0 ].DataType == WirePortDataType.FLOAT3x3 || m_inputPorts[ 0 ].DataType == WirePortDataType.FLOAT4x4 || m_inputPorts[ 1 ].DataType == WirePortDataType.FLOAT3x3 || m_inputPorts[ 1 ].DataType == WirePortDataType.FLOAT4x4; if( m_inputPorts.Count != m_beforePreviewCount ) { if( hasMatrix ) { bool showError = false; for( int i = m_inputPorts.Count - 1; i >= 2; i-- ) { if( m_inputPorts[ i ].IsConnected ) { showError = true; m_inputPorts[ i ].FullDeleteConnections(); } hasEmptyConnections = true; } if( showError ) m_containerGraph.ParentWindow.ShowMessage( "Matrix operations are only valid for the first two inputs to prevent errors" ); } else { for( int i = m_inputPorts.Count - 1; i >= 2; i-- ) { if( m_inputPorts[ i ].DataType == WirePortDataType.FLOAT3x3 || m_inputPorts[ i ].DataType == WirePortDataType.FLOAT4x4 ) { m_containerGraph.ParentWindow.ShowMessage( "Matrix operations are only valid for the first two inputs to prevent errors" ); m_inputPorts[ i ].FullDeleteConnections(); hasEmptyConnections = true; } else if( !m_inputPorts[ i ].IsConnected ) { hasEmptyConnections = true; } } } } return hasEmptyConnections; } private void UpdateEmptyInputPorts( bool recordUndo ) { if( !m_extensibleInputPorts ) return; if( !m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid ) { if( recordUndo ) { Undo.RegisterCompleteObjectUndo( m_containerGraph.ParentWindow, Constants.UndoDeleteDynamicPortId ); RecordObject( Constants.UndoDeleteDynamicPortId ); } bool hasDeleted = false; m_inputCount = 2; for( int i = m_inputPorts.Count - 1; i >= 2; i-- ) { if( !m_inputPorts[ i ].IsConnected ) { hasDeleted = true; DeleteInputPortByArrayIdx( i ); } else { m_inputCount++; } } if( hasDeleted || m_inputCount != m_lastInputCount ) { for( int i = 2; i < m_inputPorts.Count; i++ ) { m_inputPorts[ i ].Name = ( ( char ) ( 'A' + i ) ).ToString(); } m_beforePreviewCount = m_inputPorts.Count; m_inputCount = m_beforePreviewCount; m_lastInputCount = m_inputCount; m_sizeIsDirty = true; m_isDirty = true; SetSaveIsDirty(); } } m_inputCount = Mathf.Clamp( m_inputCount, 2, 10 ); } public virtual string BuildResults( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar ) { if( !m_extensibleInputPorts ) SetInputData( outputId, ref dataCollector, ignoreLocalvar ); else SetExtensibleInputData( outputId, ref dataCollector, ignoreLocalvar ); return string.Empty; } public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar ) { string result = BuildResults( outputId, ref dataCollector, ignoreLocalvar ); return CreateOutputLocalVariable( 0, result, ref dataCollector ); } protected void SetInputData( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar ) { m_inputA = m_inputPorts[ 0 ].GeneratePortInstructions( ref dataCollector ); if( m_inputPorts[ 0 ].DataType != m_mainDataType ) { m_inputA = UIUtils.CastPortType( ref dataCollector, m_currentPrecisionType, new NodeCastInfo( UniqueId, outputId ), m_inputA, m_inputPorts[ 0 ].DataType, m_mainDataType, m_inputA ); } m_inputB = m_inputPorts[ 1 ].GeneratePortInstructions( ref dataCollector ); if( m_inputPorts[ 1 ].DataType != m_mainDataType ) { m_inputB = UIUtils.CastPortType( ref dataCollector, m_currentPrecisionType, new NodeCastInfo( UniqueId, outputId ), m_inputB, m_inputPorts[ 1 ].DataType, m_mainDataType, m_inputB ); } } protected void SetExtensibleInputData( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar ) { m_extensibleInputResults = new List(); for( int i = 0; i < m_inputPorts.Count; i++ ) { m_extensibleInputResults.Add( m_inputPorts[ i ].GeneratePortInstructions( ref dataCollector ) ); if( m_inputPorts[ i ].DataType != m_mainDataType && m_inputPorts[ i ].DataType != WirePortDataType.FLOAT && m_inputPorts[ i ].DataType != WirePortDataType.INT ) { m_extensibleInputResults[ i ] = UIUtils.CastPortType( ref dataCollector, m_currentPrecisionType, new NodeCastInfo( UniqueId, outputId ), m_extensibleInputResults[ i ], m_inputPorts[ i ].DataType, m_mainDataType, m_extensibleInputResults[ i ] ); } } } void UpdatePorts() { m_lastInputCount = Mathf.Clamp( m_inputCount, 2, 10 ); for( int i = 2; i < m_inputCount; i++ ) { AddInputPort( m_mainDataType, false, ( ( char ) ( 'A' + i ) ).ToString() ); m_inputPorts[ i ].CreatePortRestrictions( m_dynamicRestrictions ); } m_sizeIsDirty = true; SetSaveIsDirty(); } public override void ReadFromString( ref string[] nodeParams ) { base.ReadFromString( ref nodeParams ); if( m_extensibleInputPorts && UIUtils.CurrentShaderVersion() > 10005 ) { m_inputCount = Convert.ToInt32( GetCurrentParam( ref nodeParams ) ); UpdatePorts(); } } public override void WriteToString( ref string nodeInfo, ref string connectionsInfo ) { base.WriteToString( ref nodeInfo, ref connectionsInfo ); if( m_extensibleInputPorts ) IOUtils.AddFieldValueToString( ref nodeInfo, m_inputCount ); } } }