// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.Scripting; using FlaxEditor.Utilities; using FlaxEngine.Utilities; using FlaxEngine; 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 return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture); } } 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 GraphParameterData[] InitGraphParameters(IEnumerable 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 SurfaceParameter[] surfaceParameters = null; try { Profiler.BeginEvent("Init Material Parameters UI Data"); if (material != null && !material.WaitForLoaded()) { var surfaceData = material.LoadSurface(false); if (surfaceData != null && surfaceData.Length > 0) { using (var memoryStream = new MemoryStream(surfaceData)) using (var stream = new BinaryReader(memoryStream)) { // IMPORTANT! This must match C++ Graph format // Magic Code int tmp = stream.ReadInt32(); if (tmp != 1963542358) { // Error throw new Exception("Invalid Graph format version"); } // Version var version = stream.ReadUInt32(); var guidBytes = new byte[16]; if (version < 7000) { // Time saved (not used anymore to prevent binary diffs after saving unmodified surface) stream.ReadInt64(); // Nodes count int nodesCount = stream.ReadInt32(); // Parameters count int parametersCount = stream.ReadInt32(); // For each node for (int j = 0; j < nodesCount; j++) { // ID stream.ReadUInt32(); // Type stream.ReadUInt16(); stream.ReadUInt16(); } // For each param surfaceParameters = new SurfaceParameter[parametersCount]; for (int j = 0; j < parametersCount; j++) { // Create param var param = new SurfaceParameter(); surfaceParameters[j] = param; // Properties param.Type = new ScriptType(VisjectSurfaceContext.GetGraphParameterValueType((VisjectSurfaceContext.GraphParamType_Deprecated)stream.ReadByte())); stream.Read(guidBytes, 0, 16); param.ID = new Guid(guidBytes); param.Name = stream.ReadStr(97); param.IsPublic = stream.ReadByte() != 0; var isStatic = stream.ReadByte() != 0; var isUIVisible = stream.ReadByte() != 0; var isUIEditable = stream.ReadByte() != 0; // References [Deprecated] int refsCount = stream.ReadInt32(); for (int k = 0; k < refsCount; k++) stream.ReadUInt32(); // Value stream.ReadCommonValue(ref param.Value); // Meta param.Meta.Load(stream); } } else if (version == 7000) { // Nodes count int nodesCount = stream.ReadInt32(); // Parameters count int parametersCount = stream.ReadInt32(); // For each node for (int j = 0; j < nodesCount; j++) { // ID stream.ReadUInt32(); // Type stream.ReadUInt16(); stream.ReadUInt16(); } // For each param surfaceParameters = new SurfaceParameter[parametersCount]; for (int j = 0; j < parametersCount; j++) { // Create param var param = new SurfaceParameter(); surfaceParameters[j] = param; // Properties param.Type = stream.ReadVariantScriptType(); stream.Read(guidBytes, 0, 16); param.ID = new Guid(guidBytes); param.Name = stream.ReadStr(97); param.IsPublic = stream.ReadByte() != 0; // Value param.Value = stream.ReadVariant(); // Meta param.Meta.Load(stream); } } } } } } catch (Exception ex) { Editor.LogError("Failed to get material parameters metadata."); Editor.LogWarning(ex); } finally { Profiler.EndEvent(); } foreach (var parameter in parameters) { var surfaceParameter = surfaceParameters?.FirstOrDefault(x => x.ID == parameter.ParameterID); var attributes = surfaceParameter?.Meta.GetAttributes() ?? FlaxEngine.Utils.GetEmptyArray(); 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 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 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 !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 !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 GetVisualScriptMemberInfoDescription(ScriptMemberInfo member) { var name = member.Name; var declaringType = member.DeclaringType; var valueType = member.ValueType; var sb = new StringBuilder(); if (member.IsStatic) sb.Append("static "); else if (member.IsVirtual) sb.Append("virtual "); sb.Append(valueType.Name); sb.Append(' '); sb.Append(declaringType.Name); sb.Append('.'); sb.Append(name); if (member.IsMethod) { // Getter/setter method of the property if (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 GetVisualScriptMemberInfoDescription(property[0]); } } // Method else { 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('}'); } // Tooltip var tooltip = Editor.Instance.CodeDocs.GetTooltip(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; } } }