You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

654 lines
21 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.VFX.UI;
using UnityEngine.VFX;
using UnityEngine.Profiling;
namespace UnityEditor.VFX
{
class VFXObject : ScriptableObject
{
//Explicitly disable the Reset option on all VFXObject
//Internal Reset() behavior leads to a dandling state in graph object
[MenuItem("CONTEXT/VFXObject/Reset", false)]
public static void DummyReset()
{
}
[MenuItem("CONTEXT/VFXObject/Reset", true)]
static bool ValidateDummyReset()
{
return false;
}
public Action<VFXObject, bool> onModified;
void OnValidate()
{
if (!VFXGraph.restoringGraph)
Modified(false);
}
public void Modified(bool uiChange)
{
if (onModified != null)
onModified(this, uiChange);
}
}
[Serializable]
abstract class VFXModel : VFXObject
{
public enum InvalidationCause
{
kStructureChanged, // Model structure (hierarchy) has changed
kParamChanged, // Some parameter values have changed
kSettingChanged, // A setting value has changed
kSpaceChanged, // Space has been changed
kConnectionChanged, // Connection have changed
kExpressionInvalidated, // No direct change to the model but a change in connection was propagated from the parents
kExpressionValueInvalidated, // No direct change but a kParamChanged upstream was propagated meaning the expression in itself has not changed but it's evaluated value may have
kExpressionGraphChanged, // Expression graph must be recomputed
kUIChanged, // UI stuff has changed
kUIChangedTransient, // UI stuff has been changed be does not require serialization
kMaterialChanged, // Some asset material properties has changed
kEnableChanged, // Node has been enabled/disabled
kInitValueChanged, // A value has changed in a spawn/init context and may require a reinit
}
public new virtual string name => string.Empty;
public delegate void InvalidateEvent(VFXModel model, InvalidationCause cause);
public event InvalidateEvent onInvalidateDelegate;
protected VFXModel()
{
m_UICollapsed = true;
}
public virtual void OnEnable()
{
if (m_Children == null)
m_Children = new List<VFXModel>();
else
{
int nbRemoved = m_Children.RemoveAll(c => c == null);// Remove bad references if any
if (nbRemoved > 0)
Debug.LogWarning(String.Format("Remove {0} child(ren) that couldnt be deserialized from {1} of type {2}", nbRemoved, name, GetType()));
}
}
public virtual void Sanitize(int version) { }
public virtual void CheckGraphBeforeImport() { }
public virtual void OnUnknownChange() { }
public virtual void OnSRPChanged() { }
public virtual void GetSourceDependentAssets(HashSet<string> dependencies)
{
foreach (var child in children)
child.GetSourceDependentAssets(dependencies);
}
public virtual void GetImportDependentAssets(HashSet<int> dependencies)
{
foreach (var child in children)
{
child.GetImportDependentAssets(dependencies);
}
}
public virtual void CollectDependencies(HashSet<ScriptableObject> objs, bool ownedOnly = true)
{
foreach (var child in children)
{
objs.Add(child);
child.CollectDependencies(objs, ownedOnly);
}
}
protected virtual void OnInvalidate(VFXModel model, InvalidationCause cause)
{
if (onInvalidateDelegate != null)
{
Profiler.BeginSample("VFXEditor.OnInvalidateDelegate");
try
{
onInvalidateDelegate(model, cause);
}
finally
{
Profiler.EndSample();
}
}
}
public virtual void RefreshErrors()
{
GetGraph()?.errorManager.RefreshInvalidateReport(this);
}
protected virtual void OnAdded() { }
protected virtual void OnRemoved() { }
public virtual bool AcceptChild(VFXModel model, int index = -1)
{
return false;
}
public void AddChild(VFXModel model, int index = -1, bool notify = true)
{
int realIndex = index == -1 ? m_Children.Count : index;
if (model.m_Parent != this || realIndex != GetIndex(model))
{
if (!AcceptChild(model, index))
throw new ArgumentException("Cannot attach " + model + " to " + this);
model.Detach(notify && model.m_Parent != this); // Dont notify if the owner is already this to avoid double invalidation
realIndex = index == -1 ? m_Children.Count : index; // Recompute as the child may have been removed
m_Children.Insert(realIndex, model);
model.m_Parent = this;
model.OnAdded();
if (notify)
Invalidate(InvalidationCause.kStructureChanged);
}
}
public void RemoveChild(VFXModel model, bool notify = true)
{
if (model.m_Parent != this)
return;
model.OnRemoved();
m_Children.Remove(model);
model.m_Parent = null;
if (notify)
Invalidate(InvalidationCause.kStructureChanged);
}
public void RemoveAllChildren(bool notify = true)
{
while (m_Children.Count > 0)
RemoveChild(m_Children[m_Children.Count - 1], notify);
}
public VFXModel GetParent()
{
return m_Parent;
}
public T GetFirstOfType<T>() where T : VFXModel
{
if (this is T)
return this as T;
var parent = GetParent();
if (parent == null)
return null;
return parent.GetFirstOfType<T>();
}
public void Attach(VFXModel parent, bool notify = true)
{
parent.AddChild(this, -1, notify);
}
public void Detach(bool notify = true)
{
if (m_Parent == null)
return;
m_Parent.RemoveChild(this, notify);
}
public IEnumerable<VFXModel> children
{
get { return m_Children; }
}
public VFXModel this[int index]
{
get { return m_Children[index]; }
}
public Vector2 position
{
get { return m_UIPosition; }
set
{
if (m_UIPosition != value)
{
m_UIPosition = value;
Invalidate(InvalidationCause.kUIChanged);
}
}
}
public bool collapsed
{
get { return m_UICollapsed; }
set
{
if (m_UICollapsed != value)
{
m_UICollapsed = value;
Invalidate(InvalidationCause.kUIChanged);
}
}
}
public bool superCollapsed
{
get { return m_UISuperCollapsed; }
set
{
if (m_UISuperCollapsed != value)
{
m_UISuperCollapsed = value;
Invalidate(InvalidationCause.kUIChanged);
}
}
}
public int GetNbChildren()
{
return m_Children.Count;
}
public int GetIndex(VFXModel child)
{
return m_Children.IndexOf(child);
}
public object GetSettingValue(string name)
{
var setting = GetSetting(name);
if (setting.field == null)
{
throw new ArgumentException(string.Format("Unable to find field {0} in {1}", name, GetType().ToString()));
}
return setting.value;
}
public void SetSettingValue(string name, object value)
{
SetSettingValue(name, value, true);
}
public void SetSettingValues(IEnumerable<KeyValuePair<string, object>> nameValues, bool notify = true)
{
bool hasChanged = false;
foreach (var kvp in nameValues)
{
if (SetSettingValueAndReturnIfChanged(kvp.Key, kvp.Value))
hasChanged = true;
}
if (hasChanged && notify)
Invalidate(InvalidationCause.kSettingChanged);
}
protected void SetSettingValue(string name, object value, bool notify)
{
bool hasChanged = SetSettingValueAndReturnIfChanged(name, value);
if (hasChanged && notify)
Invalidate(InvalidationCause.kSettingChanged);
}
private bool SetSettingValueAndReturnIfChanged(string name, object value)
{
var setting = GetSetting(name);
if (setting.field == null)
{
throw new ArgumentException(string.Format("Unable to find field {0} in {1}", name, GetType().ToString()));
}
var currentValue = setting.value;
var isUnityObject = setting.field.FieldType.IsSubclassOf(typeof(UnityEngine.Object));
// Unity object Equals implementation consider null as equal which is not what we expect
// Because then we cannot assign "None" (or null) anymore
if ((isUnityObject && currentValue != value) || (!currentValue?.Equals(value) ?? value != null))
{
setting.field.SetValue(setting.instance, value);
OnSettingModified(setting);
if (setting.instance != (object)this)
{
if (setting.instance is VFXModel model)
model.OnSettingModified(setting);
}
return true;
}
return false;
}
// Override this method to update other settings based on a setting modification
// Use OnInvalidate with KSettingChanged and not this method to handle other side effects
public virtual void OnSettingModified(VFXSetting setting) { }
public virtual IEnumerable<int> GetFilteredOutEnumerators(string name) { return null; }
public void Invalidate(InvalidationCause cause)
{
if (cause != InvalidationCause.kExpressionGraphChanged && cause != InvalidationCause.kExpressionInvalidated)
Modified(cause == InvalidationCause.kUIChanged || cause == InvalidationCause.kUIChangedTransient);
string sampleName = GetType().Name + "-" + name + "-" + cause;
Profiler.BeginSample("VFXEditor.Invalidate" + sampleName);
try
{
Invalidate(this, cause);
}
finally
{
Profiler.EndSample();
}
}
[SerializeField]
List<string> m_UIIgnoredErrors = new();
public void IgnoreError(string error)
{
if (!m_UIIgnoredErrors.Contains(error))
m_UIIgnoredErrors.Add(error);
}
public bool IsErrorIgnored(string error)
{
return m_UIIgnoredErrors.Contains(error);
}
public void ClearIgnoredErrors()
{
m_UIIgnoredErrors.Clear();
RefreshErrors();
}
public bool HasIgnoredErrors()
{
return m_UIIgnoredErrors.Any();
}
internal virtual void GenerateErrors(VFXErrorReporter report)
{
}
protected internal virtual void Invalidate(VFXModel model, InvalidationCause cause)
{
OnInvalidate(model, cause);
if (m_Parent != null)
m_Parent.Invalidate(model, cause);
if (cause is InvalidationCause.kParamChanged or InvalidationCause.kExpressionValueInvalidated or InvalidationCause.kExpressionInvalidated or InvalidationCause.kSettingChanged)
RefreshErrors();
}
private static Dictionary<Type, List<(FieldInfo field, VFXSettingAttribute attribute)>> s_CacheFieldByType;
private static readonly BindingFlags kSettingsBindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static IEnumerable<(FieldInfo field, VFXSettingAttribute attribute)> GetFields(Type type)
{
s_CacheFieldByType ??= new();
if (s_CacheFieldByType.TryGetValue(type, out var listOfSettings))
return listOfSettings;
listOfSettings = new List<(FieldInfo settings, VFXSettingAttribute attribute)>();
foreach (var field in type.GetFields(kSettingsBindingFlag))
{
var attrArray = field.GetCustomAttributes(typeof(VFXSettingAttribute), true);
if (attrArray.Length == 1)
{
var attr = (VFXSettingAttribute)attrArray[0];
listOfSettings.Add((field, attr));
}
}
s_CacheFieldByType.Add(type, listOfSettings);
return listOfSettings;
}
public virtual VFXSetting GetSetting(string name)
{
return new VFXSetting(GetType().GetField(name, kSettingsBindingFlag), this);
}
protected static bool ShouldSettingBeListed(FieldInfo field, VFXSettingAttribute attribute, bool listHidden, VFXSettingAttribute.VisibleFlags flags, IEnumerable<string> filteredOutSettings)
{
return listHidden || (attribute.visibleFlags.HasFlag(flags)
&& !filteredOutSettings.Contains(field.Name));
}
public virtual IEnumerable<VFXSetting> GetSettings(bool listHidden, VFXSettingAttribute.VisibleFlags flags = VFXSettingAttribute.VisibleFlags.Default)
{
foreach (var settings in GetFields(GetType()))
{
if (ShouldSettingBeListed(settings.field, settings.attribute, listHidden, flags, filteredOutSettings))
yield return new VFXSetting(settings.field, this, settings.attribute.visibleFlags);
}
}
static protected VFXExpression TransformExpression(VFXExpression input, SpaceableType dstSpaceType, VFXExpression matrix)
{
if (dstSpaceType == SpaceableType.Position)
{
input = new VFXExpressionTransformPosition(matrix, input);
}
else if (dstSpaceType == SpaceableType.Direction)
{
input = new VFXExpressionTransformDirection(matrix, input);
}
else if (dstSpaceType == SpaceableType.Matrix)
{
input = new VFXExpressionTransformMatrix(matrix, input);
}
else if (dstSpaceType == SpaceableType.Vector)
{
input = new VFXExpressionTransformVector(matrix, input);
}
else
{
//Not a transformable subSlot
}
return input;
}
static protected VFXExpression ConvertSpace(VFXExpression input, VFXSpace srcSpace, SpaceableType dstSpaceType, VFXSpace dstSpace)
{
if (dstSpace == VFXSpace.None || srcSpace == VFXSpace.None)
{
return input;
}
VFXExpression matrix = null;
if (dstSpace == VFXSpace.Local)
{
matrix = VFXBuiltInExpression.WorldToLocal;
}
else if (dstSpace == VFXSpace.World)
{
matrix = VFXBuiltInExpression.LocalToWorld;
}
else
{
throw new InvalidOperationException("Cannot Convert to unknown space");
}
return TransformExpression(input, dstSpaceType, matrix);
}
static protected VFXExpression ConvertSpace(VFXExpression input, SpaceableType spaceType, VFXSpace space)
{
VFXExpression matrix = null;
if (space == VFXSpace.Local)
{
matrix = VFXBuiltInExpression.WorldToLocal;
}
else if (space == VFXSpace.World)
{
matrix = VFXBuiltInExpression.LocalToWorld;
}
else
{
throw new InvalidOperationException("Cannot Convert to unknown space");
}
return TransformExpression(input, spaceType, matrix);
}
protected virtual IEnumerable<string> filteredOutSettings
{
get
{
return Enumerable.Empty<string>();
}
}
public VisualEffectResource GetResource()
{
var graph = GetGraph();
if (graph != null)
return graph.visualEffectResource;
return null;
}
public VFXGraph GetGraph()
{
switch (this)
{
case VFXGraph graph:
return graph;
case VFXSlot { owner: VFXModel m }:
return m.GetGraph();
case VFXData data:
return data.owners.FirstOrDefault()?.GetGraph();
case { } m when m.GetParent() is { } parent:
return parent.GetGraph();
}
return null;
}
public IEnumerable<VFXModel> GetRecursiveChildren()
{
yield return this;
foreach (var model in children.SelectMany(x => x.GetRecursiveChildren()))
{
yield return model;
}
}
public static void UnlinkModel(VFXModel model, bool notify = true)
{
if (model is IVFXSlotContainer slotContainer)
{
VFXSlot slotToClean = null;
do
{
slotToClean = slotContainer.inputSlots.Concat(slotContainer.outputSlots).Append(slotContainer.activationSlot).FirstOrDefault(o => o != null && o.HasLink(true));
if (slotToClean)
slotToClean.UnlinkAll(true, notify);
}
while (slotToClean != null);
}
}
public static void RemoveModel(VFXModel model, bool notify = true)
{
VFXGraph graph = model.GetGraph();
if (graph != null)
graph.UIInfos.Sanitize(graph); // Remove reference from groupInfos
UnlinkModel(model);
model.Detach(notify);
}
public static void ReplaceModel(VFXModel dst, VFXModel src, bool notify = true, bool unlink = true)
{
// UI
dst.m_UIPosition = src.m_UIPosition;
dst.m_UICollapsed = src.m_UICollapsed;
dst.m_UISuperCollapsed = src.m_UISuperCollapsed;
if (notify)
dst.Invalidate(InvalidationCause.kUIChanged);
VFXGraph graph = src.GetGraph();
if (graph != null && graph.UIInfos != null && graph.UIInfos.groupInfos != null)
{
// Update group nodes
foreach (var groupInfo in graph.UIInfos.groupInfos)
if (groupInfo.contents != null)
for (int i = 0; i < groupInfo.contents.Length; ++i)
if (groupInfo.contents[i].model == src)
groupInfo.contents[i].model = dst;
}
// Unlink everything
if (unlink)
UnlinkModel(src);
// Replace model
var parent = src.GetParent();
int index = parent.GetIndex(src);
src.Detach(notify);
if (parent)
parent.AddChild(dst, index, notify);
}
[SerializeField]
protected VFXModel m_Parent;
[SerializeField]
protected List<VFXModel> m_Children;
[SerializeField]
protected Vector2 m_UIPosition;
[SerializeField]
protected bool m_UICollapsed;
[SerializeField]
protected bool m_UISuperCollapsed;
}
abstract class VFXModel<ParentType, ChildrenType> : VFXModel
where ParentType : VFXModel
where ChildrenType : VFXModel
{
public override bool AcceptChild(VFXModel model, int index = -1)
{
return index >= -1 && index <= m_Children.Count && model is ChildrenType;
}
public new ParentType GetParent()
{
return (ParentType)m_Parent;
}
public new int GetNbChildren()
{
return m_Children.Count;
}
public new ChildrenType this[int index]
{
get { return m_Children[index] as ChildrenType; }
}
public new IEnumerable<ChildrenType> children
{
get { return m_Children.Cast<ChildrenType>(); }
}
}
}