diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index ccf712904..c7832cfe1 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -343,6 +343,7 @@ namespace FlaxEditor.CustomEditors } private bool _buildOnUpdate; + private bool _initialized; private bool _readOnly; /// @@ -430,6 +431,7 @@ namespace FlaxEditor.CustomEditors ClearLayout(); _buildOnUpdate = false; + _initialized = true; Editor.Setup(this); Panel.IsLayoutLocked = false; @@ -506,7 +508,11 @@ namespace FlaxEditor.CustomEditors /// protected virtual void OnSelectionChanged() { - BuildLayout(); + // Defer building the layout after we have initialized to improve initial loading times + if (!_initialized) + _buildOnUpdate = true; + else + BuildLayout(); SelectionChanged?.Invoke(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index d3af7b5cd..3eace0363 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -355,7 +355,7 @@ namespace FlaxEditor.GUI.ContextMenu if (_previouslyFocused != null) { _previouslyFocused.RootWindow?.Focus(); - _previouslyFocused.Focus(); + _previouslyFocused?.Focus(); _previouslyFocused = null; } diff --git a/Source/Editor/Gizmo/DirectionGizmo.cs b/Source/Editor/Gizmo/DirectionGizmo.cs index b76cd0607..aedfe73d0 100644 --- a/Source/Editor/Gizmo/DirectionGizmo.cs +++ b/Source/Editor/Gizmo/DirectionGizmo.cs @@ -222,7 +222,10 @@ internal class DirectionGizmo : ContainerControl else { // This could be some actual math expression, not that hack - var fov = _owner.Viewport.FieldOfView / 60.0f; + float fov = _owner.Viewport.FieldOfView; + if (_owner.Viewport.ViewportCamera is Viewport.Cameras.FPSCamera fpsCam) + fov += fpsCam.AdditionalZoomFOV; + fov /= 60.0f; float scaleAt30 = 0.1f, scaleAt60 = 1.0f, scaleAt120 = 1.5f, scaleAt180 = 3.0f; heightNormalization /= Mathf.Lerp(scaleAt30, scaleAt60, fov); heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt120, Mathf.Saturate(fov - 1)); diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 48cbda951..26032152a 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -2,12 +2,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; using FlaxEditor.Scripting; using FlaxEngine; @@ -21,7 +17,6 @@ namespace FlaxEditor.Modules.SourceCodeEditing { private Dictionary _typeCache = new Dictionary(); private Dictionary _memberCache = new Dictionary(); - private Dictionary> _xmlCache = new Dictionary>(); internal CodeDocsModule(Editor editor) : base(editor) @@ -61,13 +56,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing else if (type.Type != null) { // Try to use xml docs for managed type - var xml = GetXmlDocs(type.Type.Assembly); - if (xml != null) - { - var key = "T:" + GetXmlKey(type.Type.FullName); - if (xml.TryGetValue(key, out var xmlDoc)) - text += '\n' + FilterWhitespaces(xmlDoc); - } + var xmlDoc = DebugCommands.GetXml(type.Type); + if (xmlDoc != null) + text += '\n' + xmlDoc; } _typeCache.Add(type, text); @@ -108,242 +99,20 @@ namespace FlaxEditor.Modules.SourceCodeEditing else if (member.Type != null) { // Try to use xml docs for managed member - var memberInfo = member.Type; - var xml = GetXmlDocs(memberInfo.DeclaringType.Assembly); - if (xml != null) - { - // [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"] - // https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection - var memberType = memberInfo.MemberType; - string key = null; - if (memberType.HasFlag(MemberTypes.Field)) - { - var fieldInfo = (FieldInfo)memberInfo; - key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name; - } - else if (memberType.HasFlag(MemberTypes.Property)) - { - var propertyInfo = (PropertyInfo)memberInfo; - key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name; - } - else if (memberType.HasFlag(MemberTypes.Event)) - { - var eventInfo = (EventInfo)memberInfo; - key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name; - } - else if (memberType.HasFlag(MemberTypes.Constructor)) - { - var constructorInfo = (ConstructorInfo)memberInfo; - key = GetXmlKey(constructorInfo); - } - else if (memberType.HasFlag(MemberTypes.Method)) - { - var methodInfo = (MethodInfo)memberInfo; - key = GetXmlKey(methodInfo); - } - else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType)) - { - var typeInfo = (TypeInfo)memberInfo; - key = "T:" + GetXmlKey(typeInfo.FullName); - } - if (key != null) - xml.TryGetValue(key, out text); - - // Customize tooltips for properties to be more human-readable in UI - if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal)) - { - text = text.Substring(13); - unsafe - { - fixed (char* e = text) - e[0] = char.ToUpper(e[0]); - } - } - } + var xmlDoc = DebugCommands.GetXml(member.Type); + if (xmlDoc != null) + text = xmlDoc; } _memberCache.Add(member, text); return text; } - // [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"] - // https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection - - private string GetXmlKey(MethodInfo methodInfo) - { - var typeGenericMap = new Dictionary(); - var methodGenericMap = new Dictionary(); - ParameterInfo[] parameterInfos = methodInfo.GetParameters(); - - if (methodInfo.DeclaringType.IsGenericType) - { - var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); - methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken); - } - - Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments(); - for (int i = 0; i < typeGenericArguments.Length; i++) - { - Type typeGeneric = typeGenericArguments[i]; - typeGenericMap[typeGeneric.Name] = i; - } - - Type[] methodGenericArguments = methodInfo.GetGenericArguments(); - for (int i = 0; i < methodGenericArguments.Length; i++) - { - Type methodGeneric = methodGenericArguments[i]; - methodGenericMap[methodGeneric.Name] = i; - } - - string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap); - string memberNameString = methodInfo.Name; - string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty; - string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty; - - string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString; - if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit") - { - key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap); - } - return key; - } - - private string GetXmlKey(ConstructorInfo constructorInfo) - { - var typeGenericMap = new Dictionary(); - var methodGenericMap = new Dictionary(); - ParameterInfo[] parameterInfos = constructorInfo.GetParameters(); - - Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments(); - for (int i = 0; i < typeGenericArguments.Length; i++) - { - Type typeGeneric = typeGenericArguments[i]; - typeGenericMap[typeGeneric.Name] = i; - } - - string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap); - string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty; - string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty; - - return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString; - } - - internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary typeGenericMap, Dictionary methodGenericMap) - { - if (type.IsGenericParameter) - { - if (methodGenericMap.TryGetValue(type.Name, out var methodIndex)) - return "``" + methodIndex; - if (typeGenericMap.TryGetValue(type.Name, out var typeKey)) - return "`" + typeKey; - return "`"; - } - if (type.HasElementType) - { - string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap); - if (type.IsPointer) - return elementTypeString + "*"; - if (type.IsByRef) - return elementTypeString + "@"; - if (type.IsArray) - { - int rank = type.GetArrayRank(); - string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]"; - return elementTypeString + arrayDimensionsString; - } - throw new Exception(); - } - - string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + "."; - string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name; - string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty; - return prefaceString + typeNameString + genericArgumentsString; - } - - private static string GetXmlKey(string typeFullNameString) - { - return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.'); - } - - private static string FilterWhitespaces(string str) - { - if (str.Contains(" ", StringComparison.Ordinal)) - { - var sb = new StringBuilder(); - var prev = str[0]; - sb.Append(prev); - for (int i = 1; i < str.Length; i++) - { - var c = str[i]; - if (prev != ' ' || c != ' ') - { - sb.Append(c); - } - prev = c; - } - str = sb.ToString(); - } - return str; - } - - private Dictionary GetXmlDocs(Assembly assembly) - { - if (!_xmlCache.TryGetValue(assembly, out var result)) - { - Profiler.BeginEvent("GetXmlDocs"); - - var assemblyPath = Utils.GetAssemblyLocation(assembly); - var assemblyName = assembly.GetName().Name; - var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml"); - if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath)) - { - var uri = new UriBuilder(assemblyPath); - var path = Uri.UnescapeDataString(uri.Path); - xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml"); - } - if (File.Exists(xmlFilePath)) - { - Profiler.BeginEvent(assemblyName); - try - { - // Parse xml documentation - using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath))) - { - result = new Dictionary(); - while (xmlReader.Read()) - { - if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal)) - { - string rawName = xmlReader["name"]; - var memberReader = xmlReader.ReadSubtree(); - if (memberReader.ReadToDescendant("summary")) - { - // Remove and replace them with the captured group (the content of the cref). Additionally, getting rid of prefixes - const string crefPattern = @""; - result[rawName] = Regex.Replace(memberReader.ReadInnerXml(), crefPattern, "$1").Replace('\n', ' ').Trim(); - } - } - } - } - } - catch - { - // Ignore errors - } - Profiler.EndEvent(); - } - - _xmlCache[assembly] = result; - Profiler.EndEvent(); - } - return result; - } - private void OnTypesCleared() { _typeCache.Clear(); _memberCache.Clear(); - _xmlCache.Clear(); + DebugCommands.ClearXml(); } /// diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 82ccc21d5..ca0f38b7b 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -347,6 +347,14 @@ namespace FlaxEditor.Options [EditorDisplay("Viewport"), EditorOrder(1550)] public InputBinding Down = new InputBinding(KeyboardKeys.Q); + [DefaultValue(typeof(InputBinding), "C")] + [EditorDisplay("Viewport"), EditorOrder(1551)] + public InputBinding ZoomIn = new InputBinding(KeyboardKeys.C); + + [DefaultValue(typeof(InputBinding), "Z")] + [EditorDisplay("Viewport"), EditorOrder(1552)] + public InputBinding ZoomOut = new InputBinding(KeyboardKeys.Z); + [DefaultValue(typeof(InputBinding), "None")] [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)] public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None); diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 072491c5a..61464ad77 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -182,7 +182,7 @@ namespace FlaxEditor.SceneGraph.GUI _highlights?.Clear(); isThisVisible = true; } - else if (filterText.Contains(',')) + else if (filterText.Contains(':')) { var splitFilter = filterText.Split(','); var hasAllFilters = true; diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 5b3aaf310..c5579ff9f 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -807,8 +807,8 @@ namespace FlaxEditor.Surface.Archetypes }, Elements = new[] { - NodeElementArchetype.Factory.Input(0, "A", true, null, 0), - NodeElementArchetype.Factory.Input(1, "B", true, null, 1), + NodeElementArchetype.Factory.Input(0, "UV", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Center", true, null, 1), NodeElementArchetype.Factory.Input(2, "Radius", true, typeof(float), 2, 0), NodeElementArchetype.Factory.Input(3, "Hardness", true, typeof(float), 3, 1), NodeElementArchetype.Factory.Input(4, "Invert", true, typeof(bool), 4, 2), diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index 26e996c41..228994929 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.Viewport.Cameras private Transform _startMove; private Transform _endMove; private float _moveStartTime = -1; + private float _additionalFOV; /// /// Gets a value indicating whether this viewport is animating movement. @@ -32,6 +33,15 @@ namespace FlaxEditor.Viewport.Cameras /// public Vector3 TargetPoint = new Vector3(-200); + /// + /// Additional field of view used for zooming the camera in and out. + /// + public float AdditionalZoomFOV + { + get => _additionalFOV; + private set => _additionalFOV = Mathf.Clamp(value, 5 - Viewport.FieldOfView, 160f - Viewport.FieldOfView); + } + /// /// Sets view. /// @@ -216,7 +226,7 @@ namespace FlaxEditor.Viewport.Cameras pitch += mouseDelta.Y; } - // Zoom in/out + // Zoom in/out with mouse wheel if (input.IsZooming && !input.IsRotating) { position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f); @@ -226,6 +236,17 @@ namespace FlaxEditor.Viewport.Cameras } } + // Zoom in and out by changing FOV + if (input.IsRotating && (input.ZoomInDown || input.ZoomOutDown)) + { + float delta = (input.ZoomInDown ? -0.8f : 0.8f); + AdditionalZoomFOV += delta; + } + else if (!input.IsRotating) + { + AdditionalZoomFOV = 0f; + } + // Move camera with the gizmo if (input.IsOrbiting && isUsingGizmo) { diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index b8d0d8515..4dbc61917 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -51,6 +51,16 @@ namespace FlaxEditor.Viewport /// public bool IsOrbiting; + /// + /// The zoom in state. + /// + public bool ZoomInDown; + + /// + /// The zoom out state. + /// + public bool ZoomOutDown; + /// /// The is control down flag. /// @@ -109,6 +119,10 @@ namespace FlaxEditor.Viewport IsAltDown = window.GetKey(KeyboardKeys.Alt); WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown; + InputOptions inputOptions = Editor.Instance.Options.Options.Input; + ZoomInDown = window.GetKey(inputOptions.ZoomIn.Key); + ZoomOutDown = window.GetKey(inputOptions.ZoomOut.Key); + IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right); IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle); IsMouseLeftDown = useMouse && window.GetMouseButton(MouseButton.Left); @@ -1433,7 +1447,10 @@ namespace FlaxEditor.Viewport else { float aspect = Width / Height; - Matrix.PerspectiveFov(_fieldOfView * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result); + float fov = _fieldOfView; + if (_camera is FPSCamera fpsCam) + fov += fpsCam.AdditionalZoomFOV; + Matrix.PerspectiveFov(fov * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result); } } diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index b88c06b27..1478a9ac9 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -176,7 +176,8 @@ namespace FlaxEditor.Windows if (Owner != null && (!Owner._searchPopup?.Visible ?? true)) { // Focus back the input field as user want to modify command from history - Owner._searchPopup?.Hide(); + Owner.HideHistory(); + Owner.HideSearch(); Owner.RootWindow.Focus(); Owner.Focus(); Owner.OnKeyDown(key); @@ -209,6 +210,7 @@ namespace FlaxEditor.Windows private OutputLogWindow _window; private ItemsListContextMenu _searchPopup; + private ItemsListContextMenu _historyPopup; private bool _isSettingText; public CommandLineBox(float x, float y, float width, OutputLogWindow window) @@ -226,6 +228,24 @@ namespace FlaxEditor.Windows _isSettingText = false; } + private void HideSearch() + { + if (_searchPopup != null) + { + _searchPopup.Hide(); + _searchPopup = null; + } + } + + private void HideHistory() + { + if (_historyPopup != null) + { + _historyPopup.Dispose(); + _historyPopup = null; + } + } + private void ShowPopup(ref ItemsListContextMenu cm, IEnumerable commands, string searchText = null) { if (cm == null) @@ -295,7 +315,7 @@ namespace FlaxEditor.Windows private void OnRootWindowLostFocus() { // Prevent popup from staying active when editor window looses focus - _searchPopup?.Hide(); + HideSearch(); if (RootWindow?.Window != null) RootWindow.Window.LostFocus -= OnRootWindowLostFocus; } @@ -330,6 +350,7 @@ namespace FlaxEditor.Windows if (isWhitespaceOnly) DebugCommands.GetAllCommands(out commands); + HideHistory(); ShowPopup(ref _searchPopup, isWhitespaceOnly ? commands : matches, text); if (isWhitespaceOnly) @@ -342,7 +363,7 @@ namespace FlaxEditor.Windows return; } } - _searchPopup?.Hide(); + HideSearch(); } /// @@ -353,7 +374,8 @@ namespace FlaxEditor.Windows case KeyboardKeys.Return: { // Run command - _searchPopup?.Hide(); + HideSearch(); + HideHistory(); var command = Text.Trim(); if (command.Length == 0) return true; @@ -430,9 +452,8 @@ namespace FlaxEditor.Windows if (_window._commandHistory != null && _window._commandHistory.Count != 0) { // Show command history popup - _searchPopup?.Hide(); - ItemsListContextMenu cm = null; - ShowPopup(ref cm, _window._commandHistory); + HideSearch(); + ShowPopup(ref _historyPopup, _window._commandHistory); } } return true; diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h index b1c39ffc6..cdad7331b 100644 --- a/Source/Engine/AI/Behavior.h +++ b/Source/Engine/AI/Behavior.h @@ -57,6 +57,15 @@ public: { return &_knowledge; } + + /// + /// Gets the blackboard of a given type. + /// + template + FORCE_INLINE T* GetBlackboard() + { + return _knowledge.GetBlackboard(); + } /// /// Gets the last behavior tree execution result. diff --git a/Source/Engine/AI/BehaviorKnowledge.h b/Source/Engine/AI/BehaviorKnowledge.h index cd5aeab80..8924ca0e7 100644 --- a/Source/Engine/AI/BehaviorKnowledge.h +++ b/Source/Engine/AI/BehaviorKnowledge.h @@ -124,6 +124,18 @@ public: RemoveGoal(T::TypeInitializer); } + /// + /// Gets the blackboard of a given type. + /// + template + FORCE_INLINE T* GetBlackboard() + { + auto* structure = Blackboard.AsStructure(); + if (structure) + return structure; + return Cast((ScriptingObject*)Blackboard); + } + public: /// /// Compares two values and returns the comparision result. diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs index b7925aee0..aebc07343 100644 --- a/Source/Engine/AI/BehaviorTree.cs +++ b/Source/Engine/AI/BehaviorTree.cs @@ -11,6 +11,18 @@ using FlaxEngine.GUI; namespace FlaxEngine { + partial class Behavior + { + /// + /// Gets the blackboard of the given type. + /// + /// The blackboard type. + public T GetBlackboard() + { + return Knowledge.GetBlackboard(); + } + } + partial class BehaviorKnowledge { /// @@ -33,6 +45,16 @@ namespace FlaxEngine { RemoveGoal(typeof(T)); } + + /// + /// Gets the blackboard of the given type. + /// + /// The blackboard type. + [Unmanaged] + public T GetBlackboard() + { + return (T)Blackboard; + } } partial class BehaviorTreeRootNode diff --git a/Source/Engine/Debug/Debug.Build.cs b/Source/Engine/Debug/Debug.Build.cs index 76dd20137..2208d80bd 100644 --- a/Source/Engine/Debug/Debug.Build.cs +++ b/Source/Engine/Debug/Debug.Build.cs @@ -19,6 +19,14 @@ public class Debug : EngineModule { options.PublicDefinitions.Add("COMPILE_WITH_DEBUG_DRAW"); } + + if (options.Target.IsEditor || options.Configuration != TargetConfiguration.Release) + { + // Used by DebugCommands to parse Xml documentation + options.ScriptingAPI.SystemReferences.Add("System.Xml"); + options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter"); + options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); + } } /// diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 58cf2894b..feb985cd4 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -16,8 +16,11 @@ #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MField.h" #include "Engine/Scripting/ManagedCLR/MProperty.h" +#include "Engine/Scripting/ManagedCLR/MUtils.h" #include "FlaxEngine.Gen.h" +#define WITH_HELP (USE_EDITOR || !BUILD_RELEASE) && USE_CSHARP + struct CommandData { String Name; @@ -26,6 +29,38 @@ struct CommandData void* MethodGet = nullptr; void* MethodSet = nullptr; void* Field = nullptr; +#if WITH_HELP + mutable String Help; + + StringView GetHelp() const + { + if (Help.IsEmpty()) + { + if (dynamic_cast(Module)) + { + // Get C# type and member name + const MClass* mclass = nullptr; + StringAnsiView name; + if (auto field = (MField*)Field) + { + mclass = field->GetParentClass(); + name = field->GetName(); + } + else if (auto method = (MMethod*)(Method ? Method : (MethodGet ? MethodGet : MethodSet))) + { + mclass = method->GetParentClass(); + name = method->GetName(); + } + + // Use Xml docs reader used by Editor to get tooltips + auto getXmlInternal = DebugCommands::TypeInitializer.GetClass()->GetMethod("GetXmlInternal", 2); + void* params[2] = { INTERNAL_TYPE_GET_OBJECT(mclass->GetType()), MUtils::ToString(name) }; + Help = MUtils::ToString((MString*)getXmlInternal->Invoke(nullptr, params, nullptr)); + } + } + return Help; + } +#endif static void PrettyPrint(StringBuilder& sb, const Variant& value) { @@ -122,7 +157,11 @@ struct CommandData // Parse arguments if (args == StringView(TEXT("?"), 1)) { - LOG(Warning, "TODO: debug commands help/docs printing"); // TODO: debug commands help/docs printing (use CodeDocsModule that parses XML docs) +#if WITH_HELP + // Print command description + LOG(Info, "> {} ?", Name); + LOG_STR(Info, GetHelp()); +#endif return; } Array params; @@ -356,6 +395,22 @@ namespace InitCommands(); Locker.Unlock(); } + + const CommandData* GetCommand(StringView command) + { + if (command.FindLast(' ') != -1) + command = command.Left(command.Find(' ')); + // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) + String commandCopy = command; + command = commandCopy; + EnsureInited(); + for (auto& e : Commands) + { + if (e.Name == command) + return &e; + } + return nullptr; + } } class DebugCommandsService : public EngineService @@ -475,30 +530,32 @@ void DebugCommands::GetAllCommands(Array& commands) DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command) { CommandFlags result = CommandFlags::None; - if (command.FindLast(' ') != -1) - command = command.Left(command.Find(' ')); - // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) - String commandCopy = command; - command = commandCopy; - EnsureInited(); - for (auto& e : Commands) + if (auto cmd = GetCommand(command)) { - if (e.Name == command) - { - if (e.Method) - result |= CommandFlags::Exec; - else if (e.Field) - result |= CommandFlags::ReadWrite; - if (e.MethodGet) - result |= CommandFlags::Read; - if (e.MethodSet) - result |= CommandFlags::Write; - break; - } + if (cmd->Method) + result |= CommandFlags::Exec; + else if (cmd->Field) + result |= CommandFlags::ReadWrite; + if (cmd->MethodGet) + result |= CommandFlags::Read; + if (cmd->MethodSet) + result |= CommandFlags::Write; } return result; } +StringView DebugCommands::GetCommandHelp(StringView command) +{ + StringView result; +#if WITH_HELP + if (auto cmd = GetCommand(command)) + { + result = cmd->GetHelp(); + } +#endif + return result; +} + bool DebugCommands::Iterate(const StringView& searchText, int32& index) { EnsureInited(); diff --git a/Source/Engine/Debug/DebugCommands.cs b/Source/Engine/Debug/DebugCommands.cs index 93381bd0f..afe7e3cf6 100644 --- a/Source/Engine/Debug/DebugCommands.cs +++ b/Source/Engine/Debug/DebugCommands.cs @@ -1,6 +1,17 @@ // Copyright (c) Wojciech Figat. All rights reserved. +#if USE_EDITOR || !BUILD_RELEASE +#define WITH_HELP +#endif + using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; namespace FlaxEngine { @@ -12,4 +23,331 @@ namespace FlaxEngine public sealed class DebugCommand : Attribute { } + + partial class DebugCommands + { +#if WITH_HELP + private static Dictionary> _xmlCache; + private static StringBuilder _sb; + + internal static void ClearXml() + { + if (_xmlCache == null) + return; + foreach (var asm in _xmlCache.Keys.ToArray()) + { + if (asm.IsCollectible) + _xmlCache.Remove(asm); + } + } + + internal static string GetXmlInternal(Type type, string memberName) + { + // Redirect into type when no member specified + if (string.IsNullOrEmpty(memberName)) + return GetXml(type); + + // Redirect property function getter/setter into owning property docs + if (memberName.StartsWith("get_") || memberName.StartsWith("set_")) + memberName = memberName.Substring(4); + + // Find member of that name + var members = type.GetMember(memberName, BindingFlags.Static | BindingFlags.Public); + if (members.Length == 0) + return null; + return GetXml(members[0]); + } + + /// + /// Gets the XML docs text for the type. + /// + /// The type. + /// The documentation help. + public static string GetXml(Type type) + { + var xml = GetXmlDocs(type.Assembly); + if (xml != null) + { + var key = "T:" + GetXmlKey(type.FullName); + if (xml.TryGetValue(key, out var xmlDoc)) + return FilterWhitespaces(xmlDoc); + } + return null; + } + + /// + /// Gets the XML docs text for the type member. + /// + /// The type member. + /// The documentation help. + public static string GetXml(MemberInfo member) + { + string text = null; + var xml = GetXmlDocs(member.DeclaringType.Assembly); + if (xml != null) + { + // [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"] + // https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection + var memberType = member.MemberType; + string key = null; + if (memberType.HasFlag(MemberTypes.Field)) + { + var fieldInfo = (FieldInfo)member; + key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name; + } + else if (memberType.HasFlag(MemberTypes.Property)) + { + var propertyInfo = (PropertyInfo)member; + key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name; + } + else if (memberType.HasFlag(MemberTypes.Event)) + { + var eventInfo = (EventInfo)member; + key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name; + } + else if (memberType.HasFlag(MemberTypes.Constructor)) + { + var constructorInfo = (ConstructorInfo)member; + key = GetXmlKey(constructorInfo); + } + else if (memberType.HasFlag(MemberTypes.Method)) + { + var methodInfo = (MethodInfo)member; + key = GetXmlKey(methodInfo); + } + else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType)) + { + var typeInfo = (TypeInfo)member; + key = "T:" + GetXmlKey(typeInfo.FullName); + } + if (key != null) + { + xml.TryGetValue(key, out text); + text = FilterWhitespaces(text); + } + + // Customize tooltips for properties to be more human-readable in UI + if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal)) + { + text = text.Substring(13); + unsafe + { + fixed (char* e = text) + e[0] = char.ToUpper(e[0]); + } + } + } + return text; + } + + private static string FilterWhitespaces(string str) + { + if (str != null && str.Contains(" ", StringComparison.Ordinal)) + { + if (_sb == null) + _sb = new StringBuilder(); + else + _sb.Clear(); + var sb = _sb; + var prev = str[0]; + sb.Append(prev); + for (int i = 1; i < str.Length; i++) + { + var c = str[i]; + if (prev != ' ' || c != ' ') + sb.Append(c); + prev = c; + } + str = sb.ToString(); + } + return str; + } + + // [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"] + // https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection + + private static string GetXmlKey(MethodInfo methodInfo) + { + var typeGenericMap = new Dictionary(); + var methodGenericMap = new Dictionary(); + ParameterInfo[] parameterInfos = methodInfo.GetParameters(); + + if (methodInfo.DeclaringType.IsGenericType) + { + var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken); + } + + Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments(); + for (int i = 0; i < typeGenericArguments.Length; i++) + { + Type typeGeneric = typeGenericArguments[i]; + typeGenericMap[typeGeneric.Name] = i; + } + + Type[] methodGenericArguments = methodInfo.GetGenericArguments(); + for (int i = 0; i < methodGenericArguments.Length; i++) + { + Type methodGeneric = methodGenericArguments[i]; + methodGenericMap[methodGeneric.Name] = i; + } + + string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap); + string memberNameString = methodInfo.Name; + string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty; + string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty; + + string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString; + if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit") + { + key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap); + } + return key; + } + + private static string GetXmlKey(ConstructorInfo constructorInfo) + { + var typeGenericMap = new Dictionary(); + var methodGenericMap = new Dictionary(); + ParameterInfo[] parameterInfos = constructorInfo.GetParameters(); + + Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments(); + for (int i = 0; i < typeGenericArguments.Length; i++) + { + Type typeGeneric = typeGenericArguments[i]; + typeGenericMap[typeGeneric.Name] = i; + } + + string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap); + string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty; + string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty; + + return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString; + } + + internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary typeGenericMap, Dictionary methodGenericMap) + { + if (type.IsGenericParameter) + { + if (methodGenericMap.TryGetValue(type.Name, out var methodIndex)) + return "``" + methodIndex; + if (typeGenericMap.TryGetValue(type.Name, out var typeKey)) + return "`" + typeKey; + return "`"; + } + if (type.HasElementType) + { + string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap); + if (type.IsPointer) + return elementTypeString + "*"; + if (type.IsByRef) + return elementTypeString + "@"; + if (type.IsArray) + { + int rank = type.GetArrayRank(); + string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]"; + return elementTypeString + arrayDimensionsString; + } + throw new Exception(); + } + + string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + "."; + string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name; + string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty; + return prefaceString + typeNameString + genericArgumentsString; + } + + private static string GetXmlKey(string typeFullNameString) + { + return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.'); + } + + private static Dictionary GetXmlDocs(Assembly assembly) + { + if (_xmlCache == null) + _xmlCache = new Dictionary>(); + if (!_xmlCache.TryGetValue(assembly, out var result)) + { + Profiler.BeginEvent("GetXmlDocs"); + + // Find XML file path (based on assembly location) + var assemblyPath = Utils.GetAssemblyLocation(assembly); + var assemblyName = assembly.GetName().Name; + var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml"); + if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath)) + { + var uri = new UriBuilder(assemblyPath); + var path = Uri.UnescapeDataString(uri.Path); + xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml"); + } + if (File.Exists(xmlFilePath)) + { + Profiler.BeginEvent(assemblyName); + try + { + // Parse xml documentation + using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath))) + { + result = new Dictionary(); + StringBuilder content = new StringBuilder(2048); + while (xmlReader.Read()) + { + if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal)) + { + string rawName = xmlReader["name"]; + var memberReader = xmlReader.ReadSubtree(); + if (memberReader.ReadToDescendant("summary")) + { + content.Clear(); + do + { + if (memberReader.NodeType == XmlNodeType.Element && memberReader.Read()) + { + while (memberReader.NodeType == XmlNodeType.Text) + { + content.Append(memberReader.Value); + if (memberReader.Read() && memberReader.NodeType == XmlNodeType.Element) + { + var nodeRef = TrimRef(memberReader.GetAttribute("cref")); // + if (nodeRef == null) + nodeRef = memberReader.GetAttribute("name"); // + content.Append(nodeRef); + memberReader.Read(); + + string TrimRef(string str) + { + if (str == null) + return null; + if (str.IndexOf(":FlaxEngine.") == 1) + return str.Substring("T:FlaxEngine.".Length); + return str.Substring("T:".Length); + } + } + } + } + + if (memberReader.NodeType == XmlNodeType.EndElement && memberReader.Name == "summary") + break; + } while (memberReader.Read()); + + result[rawName] = content.ToString().Trim(' ', '\r', '\n'); + } + } + } + } + } + catch + { + // Ignore errors + } + Profiler.EndEvent(); + } + + _xmlCache[assembly] = result; + Profiler.EndEvent(); + } + return result; + } +#endif + } } diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index f25fe0581..71c90f30c 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -56,8 +56,17 @@ public: /// Returns flags of the command. /// /// The full name of the command. + /// Command flags. API_FUNCTION() static CommandFlags GetCommandFlags(StringView command); + /// + /// Returns help text of the command (from documentation comment). + /// + /// Only available in non-Release builds and Editor. + /// The full name of the command. + /// Command help text or empty if failed to get it. + API_FUNCTION() static StringView GetCommandHelp(StringView command); + public: static bool Iterate(const StringView& searchText, int32& index); static StringView GetCommandName(int32 index); diff --git a/Source/Engine/Engine/GameplayGlobals.cs b/Source/Engine/Engine/GameplayGlobals.cs new file mode 100644 index 000000000..9dabae525 --- /dev/null +++ b/Source/Engine/Engine/GameplayGlobals.cs @@ -0,0 +1,18 @@ +namespace FlaxEngine; + +partial class GameplayGlobals +{ + /// + /// Gets a value of a given type from the global variables. (it must be added first). + /// + /// The name of the variable to retrieve. + /// The type of the variable to retrieve. + /// The value of the variable, or default if not found or type mismatch. + public T GetValue(string name) + { + var obj = GetValue(name); + if (obj is T t) + return t; + return default; + } +} diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h index 16e2c2ce3..62c510f60 100644 --- a/Source/Engine/Engine/Globals.h +++ b/Source/Engine/Engine/Globals.h @@ -13,8 +13,6 @@ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Globals DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); public: - // Paths - // Main engine directory path. API_FIELD(ReadOnly) static String StartupFolder; @@ -54,8 +52,6 @@ public: #endif public: - // State - // True if fatal error occurred (engine is exiting). // [Deprecated in v1.10] DEPRECATED("Use Engine::FatalError instead.") static bool FatalErrorOccurred; @@ -91,14 +87,10 @@ public: DEPRECATED("Use Engine::ExitCode instead.") static int32 ExitCode; public: - // Threading - // Main Engine thread id. API_FIELD(ReadOnly) static uint64 MainThreadID; public: - // Config - /// /// The full engine version. /// diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 5fbf1d454..c2f0c6386 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -19,32 +19,32 @@ public: API_FIELD() static bool UseVSync; /// - /// Anti Aliasing quality setting. + /// Anti Aliasing quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality AAQuality; /// - /// Screen Space Reflections quality setting. + /// Screen Space Reflections quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality SSRQuality; /// - /// Screen Space Ambient Occlusion quality setting. + /// Screen Space Ambient Occlusion quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality SSAOQuality; /// - /// Volumetric Fog quality setting. + /// Volumetric Fog quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality VolumetricFogQuality; /// - /// The shadows filtering quality (sampling). + /// The shadows filtering quality (sampling). Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality ShadowsQuality; /// - /// The shadow maps quality (textures resolution). + /// The shadow maps quality (textures resolution). Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality ShadowMapsQuality; @@ -59,12 +59,12 @@ public: API_FIELD() static bool AllowCSMBlending; /// - /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. + /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality GlobalSDFQuality; /// - /// The Global Illumination quality. Controls the quality of the GI effect. + /// The Global Illumination quality. Controls the quality of the GI effect. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3). /// API_FIELD() static Quality GIQuality; diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 05bb29c4a..082ac497b 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -507,6 +507,7 @@ void MaterialParameter::clone(const MaterialParameter* param) break; case MaterialParameterType::Integer: case MaterialParameterType::SceneTexture: + case MaterialParameterType::ChannelMask: case MaterialParameterType::TextureGroupSampler: _asInteger = param->_asInteger; break; @@ -647,10 +648,7 @@ bool MaterialParams::Load(ReadStream* stream) PROFILE_MEM(GraphicsMaterials); bool result = false; - // Release - Resize(0); - - // Check for not empty params + Clear(); if (stream != nullptr && stream->CanRead()) { // Version diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index 697be453d..75252a8e9 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -199,7 +199,7 @@ void RenderView::CopyFrom(const Camera* camera, const Viewport* viewport) const Vector3 cameraPos = camera->GetPosition(); LargeWorlds::UpdateOrigin(Origin, cameraPos); Position = cameraPos - Origin; - Direction = camera->GetDirection(); + Direction = camera->GetForward(); Near = camera->GetNearPlane(); Far = camera->GetFarPlane(); camera->GetMatrices(View, Projection, viewport ? *viewport : camera->GetViewport(), Origin); diff --git a/Source/Engine/Graphics/RenderView.cs b/Source/Engine/Graphics/RenderView.cs index 2cd27ad83..b71b2d19b 100644 --- a/Source/Engine/Graphics/RenderView.cs +++ b/Source/Engine/Graphics/RenderView.cs @@ -96,7 +96,7 @@ namespace FlaxEngine Vector3 cameraPos = camera.Position; LargeWorlds.UpdateOrigin(ref Origin, cameraPos); Position = cameraPos - Origin; - Direction = camera.Direction; + Direction = camera.Forward; Near = camera.NearPlane; Far = camera.FarPlane; camera.GetMatrices(out View, out Projection, ref viewport, ref Origin); diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index eae1f9882..4bec6440e 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -360,14 +360,12 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) return true; } - ASSERT(result.Data.IsValid()); - #if COMPILE_WITH_SHADER_COMPILER // Read includes from cache IsValidShaderCache(result.Data, result.Includes); #endif - return false; + return result.Data.IsInvalid(); } #if COMPILE_WITH_SHADER_COMPILER diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 3f1ab905b..8ce79fea4 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -822,6 +822,11 @@ void Actor::SetRotation(const Matrix& value) } void Actor::SetDirection(const Float3& value) +{ + SetForward(value); +} + +void Actor::SetForward(const Float3& value) { CHECK(!value.IsNanOrInfinity()); Quaternion orientation; @@ -1714,17 +1719,22 @@ Actor* Actor::Intersects(const Ray& ray, Real& distance, Vector3& normal) void Actor::LookAt(const Vector3& worldPos) { - const Quaternion orientation = LookingAt(worldPos); + const Quaternion orientation = GetLookAtDirection(worldPos); SetOrientation(orientation); } void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp) { - const Quaternion orientation = LookingAt(worldPos, worldUp); + const Quaternion orientation = GetLookAtDirection(worldPos, worldUp); SetOrientation(orientation); } Quaternion Actor::LookingAt(const Vector3& worldPos) const +{ + return GetLookAtDirection(worldPos); +} + +Quaternion Actor::GetLookAtDirection(const Vector3& worldPos) const { const Vector3 direction = worldPos - _transform.Translation; if (direction.LengthSquared() < ZeroTolerance) @@ -1752,16 +1762,20 @@ Quaternion Actor::LookingAt(const Vector3& worldPos) const } Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) const +{ + return GetLookAtDirection(worldPos, worldUp); +} + +Quaternion Actor::GetLookAtDirection(const Vector3& worldPos, const Vector3& worldUp) const { const Vector3 direction = worldPos - _transform.Translation; if (direction.LengthSquared() < ZeroTolerance) return _parent ? _parent->GetOrientation() : Quaternion::Identity; + const Float3 forward = Vector3::Normalize(direction); const Float3 up = Vector3::Normalize(worldUp); if (Math::IsOne(Float3::Dot(forward, up))) - { - return LookingAt(worldPos); - } + return GetLookAtDirection(worldPos); Quaternion orientation; Quaternion::LookRotation(direction, up, orientation); diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index cd596e86c..a1ca01cc6 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -549,17 +549,35 @@ public: /// /// Gets actor direction vector (forward vector). + /// [Deprecated in v1.13] /// - API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const + API_PROPERTY(Attributes="HideInEditor, NoSerialize") DEPRECATED("Use GetForward instead.") + FORCE_INLINE Float3 GetDirection() const + { + return GetForward(); + } + + /// + /// Gets the actor's forward vector (direction). + /// + API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetForward() const { return Float3::Transform(Float3::Forward, GetOrientation()); } /// /// Sets actor direction vector (forward) + /// [Deprecated in v1.13] /// /// The value to set. - API_PROPERTY() void SetDirection(const Float3& value); + API_PROPERTY() DEPRECATED("Use SetForward instead.") + void SetDirection(const Float3& value); + + /// + /// Rotates the actor to align its forward vector with the passed in value (direction). + /// + /// The value to align to. + API_PROPERTY() void SetForward(const Float3& value); public: /// @@ -897,16 +915,31 @@ public: /// /// Gets rotation of the actor oriented towards the specified world position. + /// [Deprecated in v1.13] /// /// The world position to orient towards. - API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos) const; + API_FUNCTION() DEPRECATED("Use GetLookAtDirection instead.") Quaternion LookingAt(const Vector3& worldPos) const; + + /// + /// Gets rotation of the actor oriented towards the specified world position. + /// + /// The world position to orient towards. + API_FUNCTION() Quaternion GetLookAtDirection(const Vector3& worldPos) const; + /// + /// Gets rotation of the actor oriented towards the specified world position with upwards direction. + /// [Deprecated in v1.13] + /// + /// The world position to orient towards. + /// The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. + API_FUNCTION() DEPRECATED("Use GetLookAtDirection instead.") Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const; + /// /// Gets rotation of the actor oriented towards the specified world position with upwards direction. /// /// The world position to orient towards. /// The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. - API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const; + API_FUNCTION() Quaternion GetLookAtDirection(const Vector3& worldPos, const Vector3& worldUp) const; public: /// diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index b5b30e52d..fa4c2abfb 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -210,7 +210,7 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp { Vector3 position = GetPosition(); if (viewport.Width < ZeroTolerance || viewport.Height < ZeroTolerance || mousePosition.IsNaN()) - return Ray(position, GetDirection()); + return Ray(position, GetForward()); // Use different logic in orthographic projection if (!_usePerspective) diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index f3c510b60..e8e895950 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -34,7 +34,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.ShadowsDistance = ShadowsDistance; data.Color = color; data.ShadowsStrength = ShadowsStrength; - data.Direction = GetDirection(); + data.Direction = GetForward(); data.ShadowsFadeDistance = ShadowsFadeDistance; data.ShadowsNormalOffsetScale = ShadowsNormalOffsetScale; data.ShadowsDepthBias = ShadowsDepthBias; diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 2a89771c9..cfb5217a0 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -163,7 +163,7 @@ void ExponentialHeightFog::GetExponentialHeightFogData(const RenderView& view, S result.FogCutoffDistance = FogCutoffDistance >= 0 ? FogCutoffDistance : view.Far + FogCutoffDistance; if (useDirectionalLightInscattering) { - result.InscatteringLightDirection = -DirectionalInscatteringLight->GetDirection(); + result.InscatteringLightDirection = -DirectionalInscatteringLight->GetForward(); result.DirectionalInscatteringColor = DirectionalInscatteringColor.ToFloat3(); result.DirectionalInscatteringExponent = Math::Clamp(DirectionalInscatteringExponent, 0.000001f, 1000.0f); result.DirectionalInscatteringStartDistance = Math::Min(DirectionalInscatteringStartDistance, view.Far - 1.0f); diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index fd95e61d0..4b7292542 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -73,7 +73,7 @@ void Sky::InitConfig(ShaderAtmosphericFogData& config) const if (SunLight) { - config.AtmosphericFogSunDirection = -SunLight->GetDirection(); + config.AtmosphericFogSunDirection = -SunLight->GetForward(); config.AtmosphericFogSunColor = SunLight->Color.ToFloat3() * SunLight->Color.A; } else diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index f733753ae..0809b0b7b 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -27,7 +27,7 @@ SpotLight::SpotLight(const SpawnParams& params) _cosInnerCone = Math::Cos(_innerConeAngle * DegreesToRadians); _invCosConeDifference = 1.0f / (_cosInnerCone - _cosOuterCone); const float boundsRadius = Math::Sqrt(1.25f * _radius * _radius - _radius * _radius * _cosOuterCone); - _sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * _radius, boundsRadius); + _sphere = BoundingSphere(GetPosition() + 0.5f * GetForward() * _radius, boundsRadius); BoundingBox::FromSphere(_sphere, _box); } @@ -113,7 +113,7 @@ void SpotLight::UpdateBounds() // Note: we use the law of cosines to find the distance to the furthest edge of the spotlight cone from a position that is halfway down the spotlight direction const float radius = GetScaledRadius(); const float boundsRadius = Math::Sqrt(1.25f * radius * radius - radius * radius * _cosOuterCone); - _sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * radius, boundsRadius); + _sphere = BoundingSphere(GetPosition() + 0.5f * GetForward() * radius, boundsRadius); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) @@ -199,7 +199,7 @@ void SpotLight::OnDebugDrawSelected() const auto color = Color::Yellow; Vector3 right = _transform.GetRight(); Vector3 up = _transform.GetUp(); - Vector3 forward = GetDirection(); + Vector3 forward = GetForward(); float radius = GetScaledRadius(); float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians); float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians); @@ -231,7 +231,7 @@ void SpotLight::DrawLightsDebug(RenderView& view) const auto color = Color::Yellow; Vector3 right = _transform.GetRight(); Vector3 up = _transform.GetUp(); - Vector3 forward = GetDirection(); + Vector3 forward = GetForward(); float radius = GetScaledRadius(); float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians); float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 0c1901e4b..3eea438a0 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -1636,7 +1636,7 @@ FORCE_INLINE StringAnsiView GetPropertyMethodName(MProperty* property, StringAns Platform::MemoryCopy(mem, prefix.Get(), prefix.Length()); Platform::MemoryCopy(mem + prefix.Length(), name.Get(), name.Length()); mem[name.Length() + prefix.Length()] = 0; - return StringAnsiView(mem, name.Length() + prefix.Length() + 1); + return StringAnsiView(mem, name.Length() + prefix.Length()); } MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes) diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 4a6bb7ce1..225213dba 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -465,7 +465,7 @@ namespace FlaxEngine Quaternion.Euler(180, 180, 0, out var quat); Matrix.RotationQuaternion(ref quat, out m2); Matrix.Multiply(ref m3, ref m2, out m1); - m2 = Matrix.Transformation(Vector3.One, Quaternion.FromDirection(-camera.Direction), translation); + m2 = Matrix.Transformation(Vector3.One, Quaternion.FromDirection(-camera.Forward), translation); Matrix.Multiply(ref m1, ref m2, out world); } else if (_renderMode == CanvasRenderMode.CameraSpace && camera) diff --git a/Source/Engine/Video/VideoPlayer.h b/Source/Engine/Video/VideoPlayer.h index 4447337ea..ecae37e61 100644 --- a/Source/Engine/Video/VideoPlayer.h +++ b/Source/Engine/Video/VideoPlayer.h @@ -174,6 +174,30 @@ public: return _state; } + /// + /// Gets the value that determines whether the video playback is playing. + /// + API_PROPERTY() FORCE_INLINE bool IsPlaying() const + { + return _state == States::Playing; + } + + /// + /// Gets the value that determines whether the video playback is paused. + /// + API_PROPERTY() FORCE_INLINE bool IsPaused() const + { + return _state == States::Paused; + } + + /// + /// Gets the value that determines whether the video playback is stopped. + /// + API_PROPERTY() FORCE_INLINE bool IsStopped() const + { + return _state == States::Stopped; + } + /// /// Gets the current time of playback. The time is in seconds, in range [0, Duration]. /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index d5fc92093..d705b88c9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1054,7 +1054,8 @@ namespace Flax.Build.Bindings propertyInfo.Getter = functionInfo; else propertyInfo.Setter = functionInfo; - propertyInfo.DeprecatedMessage = functionInfo.DeprecatedMessage; + if (propertyInfo.DeprecatedMessage == null) + propertyInfo.DeprecatedMessage = functionInfo.DeprecatedMessage; propertyInfo.IsHidden |= functionInfo.IsHidden; if (propertyInfo.Getter != null && propertyInfo.Setter != null)