// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using Newtonsoft.Json; namespace FlaxEditor.Utilities { /// /// Editor utilities and helper functions. /// public static class Utils { private static readonly string[] MemorySizePostfixes = { "B", "kB", "MB", "GB", "TB", "PB" }; internal enum VariantType { Null = 0, Void, Bool, Int, Uint, Int64, Uint64, Float, Double, Pointer, String, Object, Structure, Asset, Blob, Enum, Vector2, Vector3, Vector4, Color, Guid, BoundingBox, BoundingSphere, Quaternion, Transform, Rectangle, Ray, Matrix, Array, Dictionary, ManagedObject, Typename, } /// /// The name of the Flax Engine C# assembly name. /// public static readonly string FlaxEngineAssemblyName = "FlaxEngine.CSharp"; /// /// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB /// /// The bytes. /// The formatted amount of bytes. public static string FormatBytesCount(int bytes) { int order = 0; while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1) { order++; bytes = bytes / 1024; } return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]); } /// /// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB /// /// The bytes. /// The formatted amount of bytes. public static string FormatBytesCount(ulong bytes) { int order = 0; while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1) { order++; bytes = bytes / 1024; } return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]); } /// /// The colors for the keyframes used by the curve editor. /// internal static readonly Color[] CurveKeyframesColors = { Color.OrangeRed, Color.ForestGreen, Color.CornflowerBlue, Color.White, }; /// /// The time/value axes tick steps for editors with timeline. /// internal static readonly float[] CurveTickSteps = { 0.0000001f, 0.0000005f, 0.000001f, 0.000005f, 0.00001f, 0.00005f, 0.0001f, 0.0005f, 0.001f, 0.005f, 0.01f, 0.05f, 0.1f, 0.5f, 1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000, 5000000, 10000000, 100000000 }; /// /// Determines whether the specified path string contains any invalid character. /// /// The path. /// true if the given string cannot be used as a path because it contains one or more illegal characters; otherwise, false. public static bool HasInvalidPathChar(string path) { char[] illegalChars = { '?', '\\', '/', '\"', '<', '>', '|', ':', '*', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F' }; return path.IndexOfAny(illegalChars) != -1; } internal static Transform[] GetTransformsAndBounds(List nodes, out BoundingBox bounds) { var transforms = new Transform[nodes.Count]; bounds = BoundingBox.Empty; for (int i = 0; i < nodes.Count; i++) { transforms[i] = nodes[i].Transform; if (nodes[i] is ActorNode actorNode) { bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren); } } return transforms; } /// /// Removes the file if it exists. /// /// The file path. public static void RemoveFileIfExists(string file) { if (File.Exists(file)) File.Delete(file); } /// /// Copies the directory. Supports subdirectories copy with files override option. /// /// The source directory path. /// The destination directory path. /// If set to true copy subdirectories. /// if set to true override existing files. public static void DirectoryCopy(string srcDirectoryPath, string dstDirectoryPath, bool copySubDirs = true, bool overrideFiles = false) { var dir = new DirectoryInfo(srcDirectoryPath); if (!dir.Exists) { throw new DirectoryNotFoundException("Missing source directory to copy. " + srcDirectoryPath); } if (!Directory.Exists(dstDirectoryPath)) { Directory.CreateDirectory(dstDirectoryPath); } var files = dir.GetFiles(); for (int i = 0; i < files.Length; i++) { string tmp = Path.Combine(dstDirectoryPath, files[i].Name); files[i].CopyTo(tmp, overrideFiles); } if (copySubDirs) { var dirs = dir.GetDirectories(); for (int i = 0; i < dirs.Length; i++) { string tmp = Path.Combine(dstDirectoryPath, dirs[i].Name); DirectoryCopy(dirs[i].FullName, tmp, true, overrideFiles); } } } /// /// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects. /// /// The structure type. /// The data bytes. /// The structure. public static T ByteArrayToStructure(byte[] bytes) where T : struct { // #stupid c# GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); return stuff; } /// /// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects. /// /// The data bytes. /// The structure type. /// The structure. public static object ByteArrayToStructure(byte[] bytes, Type type) { // #stupid c# GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); object stuff = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type); handle.Free(); return stuff; } /// /// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects. /// /// The structure type. /// The data bytes. /// The result. public static void ByteArrayToStructure(byte[] bytes, out T result) where T : struct { // #stupid c# GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); result = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); } /// /// Converts the structure to the raw bytes. Supported only for structures with simple types and no GC objects. /// /// The structure type. /// The structure value. /// The bytes array that contains a structure data. public static byte[] StructureToByteArray(ref T value) where T : struct { // #stupid c# int size = Marshal.SizeOf(typeof(T)); byte[] arr = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(value, ptr, true); Marshal.Copy(ptr, arr, 0, size); Marshal.FreeHGlobal(ptr); return arr; } /// /// Converts the structure to the raw bytes. Supported only for structures with simple types and no GC objects. /// /// The structure value. /// The structure type. /// The bytes array that contains a structure data. public static byte[] StructureToByteArray(object value, Type type) { // #stupid c# int size = Marshal.SizeOf(type); byte[] arr = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(value, ptr, true); Marshal.Copy(ptr, arr, 0, size); Marshal.FreeHGlobal(ptr); return arr; } internal static unsafe string ReadStr(this BinaryReader stream, int check) { int length = stream.ReadInt32(); if (length > 0 && length < 4 * 1024) { var str = stream.ReadBytes(length * 2); fixed (byte* strPtr = str) { var ptr = (char*)strPtr; for (int j = 0; j < length; j++) ptr[j] = (char)(ptr[j] ^ check); } return System.Text.Encoding.Unicode.GetString(str); } return string.Empty; } internal static unsafe string ReadStrAnsi(this BinaryReader stream, int check) { int length = stream.ReadInt32(); if (length > 0 && length < 4 * 1024) { var str = stream.ReadBytes(length); fixed (byte* strPtr = str) { var ptr = strPtr; for (int j = 0; j < length; j++) ptr[j] = (byte)(ptr[j] ^ check); } return System.Text.Encoding.ASCII.GetString(str); } return string.Empty; } internal static unsafe void WriteStr(this BinaryWriter stream, string str, int check) { int length = str?.Length ?? 0; stream.Write(length); if (length == 0) return; var bytes = System.Text.Encoding.Unicode.GetBytes(str); if (bytes.Length != length * 2) throw new ArgumentException(); fixed (byte* bytesPtr = bytes) { var ptr = (char*)bytesPtr; for (int j = 0; j < length; j++) ptr[j] = (char)(ptr[j] ^ check); } stream.Write(bytes); } internal static unsafe void WriteStrAnsi(this BinaryWriter stream, string str, int check) { int length = str?.Length ?? 0; stream.Write(length); if (length == 0) return; var bytes = System.Text.Encoding.ASCII.GetBytes(str); if (bytes.Length != length) throw new ArgumentException(); fixed (byte* bytesPtr = bytes) { var ptr = bytesPtr; for (int j = 0; j < length; j++) ptr[j] = (byte)(ptr[j] ^ check); } stream.Write(bytes); } internal static VariantType ToVariantType(this Type type) { VariantType variantType; if (type == null) variantType = VariantType.Null; else if (type == typeof(void)) variantType = VariantType.Void; else if (type == typeof(bool)) variantType = VariantType.Bool; else if (type == typeof(int)) variantType = VariantType.Int; else if (type == typeof(uint)) variantType = VariantType.Uint; else if (type == typeof(long)) variantType = VariantType.Int64; else if (type == typeof(ulong)) variantType = VariantType.Uint64; else if (type.IsEnum) variantType = VariantType.Enum; else if (type == typeof(float)) variantType = VariantType.Float; else if (type == typeof(double)) variantType = VariantType.Double; else if (type == typeof(IntPtr)) variantType = VariantType.Pointer; else if (type == typeof(string)) variantType = VariantType.String; else if (type == typeof(Type) || type == typeof(ScriptType)) variantType = VariantType.Typename; else if (typeof(Asset).IsAssignableFrom(type)) variantType = VariantType.Asset; else if (typeof(FlaxEngine.Object).IsAssignableFrom(type)) variantType = VariantType.Object; else if (type == typeof(BoundingBox)) variantType = VariantType.BoundingBox; else if (type == typeof(Transform)) variantType = VariantType.Transform; else if (type == typeof(Ray)) variantType = VariantType.Ray; else if (type == typeof(Matrix)) variantType = VariantType.Matrix; else if (type == typeof(Vector2)) variantType = VariantType.Vector2; else if (type == typeof(Vector3)) variantType = VariantType.Vector3; else if (type == typeof(Vector4)) variantType = VariantType.Vector4; else if (type == typeof(Color)) variantType = VariantType.Color; else if (type == typeof(Guid)) variantType = VariantType.Guid; else if (type == typeof(Quaternion)) variantType = VariantType.Quaternion; else if (type == typeof(Rectangle)) variantType = VariantType.Rectangle; else if (type == typeof(BoundingSphere)) variantType = VariantType.BoundingSphere; else if (type.IsValueType) variantType = VariantType.Structure; else if (type == typeof(byte[])) variantType = VariantType.Blob; else if (type == typeof(object[])) variantType = VariantType.Array; else if (type == typeof(Dictionary)) variantType = VariantType.Dictionary; else if (type.IsPointer || type.IsByRef) { // Find underlying type without `*` or `&` var typeName = type.FullName; type = TypeUtils.GetManagedType(typeName.Substring(0, typeName.Length - 1)); return ToVariantType(type); } else variantType = VariantType.ManagedObject; return variantType; } internal static void WriteVariantType(this BinaryWriter stream, ScriptType type) { if (type == ScriptType.Null) { stream.Write((byte)0); stream.Write(0); return; } if (type.Type != null) { WriteVariantType(stream, type.Type); } else { if (type.IsEnum) stream.Write((byte)VariantType.Enum); else if (type.IsValueType) stream.Write((byte)VariantType.Structure); else stream.Write((byte)VariantType.Object); stream.Write(int.MaxValue); stream.WriteStrAnsi(type.TypeName, 77); } } internal static void WriteVariantType(this BinaryWriter stream, Type type) { if (type == null) { stream.Write((byte)0); stream.Write(0); return; } var variantType = ToVariantType(type); stream.Write((byte)variantType); switch (variantType) { case VariantType.Object: if (type != typeof(FlaxEngine.Object)) { stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); } else stream.Write(0); break; case VariantType.Asset: if (type != typeof(Asset)) { stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); } else stream.Write(0); break; case VariantType.Enum: case VariantType.Structure: stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); break; default: stream.Write(0); break; } } internal static Guid ReadGuid(this BinaryReader stream) { // TODO: use static bytes array to reduce dynamic allocs return new Guid(stream.ReadBytes(16)); } internal static void WriteGuid(this BinaryWriter stream, ref Guid value) { // TODO: use static bytes array to reduce dynamic allocs stream.Write(value.ToByteArray()); } internal static Rectangle ReadRectangle(this BinaryReader stream) { return new Rectangle(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()); } internal static void WriteRectangle(this BinaryWriter stream, ref Rectangle value) { stream.Write(value.Location.X); stream.Write(value.Location.Y); stream.Write(value.Size.X); stream.Write(value.Size.Y); } internal static void ReadCommonValue(this BinaryReader stream, ref object value) { byte type = stream.ReadByte(); switch (type) { case 0: // CommonType::Bool: value = stream.ReadByte() != 0; break; case 1: // CommonType::Integer: { value = stream.ReadInt32(); } break; case 2: // CommonType::Float: { value = stream.ReadSingle(); } break; case 3: // CommonType::Vector2: { value = stream.ReadVector2(); } break; case 4: // CommonType::Vector3: { value = stream.ReadVector3(); } break; case 5: // CommonType::Vector4: { value = stream.ReadVector4(); } break; case 6: // CommonType::Color: { value = stream.ReadColor(); } break; case 7: // CommonType::Guid: { value = stream.ReadGuid(); } break; case 8: // CommonType::String: { int length = stream.ReadInt32(); if (length <= 0) { value = string.Empty; } else { var data = new char[length]; for (int i = 0; i < length; i++) { var c = stream.ReadUInt16(); data[i] = (char)(c ^ 953); } value = new string(data); } break; } case 9: // CommonType::Box: { value = new BoundingBox(new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()), new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle())); } break; case 10: // CommonType::Rotation: { value = stream.ReadQuaternion(); } break; case 11: // CommonType::Transform: { value = new Transform(new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()), new Quaternion(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()), new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle())); } break; case 12: // CommonType::Sphere: { value = new BoundingSphere(new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()), stream.ReadSingle()); } break; case 13: // CommonType::Rect: { value = new Rectangle(new Vector2(stream.ReadSingle(), stream.ReadSingle()), new Vector2(stream.ReadSingle(), stream.ReadSingle())); } break; case 15: // CommonType::Matrix { value = new Matrix(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()); break; } case 16: // CommonType::Blob { int length = stream.ReadInt32(); value = stream.ReadBytes(length); break; } case 18: // CommonType::Ray { value = new Ray(new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()), new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle())); break; } default: throw new SystemException(); } } internal static ScriptType ReadVariantScriptType(this BinaryReader stream) { var variantType = (VariantType)stream.ReadByte(); var typeNameLength = stream.ReadInt32(); if (typeNameLength == int.MaxValue) { typeNameLength = stream.ReadInt32(); var data = new byte[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadByte(); data[i] = (byte)(c ^ 77); } var typeName = System.Text.Encoding.ASCII.GetString(data); return TypeUtils.GetType(typeName); } if (typeNameLength > 0) { // [Deprecated on 27.08.2020, expires on 27.08.2021] var data = new char[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadUInt16(); data[i] = (char)(c ^ 77); } var typeName = new string(data); return TypeUtils.GetType(typeName); } switch (variantType) { case VariantType.Null: return ScriptType.Null; case VariantType.Void: return new ScriptType(typeof(void)); case VariantType.Bool: return new ScriptType(typeof(bool)); case VariantType.Int: return new ScriptType(typeof(int)); case VariantType.Uint: return new ScriptType(typeof(uint)); case VariantType.Int64: return new ScriptType(typeof(long)); case VariantType.Uint64: return new ScriptType(typeof(ulong)); case VariantType.Float: return new ScriptType(typeof(float)); case VariantType.Double: return new ScriptType(typeof(double)); case VariantType.Pointer: return new ScriptType(typeof(IntPtr)); case VariantType.String: return new ScriptType(typeof(string)); case VariantType.Typename: return new ScriptType(typeof(Type)); case VariantType.Object: return new ScriptType(typeof(FlaxEngine.Object)); case VariantType.Asset: return new ScriptType(typeof(Asset)); case VariantType.Vector2: return new ScriptType(typeof(Vector2)); case VariantType.Vector3: return new ScriptType(typeof(Vector3)); case VariantType.Vector4: return new ScriptType(typeof(Vector4)); case VariantType.Color: return new ScriptType(typeof(Color)); case VariantType.Guid: return new ScriptType(typeof(Guid)); case VariantType.BoundingBox: return new ScriptType(typeof(BoundingBox)); case VariantType.BoundingSphere: return new ScriptType(typeof(BoundingSphere)); case VariantType.Quaternion: return new ScriptType(typeof(Quaternion)); case VariantType.Transform: return new ScriptType(typeof(Transform)); case VariantType.Rectangle: return new ScriptType(typeof(Rectangle)); case VariantType.Ray: return new ScriptType(typeof(Ray)); case VariantType.Matrix: return new ScriptType(typeof(Matrix)); case VariantType.Array: return new ScriptType(typeof(object[])); case VariantType.Dictionary: return new ScriptType(typeof(Dictionary)); case VariantType.ManagedObject: return new ScriptType(typeof(object)); default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); } } internal static Type ReadVariantType(this BinaryReader stream) { var variantType = (VariantType)stream.ReadByte(); var typeNameLength = stream.ReadInt32(); if (typeNameLength == int.MaxValue) { typeNameLength = stream.ReadInt32(); var data = new byte[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadByte(); data[i] = (byte)(c ^ 77); } var typeName = System.Text.Encoding.ASCII.GetString(data); return TypeUtils.GetType(typeName).Type; } if (typeNameLength > 0) { // [Deprecated on 27.08.2020, expires on 27.08.2021] var data = new char[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadUInt16(); data[i] = (char)(c ^ 77); } var typeName = new string(data); return TypeUtils.GetType(typeName).Type; } switch (variantType) { case VariantType.Null: return null; case VariantType.Void: return typeof(void); case VariantType.Bool: return typeof(bool); case VariantType.Int: return typeof(int); case VariantType.Uint: return typeof(uint); case VariantType.Int64: return typeof(long); case VariantType.Uint64: return typeof(ulong); case VariantType.Float: return typeof(float); case VariantType.Double: return typeof(double); case VariantType.Pointer: return typeof(IntPtr); case VariantType.String: return typeof(string); case VariantType.Typename: return typeof(Type); case VariantType.Object: return typeof(FlaxEngine.Object); case VariantType.Asset: return typeof(Asset); case VariantType.Vector2: return typeof(Vector2); case VariantType.Vector3: return typeof(Vector3); case VariantType.Vector4: return typeof(Vector4); case VariantType.Color: return typeof(Color); case VariantType.Guid: return typeof(Guid); case VariantType.BoundingBox: return typeof(BoundingBox); case VariantType.BoundingSphere: return typeof(BoundingSphere); case VariantType.Quaternion: return typeof(Quaternion); case VariantType.Transform: return typeof(Transform); case VariantType.Rectangle: return typeof(Rectangle); case VariantType.Ray: return typeof(Ray); case VariantType.Matrix: return typeof(Matrix); case VariantType.Array: return typeof(object[]); case VariantType.Dictionary: return typeof(Dictionary); case VariantType.ManagedObject: return typeof(object); default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); } } internal static unsafe object ReadVariant(this BinaryReader stream) { Type type = null; var variantType = (VariantType)stream.ReadByte(); var typeNameLength = stream.ReadInt32(); if (typeNameLength == int.MaxValue) { typeNameLength = stream.ReadInt32(); var data = new byte[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadByte(); data[i] = (byte)(c ^ 77); } var typeName = System.Text.Encoding.ASCII.GetString(data); type = TypeUtils.GetType(typeName).Type; } else if (typeNameLength > 0) { // [Deprecated on 27.08.2020, expires on 27.08.2021] var data = new char[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadUInt16(); data[i] = (char)(c ^ 77); } var typeName = new string(data); type = TypeUtils.GetType(typeName).Type; } switch (variantType) { case VariantType.Null: case VariantType.ManagedObject: case VariantType.Void: return null; case VariantType.Bool: return stream.ReadByte() != 0; case VariantType.Int: return stream.ReadInt32(); case VariantType.Uint: return stream.ReadUInt32(); case VariantType.Int64: return stream.ReadInt64(); case VariantType.Uint64: return stream.ReadUInt64(); case VariantType.Float: return stream.ReadSingle(); case VariantType.Double: return stream.ReadDouble(); case VariantType.Pointer: return new IntPtr((void*)stream.ReadUInt64()); case VariantType.String: { typeNameLength = stream.ReadInt32(); var data = new char[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadUInt16(); data[i] = (char)(c ^ -14); } return new string(data); } case VariantType.Object: { var id = stream.ReadGuid(); return FlaxEngine.Object.Find(ref id, type ?? typeof(FlaxEngine.Object)); } case VariantType.Structure: { if (type == null) throw new Exception("Missing structure type of the Variant."); var data = stream.ReadBytes(stream.ReadInt32()); return ByteArrayToStructure(data, type); } case VariantType.Asset: { var id = stream.ReadGuid(); return FlaxEngine.Object.Find(ref id, type ?? typeof(Asset)); } case VariantType.Blob: return stream.ReadBytes(stream.ReadInt32()); case VariantType.Enum: { if (type == null) throw new Exception("Missing enum type of the Variant."); return Enum.ToObject(type, stream.ReadUInt64()); } case VariantType.Vector2: return stream.ReadVector2(); case VariantType.Vector3: return stream.ReadVector3(); case VariantType.Vector4: return stream.ReadVector4(); case VariantType.Color: return stream.ReadColor(); case VariantType.Guid: return stream.ReadGuid(); case VariantType.BoundingBox: return stream.ReadBoundingBox(); case VariantType.BoundingSphere: return stream.ReadBoundingSphere(); case VariantType.Quaternion: return stream.ReadQuaternion(); case VariantType.Transform: return stream.ReadTransform(); case VariantType.Rectangle: return stream.ReadRectangle(); case VariantType.Ray: return stream.ReadRay(); case VariantType.Matrix: return stream.ReadMatrix(); case VariantType.Array: { var count = stream.ReadInt32(); var result = new object[count]; for (int i = 0; i < count; i++) result[i] = stream.ReadVariant(); return result; } case VariantType.Dictionary: { var count = stream.ReadInt32(); var result = new Dictionary(); for (int i = 0; i < count; i++) result.Add(stream.ReadVariant(), stream.ReadVariant()); return result; } case VariantType.Typename: { typeNameLength = stream.ReadInt32(); var data = new byte[typeNameLength]; for (int i = 0; i < typeNameLength; i++) { var c = stream.ReadByte(); data[i] = (byte)(c ^ -14); } var typeName = System.Text.Encoding.ASCII.GetString(data); return TypeUtils.GetType(typeName); } default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.FullName}" : string.Empty)); } } internal static void WriteCommonValue(this BinaryWriter stream, object value) { if (value is bool asBool) { stream.Write((byte)0); stream.Write((byte)(asBool ? 1 : 0)); } else if (value is int asInt) { stream.Write((byte)1); stream.Write(asInt); } else if (value is long asLong) { stream.Write((byte)1); stream.Write((int)asLong); } else if (value is float asFloat) { stream.Write((byte)2); stream.Write(asFloat); } else if (value is double asDouble) { stream.Write((byte)2); stream.Write((float)asDouble); } else if (value is Vector2 asVector2) { stream.Write((byte)3); stream.Write(asVector2.X); stream.Write(asVector2.Y); } else if (value is Vector3 asVector3) { stream.Write((byte)4); stream.Write(asVector3.X); stream.Write(asVector3.Y); stream.Write(asVector3.Z); } else if (value is Vector4 asVector4) { stream.Write((byte)5); stream.Write(asVector4.X); stream.Write(asVector4.Y); stream.Write(asVector4.Z); stream.Write(asVector4.W); } else if (value is Color asColor) { stream.Write((byte)6); stream.Write(asColor.R); stream.Write(asColor.G); stream.Write(asColor.B); stream.Write(asColor.A); } else if (value is Guid asGuid) { stream.Write((byte)7); stream.WriteGuid(ref asGuid); } else if (value is string asString) { stream.Write((byte)8); stream.Write(asString.Length); for (int i = 0; i < asString.Length; i++) stream.Write((ushort)(asString[i] ^ -14)); } else if (value is BoundingBox asBox) { stream.Write((byte)9); stream.Write(asBox.Minimum.X); stream.Write(asBox.Minimum.Y); stream.Write(asBox.Minimum.Z); stream.Write(asBox.Maximum.X); stream.Write(asBox.Maximum.Y); stream.Write(asBox.Maximum.Z); } else if (value is Quaternion asRotation) { stream.Write((byte)10); stream.Write(asRotation.X); stream.Write(asRotation.Y); stream.Write(asRotation.Z); stream.Write(asRotation.X); } else if (value is Transform asTransform) { stream.Write((byte)11); stream.Write(asTransform.Translation.X); stream.Write(asTransform.Translation.Y); stream.Write(asTransform.Translation.Z); stream.Write(asTransform.Orientation.X); stream.Write(asTransform.Orientation.Y); stream.Write(asTransform.Orientation.Z); stream.Write(asTransform.Orientation.X); stream.Write(asTransform.Scale.X); stream.Write(asTransform.Scale.Y); stream.Write(asTransform.Scale.Z); } else if (value is BoundingSphere asSphere) { stream.Write((byte)12); stream.Write(asSphere.Center.X); stream.Write(asSphere.Center.Y); stream.Write(asSphere.Center.Z); stream.Write(asSphere.Radius); } else if (value is Rectangle asRect) { stream.Write((byte)13); stream.Write(asRect.Location.X); stream.Write(asRect.Location.Y); stream.Write(asRect.Size.X); stream.Write(asRect.Size.Y); } else if (value is Matrix asMatrix) { stream.Write((byte)15); stream.Write(asMatrix.M11); stream.Write(asMatrix.M12); stream.Write(asMatrix.M13); stream.Write(asMatrix.M14); stream.Write(asMatrix.M21); stream.Write(asMatrix.M22); stream.Write(asMatrix.M23); stream.Write(asMatrix.M24); stream.Write(asMatrix.M31); stream.Write(asMatrix.M32); stream.Write(asMatrix.M33); stream.Write(asMatrix.M34); stream.Write(asMatrix.M41); stream.Write(asMatrix.M42); stream.Write(asMatrix.M43); stream.Write(asMatrix.M44); } else if (value is byte[] asBlob) { stream.Write((byte)16); stream.Write(asBlob.Length); stream.Write(asBlob); } else if (value is Ray asRay) { stream.Write((byte)18); stream.Write(asRay.Position.X); stream.Write(asRay.Position.Y); stream.Write(asRay.Position.Z); stream.Write(asRay.Direction.X); stream.Write(asRay.Direction.Y); stream.Write(asRay.Direction.Z); } else { throw new NotSupportedException(string.Format("Invalid Common Value type {0}", value != null ? value.GetType().ToString() : "null")); } } internal static void WriteVariant(this BinaryWriter stream, object value) { if (value == null) { stream.Write((byte)0); stream.Write(0); return; } var type = value.GetType(); var variantType = ToVariantType(type); stream.Write((byte)variantType); switch (variantType) { case VariantType.Object: if (type != typeof(FlaxEngine.Object)) { stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); } else stream.Write(0); break; case VariantType.Asset: if (type != typeof(Asset)) { stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); } else stream.Write(0); break; case VariantType.Enum: case VariantType.Structure: stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); break; default: stream.Write(0); break; } Guid id; switch (variantType) { case VariantType.Bool: stream.Write((byte)((bool)value ? 1 : 0)); break; case VariantType.Int: stream.Write((int)value); break; case VariantType.Uint: stream.Write((uint)value); break; case VariantType.Int64: stream.Write((long)value); break; case VariantType.Uint64: stream.Write((ulong)value); break; case VariantType.Float: stream.Write((float)value); break; case VariantType.Double: stream.Write((double)value); break; case VariantType.Pointer: stream.Write((ulong)(IntPtr)value); break; case VariantType.String: stream.WriteStr((string)value, -14); break; case VariantType.Object: id = ((FlaxEngine.Object)value).ID; stream.WriteGuid(ref id); break; case VariantType.Structure: { var data = StructureToByteArray(value, type); stream.Write(data.Length); stream.Write(data); break; } case VariantType.Asset: id = ((Asset)value).ID; stream.WriteGuid(ref id); break; case VariantType.Blob: stream.Write(((byte[])value).Length); stream.Write((byte[])value); break; case VariantType.Enum: stream.Write(Convert.ToUInt64(value)); break; case VariantType.Vector2: stream.Write((Vector2)value); break; case VariantType.Vector3: stream.Write((Vector3)value); break; case VariantType.Vector4: stream.Write((Vector4)value); break; case VariantType.Color: stream.Write((Color)value); break; case VariantType.Guid: id = (Guid)value; stream.WriteGuid(ref id); break; case VariantType.BoundingBox: stream.Write((BoundingBox)value); break; case VariantType.BoundingSphere: stream.Write((BoundingSphere)value); break; case VariantType.Quaternion: stream.Write((Quaternion)value); break; case VariantType.Transform: stream.Write((Transform)value); break; case VariantType.Rectangle: stream.Write((Rectangle)value); break; case VariantType.Ray: stream.Write((Ray)value); break; case VariantType.Matrix: stream.Write((Matrix)value); break; case VariantType.Array: stream.Write(((object[])value).Length); foreach (var e in (object[])value) stream.WriteVariant(e); break; case VariantType.Dictionary: stream.Write(((Dictionary)value).Count); foreach (var e in (Dictionary)value) { stream.WriteVariant(e.Key); stream.WriteVariant(e.Value); } break; case VariantType.Typename: if (value is Type) stream.WriteStrAnsi(((Type)value).FullName, -14); else if (value is ScriptType) stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); break; } } internal static void WriteVariantType(this JsonWriter stream, Type value) { var variantType = ToVariantType(value); var withoutTypeName = true; switch (variantType) { case VariantType.Object: if (value != typeof(FlaxEngine.Object)) withoutTypeName = false; break; case VariantType.Asset: if (value != typeof(Asset)) withoutTypeName = false; break; case VariantType.Enum: case VariantType.Structure: withoutTypeName = false; break; } if (withoutTypeName) { stream.WriteValue((int)variantType); } else { stream.WriteStartObject(); stream.WritePropertyName("Type"); stream.WriteValue((int)variantType); stream.WritePropertyName("TypeName"); stream.WriteValue(value.FullName); stream.WriteEndObject(); } } [SuppressMessage("ReSharper", "PossibleNullReferenceException")] [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] internal static void WriteVariant(this JsonWriter stream, object value) { var type = value?.GetType(); stream.WriteStartObject(); stream.WritePropertyName("Type"); stream.WriteVariantType(type); stream.WritePropertyName("Value"); var variantType = ToVariantType(type); switch (variantType) { case VariantType.Null: case VariantType.ManagedObject: case VariantType.Void: stream.WriteStartObject(); stream.WriteEndObject(); break; case VariantType.Bool: stream.WriteValue((bool)value); break; case VariantType.Int: stream.WriteValue((int)value); break; case VariantType.Uint: stream.WriteValue((uint)value); break; case VariantType.Int64: stream.WriteValue((long)value); break; case VariantType.Uint64: stream.WriteValue((ulong)value); break; case VariantType.Float: stream.WriteValue((float)value); break; case VariantType.Double: stream.WriteValue((double)value); break; case VariantType.Pointer: stream.WriteValue((ulong)(IntPtr)value); break; case VariantType.String: stream.WriteValue((string)value); break; case VariantType.Object: stream.WriteValue(((FlaxEngine.Object)value).ID); break; case VariantType.Asset: stream.WriteValue(((Asset)value).ID); break; case VariantType.Blob: stream.WriteValue(Convert.ToBase64String((byte[])value)); break; case VariantType.Enum: stream.WriteValue(Convert.ToUInt64(value)); break; case VariantType.Vector2: { var asVector2 = (Vector2)value; stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asVector2.X); stream.WritePropertyName("Y"); stream.WriteValue(asVector2.Y); stream.WriteEndObject(); break; } case VariantType.Vector3: { var asVector3 = (Vector3)value; stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asVector3.X); stream.WritePropertyName("Y"); stream.WriteValue(asVector3.Y); stream.WritePropertyName("Z"); stream.WriteValue(asVector3.Z); stream.WriteEndObject(); break; } case VariantType.Vector4: { var asVector4 = (Vector4)value; stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asVector4.X); stream.WritePropertyName("Y"); stream.WriteValue(asVector4.Y); stream.WritePropertyName("Z"); stream.WriteValue(asVector4.Z); stream.WritePropertyName("W"); stream.WriteValue(asVector4.W); stream.WriteEndObject(); break; } case VariantType.Color: { var asColor = (Color)value; stream.WriteStartObject(); stream.WritePropertyName("R"); stream.WriteValue(asColor.R); stream.WritePropertyName("G"); stream.WriteValue(asColor.G); stream.WritePropertyName("B"); stream.WriteValue(asColor.B); stream.WritePropertyName("A"); stream.WriteValue(asColor.A); stream.WriteEndObject(); break; } case VariantType.Guid: stream.WriteValue((Guid)value); break; case VariantType.BoundingBox: { var asBoundingBox = (BoundingBox)value; stream.WriteStartObject(); stream.WritePropertyName("Minimum"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asBoundingBox.Minimum.X); stream.WritePropertyName("Y"); stream.WriteValue(asBoundingBox.Minimum.Y); stream.WritePropertyName("Z"); stream.WriteValue(asBoundingBox.Minimum.Z); stream.WriteEndObject(); stream.WritePropertyName("Maximum"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asBoundingBox.Maximum.X); stream.WritePropertyName("Y"); stream.WriteValue(asBoundingBox.Maximum.Y); stream.WritePropertyName("Z"); stream.WriteValue(asBoundingBox.Maximum.Z); stream.WriteEndObject(); stream.WriteEndObject(); break; } case VariantType.BoundingSphere: { var asBoundingSphere = (BoundingSphere)value; stream.WriteStartObject(); stream.WritePropertyName("Center"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asBoundingSphere.Center.X); stream.WritePropertyName("Y"); stream.WriteValue(asBoundingSphere.Center.Y); stream.WritePropertyName("Z"); stream.WriteValue(asBoundingSphere.Center.Z); stream.WriteEndObject(); stream.WritePropertyName("Radius"); stream.WriteValue(asBoundingSphere.Radius); stream.WriteEndObject(); break; } case VariantType.Quaternion: { var asQuaternion = (Quaternion)value; stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asQuaternion.X); stream.WritePropertyName("Y"); stream.WriteValue(asQuaternion.Y); stream.WritePropertyName("Z"); stream.WriteValue(asQuaternion.Z); stream.WritePropertyName("W"); stream.WriteValue(asQuaternion.W); stream.WriteEndObject(); break; } case VariantType.Transform: { var asTransform = (Transform)value; stream.WriteStartObject(); stream.WritePropertyName("Translation"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asTransform.Translation.X); stream.WritePropertyName("Y"); stream.WriteValue(asTransform.Translation.Y); stream.WritePropertyName("Z"); stream.WriteValue(asTransform.Translation.Z); stream.WriteEndObject(); stream.WritePropertyName("Orientation"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asTransform.Orientation.X); stream.WritePropertyName("Y"); stream.WriteValue(asTransform.Orientation.Y); stream.WritePropertyName("Z"); stream.WriteValue(asTransform.Orientation.Z); stream.WritePropertyName("W"); stream.WriteValue(asTransform.Orientation.W); stream.WriteEndObject(); stream.WritePropertyName("Scale"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asTransform.Scale.X); stream.WritePropertyName("Y"); stream.WriteValue(asTransform.Scale.Y); stream.WritePropertyName("Z"); stream.WriteValue(asTransform.Scale.Z); stream.WriteEndObject(); stream.WriteEndObject(); break; } case VariantType.Rectangle: { var asRectangle = (Rectangle)value; stream.WriteStartObject(); stream.WritePropertyName("Location"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asRectangle.Location.X); stream.WritePropertyName("Y"); stream.WriteValue(asRectangle.Location.Y); stream.WriteEndObject(); stream.WritePropertyName("Size"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asRectangle.Size.X); stream.WritePropertyName("Y"); stream.WriteValue(asRectangle.Size.Y); stream.WriteEndObject(); stream.WriteEndObject(); break; } case VariantType.Ray: { var asRay = (Ray)value; stream.WriteStartObject(); stream.WritePropertyName("Position"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asRay.Position.X); stream.WritePropertyName("Y"); stream.WriteValue(asRay.Position.Y); stream.WritePropertyName("Z"); stream.WriteValue(asRay.Position.Z); stream.WriteEndObject(); stream.WritePropertyName("Direction"); stream.WriteStartObject(); stream.WritePropertyName("X"); stream.WriteValue(asRay.Direction.X); stream.WritePropertyName("Y"); stream.WriteValue(asRay.Direction.Y); stream.WritePropertyName("Z"); stream.WriteValue(asRay.Direction.Z); stream.WriteEndObject(); stream.WriteEndObject(); break; } case VariantType.Matrix: { var asMatrix = (Matrix)value; stream.WriteStartObject(); stream.WritePropertyName("M11"); stream.WriteValue(asMatrix.M11); stream.WritePropertyName("M12"); stream.WriteValue(asMatrix.M12); stream.WritePropertyName("M13"); stream.WriteValue(asMatrix.M13); stream.WritePropertyName("M14"); stream.WriteValue(asMatrix.M14); stream.WritePropertyName("M21"); stream.WriteValue(asMatrix.M21); stream.WritePropertyName("M22"); stream.WriteValue(asMatrix.M22); stream.WritePropertyName("M23"); stream.WriteValue(asMatrix.M23); stream.WritePropertyName("M24"); stream.WriteValue(asMatrix.M24); stream.WritePropertyName("M31"); stream.WriteValue(asMatrix.M31); stream.WritePropertyName("M32"); stream.WriteValue(asMatrix.M32); stream.WritePropertyName("M33"); stream.WriteValue(asMatrix.M33); stream.WritePropertyName("M34"); stream.WriteValue(asMatrix.M34); stream.WritePropertyName("M41"); stream.WriteValue(asMatrix.M41); stream.WritePropertyName("M42"); stream.WriteValue(asMatrix.M42); stream.WritePropertyName("M43"); stream.WriteValue(asMatrix.M43); stream.WritePropertyName("M44"); stream.WriteValue(asMatrix.M44); stream.WriteEndObject(); break; } case VariantType.Array: { stream.WriteStartArray(); foreach (var e in (object[])value) stream.WriteVariant(e); stream.WriteEndArray(); break; } case VariantType.Dictionary: { stream.WriteStartArray(); foreach (var e in (Dictionary)value) { stream.WritePropertyName("Key"); stream.WriteVariant(e.Key); stream.WritePropertyName("Value"); stream.WriteVariant(e.Value); } stream.WriteEndArray(); break; } case VariantType.Typename: if (value is Type) stream.WriteValue(((Type)value).FullName); else if (value is ScriptType) stream.WriteValue(((ScriptType)value).TypeName); break; default: throw new NotImplementedException($"TODO: serialize {variantType} to Json"); } stream.WriteEndObject(); } /// /// Shows the source code window. /// /// The source code. /// The window title. public static void ShowSourceCodeWindow(string source, string title) { if (string.IsNullOrEmpty(source)) { MessageBox.Show("No generated shader source code.", "No source."); return; } var settings = CreateWindowSettings.Default; settings.ActivateWhenFirstShown = true; settings.AllowMaximize = false; settings.AllowMinimize = false; settings.HasSizingFrame = false; settings.StartPosition = WindowStartPosition.CenterScreen; settings.Size = new Vector2(500, 600) * Platform.DpiScale; settings.Title = title; var dialog = Platform.CreateWindow(ref settings); var copyButton = new Button(4, 4, 100) { Text = "Copy", Parent = dialog.GUI, }; copyButton.Clicked += () => Clipboard.Text = source; var sourceTextBox = new TextBox(true, 2, copyButton.Bottom + 4, settings.Size.X - 4); sourceTextBox.Height = settings.Size.Y - sourceTextBox.Top - 2; sourceTextBox.Text = source; sourceTextBox.Parent = dialog.GUI; dialog.Show(); dialog.Focus(); } private static OrientedBoundingBox GetWriteBox(ref Vector3 min, ref Vector3 max, float margin) { var box = new OrientedBoundingBox(); Vector3 vec = max - min; Vector3 dir = Vector3.Normalize(vec); Quaternion orientation; if (Vector3.Dot(dir, Vector3.Up) >= 0.999f) orientation = Quaternion.RotationAxis(Vector3.Left, Mathf.PiOverTwo); else orientation = Quaternion.LookRotation(dir, Vector3.Cross(Vector3.Cross(dir, Vector3.Up), dir)); Vector3 up = Vector3.Up * orientation; box.Transformation = Matrix.CreateWorld(min + vec * 0.5f, dir, up); Matrix inv; Matrix.Invert(ref box.Transformation, out inv); Vector3 vecLocal = Vector3.TransformNormal(vec * 0.5f, inv); box.Extents.X = margin; box.Extents.Y = margin; box.Extents.Z = vecLocal.Z; return box; } /// /// Checks if given ray intersects with the oriented bounding wire box. /// /// The box. /// The ray. /// The result intersection distance. /// The view position used to scale the wires thickness depending on the wire distance from the view. /// True ray hits bounds, otherwise false. public static unsafe bool RayCastWire(ref OrientedBoundingBox box, ref Ray ray, out float distance, ref Vector3 viewPosition) { var corners = stackalloc Vector3[8]; box.GetCorners(corners); var minDistance = Vector3.DistanceSquared(ref viewPosition, ref corners[0]); for (int i = 1; i < 8; i++) minDistance = Mathf.Min(minDistance, Vector3.DistanceSquared(ref viewPosition, ref corners[i])); minDistance = Mathf.Sqrt(minDistance); var margin = Mathf.Clamp(minDistance / 80.0f, 0.1f, 100.0f); if (GetWriteBox(ref corners[0], ref corners[1], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[0], ref corners[3], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[0], ref corners[4], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[1], ref corners[2], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[1], ref corners[5], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[2], ref corners[3], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[2], ref corners[6], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[3], ref corners[7], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[4], ref corners[5], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[4], ref corners[7], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[5], ref corners[6], margin).Intersects(ref ray, out distance) || GetWriteBox(ref corners[6], ref corners[7], margin).Intersects(ref ray, out distance)) return true; distance = 0; return false; } } }