void HandleVirtualTextureProperty(PropertySheet propertySheet, VirtualTextureShaderProperty virtualTextureProperty) { var container = new IMGUIContainer(() => OnVTGUIHandler(virtualTextureProperty)) { name = "ListContainer" }; AddPropertyRowToSheet(propertySheet, container, "Entries"); }
private void OnVTGUIHandler(VirtualTextureShaderProperty property) { if (m_VTReorderableList == null) { VTRecreateList(property); VTAddCallbacks(property); } m_VTReorderableList.index = m_VTSelectedIndex; m_VTReorderableList.DoLayoutList(); }
private void VTRemoveEntry(ReorderableList list, VirtualTextureShaderProperty property) { this._preChangeValueCallback("Remove Virtual Texture Entry"); // Remove entry m_VTSelectedIndex = list.index; var selectedEntry = (SerializableVirtualTextureLayer)m_VTReorderableList.list[list.index]; property.value.layers.Remove(selectedEntry); // Update Blackboard & Nodes //DirtyNodes(); this._postChangeValueCallback(true); m_VTSelectedIndex = m_VTSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_VTSelectedIndex; }
// Allowed indicies are 1-MAX_ENUM_ENTRIES private int VTGetFirstUnusedID(VirtualTextureShaderProperty property) { List <int> ususedIDs = new List <int>(); foreach (SerializableVirtualTextureLayer virtualTextureEntry in property.value.layers) { ususedIDs.Add(property.value.layers.IndexOf(virtualTextureEntry)); } for (int x = 1; x <= 4; x++) { if (!ususedIDs.Contains(x)) { return(x); } } Debug.LogError("GetFirstUnusedID: Attempting to get unused ID when all IDs are used."); return(-1); }
private void VTAddEntry(ReorderableList list, VirtualTextureShaderProperty property) { this._preChangeValueCallback("Add Virtual Texture Entry"); int index = VTGetFirstUnusedID(property); if (index <= 0) { return; // Error has already occured, don't attempt to add this entry. } var layerName = "Layer" + index.ToString(); // Add new entry property.value.layers.Add(new SerializableVirtualTextureLayer(layerName, layerName, new SerializableTexture())); // Update Blackboard & Nodes //DirtyNodes(); this._postChangeValueCallback(true); m_VTSelectedIndex = list.list.Count - 1; }
public void ToSubGraph() { var graphView = graphEditorView.graphView; string path; string sessionStateResult = SessionState.GetString(k_PrevSubGraphPathKey, k_PrevSubGraphPathDefaultValue); string pathToOriginSG = Path.GetDirectoryName(AssetDatabase.GUIDToAssetPath(selectedGuid)); if (!sessionStateResult.Equals(k_PrevSubGraphPathDefaultValue)) { path = sessionStateResult; } else { path = pathToOriginSG; } path = EditorUtility.SaveFilePanelInProject("Save Sub Graph", "New Shader Sub Graph", ShaderSubGraphImporter.Extension, "", path); path = path.Replace(Application.dataPath, "Assets"); if (path.Length == 0) { return; } graphObject.RegisterCompleteObjectUndo("Convert To Subgraph"); var nodes = graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(); var bounds = Rect.MinMaxRect(float.PositiveInfinity, float.PositiveInfinity, float.NegativeInfinity, float.NegativeInfinity); foreach (var node in nodes) { var center = node.drawState.position.center; bounds = Rect.MinMaxRect( Mathf.Min(bounds.xMin, center.x), Mathf.Min(bounds.yMin, center.y), Mathf.Max(bounds.xMax, center.x), Mathf.Max(bounds.yMax, center.y)); } var middle = bounds.center; bounds.center = Vector2.zero; // Collect graph inputs var graphInputs = graphView.selection.OfType <BlackboardField>().Select(x => x.userData as ShaderInput); // Collect the property nodes and get the corresponding properties var propertyNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is PropertyNode)).Select(x => ((PropertyNode)x.node).property); var metaProperties = graphView.graph.properties.Where(x => propertyNodes.Contains(x)); // Collect the keyword nodes and get the corresponding keywords var keywordNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is KeywordNode)).Select(x => ((KeywordNode)x.node).keyword); var metaKeywords = graphView.graph.keywords.Where(x => keywordNodes.Contains(x)); var copyPasteGraph = new CopyPasteGraph(graphView.selection.OfType <ShaderGroup>().Select(x => x.userData), graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), graphView.selection.OfType <Edge>().Select(x => x.userData as Graphing.Edge), graphInputs, metaProperties, metaKeywords, graphView.selection.OfType <StickyNote>().Select(x => x.userData), true); // why do we serialize and deserialize only to make copies of everything in the steps below? // is this just to clear out all non-serialized data? var deserialized = CopyPasteGraph.FromJson(MultiJson.Serialize(copyPasteGraph), graphView.graph); if (deserialized == null) { return; } var subGraph = new GraphData { isSubGraph = true, path = "Sub Graphs" }; var subGraphOutputNode = new SubGraphOutputNode(); { var drawState = subGraphOutputNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMax + 200f, 0f), drawState.position.size); subGraphOutputNode.drawState = drawState; } subGraph.AddNode(subGraphOutputNode); subGraph.outputNode = subGraphOutputNode; // Always copy deserialized keyword inputs foreach (ShaderKeyword keyword in deserialized.metaKeywords) { var copiedInput = (ShaderKeyword)keyword.Copy(); subGraph.SanitizeGraphInputName(copiedInput); subGraph.SanitizeGraphInputReferenceName(copiedInput, keyword.overrideReferenceName); subGraph.AddGraphInput(copiedInput); // Update the keyword nodes that depends on the copied keyword var dependentKeywordNodes = deserialized.GetNodes <KeywordNode>().Where(x => x.keyword == keyword); foreach (var node in dependentKeywordNodes) { node.owner = graphView.graph; node.keyword = copiedInput; } } foreach (GroupData groupData in deserialized.groups) { subGraph.CreateGroup(groupData); } foreach (var node in deserialized.GetNodes <AbstractMaterialNode>()) { var drawState = node.drawState; drawState.position = new Rect(drawState.position.position - middle, drawState.position.size); node.drawState = drawState; // Checking if the group guid is also being copied. // If not then nullify that guid if (node.group != null && !subGraph.groups.Contains(node.group)) { node.group = null; } subGraph.AddNode(node); } foreach (var note in deserialized.stickyNotes) { if (note.group != null && !subGraph.groups.Contains(note.group)) { note.group = null; } subGraph.AddStickyNote(note); } // figure out what needs remapping var externalOutputSlots = new List <Graphing.Edge>(); var externalInputSlots = new List <Graphing.Edge>(); foreach (var edge in deserialized.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; var outputSlotExistsInSubgraph = subGraph.ContainsNode(outputSlot.node); var inputSlotExistsInSubgraph = subGraph.ContainsNode(inputSlot.node); // pasting nice internal links! if (outputSlotExistsInSubgraph && inputSlotExistsInSubgraph) { subGraph.Connect(outputSlot, inputSlot); } // one edge needs to go to outside world else if (outputSlotExistsInSubgraph) { externalInputSlots.Add(edge); } else if (inputSlotExistsInSubgraph) { externalOutputSlots.Add(edge); } } // Find the unique edges coming INTO the graph var uniqueIncomingEdges = externalOutputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slotRef = key, edges = edges.ToList() }); var externalInputNeedingConnection = new List <KeyValuePair <IEdge, AbstractShaderProperty> >(); var amountOfProps = uniqueIncomingEdges.Count(); const int height = 40; const int subtractHeight = 20; var propPos = new Vector2(0, -((amountOfProps / 2) + height) - subtractHeight); foreach (var group in uniqueIncomingEdges) { var sr = group.slotRef; var fromNode = sr.node; var fromSlot = sr.slot; var materialGraph = graphObject.graph; var fromProperty = fromNode is PropertyNode fromPropertyNode ? materialGraph.properties.FirstOrDefault(p => p == fromPropertyNode.property) : null; AbstractShaderProperty prop; switch (fromSlot.concreteValueType) { case ConcreteSlotValueType.Texture2D: prop = new Texture2DShaderProperty(); break; case ConcreteSlotValueType.Texture2DArray: prop = new Texture2DArrayShaderProperty(); break; case ConcreteSlotValueType.Texture3D: prop = new Texture3DShaderProperty(); break; case ConcreteSlotValueType.Cubemap: prop = new CubemapShaderProperty(); break; case ConcreteSlotValueType.Vector4: prop = new Vector4ShaderProperty(); break; case ConcreteSlotValueType.Vector3: prop = new Vector3ShaderProperty(); break; case ConcreteSlotValueType.Vector2: prop = new Vector2ShaderProperty(); break; case ConcreteSlotValueType.Vector1: prop = new Vector1ShaderProperty(); break; case ConcreteSlotValueType.Boolean: prop = new BooleanShaderProperty(); break; case ConcreteSlotValueType.Matrix2: prop = new Matrix2ShaderProperty(); break; case ConcreteSlotValueType.Matrix3: prop = new Matrix3ShaderProperty(); break; case ConcreteSlotValueType.Matrix4: prop = new Matrix4ShaderProperty(); break; case ConcreteSlotValueType.SamplerState: prop = new SamplerStateShaderProperty(); break; case ConcreteSlotValueType.Gradient: prop = new GradientShaderProperty(); break; case ConcreteSlotValueType.VirtualTexture: prop = new VirtualTextureShaderProperty() { // also copy the VT settings over from the original property (if there is one) value = (fromProperty as VirtualTextureShaderProperty)?.value ?? new SerializableVirtualTexture() }; break; default: throw new ArgumentOutOfRangeException(); } prop.displayName = fromProperty != null ? fromProperty.displayName : fromSlot.concreteValueType.ToString(); prop.displayName = GraphUtil.SanitizeName(subGraph.addedInputs.Select(p => p.displayName), "{0} ({1})", prop.displayName); subGraph.AddGraphInput(prop); var propNode = new PropertyNode(); { var drawState = propNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMin - 300f, 0f) + propPos, drawState.position.size); propPos += new Vector2(0, height); propNode.drawState = drawState; } subGraph.AddNode(propNode); propNode.property = prop; foreach (var edge in group.edges) { subGraph.Connect( new SlotReference(propNode, PropertyNode.OutputSlotId), edge.inputSlot); externalInputNeedingConnection.Add(new KeyValuePair <IEdge, AbstractShaderProperty>(edge, prop)); } } var uniqueOutgoingEdges = externalInputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slot = key, edges = edges.ToList() }); var externalOutputsNeedingConnection = new List <KeyValuePair <IEdge, IEdge> >(); foreach (var group in uniqueOutgoingEdges) { var outputNode = subGraph.outputNode as SubGraphOutputNode; AbstractMaterialNode node = group.edges[0].outputSlot.node; MaterialSlot slot = node.FindSlot <MaterialSlot>(group.edges[0].outputSlot.slotId); var slotId = outputNode.AddSlot(slot.concreteValueType); var inputSlotRef = new SlotReference(outputNode, slotId); foreach (var edge in group.edges) { var newEdge = subGraph.Connect(edge.outputSlot, inputSlotRef); externalOutputsNeedingConnection.Add(new KeyValuePair <IEdge, IEdge>(edge, newEdge)); } } if (FileUtilities.WriteShaderGraphToDisk(path, subGraph)) { AssetDatabase.ImportAsset(path); } // Store path for next time if (!pathToOriginSG.Equals(Path.GetDirectoryName(path))) { SessionState.SetString(k_PrevSubGraphPathKey, Path.GetDirectoryName(path)); } else { // Or continue to make it so that next time it will open up in the converted-from SG's directory SessionState.EraseString(k_PrevSubGraphPathKey); } var loadedSubGraph = AssetDatabase.LoadAssetAtPath(path, typeof(SubGraphAsset)) as SubGraphAsset; if (loadedSubGraph == null) { return; } var subGraphNode = new SubGraphNode(); var ds = subGraphNode.drawState; ds.position = new Rect(middle - new Vector2(100f, 150f), Vector2.zero); subGraphNode.drawState = ds; // Add the subgraph into the group if the nodes was all in the same group group var firstNode = copyPasteGraph.GetNodes <AbstractMaterialNode>().FirstOrDefault(); if (firstNode != null && copyPasteGraph.GetNodes <AbstractMaterialNode>().All(x => x.group == firstNode.group)) { subGraphNode.group = firstNode.group; } subGraphNode.asset = loadedSubGraph; graphObject.graph.AddNode(subGraphNode); foreach (var edgeMap in externalInputNeedingConnection) { graphObject.graph.Connect(edgeMap.Key.outputSlot, new SlotReference(subGraphNode, edgeMap.Value.guid.GetHashCode())); } foreach (var edgeMap in externalOutputsNeedingConnection) { graphObject.graph.Connect(new SlotReference(subGraphNode, edgeMap.Value.inputSlot.slotId), edgeMap.Key.inputSlot); } graphObject.graph.RemoveElements( graphView.selection.OfType <IShaderNodeView>().Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), new IEdge[] {}, new GroupData[] {}, graphView.selection.OfType <StickyNote>().Select(x => x.userData).ToArray()); graphObject.graph.ValidateGraph(); }
private void VTAddCallbacks(VirtualTextureShaderProperty property) { // Draw Header m_VTReorderableList.drawHeaderCallback = (Rect rect) => { int indent = 14; var displayRect = new Rect(rect.x + indent, rect.y, (rect.width - indent) / 3, rect.height); EditorGUI.LabelField(displayRect, "Display Name"); var referenceRect = new Rect((rect.x) + (rect.width - indent) / 3, rect.y, (rect.width - indent) / 3, rect.height); EditorGUI.LabelField(referenceRect, "Reference Name"); var textureRect = new Rect((rect.x) + (rect.width - indent) / 3 * 2, rect.y, (rect.width - indent) / 3, rect.height); EditorGUI.LabelField(textureRect, "Texture Asset"); }; // Draw Element m_VTReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { SerializableVirtualTextureLayer entry = ((SerializableVirtualTextureLayer)m_VTReorderableList.list[index]); EditorGUI.BeginChangeCheck(); var layerName = EditorGUI.DelayedTextField(new Rect(rect.x, rect.y, rect.width / 3, EditorGUIUtility.singleLineHeight), entry.layerName, EditorStyles.label); var layerRefName = EditorGUI.DelayedTextField(new Rect((rect.x + rect.width) / 3, rect.y, rect.width / 3, EditorGUIUtility.singleLineHeight), entry.layerRefName, EditorStyles.label); var selectedObject = EditorGUI.ObjectField(new Rect((rect.x + rect.width) / 3 * 2, rect.y, rect.width / 3, EditorGUIUtility.singleLineHeight), entry.layerTexture.texture, typeof(Texture), false); SerializableTexture layerTexture = new SerializableTexture(); layerTexture.texture = (Texture)selectedObject; if (EditorGUI.EndChangeCheck()) { //need to sanitize each layer with all existing properties string oldLayerName = property.value.layers[index].layerName; if (layerName != oldLayerName) { var otherPropertyNames = graphData.BuildPropertyDisplayNameList(property, oldLayerName); layerName = GraphUtil.SanitizeName(otherPropertyNames, "{0} ({1})", layerName); } string oldLayerRefName = property.value.layers[index].layerRefName; if (layerRefName != oldLayerRefName) { if (!string.IsNullOrEmpty(layerRefName)) { string name = layerRefName.Trim(); if (!string.IsNullOrEmpty(layerRefName)) { if (Regex.IsMatch(name, @"^\d+")) { name = "_" + name; } name = Regex.Replace(name, @"(?:[^A-Za-z_0-9])|(?:\s)", "_"); var otherPropertyRefNames = graphData.BuildPropertyReferenceNameList(property, oldLayerRefName); layerRefName = GraphUtil.SanitizeName(otherPropertyRefNames, "{0}_{1}", name); } } } property.value.layers[index] = new SerializableVirtualTextureLayer(layerName, layerRefName, layerTexture); //DirtyNodes(); this._postChangeValueCallback(true); } }; // Element height m_VTReorderableList.elementHeightCallback = (int indexer) => { return(m_VTReorderableList.elementHeight); }; // Can add m_VTReorderableList.onCanAddCallback = (ReorderableList list) => { return(list.count < 4); }; // Can remove m_VTReorderableList.onCanRemoveCallback = (ReorderableList list) => { return(list.count > 1); }; void AddEntryLamda(ReorderableList list) => VTAddEntry(list, property); void RemoveEntryLamda(ReorderableList list) => VTRemoveEntry(list, property); // Add callback delegates m_VTReorderableList.onSelectCallback += VTSelectEntry; m_VTReorderableList.onAddCallback += AddEntryLamda; m_VTReorderableList.onRemoveCallback += RemoveEntryLamda; m_VTReorderableList.onReorderCallback += VTReorderEntries; }
internal void VTRecreateList(VirtualTextureShaderProperty property) { // Create reorderable list from entries m_VTReorderableList = new ReorderableList(property.value.layers, typeof(SerializableVirtualTextureLayer), true, true, true, true); }