Files
FlaxEngine/Source/Editor/Surface/SurfaceUtils.cs

596 lines
25 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Options;
using FlaxEditor.Scripting;
using FlaxEngine.Utilities;
using FlaxEngine;
using FlaxEditor.GUI;
namespace FlaxEditor.Surface
{
internal static class SurfaceUtils
{
internal struct GraphParameterData
{
public GraphParameter Parameter;
public bool IsPublic;
public Type Type;
public object Tag;
public Attribute[] Attributes;
public EditorOrderAttribute Order;
public EditorDisplayAttribute Display;
public TooltipAttribute Tooltip;
public SpaceAttribute Space;
public HeaderAttribute Header;
public string DisplayName;
public GraphParameterData(GraphParameter parameter, object tag = null)
: this(parameter, null, parameter.IsPublic, parameter.Type, SurfaceMeta.GetAttributes(parameter), tag)
{
}
public GraphParameterData(GraphParameter parameter, string name, bool isPublic, Type type, Attribute[] attributes, object tag)
{
Parameter = parameter;
IsPublic = isPublic;
Type = type;
Tag = tag;
Attributes = attributes;
Order = (EditorOrderAttribute)Attributes.FirstOrDefault(x => x is EditorOrderAttribute);
Display = (EditorDisplayAttribute)Attributes.FirstOrDefault(x => x is EditorDisplayAttribute);
Tooltip = (TooltipAttribute)Attributes.FirstOrDefault(x => x is TooltipAttribute);
Space = (SpaceAttribute)Attributes.FirstOrDefault(x => x is SpaceAttribute);
Header = (HeaderAttribute)Attributes.FirstOrDefault(x => x is HeaderAttribute);
DisplayName = Display?.Name ?? name ?? parameter.Name;
}
public static int Compare(GraphParameterData x, GraphParameterData y)
{
// By order
if (x.Order != null)
{
if (y.Order != null)
return x.Order.Order - y.Order.Order;
return -1;
}
if (y.Order != null)
return 1;
// By group name
if (x.Display?.Group != null)
{
if (y.Display?.Group != null)
return string.Compare(x.Display.Group, y.Display.Group, StringComparison.InvariantCulture);
}
// By name
if (Editor.Instance.Options.Options.General.ScriptMembersOrder == GeneralOptions.MembersOrder.Alphabetical)
return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture);
// Keep same order
return 0;
}
}
private static Type ToType(MaterialParameterType type)
{
switch (type)
{
case MaterialParameterType.Bool: return typeof(bool);
case MaterialParameterType.Integer: return typeof(int);
case MaterialParameterType.Float: return typeof(float);
case MaterialParameterType.Vector2: return typeof(Vector2);
case MaterialParameterType.Vector3: return typeof(Vector3);
case MaterialParameterType.Vector4: return typeof(Vector4);
case MaterialParameterType.Color: return typeof(Color);
case MaterialParameterType.Texture: return typeof(Texture);
case MaterialParameterType.CubeTexture: return typeof(CubeTexture);
case MaterialParameterType.NormalMap: return typeof(Texture);
case MaterialParameterType.SceneTexture: return typeof(MaterialSceneTextures);
case MaterialParameterType.GPUTexture: return typeof(GPUTexture);
case MaterialParameterType.Matrix: return typeof(Matrix);
case MaterialParameterType.ChannelMask: return typeof(ChannelMask);
case MaterialParameterType.GameplayGlobal: return typeof(GameplayGlobals);
case MaterialParameterType.TextureGroupSampler: return typeof(int);
case MaterialParameterType.GlobalSDF: return typeof(object);
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
internal static GroupElement InitGraphParametersGroup(LayoutElementsContainer layout)
{
return CustomEditors.Editors.GenericEditor.OnGroup(layout, "Parameters");
}
private sealed class DummyMaterialSurfaceOwner : IVisjectSurfaceOwner
{
public Asset SurfaceAsset => null;
public string SurfaceName => null;
public FlaxEditor.Undo Undo => null;
public byte[] SurfaceData { get; set; }
public VisjectSurfaceContext ParentContext => null;
public void OnContextCreated(VisjectSurfaceContext context)
{
}
public void OnSurfaceEditedChanged()
{
}
public void OnSurfaceGraphEdited()
{
}
public void OnSurfaceClose()
{
}
}
private static void FindGraphParameters(Material material, List<SurfaceParameter> surfaceParameters)
{
if (material == null || material.WaitForLoaded())
return;
var surfaceData = material.LoadSurface(false);
if (surfaceData != null && surfaceData.Length > 0)
{
var surfaceOwner = new DummyMaterialSurfaceOwner { SurfaceData = surfaceData };
var surface = new MaterialSurface(surfaceOwner);
if (!surface.Load())
{
surfaceParameters.AddRange(surface.Parameters);
// Search for any nested parameters (eg. via Sample Layer)
foreach (var node in surface.Nodes)
{
if (node.GroupArchetype.GroupID == 8 && node.Archetype.TypeID == 1) // Sample Layer
{
if (node.Values != null && node.Values.Length > 0 && node.Values[0] is Guid layerId)
{
var layer = FlaxEngine.Content.Load<MaterialBase>(layerId);
if (layer)
{
FindGraphParameters(layer, surfaceParameters);
}
}
}
}
}
}
}
private static void FindGraphParameters(MaterialBase materialBase, List<SurfaceParameter> surfaceParameters)
{
while (materialBase != null && !materialBase.WaitForLoaded())
{
if (materialBase is MaterialInstance materialInstance)
{
materialBase = materialInstance.BaseMaterial;
}
else if (materialBase is Material material)
{
FindGraphParameters(material, surfaceParameters);
break;
}
}
}
internal static GraphParameterData[] InitGraphParameters(IEnumerable<MaterialParameter> parameters, Material material)
{
int count = parameters.Count();
var data = new GraphParameterData[count];
int i = 0;
// Load material surface parameters meta to use it for material instance parameters editing
var surfaceParameters = new List<SurfaceParameter>();
try
{
Profiler.BeginEvent("Init Material Parameters UI Data");
FindGraphParameters(material, surfaceParameters);
}
catch (Exception ex)
{
Editor.LogError("Failed to get material parameters metadata.");
Editor.LogWarning(ex);
}
finally
{
Profiler.EndEvent();
}
foreach (var parameter in parameters)
{
var parameterId = parameter.ParameterID;
var surfaceParameter = surfaceParameters.FirstOrDefault(x => x.ID == parameterId);
if (surfaceParameter == null)
{
// Permutate original parameter ID to reflect logic in MaterialGenerator::prepareLayer used for nested layers
unsafe
{
var raw = parameterId;
var interop = *(FlaxEngine.Json.JsonSerializer.GuidInterop*)&raw;
interop.A -= (uint)(i * 17 + 13);
parameterId = *(Guid*)&interop;
}
surfaceParameter = surfaceParameters.FirstOrDefault(x => x.ID == parameterId);
}
if (surfaceParameter != null)
{
// Reorder so it won't be picked by other parameter that uses the same ID (eg. params from duplicated materials used as layers in other material)
surfaceParameters.Remove(surfaceParameter);
surfaceParameters.Add(surfaceParameter);
}
var attributes = surfaceParameter?.Meta.GetAttributes() ?? FlaxEngine.Utils.GetEmptyArray<Attribute>();
data[i] = new GraphParameterData(null, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
return data;
}
internal static GraphParameterData[] InitGraphParameters(IEnumerable<ParticleEffectParameter> parameters)
{
int count = parameters.Count();
var data = new GraphParameterData[count];
int i = 0;
foreach (var parameter in parameters)
{
data[i] = new GraphParameterData(parameter.EmitterParameter, parameter);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
return data;
}
internal static GraphParameterData[] InitGraphParameters(IEnumerable<GraphParameter> parameters)
{
int count = parameters.Count();
var data = new GraphParameterData[count];
int i = 0;
foreach (var parameter in parameters)
{
data[i] = new GraphParameterData(parameter);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
return data;
}
internal delegate object GetGraphParameterDelegate(object instance, GraphParameter parameter, object parameterTag);
internal delegate void SetGraphParameterDelegate(object instance, object value, GraphParameter parameter, object parameterTag);
internal delegate void CustomPropertySpawnDelegate(LayoutElementsContainer itemLayout, ValueContainer valueContainer, ref GraphParameterData e);
internal static void DisplayGraphParameters(LayoutElementsContainer layout, GraphParameterData[] data, GetGraphParameterDelegate getter, SetGraphParameterDelegate setter, ValueContainer values, GetGraphParameterDelegate defaultValueGetter = null, CustomPropertySpawnDelegate propertySpawn = null)
{
CustomEditors.Editors.GenericEditor.OnGroupsBegin();
for (int i = 0; i < data.Length; i++)
{
ref var e = ref data[i];
if (!e.IsPublic)
continue;
var tag = e.Tag;
var parameter = e.Parameter;
// Editor Display
var itemLayout = CustomEditors.Editors.GenericEditor.OnGroup(layout, e.Display);
if (itemLayout is GroupElement groupElement)
groupElement.Panel.Open(false);
// Space
if (e.Space != null)
itemLayout.Space(e.Space.Height);
// Header
if (e.Header != null)
itemLayout.Header(e.Header);
// Values container
var valueType = new ScriptType(e.Type);
var valueContainer = new CustomValueContainer(valueType, (instance, index) => getter(values[index], parameter, tag), (instance, index, value) => setter(values[index], value, parameter, tag), e.Attributes);
for (int j = 0; j < values.Count; j++)
valueContainer.Add(getter(values[j], parameter, tag));
// Default value
if (defaultValueGetter != null)
valueContainer.SetDefaultValue(defaultValueGetter(values[0], parameter, tag));
// Prefab value
if (values.HasReferenceValue)
{
var referenceValue = getter(values.ReferenceValue, parameter, tag);
if (referenceValue != null)
{
valueContainer.SetReferenceValue(referenceValue);
}
}
if (propertySpawn == null)
itemLayout.Property(e.DisplayName, valueContainer, null, e.Tooltip?.Text);
else
propertySpawn(itemLayout, valueContainer, ref e);
}
CustomEditors.Editors.GenericEditor.OnGroupsEnd();
}
internal static string GetMethodDisplayName(string methodName)
{
if (methodName.StartsWith("get_"))
return "Get " + methodName.Substring(4);
if (methodName.StartsWith("set_"))
return "Set " + methodName.Substring(4);
if (methodName.StartsWith("op_"))
return "Operator " + methodName.Substring(3);
return methodName;
}
internal static bool IsValidVisualScriptInvokeMethod(ScriptMemberInfo member, out ScriptMemberInfo.Parameter[] parameters)
{
parameters = null;
if (member.IsMethod &&
IsValidVisualScriptType(member.ValueType) &&
!member.HasAttribute(typeof(HideInEditorAttribute), true))
{
parameters = member.GetParameters();
if (parameters.Length <= 27)
{
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (!IsValidVisualScriptType(parameter.Type))
return false;
}
return true;
}
}
return false;
}
internal static bool IsValidVisualScriptOverrideMethod(ScriptMemberInfo member, out ScriptMemberInfo.Parameter[] parameters)
{
if (member.IsMethod &&
member.IsVirtual &&
IsValidVisualScriptType(member.ValueType) &&
member.HasAttribute(typeof(UnmanagedAttribute), true) &&
!member.HasAttribute(typeof(HideInEditorAttribute), true))
{
parameters = member.GetParameters();
if (parameters.Length < 31)
{
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (!IsValidVisualScriptType(parameter.Type) || parameter.IsOut)
return false;
}
return true;
}
}
parameters = null;
return false;
}
internal static bool IsValidVisualScriptField(ScriptMemberInfo member)
{
return member.IsField && IsValidVisualScriptType(member.ValueType);
}
internal static bool IsValidVisualScriptEvent(ScriptMemberInfo member)
{
return member.IsEvent && member.HasAttribute(typeof(UnmanagedAttribute));
}
internal static bool IsValidVisualScriptType(ScriptType scriptType)
{
if (!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
if (scriptType.IsGenericType)
{
// Only Dictionary generic type is valid
return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
}
var managedType = TypeUtils.GetType(scriptType);
return managedType != null && !TypeUtils.IsDelegate(managedType);
}
internal static bool IsValidVisualScriptFunctionType(ScriptType scriptType)
{
if (scriptType.IsGenericType || scriptType.IsStatic || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
return false;
var managedType = TypeUtils.GetType(scriptType);
return managedType != null && !TypeUtils.IsDelegate(managedType);
}
internal static string GetVisualScriptTypeDescription(ScriptType type)
{
if (type == ScriptType.Null)
return "null";
var sb = new StringBuilder();
if (type.IsStatic)
sb.Append("static ");
else if (type.IsAbstract)
sb.Append("abstract ");
sb.Append(Editor.Instance.CodeDocs.GetTooltip(type));
return sb.ToString();
}
internal static string GetVisualScriptMemberInfoSignature(ScriptMemberInfo member)
{
var name = member.Name;
var declaringType = member.DeclaringType;
var valueType = member.ValueType;
// Getter/setter method of the property - we can return early here
if (member.IsMethod && (name.StartsWith("get_") || name.StartsWith("set_")))
{
var flags = member.IsStatic ? BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly : BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
var property = declaringType.GetMembers(name.Substring(4), MemberTypes.Property, flags);
if (property != null && property.Length != 0)
{
return GetVisualScriptMemberInfoSignature(property[0]);
}
}
var sb = new StringBuilder();
if (member.IsStatic)
sb.Append("static ");
else if (member.IsVirtual)
sb.Append("virtual ");
sb.Append(valueType.Name);
sb.Append(' ');
if (member.IsMethod)
sb.Append(member.DeclaringType.Namespace).Append('.');
sb.Append(declaringType.Name);
sb.Append('.');
sb.Append(name);
if (member.IsMethod)
{
sb.Append('(');
var parameters = member.GetParameters();
for (int i = 0; i < parameters.Length; i++)
{
if (i != 0)
sb.Append(", ");
ref var param = ref parameters[i];
param.ToString(sb);
}
sb.Append(')');
}
else if (member.IsProperty)
{
sb.Append(' ');
sb.Append('{');
if (member.HasGet)
sb.Append(" get;");
if (member.HasSet)
sb.Append(" set;");
sb.Append(' ');
sb.Append('}');
}
return sb.ToString();
}
internal static string GetVisualScriptMemberShortDescription(ScriptMemberInfo member)
{
var name = member.Name;
var declaringType = member.DeclaringType;
// Getter/setter method of the property - we can return early here
if (member.IsMethod && (name.StartsWith("get_") || name.StartsWith("set_")))
{
var flags = member.IsStatic ? BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly : BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
var property = declaringType.GetMembers(name.Substring(4), MemberTypes.Property, flags);
if (property != null && property.Length != 0)
{
return GetVisualScriptMemberShortDescription(property[0]);
}
}
return Editor.Instance.CodeDocs.GetTooltip(member);
}
internal static string GetVisualScriptMemberInfoDescription(ScriptMemberInfo member)
{
var signature = GetVisualScriptMemberInfoSignature(member);
var sb = new StringBuilder(signature);
// Tooltip
var tooltip = GetVisualScriptMemberShortDescription(member);
if (!string.IsNullOrEmpty(tooltip))
sb.Append("\n").Append(tooltip);
return sb.ToString();
}
internal static Double4 GetDouble4(object v, float w = 0.0f)
{
var value = new Double4(0, 0, 0, w);
if (v is Vector2 vec2)
value = new Double4(vec2, 0.0f, w);
else if (v is Vector3 vec3)
value = new Double4(vec3, w);
else if (v is Vector4 vec4)
value = vec4;
else if (v is Float2 float2)
value = new Double4(float2, 0.0, w);
else if (v is Float3 float3)
value = new Double4(float3, w);
else if (v is Float4 float4)
value = float4;
else if (v is Double2 double2)
value = new Double4(double2, 0.0, w);
else if (v is Double3 double3)
value = new Double4(double3, w);
else if (v is Double4 double4)
value = double4;
else if (v is Color col)
value = new Double4(col.R, col.G, col.B, col.A);
else if (v is float f)
value = new Double4(f);
else if (v is int i)
value = new Double4(i);
return value;
}
private static bool AreScriptTypesEqualInner(ScriptType left, ScriptType right)
{
// Special case for Vector types that use typedefs and might overlap
if (left.Type == typeof(Vector2) && (right.Type == typeof(Float2) || right.Type == typeof(Double2)))
return true;
if (left.Type == typeof(Vector3) && (right.Type == typeof(Float3) || right.Type == typeof(Double3)))
return true;
if (left.Type == typeof(Vector4) && (right.Type == typeof(Float4) || right.Type == typeof(Double4)))
return true;
return false;
}
internal static bool AreScriptTypesEqual(ScriptType left, ScriptType right)
{
if (left == right)
return true;
return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left);
}
internal static void PerformCommonSetup(Windows.Assets.AssetEditorWindow window, ToolStrip toolStrip, VisjectSurface surface,
out ToolStripButton saveButton, out ToolStripButton undoButton, out ToolStripButton redoButton)
{
var editor = window.Editor;
var interfaceOptions = editor.Options.Options.Interface;
var inputOptions = editor.Options.Options.Input;
var undo = surface.Undo;
var showSearch = () => editor.ContentFinding.ShowSearch(window);
// Toolstrip
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save);
toolStrip.AddSeparator();
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo);
toolStrip.AddSeparator();
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph");
var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping);
gridSnapButton.LinkTooltip("Toggle grid snapping for nodes.");
gridSnapButton.AutoCheck = true;
gridSnapButton.Checked = surface.GridSnappingEnabled = interfaceOptions.SurfaceGridSnapping;
surface.GridSnappingSize = interfaceOptions.SurfaceGridSnappingSize;
// Setup input actions
window.InputActions.Add(options => options.Undo, undo.PerformUndo);
window.InputActions.Add(options => options.Redo, undo.PerformRedo);
window.InputActions.Add(options => options.Search, showSearch);
}
}
}