using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using System.Collections.ObjectModel; using UnityEditor.VFX.UI; using UnityEngine.Serialization; namespace UnityEditor.VFX { enum VFXValueFilter { Default, Range, Enum } [ExcludeFromPreset] class VFXParameter : VFXSlotContainerModel { protected VFXParameter() { m_ExposedName = "exposedName"; m_Exposed = false; m_UICollapsed = false; } public static VFXParameter Duplicate(string copyName, VFXParameter source) { var newVfxParameter = (VFXParameter)ScriptableObject.CreateInstance(source.GetType()); newVfxParameter.m_ExposedName = copyName; newVfxParameter.m_Exposed = source.m_Exposed; newVfxParameter.m_UICollapsed = source.m_UICollapsed; newVfxParameter.m_Order = source.m_Order + 1; newVfxParameter.m_Category = source.m_Category; newVfxParameter.m_Min = source.m_Min; newVfxParameter.m_Max = source.m_Max; newVfxParameter.m_IsOutput = source.m_IsOutput; newVfxParameter.m_EnumValues = source.m_EnumValues?.ToList(); newVfxParameter.m_Tooltip = source.m_Tooltip; newVfxParameter.m_ValueFilter = source.m_ValueFilter; newVfxParameter.subgraphMode = source.subgraphMode; newVfxParameter.m_ValueExpr = source.m_ValueExpr; newVfxParameter.Init(source.type); if (!source.isOutput) { newVfxParameter.value = source.value; } return newVfxParameter; } [VFXSetting(VFXSettingAttribute.VisibleFlags.None), SerializeField, FormerlySerializedAs("m_exposedName")] private string m_ExposedName; [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField, FormerlySerializedAs("m_exposed")] private bool m_Exposed; [SerializeField] private int m_Order; [SerializeField] private string m_Category; [VFXSetting(VFXSettingAttribute.VisibleFlags.None), SerializeField] protected VFXSerializableObject m_Min; [VFXSetting(VFXSettingAttribute.VisibleFlags.None), SerializeField] protected VFXSerializableObject m_Max; [SerializeField] private bool m_IsOutput; [VFXSetting(VFXSettingAttribute.VisibleFlags.None), SerializeField] protected List m_EnumValues; public List enumValues { get { return m_EnumValues; } set { m_EnumValues = value; Invalidate(InvalidationCause.kSettingChanged); } } public object min { get { if (m_Min != null) return m_Min.Get(); else return null; } set { var invalidateCause = InvalidationCause.kParamChanged; if (m_Min == null || m_Min.type != type) { m_Min = new VFXSerializableObject(type, value); invalidateCause = InvalidationCause.kSettingChanged; } else m_Min.Set(value); Invalidate(invalidateCause); } } public object max { get { if (m_Max != null) return m_Max.Get(); else return null; } set { var invalidateCause = InvalidationCause.kParamChanged; if (m_Max == null || m_Max.type != type) { m_Max = new VFXSerializableObject(type, value); invalidateCause = InvalidationCause.kSettingChanged; } else m_Max.Set(value); Invalidate(invalidateCause); } } [SerializeField] VFXValueFilter m_ValueFilter; public VFXValueFilter valueFilter { get => m_ValueFilter; set { if (value != m_ValueFilter) { m_ValueFilter = value; switch (m_ValueFilter) { case VFXValueFilter.Default: m_Max = m_Min = null; m_EnumValues = null; break; case VFXValueFilter.Range: m_Min = new VFXSerializableObject(type, this.value); m_Max = new VFXSerializableObject(type, this.value); m_EnumValues = null; break; case VFXValueFilter.Enum: m_EnumValues = new List(); m_EnumValues.Add("Zero"); m_EnumValues.Add("One"); m_Max = m_Min = null; break; } Invalidate(InvalidationCause.kSettingChanged); } } } protected override IEnumerable filteredOutSettings { get { return m_IsOutput ? Enumerable.Repeat("m_Exposed", 1) : Enumerable.Empty(); } } public bool isOutput { get { return m_IsOutput; } set { if (m_IsOutput != value) { m_IsOutput = value; if (m_IsOutput) { var oldSlot = outputSlots[0]; var newSlot = VFXSlot.Create(new VFXProperty(oldSlot.property.type, "i"), VFXSlot.Direction.kInput); newSlot.value = oldSlot.value; oldSlot.UnlinkAll(true); ReplaceSlot(oldSlot, newSlot); if (m_Nodes != null && m_Nodes.Count > 1) { m_Nodes.RemoveRange(1, m_Nodes.Count - 2); } m_ExprSlots = null; m_ValueExpr = null; m_Exposed = false; } else { var oldSlot = inputSlots[0]; var newSlot = VFXSlot.Create(new VFXProperty(oldSlot.property.type, "o"), VFXSlot.Direction.kOutput); newSlot.value = oldSlot.value; oldSlot.UnlinkAll(true); ReplaceSlot(oldSlot, newSlot); ResetOutputValueExpression(); } } } } public void ResetOutputValueExpression() { if (!isOutput) { MarkOutputExpressionsAsOutOfDate(); m_ExprSlots = outputSlots[0].GetVFXValueTypeSlots().ToArray(); m_ValueExpr = m_ExprSlots.Select(t => t.DefaultExpression(valueMode)).ToArray(); } } public bool canHaveValueFilter { get { return !isOutput && (type == typeof(float) || type == typeof(int) || type == typeof(uint)); } } [SerializeField] string m_Tooltip; public string tooltip { get { return m_Tooltip; } set { m_Tooltip = value; Invalidate(InvalidationCause.kUIChanged); } } [System.Serializable] public struct NodeLinkedSlot { public VFXSlot outputSlot; // some slot from the parameter public VFXSlot inputSlot; } [System.Serializable] public class Node { public Node(int id) { m_Id = id; expanded = true; supecollapsed = false; } [SerializeField] private int m_Id; public int id => m_Id; public List linkedSlots; public Vector2 position; public List expandedSlots; public bool expanded; public bool supecollapsed; //Should only be called by ValidateNodes if something very wrong happened with serialization internal void ChangeId(int newId) { m_Id = newId; } } [SerializeField] protected List m_Nodes; [NonSerialized] int m_IDCounter = 0; public string exposedName { get { return m_ExposedName; } } public bool exposed { get { return m_Exposed; } } public int order { get { return m_Order; } set { if (m_Order != value) { m_Order = value; Invalidate(InvalidationCause.kUIChanged); } } } public string category { get { return m_Category; } set { if (m_Category != value) { m_Category = value; Invalidate(InvalidationCause.kUIChanged); } } } private void OnModified(VFXObject obj, bool uiChange) { if (!isOutput && (m_ExprSlots == null || m_ValueExpr == null)) { ResetOutputValueExpression(); } } public Type type { get { if (isOutput) { return inputSlots[0].property.type; } else return outputSlots[0].property.type; } } public object value { get { if (!isOutput) return outputSlots[0].value; return null; } set { if (isOutput) throw new System.InvalidOperationException("output parameters have no value"); outputSlots[0].value = value; } } public ReadOnlyCollection nodes { get { if (m_Nodes == null) { m_Nodes = new List(); } return m_Nodes.AsReadOnly(); } } public Node GetNode(int id) { return m_Nodes.FirstOrDefault(t => t.id == id); } internal override void GenerateErrors(VFXErrorReporter report) { base.GenerateErrors(report); var type = this.type; if (Deprecated.s_Types.Contains(type)) { report.RegisterError( "DeprecatedTypeParameter", VFXErrorType.Warning, string.Format("The structure of the '{0}' has changed, the position property has been moved to a transform type. You should consider to recreate this parameter.", type.Name), this); } } protected sealed override void OnInvalidate(VFXModel model, InvalidationCause cause) { base.OnInvalidate(model, cause); if (isOutput) return; if (cause == InvalidationCause.kSettingChanged) { var valueExpr = m_ExprSlots.Select(t => t.DefaultExpression(valueMode)).ToArray(); bool valueExprChanged = true; if (m_ValueExpr.Length == valueExpr.Length) { valueExprChanged = false; for (int i = 0; i < m_ValueExpr.Length; ++i) { if (m_ValueExpr[i].ValueMode != valueExpr[i].ValueMode || m_ValueExpr[i].valueType != valueExpr[i].valueType) { valueExprChanged = true; break; } } } if (valueExprChanged) { MarkOutputExpressionsAsOutOfDate(); m_ValueExpr = valueExpr; outputSlots[0].InvalidateExpressionTree(); Invalidate(InvalidationCause.kExpressionGraphChanged); // As we need to update exposed list event if not connected to a compilable context } /* TODO : Allow VisualEffectApi to update only exposed name */ else if (exposed) { Invalidate(InvalidationCause.kExpressionGraphChanged); } } if (cause == InvalidationCause.kParamChanged) { UpdateDefaultExpressionValue(); } if (cause == InvalidationCause.kStructureChanged) { ResetOutputValueExpression(); } } protected override IEnumerable inputProperties { get { if (isOutput) return PropertiesFromSlotsOrDefaultFromClass(VFXSlot.Direction.kInput); return Enumerable.Empty(); } } protected override IEnumerable outputProperties { get { if (!isOutput) return PropertiesFromSlotsOrDefaultFromClass(VFXSlot.Direction.kOutput); return Enumerable.Empty(); } } public void Init(Type _type) { var slots = isOutput ? inputSlots : outputSlots; if (_type != null && slots.Count == 0) { VFXSlot slot = VFXSlot.Create(new VFXProperty(_type, "o"), isOutput ? VFXSlot.Direction.kInput : VFXSlot.Direction.kOutput); AddSlot(slot); if (!typeof(UnityEngine.Object).IsAssignableFrom(_type) && _type != typeof(GraphicsBuffer)) slot.value = System.Activator.CreateInstance(_type); } else { throw new InvalidOperationException("Cannot init VFXParameter"); } ResetOutputValueExpression(); } public override void OnEnable() { base.OnEnable(); onModified += OnModified; if (!isOutput && outputSlots.Count > 0) { ResetOutputValueExpression(); } if (m_Nodes != null) { foreach (var node in nodes) { if (m_IDCounter < node.id + 1) { m_IDCounter = node.id + 1; } } } } Node NewNode() { return new Node(m_IDCounter++); } public int AddNode(Vector2 pos) { Node info = NewNode(); info.position = pos; if (m_Nodes == null) { m_Nodes = new List(); } m_Nodes.Add(info); Invalidate(InvalidationCause.kUIChanged); return info.id; } public void RemoveNode(Node info) { if (m_Nodes.Contains(info)) { if (info.linkedSlots != null) { foreach (var slots in info.linkedSlots) { slots.outputSlot.Unlink(slots.inputSlot); } } m_Nodes.Remove(info); Invalidate(InvalidationCause.kUIChanged); } } public override void Sanitize(int version) { base.Sanitize(version); HashSet usedIds = new HashSet(); if (m_Min != null && m_Min.type != null && m_ValueFilter == VFXValueFilter.Default) m_ValueFilter = VFXValueFilter.Range; if (m_Nodes != null) { foreach (var node in m_Nodes) { if (usedIds.Contains(node.id)) { node.ChangeId(m_IDCounter++); } usedIds.Add(node.id); } } } //AddNodeRange will take ownership of the Nodes instead of copying them public void AddNodeRange(IEnumerable infos) { foreach (var info in infos) { if (m_Nodes.Any(t => t.id == info.id)) { info.ChangeId(m_IDCounter++); } m_Nodes.Add(info); } Invalidate(InvalidationCause.kUIChanged); } //SetNodes will take ownership of the Nodes instead of copying them public void SetNodes(IEnumerable infos) { m_Nodes = infos.ToList(); ValidateNodes(); Invalidate(InvalidationCause.kUIChanged); } void GetAllLinks(List list, VFXSlot slot) { if (isOutput) list.AddRange(slot.LinkedSlots.Select(t => new NodeLinkedSlot() { outputSlot = t, inputSlot = slot })); else list.AddRange(slot.LinkedSlots.Select(t => new NodeLinkedSlot() { outputSlot = slot, inputSlot = t })); foreach (var child in slot.children) { GetAllLinks(list, child); } } void GetAllExpandedSlots(List list, VFXSlot slot) { if (!slot.collapsed) list.Add(slot); foreach (var child in slot.children) { GetAllExpandedSlots(list, child); } } public void ValidateNodes() { // Case of the old VFXParameter we create a new one on the same place with all the Links if (position != Vector2.zero && nodes.Count == 0) { CreateDefaultNode(position); } else { // the linked slot of the outSlot decides so make sure that all appear once and only once in all the nodes List links = new List(); var targetSlot = isOutput ? inputSlots.FirstOrDefault() : outputSlots.FirstOrDefault(); if (targetSlot == null) return; GetAllLinks(links, targetSlot); HashSet usedIds = new HashSet(); foreach (var info in nodes) { // Check linkedSlots if (info.linkedSlots == null) { info.linkedSlots = new List(); } else { // first remove linkedSlots that are not existing var intersect = info.linkedSlots.Intersect(links); if (intersect.Count() != info.linkedSlots.Count()) info.linkedSlots = info.linkedSlots.Intersect(links).ToList(); } //Check that all slots needed for if (info.expandedSlots == null) { info.expandedSlots = new List(); } else { if (info.expandedSlots.Any(t => t == null)) info.expandedSlots = info.expandedSlots.Where(t => t != null).ToList(); } if (usedIds.Contains(info.id)) { info.ChangeId(m_IDCounter++); } usedIds.Add(info.id); foreach (var slot in info.linkedSlots) { links.Remove(slot); } } // if there are some links in the output slots that are in not found in the infos, find or create a node for them. foreach (var link in links) { Node newInfos = null; if (nodes.Any()) { //There are already some nodes, choose the closest one to restore the link var refPosition = Vector2.zero; object refOwner = link.inputSlot.owner; while (refOwner is VFXModel model && refPosition == Vector2.zero) { refPosition = model is VFXBlock ? Vector2.zero : model.position; refOwner = model.GetParent(); } newInfos = nodes.OrderBy(o => (refPosition - o.position).SqrMagnitude()).First(); } else { newInfos = NewNode(); m_Nodes.Add(newInfos); } if (newInfos.linkedSlots == null) newInfos.linkedSlots = new List(); newInfos.linkedSlots.Add(link); newInfos.expandedSlots = new List(); } } position = Vector2.zero; // Set that as a marker that the parameter has been touched by the new code. } public void CreateDefaultNode(Vector2 position) { if (m_Nodes != null && m_Nodes.Count != 0) { Debug.LogError("CreateDefaultNode must only be called with an empty parameter"); return; } var newInfos = NewNode(); newInfos.position = position; var targetSlot = isOutput ? inputSlots[0] : outputSlots[0]; newInfos.linkedSlots = new List(); GetAllLinks(newInfos.linkedSlots, targetSlot); newInfos.expandedSlots = new List(); GetAllExpandedSlots(newInfos.expandedSlots, targetSlot); if (m_Nodes == null) { m_Nodes = new List(); } m_Nodes.Add(newInfos); } public bool subgraphMode { get; set; } public void UpdateDefaultExpressionValue() { if (!isOutput) { for (int i = 0; i < m_ExprSlots.Length; ++i) { m_ValueExpr[i].SetContent(m_ExprSlots[i].value); m_ExprSlots[i].Invalidate(InvalidationCause.kExpressionValueInvalidated); } } } protected override void UpdateOutputExpressions() { if (!isOutput) { for (int i = 0; i < m_ExprSlots.Length; ++i) { m_ValueExpr[i].SetContent(m_ExprSlots[i].value); if (!subgraphMode) // don't erase the expression in subgraph mode. m_ExprSlots[i].SetExpression(m_ValueExpr[i]); } } } public override void OnCopyLinksOtherSlot(VFXSlot mySlot, VFXSlot prevOtherSlot, VFXSlot newOtherSlot) { foreach (var node in nodes) { if (node.linkedSlots != null) { for (int i = 0; i < node.linkedSlots.Count; ++i) { if (node.linkedSlots[i].outputSlot == mySlot && node.linkedSlots[i].inputSlot == prevOtherSlot) { node.linkedSlots[i] = new NodeLinkedSlot() { outputSlot = mySlot, inputSlot = newOtherSlot }; return; } } } } } public override void OnCopyLinksMySlot(VFXSlot myPrevSlot, VFXSlot myNewSlot, VFXSlot otherSlot) { foreach (var node in nodes) { if (node.linkedSlots != null) { for (int i = 0; i < node.linkedSlots.Count; ++i) { if (node.linkedSlots[i].outputSlot == myPrevSlot && node.linkedSlots[i].inputSlot == otherSlot) { node.linkedSlots[i] = new NodeLinkedSlot() { outputSlot = myNewSlot, inputSlot = otherSlot }; return; } } } } } private VFXValue.Mode valueMode { get { return exposed ? VFXValue.Mode.Variable : VFXValue.Mode.FoldableVariable; } } [NonSerialized] private VFXSlot[] m_ExprSlots; [NonSerialized] private VFXValue[] m_ValueExpr; } }