Rasagar/Library/PackageCache/com.unity.visualeffectgraph/Editor/Inspector/AdvancedVisualEffectEditor.cs
2024-08-26 23:07:20 +03:00

772 lines
28 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.VFX;
using UnityEditor.VFX.UI;
using EditMode = UnityEditorInternal.EditMode;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.VFX
{
static class VisualEffectSerializationUtility
{
public static string GetTypeField(Type type)
{
if (type == typeof(Vector2))
{
return "m_Vector2f";
}
else if (type == typeof(Vector3))
{
return "m_Vector3f";
}
else if (type == typeof(Vector4))
{
return "m_Vector4f";
}
else if (type == typeof(Color))
{
return "m_Vector4f";
}
else if (type == typeof(AnimationCurve))
{
return "m_AnimationCurve";
}
else if (type == typeof(Gradient))
{
return "m_Gradient";
}
else if (type == typeof(Texture))
{
return "m_NamedObject";
}
else if (type == typeof(Texture2D))
{
return "m_NamedObject";
}
else if (type == typeof(Texture2DArray))
{
return "m_NamedObject";
}
else if (type == typeof(Texture3D))
{
return "m_NamedObject";
}
else if (type == typeof(Cubemap))
{
return "m_NamedObject";
}
else if (type == typeof(CubemapArray))
{
return "m_NamedObject";
}
else if (type == typeof(Mesh))
{
return "m_NamedObject";
}
else if (type == typeof(SkinnedMeshRenderer))
{
return "m_NamedObject";
}
else if (type == typeof(float))
{
return "m_Float";
}
else if (type == typeof(int))
{
return "m_Int";
}
else if (type == typeof(uint))
{
return "m_Uint";
}
else if (type == typeof(bool))
{
return "m_Bool";
}
else if (type == typeof(Matrix4x4))
{
return "m_Matrix4x4f";
}
//Debug.LogError("unknown vfx property type:"+type.UserFriendlyName());
return null;
}
}
[CustomEditor(typeof(VisualEffect))]
[CanEditMultipleObjects]
class AdvancedVisualEffectEditor : VisualEffectEditor, IToolModeOwner
{
new void OnEnable()
{
base.OnEnable();
EditMode.editModeStarted += OnEditModeStart;
EditMode.editModeEnded += OnEditModeEnd;
Selection.selectionChanged += OnHierarchySelectionChanged;
// Try to auto attach because the selection could have changed while the VFX editor was disabled
AutoAttachToSelection();
// Force rebuilding the parameter infos
VisualEffect effect = (VisualEffect)targets[0];
var asset = effect.visualEffectAsset;
if (asset != null && asset.GetResource() != null)
{
var graph = asset.GetResource().GetOrCreateGraph();
if (graph)
{
graph.BuildParameterInfo();
}
}
Undo.undoRedoPerformed += OnUndoRedoPerformed;
}
new void OnDisable()
{
// Reset play rate only when no vfx is selected anymore
if (Selection.gameObjects.All(x => x.GetComponent<VisualEffect>() == null))
{
base.OnDisable();
}
else
{
OnDisableWithoutResetting();
}
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
m_ContextsPerComponent.Clear();
EditMode.editModeStarted -= OnEditModeStart;
EditMode.editModeEnded -= OnEditModeEnd;
Selection.selectionChanged -= OnHierarchySelectionChanged;
DetachIfDeleted();
}
public override void OnInspectorGUI()
{
m_GizmoableParameters.Clear();
base.OnInspectorGUI();
}
private void OnHierarchySelectionChanged()
{
AutoAttachToSelection();
}
void OnEditModeStart(IToolModeOwner owner, EditMode.SceneViewEditMode mode)
{
if (mode == EditMode.SceneViewEditMode.Collider && owner == (IToolModeOwner)this)
OnEditStart();
}
void OnEditModeEnd(IToolModeOwner owner)
{
if (owner == (IToolModeOwner)this)
OnEditEnd();
}
void OnUndoRedoPerformed()
{
foreach (var contextAndGizmo in m_ContextsPerComponent)
{
contextAndGizmo.Value.context.Unprepare();
}
}
protected override void AssetField(VisualEffectResource resource)
{
using (new GUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(m_VisualEffectAsset, Contents.assetPath);
bool saveEnabled = GUI.enabled;
if (m_VisualEffectAsset.objectReferenceValue == null && !m_VisualEffectAsset.hasMultipleDifferentValues)
{
GUI.enabled = saveEnabled;
if (GUILayout.Button(Contents.createAsset, EditorStyles.miniButton, Styles.MiniButtonWidth))
{
CreateNewVFX();
}
}
else
{
GUI.enabled = saveEnabled && !m_VisualEffectAsset.hasMultipleDifferentValues && m_VisualEffectAsset.objectReferenceValue != null && resource != null; // Enabled state will be kept for all content until the end of the inspectorGUI.
if (GUILayout.Button(Contents.openEditor, EditorStyles.miniButton, Styles.MiniButtonWidth))
{
var asset = m_VisualEffectAsset.objectReferenceValue as VisualEffectAsset;
VFXViewWindow window = VFXViewWindow.GetWindow(asset, true);
window.LoadAsset(asset, targets.Length > 1 ? null : target as VisualEffect);
}
}
GUI.enabled = saveEnabled;
}
}
protected override void EditorModeInspectorButton()
{
EditMode.DoEditModeInspectorModeButton(
EditMode.SceneViewEditMode.Collider,
"Show Property Gizmos",
EditorGUIUtility.IconContent("EditCollider"),
this
);
}
VFXParameter GetParameter(string name, VisualEffectResource resource)
{
VisualEffect effect = (VisualEffect)target;
if (effect.visualEffectAsset == null)
return null;
VFXGraph graph = resource.graph as VFXGraph;
if (graph == null)
return null;
var parameter = graph.children.OfType<VFXParameter>().FirstOrDefault(t => t.exposedName == name && t.exposed == true);
if (parameter == null)
return null;
return parameter;
}
VFXParameter m_GizmoedParameter;
List<VFXParameter> m_GizmoableParameters = new List<VFXParameter>();
protected override void EmptyLineControl(string name, string tooltip, VFXSpace? space, int depth, VisualEffectResource resource)
{
if (depth != 1)
{
base.EmptyLineControl(name, tooltip, space, depth, resource);
return;
}
VFXParameter parameter = GetParameter(name, resource);
if (parameter == null || !VFXGizmoUtility.HasGizmo(parameter.type))
{
base.EmptyLineControl(name, tooltip, space, depth, resource);
return;
}
if (m_EditJustStarted && m_GizmoedParameter == null)
{
m_EditJustStarted = false;
m_GizmoedParameter = parameter;
}
m_GizmoableParameters.Add(parameter);
if (!m_GizmoDisplayed)
{
base.EmptyLineControl(name, tooltip, space, depth, resource);
return;
}
GUILayout.BeginHorizontal();
GUILayout.Space(Styles.overrideWidth);
// Make the label half the width to make the tooltip
EditorGUILayout.LabelField(GetGUIContent(name, tooltip), EditorStyles.boldLabel, GUILayout.Width(EditorGUIUtility.labelWidth));
GUILayout.FlexibleSpace();
// Toggle Button
EditorGUI.BeginChangeCheck();
bool result = GUILayout.Toggle(m_GizmoedParameter == parameter, new GUIContent("Edit Gizmo"), EditorStyles.miniButton);
if (EditorGUI.EndChangeCheck() && result)
{
m_GizmoedParameter = parameter;
}
//GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
}
bool m_EditJustStarted;
bool m_GizmoDisplayed;
void OnEditStart()
{
m_GizmoDisplayed = true;
m_EditJustStarted = true;
}
void OnEditEnd()
{
m_EditJustStarted = false;
m_GizmoedParameter = null;
m_GizmoDisplayed = false;
}
struct ContextAndGizmo
{
public GizmoContext context;
public VFXGizmo gizmo;
}
protected override void PropertyOverrideChanged()
{
foreach (var context in m_ContextsPerComponent.Values.Select(t => t.context))
{
context.Unprepare();
}
}
readonly Dictionary<VisualEffect, ContextAndGizmo> m_ContextsPerComponent = new();
ContextAndGizmo GetGizmo()
{
if (!m_ContextsPerComponent.TryGetValue((VisualEffect)target, out var context))
{
context.context = new GizmoContext(new SerializedObject(target), m_GizmoedParameter);
context.gizmo = VFXGizmoUtility.CreateGizmoInstance(context.context);
m_ContextsPerComponent.Add((VisualEffect)target, context);
}
else
{
var prevType = context.context.portType;
context.context.SetParameter(m_GizmoedParameter);
if (context.context.portType != prevType)
{
context.gizmo = VFXGizmoUtility.CreateGizmoInstance(context.context);
m_ContextsPerComponent[(VisualEffect)target] = context;
}
}
return context;
}
class GizmoContext : VFXGizmoUtility.Context
{
readonly SerializedObject m_SerializedObject;
readonly SerializedProperty m_VFXPropertySheet;
readonly List<Action<List<object>>> m_ValueCmdList = new();
VFXParameter m_Parameter;
public GizmoContext(SerializedObject obj, VFXParameter parameter)
{
m_SerializedObject = obj;
m_Parameter = parameter;
m_VFXPropertySheet = m_SerializedObject.FindProperty("m_PropertySheet");
}
public override Type portType => m_Parameter.type;
public override VFXSpace space => m_Parameter.outputSlots[0].space;
private readonly List<object> m_Stack = new();
public override object value
{
get
{
m_SerializedObject.Update();
m_Stack.Clear();
foreach (var cmd in m_ValueCmdList)
{
cmd(m_Stack);
}
return m_Stack[0];
}
}
public override VFXGizmo.IProperty<T> RegisterProperty<T>(string memberPath)
{
var cmdList = new List<Action<List<object>, object>>();
bool succeeded = BuildPropertyValue<T>(cmdList, m_Parameter.type, m_Parameter.exposedName, memberPath.Split(new char[] { separator[0] }, StringSplitOptions.RemoveEmptyEntries), 0);
if (succeeded)
{
return new Property<T>(m_SerializedObject, cmdList);
}
return VFXGizmoUtility.NullProperty<T>.defaultProperty;
}
void AddNewValue(List<object> l, object o, SerializedProperty vfxField, string propertyPath, string[] memberPath, int depth)
{
vfxField.InsertArrayElementAtIndex(vfxField.arraySize);
var newEntry = vfxField.GetArrayElementAtIndex(vfxField.arraySize - 1);
newEntry.FindPropertyRelative("m_Overridden").boolValue = true;
var valueProperty = newEntry.FindPropertyRelative("m_Value");
VFXSlot slot = m_Parameter.outputSlots[0];
for (int i = 0; i < memberPath.Length && i < depth; ++i)
{
slot = slot.children.First(t => t.name == memberPath[i]);
}
l.Add(slot.value); // find the default value which is in the parameter.
newEntry.FindPropertyRelative("m_Name").stringValue = propertyPath;
Unprepare(); // if we set the value we'll have to regenerate the cmdList for next time.
}
bool BuildPropertyValue<T>(List<Action<List<object>, object>> cmdList, Type type, string propertyPath, string[] memberPath, int depth, FieldInfo specialSpacableVector3CaseField = null)
{
string field = VisualEffectSerializationUtility.GetTypeField(type);
if (field != null)
{
var vfxField = m_VFXPropertySheet.FindPropertyRelative(field + ".m_Array");
if (vfxField == null)
return false;
SerializedProperty property = null;
for (int i = 0; i < vfxField.arraySize; ++i)
{
property = vfxField.GetArrayElementAtIndex(i);
var nameProperty = property.FindPropertyRelative("m_Name").stringValue;
if (nameProperty == propertyPath)
{
break;
}
property = null;
}
if (property != null)
{
SerializedProperty overrideProperty = property.FindPropertyRelative("m_Overridden");
property = property.FindPropertyRelative("m_Value");
cmdList.Add((l, o) => overrideProperty.boolValue = true);
}
else
{
cmdList.Add((l, o) =>
{
AddNewValue(l, o, vfxField, propertyPath, memberPath, depth);
});
if (depth < memberPath.Length)
{
if (!BuildPropertySubValue<T>(cmdList, type, memberPath, depth))
return false;
}
cmdList.Add((l, o) =>
{
SetObjectValue(vfxField.GetArrayElementAtIndex(vfxField.arraySize - 1).FindPropertyRelative("m_Value"), l[l.Count - 1]);
});
return true;
}
if (depth < memberPath.Length)
{
cmdList.Add((l, o) => l.Add(GetObjectValue(property)));
if (!BuildPropertySubValue<T>(cmdList, type, memberPath, depth))
return false;
cmdList.Add((l, o) => SetObjectValue(property, l[l.Count - 1]));
return true;
}
else
{
var currentValue = GetObjectValue(property);
if (specialSpacableVector3CaseField != null)
{
cmdList.Add((l, o) =>
{
var vector3Property = specialSpacableVector3CaseField.GetValue(o);
SetObjectValue(property, vector3Property);
});
return true;
}
else if (currentValue is T)
{
cmdList.Add((l, o) => SetObjectValue(property, o));
return true;
}
return false;
}
}
else if (depth < memberPath.Length)
{
FieldInfo subField = type.GetField(memberPath[depth]);
if (subField == null)
return false;
return BuildPropertyValue<T>(cmdList, subField.FieldType, propertyPath + "_" + memberPath[depth], memberPath, depth + 1);
}
else if (typeof(Position) == type || typeof(Vector) == type || typeof(DirectionType) == type)
{
if (typeof(T) != type)
{
return false;
}
FieldInfo vector3Field = type.GetFields(BindingFlags.Instance | BindingFlags.Public).First(t => t.FieldType == typeof(Vector3));
string name = vector3Field.Name;
return BuildPropertyValue<T>(cmdList, typeof(Vector3), propertyPath + "_" + name, new string[] { name }, 1, vector3Field);
}
Debug.LogError("Setting A value across multiple property is not yet supported");
return false;
}
bool BuildPropertySubValue<T>(List<Action<List<object>, object>> cmdList, Type type, string[] memberPath, int depth)
{
FieldInfo subField = type.GetField(memberPath[depth]);
if (subField == null)
return false;
depth++;
if (depth < memberPath.Length)
{
cmdList.Add((l, o) => l.Add(subField.GetValue(l[l.Count - 1])));
BuildPropertySubValue<T>(cmdList, type, memberPath, depth);
cmdList.Add((l, o) => subField.SetValue(l[l.Count - 2], l[l.Count - 1]));
cmdList.Add((l, o) => l.RemoveAt(l.Count - 1));
}
else
{
if (subField.FieldType != typeof(T))
return false;
cmdList.Add((l, o) => subField.SetValue(l[l.Count - 1], o));
}
return true;
}
public void SetParameter(VFXParameter parameter)
{
if (parameter != m_Parameter)
{
Unprepare();
m_Parameter = parameter;
}
}
protected override void InternalPrepare()
{
m_ValueCmdList.Clear();
m_Stack.Clear();
m_ValueCmdList.Add(o => o.Add(m_Parameter.value));
BuildValue(m_ValueCmdList, portType, m_Parameter.exposedName);
}
void BuildValue(List<Action<List<object>>> cmdList, Type type, string propertyPath)
{
m_SerializedObject.Update();
string field = VisualEffectSerializationUtility.GetTypeField(type);
if (field != null)
{
var vfxField = m_VFXPropertySheet.FindPropertyRelative(field + ".m_Array");
SerializedProperty property = null;
if (vfxField != null)
{
for (int i = 0; i < vfxField.arraySize; ++i)
{
property = vfxField.GetArrayElementAtIndex(i);
var nameProperty = property.FindPropertyRelative("m_Name").stringValue;
if (nameProperty == propertyPath)
{
break;
}
property = null;
}
}
if (property?.FindPropertyRelative("m_Overridden") is { } overrideProperty)
{
property = property.FindPropertyRelative("m_Value");
cmdList.Add(o => { if (overrideProperty.boolValue) PushProperty(o, property); });
}
}
else
{
foreach (var fieldInfo in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
if (fieldInfo.FieldType == typeof(VFXSpace))
continue;
cmdList.Add(o =>
{
Push(o, fieldInfo);
});
BuildValue(cmdList, fieldInfo.FieldType, propertyPath + "_" + fieldInfo.Name);
cmdList.Add(o =>
Pop(o, fieldInfo)
);
}
}
}
void PushProperty(List<object> stack, SerializedProperty property)
{
stack[stack.Count - 1] = GetObjectValue(property);
}
void Push(List<object> stack, FieldInfo fieldInfo)
{
object prev = stack[stack.Count - 1];
stack.Add(fieldInfo.GetValue(prev));
}
void Pop(List<object> stack, FieldInfo fieldInfo)
{
fieldInfo.SetValue(stack[stack.Count - 2], stack[stack.Count - 1]);
stack.RemoveAt(stack.Count - 1);
}
class Property<T> : VFXGizmo.IProperty<T>
{
readonly List<Action<List<object>, object>> m_CmdList;
readonly List<object> m_Stack = new();
readonly SerializedObject m_SerializedObject;
public Property(SerializedObject serializedObject, List<Action<List<object>, object>> cmdList)
{
m_SerializedObject = serializedObject;
m_CmdList = cmdList;
}
public bool isEditable => true;
public void SetValue(T value)
{
m_Stack.Clear();
foreach (var cmd in m_CmdList)
{
cmd(m_Stack, value);
}
m_SerializedObject.ApplyModifiedProperties();
}
}
}
bool HasFrameBounds()
{
return targets.Length == 1;
}
//Callback used by scene view on 'F' shortcut.
Bounds OnGetFrameBounds()
{
return GetWorldBoundsOfTarget(targets[0]);
}
internal override Bounds GetWorldBoundsOfTarget(UnityObject targetObject)
{
if (m_GizmoDisplayed && m_GizmoedParameter != null)
{
ContextAndGizmo context = GetGizmo();
Bounds result = VFXGizmoUtility.GetGizmoBounds(context.context, (VisualEffect)target, context.gizmo);
return result;
}
return base.GetWorldBoundsOfTarget(targetObject);
}
private void OnSceneGUI()
{
if (m_GizmoDisplayed && m_GizmoedParameter != null && m_GizmoableParameters.Count > 0 && ((VisualEffect)target).visualEffectAsset != null)
{
ContextAndGizmo context = GetGizmo();
VFXGizmoUtility.Draw(context.context, (VisualEffect)target, context.gizmo);
}
}
protected override void SceneViewGUICallback()
{
base.SceneViewGUICallback();
if (m_GizmoableParameters.Count > 0)
{
int current = m_GizmoDisplayed ? m_GizmoableParameters.IndexOf(m_GizmoedParameter) : -1;
EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
GUILayout.Label("Gizmos", GUILayout.Width(50));
int result = EditorGUILayout.Popup(current, m_GizmoableParameters.Select(t => t.exposedName).ToArray(), GUILayout.Width(140), GUILayout.Height(20));
if (EditorGUI.EndChangeCheck() && result != current)
{
m_GizmoedParameter = m_GizmoableParameters[result];
if (!m_GizmoDisplayed)
{
m_GizmoDisplayed = true;
EditMode.ChangeEditMode(EditMode.SceneViewEditMode.Collider, this);
}
Repaint();
}
if (m_GizmoedParameter != null && m_GizmoDisplayed)
{
var context = GetGizmo();
var gizmoError = VFXGizmoUtility.CollectGizmoError(context.context, (VisualEffect)target);
if (gizmoError != GizmoError.None)
{
var content = VFXSlotContainerEditor.Contents.GetGizmoErrorContent(gizmoError);
GUILayout.Label(content, VFXSlotContainerEditor.Styles.warningStyle, GUILayout.Width(19),
GUILayout.Height(18));
}
else if (GUILayout.Button(VFXSlotContainerEditor.Contents.gizmoFrame, VFXSlotContainerEditor.Styles.frameButtonStyle, GUILayout.Width(19), GUILayout.Height(18)))
{
var bounds = VFXGizmoUtility.GetGizmoBounds(context.context, (VisualEffect)target, context.gizmo);
context.context.Unprepare(); //Restore initial state : if gizmo isn't actually rendered, it could be out of sync
var sceneView = SceneView.lastActiveSceneView;
if (sceneView)
sceneView.Frame(bounds, false);
}
}
else
{
var saveEnabled = GUI.enabled;
GUI.enabled = false;
GUILayout.Button(VFXSlotContainerEditor.Contents.gizmoFrame, VFXSlotContainerEditor.Styles.frameButtonStyle, GUILayout.Width(19), GUILayout.Height(18));
GUI.enabled = saveEnabled;
}
GUILayout.EndHorizontal();
}
}
private void AutoAttachToSelection()
{
if ((Selection.activeObject as GameObject)?.GetComponent<VisualEffect>() is VisualEffect visualEffect)
{
foreach (var window in VFXViewWindow.GetAllWindows())
{
window.AttachTo(visualEffect);
}
}
}
private void DetachIfDeleted()
{
VFXViewWindow.GetAllWindows().ToList().ForEach(x => x.DetachIfDeleted());
}
private void CreateNewVFX()
{
void OnTemplateCreate(string templateFilePath)
{
if (!string.IsNullOrEmpty(templateFilePath))
{
var asset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(templateFilePath);
m_VisualEffectAsset.objectReferenceValue = asset;
serializedObject.ApplyModifiedProperties();
VFXViewWindow.GetWindow(asset, true).LoadAsset(asset, targets.Length > 1 ? target as VisualEffect : null);
}
}
VFXTemplateWindow.ShowCreateFromTemplate(null, OnTemplateCreate);
}
}
}