using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEditor.VFX; using UnityEngine.UIElements; using System.Reflection; using NodeID = System.UInt32; namespace UnityEditor.VFX.UI { class VFXCopy : VFXCopyPasteCommon { VFXContextController[] contexts; int[] contextsIndices; VFXOperatorController[] operators; int[] operatorIndices; VFXParameterNodeController[] parameters; int[] parameterIndices; VFXData[] datas; Dictionary modelIndices = new Dictionary(); static VFXCopy s_Instance; public static object Copy(IEnumerable elements, Rect bounds) { if (s_Instance == null) s_Instance = new VFXCopy(); return s_Instance.CreateCopy(elements, bounds); } public static string SerializeElements(IEnumerable elements, Rect bounds) { if (s_Instance == null) s_Instance = new VFXCopy(); var serializableGraph = s_Instance.CreateCopy(elements, bounds) as SerializableGraph; return JsonUtility.ToJson(serializableGraph); } public static object CopyBlocks(IEnumerable blocks) { if (s_Instance == null) s_Instance = new VFXCopy(); return s_Instance.DoCopyBlocks(blocks); } object CreateCopy(IEnumerable elements, Rect bounds) { IEnumerable contexts = elements.OfType(); IEnumerable nodes = elements.Where(t => t is VFXOperatorController || t is VFXParameterNodeController).Cast(); IEnumerable blocks = elements.OfType(); SerializableGraph serializableGraph = new SerializableGraph(); serializableGraph.controllerCount = elements.Count(); if (contexts.Count() == 0 && nodes.Count() == 0 && blocks.Count() > 0) { var copiedBlocks = new List(blocks); modelIndices.Clear(); serializableGraph.operators = CopyBlocks(copiedBlocks, 0); serializableGraph.blocksOnly = true; } else { //Don't copy VFXBlockSubgraphContext because they can't be pasted anywhere. CopyNodes(serializableGraph, elements, contexts.Where(t => !(t.model is VFXBlockSubgraphContext)), nodes, bounds); } return serializableGraph; } void CopyNodes(SerializableGraph serializableGraph, IEnumerable elements, IEnumerable copiedContexts, IEnumerable nodes, Rect bounds) { Controller[] copiedElements = elements.ToArray(); serializableGraph.bounds = bounds; IEnumerable dataEdgeTargets = nodes.Concat(copiedContexts.Cast()).Concat(copiedContexts.SelectMany(t => t.blockControllers).Cast()).ToArray(); // consider only edges contained in the selection IEnumerable dataEdges = elements.OfType().Where(t => dataEdgeTargets.Contains((t.input as VFXDataAnchorController).sourceNode as VFXNodeController) && dataEdgeTargets.Contains((t.output as VFXDataAnchorController).sourceNode as VFXNodeController)).ToArray(); IEnumerable flowEdges = elements.OfType().Where(t => copiedContexts.Contains((t.input as VFXFlowAnchorController).context) && copiedContexts.Contains((t.output as VFXFlowAnchorController).context) ).ToArray(); modelIndices.Clear(); contexts = copiedContexts.ToArray(); operators = nodes.OfType().ToArray(); parameters = nodes.OfType().ToArray(); contextsIndices = contexts.Select(t => Array.IndexOf(copiedElements, t)).ToArray(); operatorIndices = operators.Select(t => Array.IndexOf(copiedElements, t)).ToArray(); parameterIndices = parameters.Select(t => Array.IndexOf(copiedElements, t)).ToArray(); datas = contexts.Select(t => t.model.GetData()).Where(t => t != null).ToArray(); CopyOperatorsAndContexts(ref serializableGraph); CopyParameters(ref serializableGraph); CopyGroupNodesAndStickyNotes(ref serializableGraph, elements); CopyDatas(ref serializableGraph); CopyDataEdge(ref serializableGraph, dataEdges); CopyFlowEdges(ref serializableGraph, flowEdges); } void CopyGroupNodesAndStickyNotes(ref SerializableGraph serializableGraph, IEnumerable elements) { VFXGroupNodeController[] groupNodes = elements.OfType().ToArray(); VFXStickyNoteController[] stickyNotes = elements.OfType().ToArray(); if (groupNodes.Length > 0 || stickyNotes.Length > 0) { var stickyNodeIndexToCopiedIndex = new Dictionary(); if (stickyNotes.Length > 0) { serializableGraph.stickyNotes = new VFXUI.StickyNoteInfo[stickyNotes.Length]; for (int i = 0; i < stickyNotes.Length; ++i) { VFXStickyNoteController stickyNote = stickyNotes[i]; stickyNodeIndexToCopiedIndex[stickyNote.index] = i; VFXUI.StickyNoteInfo info = stickyNote.model.stickyNoteInfos[stickyNote.index]; serializableGraph.stickyNotes[i] = new VFXUI.StickyNoteInfo(info); } } if (groupNodes.Length > 0) { serializableGraph.groupNodes = new GroupNode[groupNodes.Length]; for (int i = 0; i < groupNodes.Length; ++i) { VFXGroupNodeController groupNode = groupNodes[i]; VFXUI.GroupInfo info = groupNode.model.groupInfos[groupNode.index]; serializableGraph.groupNodes[i] = new GroupNode { infos = new VFXUI.UIInfo(info)}; // only keep nodes and sticky notes that are copied because a element can not be in two groups at the same time. if (info.contents != null) { var nodeIndices = groupNode.nodes.OfType().Where(t => contexts.Contains(t) || operators.Contains(t) || parameters.Contains(t)).Select(t => modelIndices[t]); var stickNoteIndices = info.contents.Where(t => t.isStickyNote && stickyNodeIndexToCopiedIndex.ContainsKey(t.id)).Select(t => (uint)stickyNodeIndexToCopiedIndex[t.id]); serializableGraph.groupNodes[i].contents = nodeIndices.Concat(stickNoteIndices).ToArray(); serializableGraph.groupNodes[i].stickNodeCount = stickNoteIndices.Count(); } } } } } void CopyDataEdge(ref SerializableGraph serializableGraph, IEnumerable dataEdges) { serializableGraph.dataEdges = new DataEdge[dataEdges.Count()]; int cpt = 0; foreach (var edge in dataEdges) { DataEdge copyPasteEdge = new DataEdge(); var inputController = edge.input as VFXDataAnchorController; var outputController = edge.output as VFXDataAnchorController; copyPasteEdge.input.slotPath = MakeSlotPath(inputController.model, true); copyPasteEdge.input.targetIndex = modelIndices[inputController.sourceNode]; copyPasteEdge.output.slotPath = MakeSlotPath(outputController.model, false); copyPasteEdge.output.targetIndex = modelIndices[outputController.sourceNode]; serializableGraph.dataEdges[cpt++] = copyPasteEdge; } } void CopyFlowEdges(ref SerializableGraph serializableGraph, IEnumerable flowEdges) { serializableGraph.flowEdges = new FlowEdge[flowEdges.Count()]; int cpt = 0; foreach (var edge in flowEdges) { FlowEdge copyPasteEdge = new FlowEdge(); var inputController = edge.input as VFXFlowAnchorController; var outputController = edge.output as VFXFlowAnchorController; copyPasteEdge.input.contextIndex = modelIndices[inputController.context]; copyPasteEdge.input.flowIndex = inputController.slotIndex; copyPasteEdge.output.contextIndex = modelIndices[outputController.context]; copyPasteEdge.output.flowIndex = outputController.slotIndex; serializableGraph.flowEdges[cpt++] = copyPasteEdge; } } void CopyDatas(ref SerializableGraph serializableGraph) { serializableGraph.datas = new Data[datas.Length]; for (int i = 0; i < datas.Length; ++i) { CopyModelSettings(ref serializableGraph.datas[i].settings, datas[i]); } } void CopyModelSettings(ref Property[] properties, VFXModel model) { // Copy all fields that are either VFXSettings or serialized by unity Type type = model.GetType(); var fields = GetFields(type); properties = new Property[fields.Count()]; for (int i = 0; i < properties.Length; ++i) { properties[i].name = fields[i].Name; properties[i].value = new VFXSerializableObject(fields[i].FieldType, fields[i].GetValue(model)); } } ParameterNode CopyParameterNode(int parameterIndex, int nodeIndex, VFXParameterNodeController controller, int indexInClipboard) { ParameterNode n = new ParameterNode(); n.position = controller.position; n.collapsed = controller.superCollapsed; n.expandedOutput = controller.infos.expandedSlots.Select(t => t.path).ToArray(); n.indexInClipboard = indexInClipboard; if (parameterIndex < (1 << 18) && nodeIndex < (1 << 11)) modelIndices[controller] = GetParameterNodeID((uint)parameterIndex, (uint)nodeIndex); else modelIndices[controller] = InvalidID; return n; } void CopyParameters(ref SerializableGraph serializableGraph) { int cpt = 0; serializableGraph.parameters = parameters.GroupBy(t => t.parentController, t => t, (p, c) => { ++cpt; return new Parameter() { originalInstanceID = p.model.GetInstanceID(), name = p.model.exposedName, value = new VFXSerializableObject(p.model.type, p.model.value), exposed = p.model.exposed, isOutput = p.model.isOutput, valueFilter = p.valueFilter, min = p.valueFilter == VFXValueFilter.Range ? new VFXSerializableObject(p.model.type, p.model.min) : null, max = p.valueFilter == VFXValueFilter.Range ? new VFXSerializableObject(p.model.type, p.model.max) : null, enumValue = p.valueFilter == VFXValueFilter.Enum ? p.model.enumValues.ToArray() : null, tooltip = p.model.tooltip, nodes = c.Select((u, i) => CopyParameterNode(cpt - 1, i, u, parameterIndices[Array.IndexOf(parameters, u)])).ToArray() }; } ).ToArray(); } void CopyOperatorsAndContexts(ref SerializableGraph serializableGraph) { serializableGraph.contexts = new Context[contexts.Length]; for (int i = 0; i < contexts.Length; ++i) { NodeID id = CopyContext(ref serializableGraph.contexts[i], contexts[i], i); modelIndices[contexts[i]] = id; } serializableGraph.operators = new Node[operators.Length]; for (int i = 0; i < operators.Length; ++i) { uint id = CopyNode(ref serializableGraph.operators[i], operators[i].model, (NodeID)i); serializableGraph.operators[i].indexInClipboard = operatorIndices[i]; modelIndices[operators[i]] = id; } } NodeID CopyNode(ref Node node, VFXModel model, uint index) { // Copy node infos node.position = model.position; node.type = model.GetType(); node.flags = 0; if (model.collapsed) node.flags = Node.Flags.Collapsed; if (model.superCollapsed) node.flags = Node.Flags.SuperCollapsed; uint id = 0; if (model is VFXOperator) { id = OperatorFlag; } else if (model is VFXContext) { id = ContextFlag; } id |= (uint)index; //Copy settings value CopyModelSettings(ref node.settings, model); var inputSlots = (model as IVFXSlotContainer).inputSlots; node.inputSlots = new Property[inputSlots.Count]; for (int i = 0; i < inputSlots.Count; i++) { node.inputSlots[i].name = inputSlots[i].name; if (inputSlots[i].spaceable) node.inputSlots[i].space = inputSlots[i].space; node.inputSlots[i].value = new VFXSerializableObject(inputSlots[i].property.type, inputSlots[i].value); } node.expandedInputs = AllSlots(inputSlots).Where(t => !t.collapsed).Select(t => t.path).ToArray(); node.expandedOutputs = AllSlots((model as IVFXSlotContainer).outputSlots).Where(t => !t.collapsed).Select(t => t.path).ToArray(); return id; } SerializableGraph DoCopyBlocks(IEnumerable blocks) { var newBlocks = new Node[blocks.Count()]; uint cpt = 0; foreach (var block in blocks) { CopyNode(ref newBlocks[(int)cpt], block.model, cpt); ++cpt; } var graph = new SerializableGraph(); graph.blocksOnly = true; graph.operators = newBlocks; return graph; } Node[] CopyBlocks(IList blocks, int contextIndex) { var newBlocks = new Node[blocks.Count]; for (uint i = 0; i < newBlocks.Length; ++i) { CopyNode(ref newBlocks[(int)i], blocks[(int)i].model, i); if (blocks[(int)i].model.enabled) { newBlocks[i].flags |= Node.Flags.Enabled; } if (contextIndex < (1 << 18) && i < (1 << 11)) modelIndices[blocks[(int)i]] = BlockFlag | (i << 18) | (uint)contextIndex; else modelIndices[blocks[(int)i]] = InvalidID; } return newBlocks; } NodeID CopyContext(ref Context context, VFXContextController controller, int index) { NodeID id = CopyNode(ref context.node, controller.model, (NodeID)index); var blocks = controller.blockControllers; context.label = controller.model.label; context.systemName = VFXSystemNames.GetSystemName(controller.model); if (controller.model.GetData() != null) context.dataIndex = Array.IndexOf(datas, controller.model.GetData()); else context.dataIndex = -1; context.blocks = CopyBlocks(controller.blockControllers, index); context.node.indexInClipboard = contextsIndices[index]; if (controller.model is VFXAbstractRenderedOutput) context.subOutputs = CopySubOutputs(((VFXAbstractRenderedOutput)controller.model).GetSubOutputs()); else context.subOutputs = null; return id; } SubOutput[] CopySubOutputs(List subOutputs) { var newSubOutputs = new SubOutput[subOutputs.Count]; for (int i = 0; i < newSubOutputs.Length; ++i) { if (subOutputs[i] != null) // Can be null if associated SRP is unknown { newSubOutputs[i].type = subOutputs[i].GetType(); CopyModelSettings(ref newSubOutputs[i].settings, subOutputs[i]); } } return newSubOutputs; } static int[] MakeSlotPath(VFXSlot slot, bool input) { List slotPath = new List(slot.depth + 1); while (slot.GetParent() != null) { slotPath.Add(slot.GetParent().GetIndex(slot)); slot = slot.GetParent(); } slotPath.Add((input ? (slot.owner as IVFXSlotContainer).inputSlots : (slot.owner as IVFXSlotContainer).outputSlots).IndexOf(slot)); return slotPath.ToArray(); } } }