using System; using UnityEngine.MaterialGraph; using System.Collections.Generic; using System.Linq; using UnityEditor.Experimental.UIElements.GraphView; using UnityEditor.Graphing.Util; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Graphing; using Debug = System.Diagnostics.Debug; namespace UnityEditor.MaterialGraph.Drawing { [Serializable] public class MaterialGraphPresenter : GraphViewPresenter { GraphTypeMapper typeMapper { get; set; } PreviewSystem m_PreviewSystem; public IGraph graph { get; private set; } [SerializeField] IMaterialGraphEditWindow m_Container; protected MaterialGraphPresenter() { typeMapper = new GraphTypeMapper(typeof(MaterialNodePresenter)); typeMapper[typeof(AbstractMaterialNode)] = typeof(MaterialNodePresenter); typeMapper[typeof(ColorNode)] = typeof(ColorNodePresenter); typeMapper[typeof(GradientNode)] = typeof(GradientNodePresenter); // typeMapper[typeof(ScatterNode)] = typeof(ScatterNodePresenter); //typeMapper[typeof(TextureNode)] = typeof(TextureNodePresenter); //typeMapper[typeof(SamplerAssetNode)] = typeof(SamplerAssetNodePresenter); //typeMapper[typeof(TextureSamplerNode)] = typeof(TextureSamplerNodePresenter); // typeMapper[typeof(Texture2DNode)] = typeof(TextureAssetNodePresenter); // typeMapper[typeof(TextureLODNode)] = typeof(TextureLODNodePresenter); typeMapper[typeof(SamplerStateNode)] = typeof(SamplerStateNodePresenter); // typeMapper[typeof(CubemapNode)] = typeof(CubeNodePresenter); // typeMapper[typeof(ToggleNode)] = typeof(ToggleNodePresenter); typeMapper[typeof(UVNode)] = typeof(UVNodePresenter); typeMapper[typeof(Vector1Node)] = typeof(Vector1NodePresenter); typeMapper[typeof(Vector2Node)] = typeof(Vector2NodePresenter); typeMapper[typeof(Vector3Node)] = typeof(Vector3NodePresenter); typeMapper[typeof(Vector4Node)] = typeof(Vector4NodePresenter); typeMapper[typeof(PropertyNode)] = typeof(PropertyNodePresenter); /* typeMapper[typeof(ScaleOffsetNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter typeMapper[typeof(RadialShearNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter typeMapper[typeof(SphereWarpNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter typeMapper[typeof(SphericalIndentationNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter typeMapper[typeof(AACheckerboardNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter typeMapper[typeof(AACheckerboard3dNode)] = typeof(AnyNodePresenter); // anything derived from AnyNode should use the AnyNodePresenter*/ typeMapper[typeof(SubGraphNode)] = typeof(SubgraphNodePresenter); // typeMapper[typeof(RemapMasterNode)] = typeof(RemapMasterNodePresenter); // typeMapper[typeof(MasterRemapInputNode)] = typeof(RemapInputNodePresenter); typeMapper[typeof(AbstractSubGraphIONode)] = typeof(SubgraphIONodePresenter); // typeMapper[typeof(AbstractSurfaceMasterNode)] = typeof(SurfaceMasterNodePresenter); typeMapper[typeof(LevelsNode)] = typeof(LevelsNodePresenter); typeMapper[typeof(ConstantsNode)] = typeof(ConstantsNodePresenter); //typeMapper[typeof(SwizzleNode)] = typeof(SwizzleNodePresenter); typeMapper[typeof(BlendModeNode)] = typeof(BlendModeNodePresenter); // typeMapper[typeof(AddManyNode)] = typeof(AddManyNodePresenter); typeMapper[typeof(IfNode)] = typeof(IfNodePresenter); //typeMapper[typeof(CustomCodeNode)] = typeof(CustomCodePresenter); typeMapper[typeof(Matrix2Node)] = typeof(Matrix2NodePresenter); typeMapper[typeof(Matrix3Node)] = typeof(Matrix3NodePresenter); typeMapper[typeof(Matrix4Node)] = typeof(Matrix4NodePresenter); typeMapper[typeof(MatrixCommonNode)] = typeof(MatrixCommonNodePresenter); typeMapper[typeof(TransformNode)] = typeof(TransformNodePresenter); // typeMapper[typeof(ConvolutionFilterNode)] = typeof(ConvolutionFilterNodePresenter); } public override List GetCompatibleAnchors(NodeAnchorPresenter startAnchor, NodeAdapter nodeAdapter) { var compatibleAnchors = new List(); var startAnchorPresenter = startAnchor as GraphAnchorPresenter; if (startAnchorPresenter == null) return compatibleAnchors; var startSlot = startAnchorPresenter.slot as MaterialSlot; if (startSlot == null) return compatibleAnchors; var goingBackwards = startSlot.isOutputSlot; var startStage = startSlot.shaderStage; if (startStage == ShaderStage.Dynamic) startStage = NodeUtils.FindEffectiveShaderStage(startSlot.owner, startSlot.isOutputSlot); foreach (var candidateAnchorPresenter in allChildren.OfType()) { if (!candidateAnchorPresenter.IsConnectable()) continue; if (candidateAnchorPresenter.orientation != startAnchor.orientation) continue; if (candidateAnchorPresenter.direction == startAnchor.direction) continue; if (nodeAdapter.GetAdapter(candidateAnchorPresenter.source, startAnchor.source) == null) continue; var candidateSlot = candidateAnchorPresenter.slot as MaterialSlot; if (candidateSlot == null) continue; if (candidateSlot.owner == startSlot.owner) continue; if (!startSlot.IsCompatibleWithInputSlotType(candidateSlot.valueType)) continue; if (startStage != ShaderStage.Dynamic) { var candidateStage = candidateSlot.shaderStage; if (candidateStage == ShaderStage.Dynamic) candidateStage = NodeUtils.FindEffectiveShaderStage(candidateSlot.owner, !startSlot.isOutputSlot); if (candidateStage != ShaderStage.Dynamic && candidateStage != startStage) continue; } compatibleAnchors.Add(candidateAnchorPresenter); } return compatibleAnchors; } void OnNodeChanged(INode inNode, ModificationScope scope) { var dependentNodes = new List(); NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, inNode); foreach (var node in dependentNodes) { var theElements = m_Elements.OfType().ToList(); var found = theElements.Where(x => x.node.guid == node.guid).ToList(); foreach (var drawableNodeData in found) drawableNodeData.OnModified(scope); } if (scope == ModificationScope.Topological) UpdateData(); } void UpdateData() { return; var deletedElementPresenters = new List(); // Find all nodes currently being drawn which are no longer in the graph (i.e. deleted) foreach (var presenter in m_Elements) { var nodePresenter = presenter as MaterialNodePresenter; if (nodePresenter != null && !graph.ContainsNodeGuid(nodePresenter.node.guid)) { nodePresenter.Dispose(); deletedElementPresenters.Add(nodePresenter); } } // Find all edges currently being drawn which are no longer in the graph (i.e. deleted) var deletedEdgePresenters = m_Elements.OfType() .Where(ed => !graph.edges.Contains(ed.edge)); foreach (var edgePresenter in deletedEdgePresenters) { // Make sure to disconnect the node, otherwise new connections won't be allowed for the used slots edgePresenter.output.Disconnect(edgePresenter); edgePresenter.input.Disconnect(edgePresenter); var toNodeGuid = edgePresenter.edge.inputSlot.nodeGuid; var toNode = graph.GetNodeFromGuid(toNodeGuid); deletedElementPresenters.Add(edgePresenter); } // Remove all nodes and edges marked for deletion foreach (var elementPresenter in deletedElementPresenters) { m_Elements.Remove(elementPresenter); } var addedNodePresenters = new List(); // Find all new nodes and mark for addition foreach (var node in graph.GetNodes()) { // Check whether node already exists if (m_Elements.OfType().Any(e => e.node == node)) continue; var nodePresenter = (MaterialNodePresenter)typeMapper.Create(node); node.onModified += OnNodeChanged; nodePresenter.Initialize(node, m_PreviewSystem); addedNodePresenters.Add(nodePresenter); } // Create edge data for nodes marked for addition var edgePresenters = new List(); foreach (var addedNodePresenter in addedNodePresenters) { var addedNode = addedNodePresenter.node; foreach (var slot in addedNode.GetOutputSlots()) { var sourceAnchors = addedNodePresenter.outputAnchors.OfType(); var sourceAnchor = sourceAnchors.FirstOrDefault(x => x.slot == slot); var edges = addedNode.owner.GetEdges(new SlotReference(addedNode.guid, slot.id)); foreach (var edge in edges) { var toNode = addedNode.owner.GetNodeFromGuid(edge.inputSlot.nodeGuid); var toSlot = toNode.FindInputSlot(edge.inputSlot.slotId); var targetNode = addedNodePresenters.FirstOrDefault(x => x.node == toNode); var targetAnchors = targetNode.inputAnchors.OfType(); var targetAnchor = targetAnchors.FirstOrDefault(x => x.slot == toSlot); var edgePresenter = CreateInstance(); edgePresenter.Initialize(edge); edgePresenter.output = sourceAnchor; edgePresenter.output.Connect(edgePresenter); edgePresenter.input = targetAnchor; edgePresenter.input.Connect(edgePresenter); edgePresenters.Add(edgePresenter); } } } // Add nodes marked for addition m_Elements.AddRange(addedNodePresenters.OfType()); // Find edges in the graph that are not being drawn and create edge data for them foreach (var edge in graph.edges) { if (m_Elements.OfType().Any(ed => ed.edge == edge)) continue; var sourceNode = graph.GetNodeFromGuid(edge.outputSlot.nodeGuid); var sourceSlot = sourceNode.FindOutputSlot(edge.outputSlot.slotId); var sourceNodePresenter = m_Elements.OfType().FirstOrDefault(x => x.node == sourceNode); var sourceAnchorPresenters = sourceNodePresenter.outputAnchors.OfType(); var sourceAnchorPresenter = sourceAnchorPresenters.FirstOrDefault(x => x.slot == sourceSlot); var targetNode = graph.GetNodeFromGuid(edge.inputSlot.nodeGuid); var targetSlot = targetNode.FindInputSlot(edge.inputSlot.slotId); var targetNodePresenter = m_Elements.OfType().FirstOrDefault(x => x.node == targetNode); var targetAnchors = targetNodePresenter.inputAnchors.OfType(); var targetAnchor = targetAnchors.FirstOrDefault(x => x.slot == targetSlot); var edgePresenter = CreateInstance(); edgePresenter.Initialize(edge); edgePresenter.output = sourceAnchorPresenter; edgePresenter.output.Connect(edgePresenter); edgePresenter.input = targetAnchor; edgePresenter.input.Connect(edgePresenter); edgePresenters.Add(edgePresenter); } m_Elements.AddRange(edgePresenters.OfType()); } public virtual void Initialize(IGraph graph, IMaterialGraphEditWindow container, PreviewSystem previewSystem) { m_PreviewSystem = previewSystem; this.graph = graph; m_Container = container; if (graph == null) return; UpdateData(); foreach (var node in graph.GetNodes()) NodeAdded(new NodeAddedGraphChange(node)); foreach (var edge in graph.edges) EdgeAdded(new EdgeAddedGraphChange(edge)); this.graph.onChange += OnChange; } void OnChange(GraphChange change) { change.Match(NodeAdded, NodeRemoved, EdgeAdded, EdgeRemoved); } void NodeAdded(NodeAddedGraphChange change) { var nodePresenter = (MaterialNodePresenter)typeMapper.Create(change.node); change.node.onModified += OnNodeChanged; nodePresenter.Initialize(change.node, m_PreviewSystem); m_Elements.Add(nodePresenter); } void NodeRemoved(NodeRemovedGraphChange change) { change.node.onModified -= OnNodeChanged; var nodePresenter = m_Elements.OfType().FirstOrDefault(p => p.node.guid == change.node.guid); if (nodePresenter != null) m_Elements.Remove(nodePresenter); } void EdgeAdded(EdgeAddedGraphChange change) { var edge = change.edge; var sourceNode = graph.GetNodeFromGuid(edge.outputSlot.nodeGuid); var sourceSlot = sourceNode.FindOutputSlot(edge.outputSlot.slotId); var sourceNodePresenter = m_Elements.OfType().FirstOrDefault(x => x.node == sourceNode); var sourceAnchorPresenter = sourceNodePresenter.outputAnchors.OfType().FirstOrDefault(x => x.slot == sourceSlot); var targetNode = graph.GetNodeFromGuid(edge.inputSlot.nodeGuid); var targetSlot = targetNode.FindInputSlot(edge.inputSlot.slotId); var targetNodePresenter = m_Elements.OfType().FirstOrDefault(x => x.node == targetNode); var targetAnchor = targetNodePresenter.inputAnchors.OfType().FirstOrDefault(x => x.slot == targetSlot); var edgePresenter = CreateInstance(); edgePresenter.Initialize(edge); edgePresenter.output = sourceAnchorPresenter; edgePresenter.output.Connect(edgePresenter); edgePresenter.input = targetAnchor; edgePresenter.input.Connect(edgePresenter); m_Elements.Add(edgePresenter); } void EdgeRemoved(EdgeRemovedGraphChange change) { var edgePresenter = m_Elements.OfType().FirstOrDefault(p => p.edge == change.edge); if (edgePresenter != null) { edgePresenter.output.Disconnect(edgePresenter); edgePresenter.input.Disconnect(edgePresenter); m_Elements.Remove(edgePresenter); } } public void AddNode(INode node) { graph.AddNode(node); UpdateData(); } public void RemoveElements(IEnumerable nodes, IEnumerable edges) { graph.RemoveElements(nodes.Select(x => x.node), edges.Select(x => x.edge)); graph.ValidateGraph(); UpdateData(); } public void Connect(GraphAnchorPresenter left, GraphAnchorPresenter right) { if (left != null && right != null) { graph.Connect(left.slot.slotReference, right.slot.slotReference); UpdateData(); } } internal static CopyPasteGraph CreateCopyPasteGraph(IEnumerable selection) { var graph = new CopyPasteGraph(); foreach (var presenter in selection) { var nodePresenter = presenter as MaterialNodePresenter; if (nodePresenter != null) { graph.AddNode(nodePresenter.node); foreach (var edge in NodeUtils.GetAllEdges(nodePresenter.node)) graph.AddEdge(edge); } var edgePresenter = presenter as GraphEdgePresenter; if (edgePresenter != null) graph.AddEdge(edgePresenter.edge); } return graph; } internal static CopyPasteGraph DeserializeCopyBuffer(string copyBuffer) { try { return JsonUtility.FromJson(copyBuffer); } catch { // ignored. just means copy buffer was not a graph :( return null; } } void InsertCopyPasteGraph(CopyPasteGraph copyGraph) { if (copyGraph == null || graph == null) return; var addedNodes = new List(); var nodeGuidMap = new Dictionary(); foreach (var node in copyGraph.GetNodes()) { var oldGuid = node.guid; var newGuid = node.RewriteGuid(); nodeGuidMap[oldGuid] = newGuid; var drawState = node.drawState; var position = drawState.position; position.x += 30; position.y += 30; drawState.position = position; node.drawState = drawState; graph.AddNode(node); addedNodes.Add(node); } // only connect edges within pasted elements, discard // external edges. var addedEdges = new List(); foreach (var edge in copyGraph.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; Guid remappedOutputNodeGuid; Guid remappedInputNodeGuid; if (nodeGuidMap.TryGetValue(outputSlot.nodeGuid, out remappedOutputNodeGuid) && nodeGuidMap.TryGetValue(inputSlot.nodeGuid, out remappedInputNodeGuid)) { var outputSlotRef = new SlotReference(remappedOutputNodeGuid, outputSlot.slotId); var inputSlotRef = new SlotReference(remappedInputNodeGuid, inputSlot.slotId); addedEdges.Add(graph.Connect(outputSlotRef, inputSlotRef)); } } graph.ValidateGraph(); UpdateData(); //TODO: Fix this //drawingData.selection = addedNodes.Select(n => n.guid); } public bool canCopy { get { return elements.Any(e => e.selected); } } public void Copy() { var graph = CreateCopyPasteGraph(elements.Where(e => e.selected)); EditorGUIUtility.systemCopyBuffer = JsonUtility.ToJson(graph, true); } public bool canCut { get { return canCopy; } } public void Cut() { Copy(); RemoveElements(elements.OfType().Where(e => e.selected), elements.OfType().Where(e => e.selected)); } public bool canPaste { get { return DeserializeCopyBuffer(EditorGUIUtility.systemCopyBuffer) != null; } } public void Paste() { var pastedGraph = DeserializeCopyBuffer(EditorGUIUtility.systemCopyBuffer); InsertCopyPasteGraph(pastedGraph); } public bool canDuplicate { get { return canCopy; } } public void Duplicate() { var graph = DeserializeCopyBuffer(JsonUtility.ToJson(CreateCopyPasteGraph(elements.Where(e => e.selected)), true)); InsertCopyPasteGraph(graph); } public bool canDelete { get { return canCopy; } } public void Delete() { RemoveElements(elements.OfType().Where(e => e.selected), elements.OfType().Where(e => e.selected)); } public override void AddElement(EdgePresenter edge) { Connect(edge.output as GraphAnchorPresenter, edge.input as GraphAnchorPresenter); } public delegate void OnSelectionChanged(IEnumerable presenters); public OnSelectionChanged onSelectionChanged; public void UpdateSelection(IEnumerable presenters) { if (graph == null) return; if (onSelectionChanged != null) onSelectionChanged(presenters.Select(x => x.node)); } public override void AddElement(GraphElementPresenter element) { throw new ArgumentException("Not supported on Serializable Graph, data comes from data store"); } public override void RemoveElement(GraphElementPresenter element) { throw new ArgumentException("Not supported on Serializable Graph, data comes from data store"); } } }