using System.Collections.Generic; using System.Linq; using UnityEditor.Graphs; using UnityEngine; namespace UnityEditor.MaterialGraph { public class MaterialSubGraph : BaseMaterialGraph, IGeneratesVertexToFragmentBlock, IGeneratesFunction, IGeneratesVertexShaderBlock, IGenerateProperties { [SerializeField] private SubGraphInputsNode m_InputsNode; public SubGraphNode processingOwner { get; set; } [SerializeField] private SubGraphOutputsNode m_OutputsNode; public SubGraphInputsNode inputsNode { get { return m_InputsNode; } } public SubGraphOutputsNode outputsNode { get { return m_OutputsNode; } } protected override void RecacheActiveNodes() { } public new void OnEnable() { base.OnEnable(); if (m_InputsNode == null) { m_InputsNode = CreateInstance(); m_InputsNode.hideFlags = HideFlags.HideInHierarchy; m_InputsNode.Init(); AddMasterNodeNoAddToAsset(m_InputsNode); } if (m_OutputsNode == null) { m_OutputsNode = CreateInstance(); m_OutputsNode.hideFlags = HideFlags.HideInHierarchy; m_OutputsNode.Init(); AddMasterNodeNoAddToAsset(m_OutputsNode); } } public void CreateSubAssets() { AssetDatabase.AddObjectToAsset(m_InputsNode, this); AssetDatabase.AddObjectToAsset(m_OutputsNode, this); } // Return if the given input slot is wired all the way to an output slot // and if that output slot is connected on the SubGraphNode public bool InputInternallyWired(string slotName, SubGraphNode subGraphNode) { var outputSlot = inputsNode.slots.FirstOrDefault(x => x.name == slotName); if (outputSlot == null || outputSlot.edges.Count == 0) return false; var usedInputSlots = new List(); foreach (var edge in outputSlot.edges) { if (edge.toSlot.node == outputsNode) usedInputSlots.Add(edge.toSlot); FindValidInputsToNodeFromNode(outputsNode, edge.toSlot.node, usedInputSlots); } //var inputWiredToSlots = new List (); foreach (var foundUsedInputSlot in usedInputSlots) { Slot slot = foundUsedInputSlot; var onExternalNodeSlot = subGraphNode.outputSlots.FirstOrDefault(x => x.name == slot.name); if (onExternalNodeSlot != null && onExternalNodeSlot.edges.Count > 0) return true; //inputWiredToSlots.Add (onExternalNodeSlot); } return false; //return inputWiredToSlots; } private static void FindValidInputsToNodeFromNode(Node toNode, Node currentNode, ICollection foundUsedInputSlots) { if (currentNode == null || toNode == null) { Debug.LogError("Recursing to find valid inputs on NULL node"); return; } foreach (var outputSlot in currentNode.outputSlots) { foreach (var edge in outputSlot.edges) { if (edge.toSlot.node == toNode && !foundUsedInputSlots.Contains(edge.toSlot)) { var validInputSlotsAtTarget = (edge.toSlot.node as BaseMaterialNode).GetValidInputSlots(); if (validInputSlotsAtTarget.Contains(edge.toSlot)) foundUsedInputSlots.Add(edge.toSlot); } else FindValidInputsToNodeFromNode(toNode, edge.toSlot.node, foundUsedInputSlots); } } } public string GetInputVariableNameForSlotByName(string slotName, BaseMaterialNode usageNode, GenerationMode generationMode) { var outputSlot = inputsNode.slots.FirstOrDefault(x => x.name == slotName); if (outputSlot == null) return "Could Not Find Slot"; return inputsNode.GetOutputVariableNameForSlot(outputSlot, generationMode); } public bool OutputInternallyWired(string slotName) { var inputSlot = outputsNode.slots.FirstOrDefault(x => x.name == slotName); if (inputSlot == null) return false; return inputSlot.edges.Count > 0; } public string GetOutputVariableNameForSlotByName(string slotName, BaseMaterialNode usageNode, GenerationMode generationMode) { var inputSlot = outputsNode.slots.FirstOrDefault(x => x.name == slotName); if (inputSlot == null || inputSlot.edges.Count == 0) return "Slot Error"; var bmn = inputSlot.edges[0].fromSlot.node as BaseMaterialNode; return bmn == null ? "Slot Error" : bmn.GetOutputVariableNameForSlot(inputSlot.edges[0].fromSlot, generationMode); } private IEnumerable GetCollectedNodes() { return outputsNode.CollectChildNodesByExecutionOrder(); } public void GenerateNodeCode(ShaderGenerator visitor, SubGraphNode generatingFor) { // First find which outputs are connected externally var externallyConnected = new List(); foreach (var slot in outputsNode.inputSlots) { var externalSlot = generatingFor.outputSlots.FirstOrDefault(x => x.name == slot.name); if (externalSlot == null) continue; if (externalSlot.edges.Count > 0) externallyConnected.Add(slot); } // then collect the valid nodes var collectedNodes = new List(); foreach (var s in externallyConnected) outputsNode.CollectChildNodesByExecutionOrder(collectedNodes, s, false); // then generate code for the connected nodes foreach (var n in collectedNodes.OfType()) n.GenerateNodeCode(visitor, GenerationMode.SurfaceShader); } public void GenerateVertexToFragmentBlock(ShaderGenerator visitor, GenerationMode generationMode) { foreach (var n in GetCollectedNodes().OfType()) n.GenerateVertexToFragmentBlock(visitor, generationMode); } public void GenerateNodeFunction(ShaderGenerator visitor, GenerationMode generationMode) { foreach (var n in GetCollectedNodes().OfType()) n.GenerateNodeFunction(visitor, generationMode); } public void GenerateVertexShaderBlock(ShaderGenerator visitor, GenerationMode generationMode) { foreach (var n in GetCollectedNodes().OfType()) n.GenerateVertexShaderBlock(visitor, generationMode); } public void GeneratePropertyBlock(PropertyGenerator visitor, GenerationMode generationMode) { foreach (var n in GetCollectedNodes().OfType()) n.GeneratePropertyBlock(visitor, generationMode); } public void GeneratePropertyUsages(ShaderGenerator visitor, GenerationMode generationMode) { foreach (var n in GetCollectedNodes().OfType()) n.GeneratePropertyUsages(visitor, generationMode); } // Returns a list of preview properties that need to be set public IEnumerable GetPreviewProperties() { var properties = new List(); foreach (var tnode in nodes.Where(x => x is TextureNode).Cast()) { properties.Add(tnode.GetPreviewProperty()); } foreach (var subNode in nodes.Where(x => x is SubGraphNode).Cast()) { if (subNode.subGraphAsset != null) properties.AddRange(subNode.subGraphAsset.GetPreviewProperties()); } return properties.Distinct(); } } }