diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 0808c4488..655924b88 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -323,6 +323,7 @@ True True True + True True True True diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index c7bbf767b..2174eebdc 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -262,6 +262,13 @@ namespace FlaxEditor.Content.Import [EditorOrder(1050), DefaultValue(true)] public bool OptimizeKeyframes { get; set; } = true; + /// + /// If checked, the importer will import scale animation tracks (otherwise scale animation will be ignored). + /// + [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] + [EditorOrder(1055), DefaultValue(false)] + public bool ImportScaleTracks { get; set; } = false; + /// /// Enables root motion extraction support from this animation. /// @@ -390,6 +397,7 @@ namespace FlaxEditor.Content.Import public float SamplingRate; public byte SkipEmptyCurves; public byte OptimizeKeyframes; + public byte ImportScaleTracks; public byte EnableRootMotion; public string RootNodeName; @@ -599,6 +607,7 @@ namespace FlaxEditor.Content.Import SamplingRate = SamplingRate, SkipEmptyCurves = (byte)(SkipEmptyCurves ? 1 : 0), OptimizeKeyframes = (byte)(OptimizeKeyframes ? 1 : 0), + ImportScaleTracks = (byte)(ImportScaleTracks ? 1 : 0), EnableRootMotion = (byte)(EnableRootMotion ? 1 : 0), RootNodeName = RootNodeName, GenerateLODs = (byte)(GenerateLODs ? 1 : 0), @@ -640,6 +649,7 @@ namespace FlaxEditor.Content.Import SamplingRate = options.SamplingRate; SkipEmptyCurves = options.SkipEmptyCurves != 0; OptimizeKeyframes = options.OptimizeKeyframes != 0; + ImportScaleTracks = options.ImportScaleTracks != 0; EnableRootMotion = options.EnableRootMotion != 0; RootNodeName = options.RootNodeName; GenerateLODs = options.GenerateLODs != 0; diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index d93ab544b..684593352 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -40,6 +40,11 @@ namespace FlaxEditor.CustomEditors [HideInEditor] public abstract class CustomEditor { + /// + /// True if Editor is during value setting (eg. by user or from copy/paste). + /// + public static bool IsSettingValue = false; + private LayoutElementsContainer _layout; private CustomEditorPresenter _presenter; private CustomEditor _parent; @@ -266,23 +271,30 @@ namespace FlaxEditor.CustomEditors // Check if need to update value if (_hasValueDirty) { - // Cleanup (won't retry update in case of exception) - object val = _valueToSet; - _hasValueDirty = false; - _valueToSet = null; - - // Assign value - SynchronizeValue(val); - - // Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object) - var obj = _parent; - while (obj._parent != null && !(obj._parent is SyncPointEditor)) + IsSettingValue = true; + try { - obj.Values.Set(obj._parent.Values, obj.Values); - obj = obj._parent; - } + // Cleanup (won't retry update in case of exception) + object val = _valueToSet; + _hasValueDirty = false; + _valueToSet = null; - OnUnDirty(); + // Assign value + SynchronizeValue(val); + + // Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object) + var obj = _parent; + while (obj._parent != null && !(obj._parent is SyncPointEditor)) + { + obj.Values.Set(obj._parent.Values, obj.Values); + obj = obj._parent; + } + } + finally + { + OnUnDirty(); + IsSettingValue = false; + } } else { diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 1977e0e59..93967c444 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -645,7 +645,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag); cm.SortItems(); - cm.Show(button.Parent, button.BottomLeft); + cm.Show(button.Parent, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); } private void SetType(ref ScriptType controlType, UIControl uiControl) diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index f65bb4b06..727575a49 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -77,11 +78,36 @@ namespace FlaxEditor.CustomEditors.Editors /// public class ScaleEditor : Float3Editor { + private Image _linkImage; + /// public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); + LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked; + + _linkImage = new Image + { + Parent = LinkedLabel, + Width = 18, + Height = 18, + Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush(), + AnchorPreset = AnchorPresets.TopLeft, + TooltipText = "Scale values are linked together.", + }; + _linkImage.LocalX += 40; + _linkImage.LocalY += 1; + + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + if (LinkValues) + menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling"); + else + menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling"); + }; + // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var grayOutFactor = 0.6f; @@ -92,6 +118,16 @@ namespace FlaxEditor.CustomEditors.Editors ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; } + + /// + /// Toggles the linking functionality. + /// + public void ToggleLink() + { + LinkValues = !LinkValues; + Editor.Instance.Windows.PropertiesWin.ScaleLinked = LinkValues; + _linkImage.Brush = LinkValues ? new SpriteBrush(Editor.Instance.Icons.Link32) : new SpriteBrush(); + } } } } diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 434d0c558..ddfe54897 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.CustomEditors.Editors var customType = TypeUtils.GetType(assetReference.TypeName); if (customType != ScriptType.Null) assetType = customType; - else + else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName)) Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName)); } } diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs index 8ea21e988..bd154056f 100644 --- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs @@ -44,6 +44,20 @@ namespace FlaxEditor.CustomEditors.Editors /// public override DisplayStyle Style => DisplayStyle.Inline; + /// + /// If true, when one value is changed, the other 2 will change as well. + /// + public bool LinkValues = false; + + private enum ValueChanged + { + X = 0, + Y = 1, + Z = 2 + } + + private ValueChanged _valueChanged; + /// public override void Initialize(LayoutElementsContainer layout) { @@ -63,28 +77,83 @@ namespace FlaxEditor.CustomEditors.Editors XElement = grid.FloatValue(); XElement.SetLimits(limit); - XElement.ValueBox.ValueChanged += OnValueChanged; + XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.FloatValue(); YElement.SetLimits(limit); - YElement.ValueBox.ValueChanged += OnValueChanged; + YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.FloatValue(); ZElement.SetLimits(limit); - ZElement.ValueBox.ValueChanged += OnValueChanged; + ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; } + private void OnXValueChanged() + { + if (IsSetBlocked) + return; + if (LinkValues) + _valueChanged = ValueChanged.X; + OnValueChanged(); + } + + private void OnYValueChanged() + { + if (IsSetBlocked) + return; + if (LinkValues) + _valueChanged = ValueChanged.Y; + OnValueChanged(); + } + + private void OnZValueChanged() + { + if (IsSetBlocked) + return; + if (LinkValues) + _valueChanged = ValueChanged.Z; + OnValueChanged(); + } + private void OnValueChanged() { if (IsSetBlocked) return; + var xValue = XElement.ValueBox.Value; + var yValue = YElement.ValueBox.Value; + var zValue = ZElement.ValueBox.Value; + + if (LinkValues) + { + var valueChange = 0.0f; + switch (_valueChanged) + { + case ValueChanged.X: + valueChange = xValue - ((Float3)Values[0]).X; + yValue += valueChange; + zValue += valueChange; + break; + case ValueChanged.Y: + valueChange = yValue - ((Float3)Values[0]).Y; + xValue += valueChange; + zValue += valueChange; + break; + case ValueChanged.Z: + valueChange = zValue - ((Float3)Values[0]).Z; + xValue += valueChange; + yValue += valueChange; + break; + default: break; + } + } + var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding; var token = isSliding ? this : null; - var value = new Float3(XElement.ValueBox.Value, YElement.ValueBox.Value, ZElement.ValueBox.Value); + var value = new Float3(xValue, yValue, zValue); object v = Values[0]; if (v is Vector3) v = (Vector3)value; diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index dfec33f49..61c223835 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -198,7 +198,7 @@ namespace FlaxEditor.CustomEditors { for (int i = 0; i < Count; i++) { - if (!Equals(this[i], _referenceValue)) + if (!ValueEquals(this[i], _referenceValue)) return true; } } @@ -228,20 +228,23 @@ namespace FlaxEditor.CustomEditors { for (int i = 0; i < Count; i++) { - if (!Equals(this[i], _defaultValue)) - { - // Special case for String (null string is kind of equal to empty string from the user perspective) - if (this[i] == null && _defaultValue is string defaultValueStr && defaultValueStr.Length == 0) - continue; - + if (!ValueEquals(this[i], _defaultValue)) return true; - } } } return false; } } + private static bool ValueEquals(object objA, object objB) + { + // Special case for String (null string is kind of equal to empty string from the user perspective) + if (objA == null && objB is string objBStr && objBStr.Length == 0) + return true; + + return Newtonsoft.Json.Utilities.MiscellaneousUtils.DefaultValueEquals(objA, objB); + } + /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index d9c1b9325..492887611 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -181,6 +181,7 @@ namespace FlaxEditor.GUI.Input _cursorChanged = false; } SlidingEnd?.Invoke(); + Defocus(); } /// diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index dc0dc8299..020cb3c61 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -183,6 +183,7 @@ struct InternalModelOptions float SamplingRate; byte SkipEmptyCurves; byte OptimizeKeyframes; + byte ImportScaleTracks; byte EnableRootMotion; MonoString* RootNodeName; @@ -231,6 +232,7 @@ struct InternalModelOptions to->SamplingRate = from->SamplingRate; to->SkipEmptyCurves = from->SkipEmptyCurves; to->OptimizeKeyframes = from->OptimizeKeyframes; + to->ImportScaleTracks = from->ImportScaleTracks; to->EnableRootMotion = from->EnableRootMotion; to->RootNodeName = MUtils::ToString(from->RootNodeName); to->GenerateLODs = from->GenerateLODs; @@ -272,6 +274,7 @@ struct InternalModelOptions to->SamplingRate = from->SamplingRate; to->SkipEmptyCurves = from->SkipEmptyCurves; to->OptimizeKeyframes = from->OptimizeKeyframes; + to->ImportScaleTracks = from->ImportScaleTracks; to->EnableRootMotion = from->EnableRootMotion; to->RootNodeName = MUtils::ToString(from->RootNodeName); to->GenerateLODs = from->GenerateLODs; diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 949d2a0a2..3af3106f0 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text; using FlaxEngine; @@ -365,7 +366,8 @@ namespace FlaxEditor.Scripting } } - Editor.LogWarning($"Failed to find type '{typeName}'."); + if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(typeName)) + Editor.LogWarning($"Failed to find type '{typeName}'."); return ScriptType.Null; } diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 18a94348d..7a550948a 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -82,6 +82,16 @@ namespace FlaxEditor.States } } + /// + /// True if play mode is starting. + /// + public bool IsPlayModeStarting; + + /// + /// True if play mode is ending. + /// + public bool IsPlayModeEnding; + internal PlayingState(Editor editor) : base(editor) { @@ -127,6 +137,7 @@ namespace FlaxEditor.States public override void OnEnter() { Profiler.BeginEvent("PlayingState.OnEnter"); + IsPlayModeStarting = true; Editor.OnPlayBeginning(); CacheSelection(); @@ -150,6 +161,7 @@ namespace FlaxEditor.States RestoreSelection(); Editor.OnPlayBegin(); + IsPlayModeStarting = false; Profiler.EndEvent(); } @@ -171,6 +183,7 @@ namespace FlaxEditor.States public override void OnExit(State nextState) { Profiler.BeginEvent("PlayingState.OnExit"); + IsPlayModeEnding = true; Editor.OnPlayEnding(); IsPaused = true; @@ -194,6 +207,7 @@ namespace FlaxEditor.States RestoreSelection(); Editor.OnPlayEnd(); + IsPlayModeEnding = false; Profiler.EndEvent(); } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 313a38c47..625fd92b4 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -114,10 +114,8 @@ namespace FlaxEditor.Windows.Assets public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { var result = base.OnDragMove(ref location, data); - if (result == DragDropEffect.None) - { + if (result == DragDropEffect.None && _dragHandlers != null) result = _dragHandlers.Effect; - } return result; } diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index aaed484a1..7b815bc98 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -817,6 +817,7 @@ namespace FlaxEditor.Windows // Selected UI controls outline bool drawAnySelectedControl = false; + // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed) for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++) { if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) @@ -827,7 +828,8 @@ namespace FlaxEditor.Windows Render2D.PushTransform(ref _viewport._cachedTransform); } var control = controlActor.Control; - var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Float2.Zero), control.PointToParent(_viewport, control.Size)); + var bounds = control.EditorBounds; + bounds = Rectangle.FromPoints(control.PointToParent(_viewport, bounds.Location), control.PointToParent(_viewport, bounds.Size)); Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); } } diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 3c2d06852..601717f38 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using System.Xml; using FlaxEditor.CustomEditors; using FlaxEngine.GUI; @@ -16,11 +17,19 @@ namespace FlaxEditor.Windows { private IEnumerable undoRecordObjects; + /// + public override bool UseLayoutData => true; + /// /// The editor. /// public readonly CustomEditorPresenter Presenter; + /// + /// Indication of if the scale is locked. + /// + public bool ScaleLinked = false; + /// /// Initializes a new instance of the class. /// @@ -52,5 +61,18 @@ namespace FlaxEditor.Windows var objects = Editor.SceneEditing.Selection.ConvertAll(x => x.EditableObject).Distinct(); Presenter.Select(objects); } + + /// + public override void OnLayoutSerialize(XmlWriter writer) + { + writer.WriteAttributeString("ScaleLinked", ScaleLinked.ToString()); + } + + /// + public override void OnLayoutDeserialize(XmlElement node) + { + if (bool.TryParse(node.GetAttribute("ScaleLinked"), out bool value1)) + ScaleLinked = value1; + } } } diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 5fb103870..13b3249ea 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -1070,7 +1070,8 @@ bool findAsset(const Guid& id, const String& directory, Array& tmpCache, tmpCache.Clear(); if (FileSystem::DirectoryGetFiles(tmpCache, directory, TEXT("*"), DirectorySearchOption::AllDirectories)) { - LOG(Error, "Cannot query files in folder '{0}'.", directory); + if (FileSystem::DirectoryExists(directory)) + LOG(Error, "Cannot query files in folder '{0}'.", directory); return false; } diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index f6423e2dd..52fa8105f 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -163,7 +163,12 @@ bool CreateAssetContext::AllocateChunk(int32 index) void CreateAssetContext::AddMeta(JsonWriter& writer) const { writer.JKEY("ImportPath"); - if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)) + if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath) +#if PLATFORM_WINDOWS + // Import path from other drive should be stored as absolute on Windows to prevent issues + && InputPath.Length() > 2 && Globals::ProjectFolder.Length() > 2 && InputPath[0] == Globals::ProjectFolder[0] +#endif + ) { const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath); writer.String(relativePath); diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index 81520c019..af3196e8d 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -15,6 +15,16 @@ namespace FlaxEditor.Content.Settings internal const string XboxOnePlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxOnePlatformSettings"; internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings"; internal const string SwitchPlatformSettingsTypename = "FlaxEditor.Content.Settings.SwitchPlatformSettings"; +#if FLAX_EDITOR + internal static string[] OptionalPlatformSettings = + { + PS4PlatformSettingsTypename, + PS5PlatformSettingsTypename, + XboxOnePlatformSettingsTypename, + XboxScarlettPlatformSettingsTypename, + SwitchPlatformSettingsTypename, + }; +#endif /// /// The default application icon. diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 61ac1b748..77ce686e0 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -2,22 +2,18 @@ #include "ObjectsRemovalService.h" #include "Collections/Dictionary.h" +#include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" -#include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ScriptingObject.h" -#include "Log.h" namespace ObjectsRemovalServiceImpl { - bool IsReady = false; CriticalSection PoolLocker; - CriticalSection NewItemsLocker; DateTime LastUpdate; float LastUpdateGameTime; Dictionary Pool(8192); - Dictionary NewItemsPool(2048); } using namespace ObjectsRemovalServiceImpl; @@ -39,41 +35,14 @@ ObjectsRemoval ObjectsRemovalInstance; bool ObjectsRemovalService::IsInPool(Object* obj) { - if (!IsReady) - return false; - - { - ScopeLock lock(NewItemsLocker); - if (NewItemsPool.ContainsKey(obj)) - return true; - } - - { - ScopeLock lock(PoolLocker); - if (Pool.ContainsKey(obj)) - return true; - } - - return false; -} - -bool ObjectsRemovalService::HasNewItemsForFlush() -{ - NewItemsLocker.Lock(); - const bool result = NewItemsPool.HasItems(); - NewItemsLocker.Unlock(); + PoolLocker.Lock(); + const bool result = Pool.ContainsKey(obj); + PoolLocker.Unlock(); return result; } void ObjectsRemovalService::Dereference(Object* obj) { - if (!IsReady) - return; - - NewItemsLocker.Lock(); - NewItemsPool.Remove(obj); - NewItemsLocker.Unlock(); - PoolLocker.Lock(); Pool.Remove(obj); PoolLocker.Unlock(); @@ -81,57 +50,37 @@ void ObjectsRemovalService::Dereference(Object* obj) void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime) { - ScopeLock lock(NewItemsLocker); - obj->Flags |= ObjectFlags::WasMarkedToDelete; if (useGameTime) obj->Flags |= ObjectFlags::UseGameTimeForDelete; else obj->Flags &= ~ObjectFlags::UseGameTimeForDelete; - NewItemsPool[obj] = timeToLive; + + PoolLocker.Lock(); + Pool[obj] = timeToLive; + PoolLocker.Unlock(); } void ObjectsRemovalService::Flush(float dt, float gameDelta) { PROFILE_CPU(); - // Add new items + PoolLocker.Lock(); + + int32 itemsLeft; + do { - ScopeLock lock(NewItemsLocker); - - for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i) - { - Pool[i->Key] = i->Value; - } - NewItemsPool.Clear(); - } - - // Update timeouts and delete objects that timed out - { - ScopeLock lock(PoolLocker); - + // Update timeouts and delete objects that timed out + itemsLeft = Pool.Count(); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) { - auto obj = i->Key; + Object* obj = i->Key; const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt); - if (ttl <= ZeroTolerance) + if (ttl <= 0.0f) { Pool.Remove(i); - -#if BUILD_DEBUG || BUILD_DEVELOPMENT - if (NewItemsPool.ContainsKey(obj)) - { - const auto asScriptingObj = dynamic_cast(obj); - if (asScriptingObj) - { - LOG(Warning, "Object {0} was marked to delete after delete timeout", asScriptingObj->GetID()); - } - } -#endif - NewItemsPool.Remove(obj); - //ASSERT(!NewItemsPool.ContainsKey(obj)); - obj->OnDeleteObject(); + itemsLeft--; } else { @@ -139,38 +88,9 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta) } } } + while (itemsLeft != Pool.Count()); // Continue removing if any new item was added during removing (eg. sub-object delete with 0 timeout) - // Perform removing in loop - // Note: objects during OnDeleteObject call can register new objects to remove with timeout=0, for example Actors do that to remove children and scripts - while (HasNewItemsForFlush()) - { - // Add new items - { - ScopeLock lock(NewItemsLocker); - - for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i) - { - Pool[i->Key] = i->Value; - } - NewItemsPool.Clear(); - } - - // Delete objects that timed out - { - ScopeLock lock(PoolLocker); - - for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) - { - if (i->Value <= ZeroTolerance) - { - auto obj = i->Key; - Pool.Remove(i); - ASSERT(!NewItemsPool.ContainsKey(obj)); - obj->OnDeleteObject(); - } - } - } - } + PoolLocker.Unlock(); } bool ObjectsRemoval::Init() @@ -204,14 +124,12 @@ void ObjectsRemoval::Dispose() ScopeLock lock(PoolLocker); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) { - auto obj = i->Key; + Object* obj = i->Key; Pool.Remove(i); obj->OnDeleteObject(); } Pool.Clear(); } - - IsReady = false; } Object::~Object() diff --git a/Source/Engine/Core/ObjectsRemovalService.h b/Source/Engine/Core/ObjectsRemovalService.h index a9fa97883..f7adac7f9 100644 --- a/Source/Engine/Core/ObjectsRemovalService.h +++ b/Source/Engine/Core/ObjectsRemovalService.h @@ -17,12 +17,6 @@ public: /// True if object has been registered in the pool for the removing, otherwise false. static bool IsInPool(Object* obj); - /// - /// Determines whether any object has been registered to be removed from pool (requests are flushed on Flush call). - /// - /// True if any object has been registered to be removed, otherwise false. - static bool HasNewItemsForFlush(); - /// /// Removes the specified object from the dead pool (clears the reference to it). /// diff --git a/Source/Engine/Engine/RandomUtil.cs b/Source/Engine/Engine/RandomUtil.cs index 41682dc53..ea95140cc 100644 --- a/Source/Engine/Engine/RandomUtil.cs +++ b/Source/Engine/Engine/RandomUtil.cs @@ -10,7 +10,10 @@ namespace FlaxEngine /// public static class RandomUtil { - private static readonly Random _random = new Random(); + /// + /// Random numbers generator. + /// + public static readonly Random Random = new Random(); /// /// Generates a pseudo-random number from normalized range [0;1]. @@ -19,7 +22,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Rand() { - return _random.Next(0, int.MaxValue) / (float)int.MaxValue; + return Random.Next(0, int.MaxValue) / (float)int.MaxValue; } } } diff --git a/Source/Engine/Graphics/Config.h b/Source/Engine/Graphics/Config.h index 254efea58..02d95c947 100644 --- a/Source/Engine/Graphics/Config.h +++ b/Source/Engine/Graphics/Config.h @@ -57,9 +57,9 @@ #define GPU_USE_SHADERS_DEBUG_LAYER (BUILD_DEBUG) // Maximum size of the texture that is supported by the engine (specific platforms can have lower limit) -#define GPU_MAX_TEXTURE_SIZE 8192 -#define GPU_MAX_TEXTURE_MIP_LEVELS 14 -#define GPU_MAX_TEXTURE_ARRAY_SIZE 512 +#define GPU_MAX_TEXTURE_SIZE 16384 +#define GPU_MAX_TEXTURE_MIP_LEVELS 15 +#define GPU_MAX_TEXTURE_ARRAY_SIZE 1024 // Define default back buffer(s) format #if GPU_USE_BGRA_BACK_BUFFER diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index fe7c59e3a..acc1f4525 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -319,6 +319,15 @@ GPUDevice::~GPUDevice() bool GPUDevice::Init() { + // Clamp texture limits (eg. if driver reports higher value) + Limits.MaximumTexture1DSize = Math::Min(Limits.MaximumTexture1DSize, GPU_MAX_TEXTURE_SIZE); + Limits.MaximumTexture2DSize = Math::Min(Limits.MaximumTexture2DSize, GPU_MAX_TEXTURE_SIZE); + Limits.MaximumTexture3DSize = Math::Min(Limits.MaximumTexture3DSize, GPU_MAX_TEXTURE_SIZE); + Limits.MaximumTextureCubeSize = Math::Min(Limits.MaximumTextureCubeSize, GPU_MAX_TEXTURE_SIZE); + Limits.MaximumTexture1DArraySize = Math::Min(Limits.MaximumTexture1DArraySize, GPU_MAX_TEXTURE_ARRAY_SIZE); + Limits.MaximumTexture2DArraySize = Math::Min(Limits.MaximumTexture2DArraySize, GPU_MAX_TEXTURE_ARRAY_SIZE); + Limits.MaximumMipLevelsCount = Math::Min(Limits.MaximumMipLevelsCount, GPU_MAX_TEXTURE_MIP_LEVELS); + _res->TasksManager.SetExecutor(CreateTasksExecutor()); LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory)); return false; diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index 70a3a39f3..fe22b4eaf 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -21,7 +21,7 @@ namespace void RenderTargetPool::Flush(bool force) { - const uint64 framesOffset = 10; + const uint64 framesOffset = 3 * 60; const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset; force |= Engine::ShouldExit(); diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 5cf5cbb18..73ef072b3 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -443,6 +443,19 @@ const String& Actor::GetLayerName() const return Level::Layers[_layer]; } +void Actor::SetLayerName(const StringView& value) +{ + for (int32 i = 0; i < 32; i++) + { + if (Level::Layers[i] == value) + { + SetLayer(i); + return; + } + } + LOG(Warning, "Unknown layer name '{0}'", value); +} + bool Actor::HasTag() const { return Tags.Count() != 0; @@ -485,10 +498,21 @@ Script* Actor::GetScript(int32 index) const Script* Actor::GetScript(const MClass* type) const { CHECK_RETURN(type, nullptr); - for (auto script : Scripts) + if (type->IsInterface()) { - if (script->GetClass()->IsSubClassOf(type)) - return script; + for (auto script : Scripts) + { + if (script->GetClass()->HasInterface(type)) + return script; + } + } + else + { + for (auto script : Scripts) + { + if (script->GetClass()->IsSubClassOf(type)) + return script; + } } return nullptr; } @@ -496,9 +520,18 @@ Script* Actor::GetScript(const MClass* type) const Array Actor::GetScripts(const MClass* type) const { Array result; - for (auto script : Scripts) - if (script->GetClass()->IsSubClassOf(type)) - result.Add(script); + if (type->IsInterface()) + { + for (auto script : Scripts) + if (script->GetClass()->HasInterface(type)) + result.Add(script); + } + else + { + for (auto script : Scripts) + if (script->GetClass()->IsSubClassOf(type)) + result.Add(script); + } return result; } diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 2a8d8039a..bd005d415 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -222,7 +222,7 @@ namespace FlaxEngine /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null if failed to find. - public T GetScript() where T : Script + public T GetScript() where T : class { return GetScript(typeof(T)) as T; } @@ -233,7 +233,7 @@ namespace FlaxEngine /// Type of the script to search for. Includes any scripts derived from the type. /// The returned script, valid only if method returns true. /// True if found a script of that type or false if failed to find. - public bool TryGetScript(out T script) where T : Script + public bool TryGetScript(out T script) where T : class { script = GetScript(typeof(T)) as T; return script != null; @@ -244,7 +244,7 @@ namespace FlaxEngine /// /// Type of the object. /// Script instance if found, null otherwise. - public T FindScript() where T : Script + public T FindScript() where T : class { return FindScript(typeof(T)) as T; } @@ -290,7 +290,7 @@ namespace FlaxEngine /// /// Type of the scripts to search for. Includes any scripts derived from the type. /// All scripts matching the specified type. - public T[] GetScripts() where T : Script + public T[] GetScripts() where T : class { var count = ScriptsCount; var length = 0; diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index e68f5ee17..38bd629a5 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -105,7 +105,13 @@ public: /// /// Gets the name of the layer. /// - API_PROPERTY() const String& GetLayerName() const; + API_PROPERTY(Attributes="HideInEditor, NoSerialize, NoAnimate") + const String& GetLayerName() const; + + /// + /// Sets the name of the layer. + /// + API_PROPERTY() void SetLayerName(const StringView& value); /// /// Determines whether this actor has any tag assigned. diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 45518efd1..a67268b6a 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1443,20 +1443,15 @@ void Level::ReloadScriptsAsync() Actor* Level::FindActor(const Guid& id) { - return Scripting::FindObject(id); + return Scripting::TryFindObject(id); } Actor* Level::FindActor(const StringView& name) { Actor* result = nullptr; - ScopeLock lock(ScenesLock); - for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - { result = Scenes[i]->FindActor(name); - } - return result; } diff --git a/Source/Engine/Networking/Drivers/ENetDriver.cpp b/Source/Engine/Networking/Drivers/ENetDriver.cpp index 2f468c7aa..d7a3617a4 100644 --- a/Source/Engine/Networking/Drivers/ENetDriver.cpp +++ b/Source/Engine/Networking/Drivers/ENetDriver.cpp @@ -164,6 +164,7 @@ void ENetDriver::Disconnect(const NetworkConnection& connection) bool ENetDriver::PopEvent(NetworkEvent* eventPtr) { + ASSERT(_host); ENetEvent event; const int result = enet_host_service(_host, &event, 0); if (result < 0) diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 3a1984fe0..c2b6fd2da 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -191,6 +191,7 @@ bool StartPeer() if (!NetworkManager::Peer) { LOG(Error, "Failed to create Network Peer at {0}:{1}", networkConfig.Address, networkConfig.Port); + NetworkManager::State = NetworkConnectionState::Offline; return true; } NetworkManager::Frame = 0; @@ -243,7 +244,10 @@ bool NetworkManager::StartServer() LOG(Info, "Starting network manager as server"); Mode = NetworkManagerMode::Server; if (StartPeer()) + { + Mode = NetworkManagerMode::Offline; return true; + } if (!Peer->Listen()) { Stop(); @@ -265,7 +269,10 @@ bool NetworkManager::StartClient() LOG(Info, "Starting network manager as client"); Mode = NetworkManagerMode::Client; if (StartPeer()) + { + Mode = NetworkManagerMode::Offline; return true; + } if (!Peer->Connect()) { Stop(); @@ -286,9 +293,15 @@ bool NetworkManager::StartHost() LOG(Info, "Starting network manager as host"); Mode = NetworkManagerMode::Host; if (StartPeer()) + { + Mode = NetworkManagerMode::Offline; return true; + } if (!Peer->Listen()) + { + Mode = NetworkManagerMode::Offline; return true; + } LocalClientId = ServerClientId; NextClientId = ServerClientId + 1; LocalClient = New(LocalClientId, NetworkConnection{ 0 }); diff --git a/Source/Engine/Networking/NetworkManager.h b/Source/Engine/Networking/NetworkManager.h index 935a35118..db4deac12 100644 --- a/Source/Engine/Networking/NetworkManager.h +++ b/Source/Engine/Networking/NetworkManager.h @@ -140,6 +140,12 @@ public: return State == NetworkConnectionState::Connected; } + // Returns true if network is online or disconnected. + API_PROPERTY() FORCE_INLINE static bool IsOffline() + { + return State == NetworkConnectionState::Offline || State == NetworkConnectionState::Disconnected; + } + /// /// Gets the network client for a given connection. Returns null if failed to find it. /// diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 7a271ef0a..7ab1a7c4f 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -663,7 +663,7 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle, void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent) { - if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + if (!obj || NetworkManager::IsOffline()) return; ScopeLock lock(ObjectsLock); if (Objects.Contains(obj)) @@ -695,7 +695,7 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent) void NetworkReplicator::RemoveObject(ScriptingObject* obj) { - if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + if (!obj || NetworkManager::IsOffline()) return; ScopeLock lock(ObjectsLock); const auto it = Objects.Find(obj->GetID()); @@ -715,7 +715,7 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj) void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainer& clientIds) { - if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + if (!obj || NetworkManager::IsOffline()) return; ScopeLock lock(ObjectsLock); const auto it = Objects.Find(obj->GetID()); @@ -730,7 +730,7 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainerGetID()); @@ -887,7 +887,7 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC() void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) { const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); - if (!info || !obj) + if (!info || !obj || NetworkManager::IsOffline()) return; ObjectsLock.Lock(); auto& rpc = RpcQueue.AddOne(); @@ -1023,15 +1023,6 @@ void NetworkInternal::NetworkReplicatorUpdate() NewClients.Clear(); } - // Collect clients for replication (from server) - BuildCachedTargets(NetworkManager::Clients); - if (!isClient && CachedTargets.Count() == 0) - { - // Early exit if server has nobody to send data to - Scripting::ObjectsLookupIdMapping.Set(nullptr); - return; - } - // Despawn if (DespawnQueue.Count() != 0) { @@ -1474,6 +1465,10 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl if (!obj->IsRegistered()) obj->RegisterObject(); const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); + if (!parent && msgDataItem.ParentId.IsValid()) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); + } // Add object to the list NetworkReplicatedObject item; diff --git a/Source/Engine/Networking/NetworkStream.cs b/Source/Engine/Networking/NetworkStream.cs index fb062c2c1..3fa4ad9dc 100644 --- a/Source/Engine/Networking/NetworkStream.cs +++ b/Source/Engine/Networking/NetworkStream.cs @@ -407,6 +407,23 @@ namespace FlaxEngine.Networking return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); } + /// + /// Writes data of type into the message. + /// + public void WriteRay(Ray value) + { + WriteVector3(value.Position); + WriteVector3(value.Direction); + } + + /// + /// Reads and returns data of type from the message. + /// + public Ray ReadRay() + { + return new Ray(ReadVector3(), ReadVector3()); + } + /// /// Writes data of type into the message. /// diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 7eaada32e..b04dfe0c0 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -39,6 +39,9 @@ void ParticleSystem::Init(ParticleEmitter* emitter, float duration, float fps) track.AsEmitter.Index = 0; track.AsEmitter.StartFrame = 0; track.AsEmitter.DurationFrames = DurationFrames; +#if !BUILD_RELEASE + _debugName = StringUtils::GetFileNameWithoutExtension(emitter->GetPath()); +#endif } } @@ -155,6 +158,9 @@ ParticleEffect* ParticleSystem::Spawn(Actor* parent, const Transform& transform, auto effect = New(); effect->SetTransform(transform); effect->ParticleSystem = this; +#if !BUILD_RELEASE + effect->SetName(_debugName); // Give usable name in development builds +#endif Level::SpawnActor(effect, parent); @@ -452,6 +458,9 @@ Asset::LoadResult ParticleSystem::load() return LoadResult::InvalidData; } +#if !BUILD_RELEASE + _debugName = StringUtils::GetFileNameWithoutExtension(GetPath()); +#endif return LoadResult::Ok; } @@ -463,6 +472,9 @@ void ParticleSystem::unload(bool isReloading) Emitters.Resize(0); EmittersParametersOverrides.SetCapacity(0); Tracks.Resize(0); +#if !BUILD_RELEASE + _debugName.Clear(); +#endif } AssetChunksFlag ParticleSystem::getChunksToPreload() const diff --git a/Source/Engine/Particles/ParticleSystem.h b/Source/Engine/Particles/ParticleSystem.h index 69212036f..48f944d2a 100644 --- a/Source/Engine/Particles/ParticleSystem.h +++ b/Source/Engine/Particles/ParticleSystem.h @@ -98,6 +98,11 @@ public: typedef Pair EmitterParameterOverrideKey; +private: +#if !BUILD_RELEASE + String _debugName; +#endif + public: /// /// The asset data version number. Used to sync the data with the instances state. Incremented each time asset gets loaded. diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 679be1e98..28efcaa44 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -150,7 +150,6 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector Vector3 displacement = speed; displacement += GetPhysicsScene()->GetGravity() * deltaTime; displacement *= deltaTime; - return Move(displacement); } @@ -162,7 +161,8 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); result = (CollisionFlags)PhysicsBackend::MoveController(_controller, _shape, displacement, _minMoveDistance, deltaTime); _lastFlags = result; - SetPosition(PhysicsBackend::GetControllerPosition(_controller)); + Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + SetPosition(position); } return result; } @@ -178,10 +178,11 @@ void CharacterController::DrawPhysicsDebug(RenderView& view) const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const Vector3 position = _transform.LocalToWorld(_center); if (view.Mode == ViewMode::PhysicsColliders) - DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true); + DEBUG_DRAW_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true); else - DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true); + DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true); } void CharacterController::OnDebugDrawSelected() @@ -190,7 +191,8 @@ void CharacterController::OnDebugDrawSelected() const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false); + const Vector3 position = _transform.LocalToWorld(_center); + DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false); // Base Collider::OnDebugDrawSelected(); @@ -204,7 +206,8 @@ void CharacterController::CreateController() ASSERT(_controller == nullptr && _shape == nullptr); _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, _transform.Translation, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + const Vector3 position = _transform.LocalToWorld(_center); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_shape, _upDirection); @@ -280,6 +283,7 @@ void CharacterController::OnActiveTransformChanged() _isUpdatingTransform = true; Transform transform; PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation); + transform.Translation -= _center; transform.Orientation = _transform.Orientation; transform.Scale = _transform.Scale; SetTransform(transform); @@ -360,9 +364,10 @@ void CharacterController::OnTransformChanged() Actor::OnTransformChanged(); // Update physics + const Vector3 position = _transform.LocalToWorld(_center); if (!_isUpdatingTransform && _controller) { - PhysicsBackend::SetControllerPosition(_controller, _transform.Translation); + PhysicsBackend::SetControllerPosition(_controller, position); const Float3 scale = GetScale(); if (!Float3::NearEqual(_cachedScale, scale)) UpdateGeometry(); @@ -370,7 +375,7 @@ void CharacterController::OnTransformChanged() } else if (!_controller) { - _box = BoundingBox(_transform.Translation); + _box = BoundingBox(position); BoundingSphere::FromBox(_box, _sphere); } } diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 7e729afeb..b8637b45a 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -127,7 +127,7 @@ public: /// /// Gets the character up vector. /// - API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(true), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(typeof(Vector3), \"0,1,0\"), EditorDisplay(\"Character Controller\")") Vector3 GetUpDirection() const; /// diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 2ca702d4f..5d87858b6 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -1143,6 +1143,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex EyeAdaptationPass::Instance()->Render(renderContext, tempBuffer); PostProcessingPass::Instance()->Render(renderContext, tempBuffer, output, colorGradingLUT); RenderTargetPool::Release(colorGradingLUT); + RenderTargetPool::Release(tempBuffer); context->ResetRenderTarget(); // Rebind resources diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index a1e2fb134..9f7d7222a 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -681,6 +681,16 @@ void* BinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const Scri void BinaryModule::Destroy(bool isReloading) { + // Destroy any default script instances + for (const auto& type : Types) + { + if (type.Type == ScriptingTypes::Script && type.Script.DefaultInstance) + { + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; + } + } + // Unregister GetModules().RemoveKeepOrder(this); } @@ -1443,6 +1453,10 @@ void NativeBinaryModule::Destroy(bool isReloading) { ManagedBinaryModule::Destroy(isReloading); + // Skip native code unloading from core libs + if (this == GetBinaryModuleCorlib() || this == GetBinaryModuleFlaxEngine()) + return; + // Release native library const auto library = Library; if (library) diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.cpp b/Source/Engine/Scripting/ManagedCLR/MClass.cpp index fb1805c64..ea944447f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MClass.cpp @@ -127,6 +127,15 @@ bool MClass::IsSubClassOf(const MonoClass* monoClass) const } #endif +bool MClass::HasInterface(const MClass* klass) const +{ +#if USE_MONO + return klass && mono_class_is_assignable_from(klass->GetNative(), _monoClass) != 0; +#else + return false; +#endif +} + bool MClass::IsInstanceOfType(MObject* object) const { if (object == nullptr) diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index 343ec4fd0..b275081ad 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -154,6 +154,13 @@ public: bool IsSubClassOf(const MonoClass* monoClass) const; #endif + /// + /// Checks if this class implements the specified interface (including any base types). + /// + /// The interface class. + /// True if this class implements the specified interface. + bool HasInterface(const MClass* klass) const; + /// /// Checks is the provided object instance of this class' type. /// diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 5c0e6a400..2f2fd7610 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -530,13 +530,6 @@ void Scripting::Release() for (int32 i = modules.Count() - 1; i >= 0; i--) { auto module = modules[i]; - if (module == GetBinaryModuleCorlib() || module == GetBinaryModuleFlaxEngine()) - { - // Just C# assembly unload for in-build modules - ((ManagedBinaryModule*)module)->Assembly->Unload(); - continue; - } - module->Destroy(false); } _nonNativeModules.ClearDelete(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index beaa60b87..daecf8c45 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -819,7 +819,8 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti ImportCurve(aAnim->mPositionKeys, aAnim->mNumPositionKeys, anim.Position); ImportCurve(aAnim->mRotationKeys, aAnim->mNumRotationKeys, anim.Rotation); - ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale); + if (options.ImportScaleTracks) + ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale); } } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 002c84b09..1eb9e1f27 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1051,9 +1051,9 @@ bool ImportAnimation(int32 index, ImportedModelData& data, OpenFbxImporterData& const ofbx::AnimationCurveNode* translationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Translation"); const ofbx::AnimationCurveNode* rotationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Rotation"); - const ofbx::AnimationCurveNode* scalingNode = nullptr; //layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling"); + const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling"); - if (translationNode || rotationNode || scalingNode) + if (translationNode || rotationNode || (scalingNode && importerData.Options.ImportScaleTracks)) animatedNodes.Add(nodeIndex); } if (animatedNodes.IsEmpty()) @@ -1069,13 +1069,14 @@ bool ImportAnimation(int32 index, ImportedModelData& data, OpenFbxImporterData& const ofbx::AnimationCurveNode* translationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Translation"); const ofbx::AnimationCurveNode* rotationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Rotation"); - //const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling"); + const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling"); anim.NodeName = aNode.Name; ImportCurve(translationNode, anim.Position, info, ExtractKeyframePosition); ImportCurve(rotationNode, anim.Rotation, info, ExtractKeyframeRotation); - //ImportCurve(scalingNode, anim.Scale, info, ExtractKeyframeScale); + if (importerData.Options.ImportScaleTracks) + ImportCurve(scalingNode, anim.Scale, info, ExtractKeyframeScale); } if (importerData.ConvertRH) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp index 3825709ca..b7fb04855 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp @@ -53,6 +53,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(SamplingRate); SERIALIZE(SkipEmptyCurves); SERIALIZE(OptimizeKeyframes); + SERIALIZE(ImportScaleTracks); SERIALIZE(EnableRootMotion); SERIALIZE(RootNodeName); SERIALIZE(GenerateLODs); @@ -93,6 +94,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(SamplingRate); DESERIALIZE(SkipEmptyCurves); DESERIALIZE(OptimizeKeyframes); + DESERIALIZE(ImportScaleTracks); DESERIALIZE(EnableRootMotion); DESERIALIZE(RootNodeName); DESERIALIZE(GenerateLODs); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 7d767bc77..b29bed71f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -235,6 +235,7 @@ public: float SamplingRate = 0.0f; bool SkipEmptyCurves = true; bool OptimizeKeyframes = true; + bool ImportScaleTracks = false; bool EnableRootMotion = false; String RootNodeName; diff --git a/Source/Engine/UI/GUI/CanvasScaler.cs b/Source/Engine/UI/GUI/CanvasScaler.cs new file mode 100644 index 000000000..d86c868fc --- /dev/null +++ b/Source/Engine/UI/GUI/CanvasScaler.cs @@ -0,0 +1,541 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System.ComponentModel; + +namespace FlaxEngine.GUI +{ + /// + /// UI canvas scaling component for user interface that targets multiple different game resolutions (eg. mobile screens). + /// + public class CanvasScaler : ContainerControl + { + /// + /// Canvas scaling modes. + /// + public enum ScalingMode + { + /// + /// Applies constant scale to the whole UI in pixels. + /// + ConstantPixelSize, + + /// + /// Applies constant scale to the whole UI in physical units (depends on the screen DPI). Ensures the UI will have specific real-world size no matter the screen resolution. + /// + ConstantPhysicalSize, + + /// + /// Applies min/max scaling to the UI depending on the screen resolution. Ensures the UI size won't go below min or above max resolution to maintain it's readability. + /// + ScaleWithResolution, + + /// + /// Applies scaling curve to the UI depending on the screen DPI. + /// + ScaleWithDpi, + } + + /// + /// Physical unit types for canvas scaling. + /// + public enum PhysicalUnitMode + { + /// + /// Centimeters (0.01 meter). + /// + Centimeters, + + /// + /// Millimeters (0.1 centimeter, 0.001 meter). + /// + Millimeters, + + /// + /// Inches (2.54 centimeters). + /// + Inches, + + /// + /// Points (1/72 inch, 1/112 of pica). + /// + Points, + + /// + /// Pica (1/6 inch). + /// + Picas, + } + + /// + /// Resolution scaling modes. + /// + public enum ResolutionScalingMode + { + /// + /// Uses the shortest side of the screen to scale the canvas for min/max rule. + /// + ShortestSide, + + /// + /// Uses the longest side of the screen to scale the canvas for min/max rule. + /// + LongestSide, + + /// + /// Uses the horizontal (X, width) side of the screen to scale the canvas for min/max rule. + /// + Horizontal, + + /// + /// Uses the vertical (Y, height) side of the screen to scale the canvas for min/max rule. + /// + Vertical, + } + + private ScalingMode _scalingMode = ScalingMode.ConstantPixelSize; + private PhysicalUnitMode _physicalUnit = PhysicalUnitMode.Points; + private ResolutionScalingMode _resolutionMode = ResolutionScalingMode.ShortestSide; + private float _scale = 1.0f; + private float _scaleFactor = 1.0f; + private float _physicalUnitSize = 1.0f; + private Float2 _resolutionMin = new Float2(1, 1); + private Float2 _resolutionMax = new Float2(10000, 10000); + + /// + /// Gets the current UI scale. Computed based on the setup when performing layout. + /// + public float CurrentScale => _scale; + + /// + /// The UI Canvas scaling mode. + /// + [EditorOrder(0), EditorDisplay("Canvas Scaler"), ExpandGroups, DefaultValue(ScalingMode.ConstantPixelSize)] + public ScalingMode Scaling + { + get => _scalingMode; + set + { + if (_scalingMode == value) + return; + _scalingMode = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas scale. Applied in all scaling modes for custom UI sizing. + /// + [EditorOrder(10), EditorDisplay("Canvas Scaler"), DefaultValue(1.0f), Limit(0.001f, 1000.0f, 0.01f)] + public float ScaleFactor + { + get => _scaleFactor; + set + { + if (Mathf.NearEqual(_scaleFactor, value)) + return; + _scaleFactor = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas physical unit to use for scaling via PhysicalUnitSize. Used only in ConstantPhysicalSize mode. + /// +#if FLAX_EDITOR + [EditorOrder(100), EditorDisplay("Canvas Scaler"), DefaultValue(PhysicalUnitMode.Points), VisibleIf(nameof(IsConstantPhysicalSize))] +#endif + public PhysicalUnitMode PhysicalUnit + { + get => _physicalUnit; + set + { + if (_physicalUnit == value) + return; + _physicalUnit = value; +#if FLAX_EDITOR + if (FlaxEditor.CustomEditors.CustomEditor.IsSettingValue) + { + // Set auto-default physical unit value for easier tweaking in Editor + _physicalUnitSize = GetUnitDpi(_physicalUnit) / Platform.Dpi; + } +#endif + PerformLayout(); + } + } + + /// + /// The UI Canvas physical unit value. Used only in ConstantPhysicalSize mode. + /// +#if FLAX_EDITOR + [EditorOrder(110), EditorDisplay("Canvas Scaler"), DefaultValue(1.0f), Limit(0.000001f, 1000000.0f, 0.0f), VisibleIf(nameof(IsConstantPhysicalSize))] +#endif + public float PhysicalUnitSize + { + get => _physicalUnitSize; + set + { + if (Mathf.NearEqual(_physicalUnitSize, value)) + return; + _physicalUnitSize = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas resolution scaling mode. Controls min/max resolutions usage in relation to the current screen resolution to compute the UI scale. Used only in ScaleWithResolution mode. + /// +#if FLAX_EDITOR + [EditorOrder(120), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))] +#endif + public ResolutionScalingMode ResolutionMode + { + get => _resolutionMode; + set + { + if (_resolutionMode == value) + return; + _resolutionMode = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas minimum resolution. If the screen has lower size, then the interface will be scaled accordingly. Used only in ScaleWithResolution mode. + /// +#if FLAX_EDITOR + [EditorOrder(120), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))] +#endif + public Float2 ResolutionMin + { + get => _resolutionMin; + set + { + value = Float2.Max(value, Float2.One); + if (Float2.NearEqual(ref _resolutionMin, ref value)) + return; + _resolutionMin = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas maximum resolution. If the screen has higher size, then the interface will be scaled accordingly. Used only in ScaleWithResolution mode. + /// +#if FLAX_EDITOR + [EditorOrder(130), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))] +#endif + public Float2 ResolutionMax + { + get => _resolutionMax; + set + { + value = Float2.Max(value, Float2.One); + if (Float2.NearEqual(ref _resolutionMax, ref value)) + return; + _resolutionMax = value; + PerformLayout(); + } + } + + /// + /// The UI Canvas scaling curve based on screen resolution - shortest/longest/vertical/horizontal (key is resolution, value is scale factor). Clear keyframes to skip using it and follow min/max rules only. Used only in ScaleWithResolution mode. + /// +#if FLAX_EDITOR + [EditorOrder(140), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))] +#endif + public LinearCurve ResolutionCurve = new LinearCurve(new[] + { + new LinearCurve.Keyframe(480, 0.444f), // 480p + new LinearCurve.Keyframe(720, 0.666f), // 720p + new LinearCurve.Keyframe(1080, 1.0f), // 1080p + new LinearCurve.Keyframe(8640, 8.0f), // 8640p + }); + + /// + /// The UI Canvas scaling curve based on screen DPI (key is DPI, value is scale factor). Used only in ScaleWithDpi mode. + /// +#if FLAX_EDITOR + [EditorOrder(150), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithDpi))] +#endif + public LinearCurve DpiCurve = new LinearCurve(new[] + { + new LinearCurve.Keyframe(1.0f, 1.0f), + new LinearCurve.Keyframe(96.0f, 1.0f), + new LinearCurve.Keyframe(200.0f, 2.0f), + new LinearCurve.Keyframe(400.0f, 4.0f), + }); + +#if FLAX_EDITOR + private bool IsConstantPhysicalSize => _scalingMode == ScalingMode.ConstantPhysicalSize; + private bool IsScaleWithResolution => _scalingMode == ScalingMode.ScaleWithResolution; + private bool IsScaleWithDpi => _scalingMode == ScalingMode.ScaleWithDpi; +#endif + + /// + /// Initializes a new instance of the class. + /// + public CanvasScaler() + { + // Fill the canvas by default + Offsets = Margin.Zero; + AnchorPreset = AnchorPresets.StretchAll; + AutoFocus = false; + } + + /// + /// Updates the scaler for the current setup. + /// + public void UpdateScale() + { + float scale = 1.0f; + if (Parent != null) + { + UICanvas canvas = (Root as CanvasRootControl)?.Canvas; + float dpi = Platform.Dpi; + switch (canvas?.RenderMode ?? CanvasRenderMode.ScreenSpace) + { + case CanvasRenderMode.WorldSpace: + case CanvasRenderMode.WorldSpaceFaceCamera: + scale = 1.0f; + break; + default: + switch (_scalingMode) + { + case ScalingMode.ConstantPixelSize: + scale = 1.0f; + break; + case ScalingMode.ConstantPhysicalSize: + { + float targetDpi = GetUnitDpi(_physicalUnit); + scale = dpi / targetDpi * _physicalUnitSize; + break; + } + case ScalingMode.ScaleWithResolution: + { + Float2 resolution = Float2.Max(Size, Float2.One); + int axis = 0; + switch (_resolutionMode) + { + case ResolutionScalingMode.ShortestSide: + axis = resolution.X > resolution.Y ? 1 : 0; + break; + case ResolutionScalingMode.LongestSide: + axis = resolution.X > resolution.Y ? 0 : 1; + break; + case ResolutionScalingMode.Horizontal: + axis = 0; + break; + case ResolutionScalingMode.Vertical: + axis = 1; + break; + } + float min = _resolutionMin[axis], max = _resolutionMax[axis], value = resolution[axis]; + if (value < min) + scale = min / value; + else if (value > max) + scale = max / value; + if (ResolutionCurve != null && ResolutionCurve.Keyframes?.Length != 0) + { + ResolutionCurve.Evaluate(out var curveScale, value, false); + scale *= curveScale; + } + break; + } + case ScalingMode.ScaleWithDpi: + DpiCurve?.Evaluate(out scale, dpi, false); + break; + } + break; + } + } + _scale = Mathf.Max(scale * _scaleFactor, 0.01f); + } + + private float GetUnitDpi(PhysicalUnitMode unit) + { + float dpi = 1.0f; + switch (unit) + { + case PhysicalUnitMode.Centimeters: + dpi = 2.54f; + break; + case PhysicalUnitMode.Millimeters: + dpi = 25.4f; + break; + case PhysicalUnitMode.Inches: + dpi = 1; + break; + case PhysicalUnitMode.Points: + dpi = 72; + break; + case PhysicalUnitMode.Picas: + dpi = 6; + break; + } + return dpi; + } + + /// + protected override void PerformLayoutBeforeChildren() + { + // Update current scaling before performing layout + UpdateScale(); + + base.PerformLayoutBeforeChildren(); + } + + #region UI Scale + +#if FLAX_EDITOR + /// + public override Rectangle EditorBounds => new Rectangle(Float2.Zero, Size / _scale); +#endif + + /// + public override void Draw() + { + DrawSelf(); + + // Draw children with scale + var scaling = new Float3(_scale, _scale, 1); + Matrix3x3.Scaling(ref scaling, out Matrix3x3 scale); + Render2D.PushTransform(scale); + if (ClipChildren) + { + GetDesireClientArea(out var clientArea); + Render2D.PushClip(ref clientArea); + DrawChildren(); + Render2D.PopClip(); + } + else + { + DrawChildren(); + } + Render2D.PopTransform(); + } + + /// + public override void GetDesireClientArea(out Rectangle rect) + { + // Scale the area for the client controls + rect = new Rectangle(Float2.Zero, Size / _scale); + } + + /// + public override bool IntersectsContent(ref Float2 locationParent, out Float2 location) + { + // Skip local PointFromParent but use base code + location = base.PointFromParent(ref locationParent); + return ContainsPoint(ref location); + } + + /// + public override Float2 PointToParent(ref Float2 location) + { + var result = base.PointToParent(ref location); + result *= _scaleFactor; + return result; + } + + /// + public override Float2 PointFromParent(ref Float2 location) + { + var result = base.PointFromParent(ref location); + result /= _scaleFactor; + return result; + } + + /// + public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) + { + location /= _scale; + return base.OnDragEnter(ref location, data); + } + + /// + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + location /= _scale; + return base.OnDragMove(ref location, data); + } + + /// + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + location /= _scale; + return base.OnDragDrop(ref location, data); + } + + /// + public override void OnMouseEnter(Float2 location) + { + location /= _scale; + base.OnMouseEnter(location); + } + + /// + public override void OnMouseMove(Float2 location) + { + location /= _scale; + base.OnMouseMove(location); + } + + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + location /= _scale; + return base.OnMouseDown(location, button); + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + location /= _scale; + return base.OnMouseUp(location, button); + } + + /// + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + location /= _scale; + return base.OnMouseDoubleClick(location, button); + } + + /// + public override bool OnMouseWheel(Float2 location, float delta) + { + location /= _scale; + return base.OnMouseWheel(location, delta); + } + + /// + public override void OnTouchEnter(Float2 location, int pointerId) + { + location /= _scale; + base.OnTouchEnter(location, pointerId); + } + + /// + public override void OnTouchMove(Float2 location, int pointerId) + { + location /= _scale; + base.OnTouchMove(location, pointerId); + } + + /// + public override bool OnTouchDown(Float2 location, int pointerId) + { + location /= _scale; + return base.OnTouchDown(location, pointerId); + } + + /// + public override bool OnTouchUp(Float2 location, int pointerId) + { + location /= _scale; + return base.OnTouchUp(location, pointerId); + } + + #endregion + } +} diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index d7b226cd1..8b2401428 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -1259,6 +1259,13 @@ namespace FlaxEngine.GUI return PointFromParent(ref location); } +#if FLAX_EDITOR + /// + /// Bounds rectangle for editor UI. + /// + public virtual Rectangle EditorBounds => new Rectangle(Float2.Zero, _bounds.Size); +#endif + #endregion #region Control Action diff --git a/Source/Engine/UI/GUI/Panels/BlurPanel.cs b/Source/Engine/UI/GUI/Panels/BlurPanel.cs index 8d4525113..0fe506e44 100644 --- a/Source/Engine/UI/GUI/Panels/BlurPanel.cs +++ b/Source/Engine/UI/GUI/Panels/BlurPanel.cs @@ -11,9 +11,15 @@ namespace FlaxEngine.GUI /// /// Gets or sets the blur strength. Defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU. /// - [EditorOrder(0), Limit(0, 100, 0.0f), Tooltip("Blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.")] + [EditorOrder(0), Limit(0, 100, 0.0f)] public float BlurStrength { get; set; } + /// + /// If checked, the blur strength will be scaled with the control size, which makes it resolution-independent. + /// + [EditorOrder(10)] + public bool BlurScaleWithSize { get; set; } = false; + /// /// Initializes a new instance of the class. /// @@ -27,10 +33,13 @@ namespace FlaxEngine.GUI { base.Draw(); - float strength = BlurStrength; + var size = Size; + var strength = BlurStrength; + if (BlurScaleWithSize) + strength *= size.MinValue / 1000.0f; if (strength > Mathf.Epsilon) { - Render2D.DrawBlur(new Rectangle(Float2.Zero, Size), strength); + Render2D.DrawBlur(new Rectangle(Float2.Zero, size), strength); } } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index dcb672f8f..6dd0d59e4 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -303,6 +303,11 @@ namespace Flax.Build.Bindings case "char": return "mono_get_char_class()"; case "IntPtr": return "mono_get_intptr_class()"; case "UIntPtr": return "mono_get_uintptr_class()"; + + // Vector2/3/4 have custom type in C# (due to lack of typename using in older C#) + case "Vector2": return "Scripting::FindClassNative(\"FlaxEngine.Vector2\")"; + case "Vector3": return "Scripting::FindClassNative(\"FlaxEngine.Vector3\")"; + case "Vector4": return "Scripting::FindClassNative(\"FlaxEngine.Vector4\")"; } // Find API type diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 63c9bacfa..fcc1ca83b 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -75,6 +75,7 @@ namespace Flax.Build.Plugins { "FlaxEngine.Float3", new InBuildSerializer("WriteFloat3", "ReadFloat3") }, { "FlaxEngine.Float4", new InBuildSerializer("WriteFloat4", "ReadFloat4") }, { "FlaxEngine.Quaternion", new InBuildSerializer("WriteQuaternion", "ReadQuaternion") }, + { "FlaxEngine.Ray", new InBuildSerializer("WriteRay", "ReadRay") }, }; /// @@ -471,7 +472,7 @@ namespace Flax.Build.Plugins if (string.Equals(binaryModule.Key, "FlaxEngine", StringComparison.Ordinal)) return; - // Skip assemblies not using netowrking + // Skip assemblies not using networking if (!binaryModule.Any(module => module.Tags.ContainsKey(Network))) return; @@ -1244,15 +1245,16 @@ namespace Flax.Build.Plugins var methodRPC = new MethodRPC(); methodRPC.Type = type; methodRPC.Method = method; - methodRPC.IsServer = (bool)attribute.GetFieldValue("Server", false); - methodRPC.IsClient = (bool)attribute.GetFieldValue("Client", false); - methodRPC.Channel = (int)attribute.GetFieldValue("Channel", 4); // int as NetworkChannelType (default is ReliableOrdered=4) + methodRPC.Channel = 4; // int as NetworkChannelType (default is ReliableOrdered=4) if (attribute.HasConstructorArguments && attribute.ConstructorArguments.Count >= 3) { methodRPC.IsServer = (bool)attribute.ConstructorArguments[0].Value; methodRPC.IsClient = (bool)attribute.ConstructorArguments[1].Value; methodRPC.Channel = (int)attribute.ConstructorArguments[2].Value; } + methodRPC.IsServer = (bool)attribute.GetFieldValue("Server", methodRPC.IsServer); + methodRPC.IsClient = (bool)attribute.GetFieldValue("Client", methodRPC.IsClient); + methodRPC.Channel = (int)attribute.GetFieldValue("Channel", methodRPC.Channel); if (methodRPC.IsServer && methodRPC.IsClient) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} cannot be both Server and Client.", method); @@ -1358,7 +1360,9 @@ namespace Flax.Build.Plugins var jumpIfBodyStart = il.Create(OpCodes.Nop); // if block body var jumpIf2Start = il.Create(OpCodes.Nop); // 2nd part of the if var jumpBodyStart = il.Create(OpCodes.Nop); // original method body start - var jumpBodyEnd = il.Body.Instructions.First(x => x.OpCode == OpCodes.Ret); + var jumpBodyEnd = il.Body.Instructions.Last(x => x.OpCode == OpCodes.Ret && x.Previous != null); + if (jumpBodyEnd == null) + throw new Exception("Missing IL Return op code in method " + method.Name); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 0)); il.InsertBefore(ilStart, il.Create(OpCodes.Brfalse_S, jumpIf2Start)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 2)); @@ -1408,7 +1412,13 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 2)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldc_I4_2)); - il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, jumpBodyEnd)); + var tmp = il.Create(OpCodes.Nop); + il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, tmp)); + il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyStart)); + il.InsertBefore(ilStart, tmp); + //il.InsertBefore(ilStart, il.Create(OpCodes.Ret)); + il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyEnd)); + } // if (client && networkMode == NetworkManagerMode.Server) return; @@ -1417,7 +1427,12 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 2)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldc_I4_1)); - il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, jumpBodyEnd)); + var tmp = il.Create(OpCodes.Nop); + il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, tmp)); + il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyStart)); + il.InsertBefore(ilStart, tmp); + //il.InsertBefore(ilStart, il.Create(OpCodes.Ret)); + il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyEnd)); } // Continue to original method body diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index c4ca880f7..9789f8dd9 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -54,6 +54,7 @@ namespace Flax.Deploy DeployFile(src, dst, "Flax.Build.xml"); DeployFile(src, dst, "Ionic.Zip.Reduced.dll"); DeployFile(src, dst, "Newtonsoft.Json.dll"); + DeployFile(src, dst, "Mono.Cecil.dll"); } // Deploy content