611 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.VFX;
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()
{
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
kExpressionGraphChanged,// Expression graph must be recomputed
kUIChanged, // UI stuff has changed
kUIChangedTransient, // UI stuff has been changed be does not require serialization
kEnableChanged, // Node has been enabled/disabled
}
public new virtual string name { get { return string.Empty; } }
public virtual string libraryName { get { return name; } }
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 GetSourceDependentAssets(HashSet<string> dependencies)
{
foreach (var child in children)
child.GetSourceDependentAssets(dependencies);
}
public virtual void GetImportDependentAssets(HashSet<int> dependencies)
{
//var monoScript = MonoScript.FromScriptableObject(this);
//dependencies.Add(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(monoScript)));
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 void RefreshErrors(VFXGraph graph)
{
if (graph != null)
{
graph.errorManager.ClearAllErrors(this, VFXErrorOrigin.Invalidate);
using (var reporter = new VFXInvalidateErrorReporter(graph.errorManager, this))
{
try
{
GenerateErrors(reporter);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
}
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 hasChanged = false;
foreach (var kvp in nameValues)
{
if (SetSettingValueAndReturnIfChanged(kvp.Key, kvp.Value))
hasChanged = true;
}
if (hasChanged)
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;
if (currentValue != value)
{
setting.field.SetValue(setting.instance, value);
OnSettingModified(setting);
if (setting.instance != this)
setting.instance.OnSettingModified(setting);
return true;
}
return false;
}
// Override this method to update other settings based on a setting modification
// Use OnIvalidate 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 virtual VFXSetting GetSetting(string name)
{
return new VFXSetting(GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), this);
}
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 List<string>();
public void IgnoreError(string error)
{
if (m_UIIgnoredErrors == null)
m_UIIgnoredErrors = new List<string>();
if (!m_UIIgnoredErrors.Contains(error))
m_UIIgnoredErrors.Add(error);
}
public bool IsErrorIgnored(string error)
{
return m_UIIgnoredErrors != null && m_UIIgnoredErrors.Contains(error);
}
public void ClearIgnoredErrors()
{
m_UIIgnoredErrors = null;
RefreshErrors(GetGraph());
}
public bool HasIgnoredErrors()
{
return m_UIIgnoredErrors != null && m_UIIgnoredErrors.Count > 0;
}
protected virtual void GenerateErrors(VFXInvalidateErrorReporter manager)
{
}
protected internal virtual void Invalidate(VFXModel model, InvalidationCause cause)
{
OnInvalidate(model, cause);
if (m_Parent != null)
m_Parent.Invalidate(model, cause);
}
public virtual IEnumerable<VFXSetting> GetSettings(bool listHidden, VFXSettingAttribute.VisibleFlags flags = VFXSettingAttribute.VisibleFlags.All)
{
return GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(f =>
{
var attrArray = f.GetCustomAttributes(typeof(VFXSettingAttribute), true);
if (attrArray.Length == 1)
{
var attr = attrArray[0] as VFXSettingAttribute;
if (listHidden)
return true;
return (attr.visibleFlags & flags) != 0 && !filteredOutSettings.Contains(f.Name);
}
return false;
}).Select(field => new VFXSetting(field, this));
}
static public VFXExpression ConvertSpace(VFXExpression input, VFXSlot targetSlot, VFXCoordinateSpace space)
{
if (targetSlot.spaceable)
{
if (targetSlot.space != space)
{
var spaceType = targetSlot.GetSpaceTransformationType();
input = ConvertSpace(input, spaceType, space);
}
}
return input;
}
static protected VFXExpression ConvertSpace(VFXExpression input, SpaceableType spaceType, VFXCoordinateSpace space)
{
VFXExpression matrix = null;
if (space == VFXCoordinateSpace.Local)
{
matrix = VFXBuiltInExpression.WorldToLocal;
}
else if (space == VFXCoordinateSpace.World)
{
matrix = VFXBuiltInExpression.LocalToWorld;
}
else
{
throw new InvalidOperationException("Cannot Convert to unknown space");
}
if (spaceType == SpaceableType.Position)
{
input = new VFXExpressionTransformPosition(matrix, input);
}
else if (spaceType == SpaceableType.Direction)
{
input = new VFXExpressionTransformDirection(matrix, input);
}
else if (spaceType == SpaceableType.Matrix)
{
input = new VFXExpressionTransformMatrix(matrix, input);
}
else if (spaceType == SpaceableType.Vector)
{
input = new VFXExpressionTransformVector(matrix, input);
}
else
{
//Not a transformable subSlot
}
return input;
}
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()
{
var graph = this as VFXGraph;
if (graph != null)
return graph;
var parent = GetParent();
if (parent != null)
return parent.GetGraph();
return null;
}
public static void UnlinkModel(VFXModel model, bool notify = true)
{
if (model is IVFXSlotContainer)
{
var slotContainer = (IVFXSlotContainer)model;
VFXSlot slotToClean = null;
do
{
slotToClean = slotContainer.inputSlots.Concat(slotContainer.outputSlots).FirstOrDefault(o => 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)
{
// 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;
}
if (dst is VFXBlock && src is VFXBlock)
{
((VFXBlock)dst).enabled = ((VFXBlock)src).enabled;
}
// Unlink everything
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>(); }
}
}
}