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)