611 lines
19 KiB
C#
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>(); }
|
|
}
|
|
}
|
|
}
|