diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs index efccfdbe5..bf7116647 100644 --- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs +++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset); } /// diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 212417e9f..45de48f56 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset); } } } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index 58d34299c..0943c2b6f 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((Material)request.Asset); } } } diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index a57ddbf61..7c3f8ae1c 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -83,7 +83,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((Model)request.Asset); } /// diff --git a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs index 9ef533b76..6c6f0c24f 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterProxy.cs @@ -133,7 +133,7 @@ namespace FlaxEditor.Content return false; // Ensure assets are ready to be used - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; var asset = (ParticleEmitter)request.Asset; if (!asset.IsLoaded) diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index ccea01998..97ca17f25 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -104,7 +104,7 @@ namespace FlaxEditor.Content return false; // Ensure assets are ready to be used - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; var asset = (ParticleSystem)request.Asset; if (!asset.IsLoaded) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index 410df23b0..32e3b83f3 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -118,7 +118,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - if (!_preview.HasLoadedAssets) + if (!_preview.HasContentLoaded) return false; // Check if asset is streamed enough @@ -140,7 +140,7 @@ namespace FlaxEditor.Content actor is SpriteRender || actor is TextRender) { - actorBounds = actor.EditorBox; + actorBounds = actor.EditorBoundingBox; } if (actorBounds != BoundingBox.Empty) { @@ -182,7 +182,7 @@ namespace FlaxEditor.Content // Update some actors data (some actor types update bounds/data later but its required to be done before rendering) var bounds = BoundingBox.Empty; Prepare(_preview.Instance, ref bounds); - //bounds = _preview.Instance.EditorBoxChildren; + //bounds = _preview.Instance.EditorBoundingBoxWithChildren; // Auto fit actor to camera if (bounds != BoundingBox.Empty) diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs index 95f2f7510..9db9a152d 100644 --- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs +++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Content /// public override bool CanDrawThumbnail(ThumbnailRequest request) { - return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset); + return _preview.HasContentLoaded && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset); } /// diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 24c0eb2fb..9797c2850 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -802,7 +802,9 @@ int32 GameCookerImpl::ThreadFunction() Build(); } + mutex.Lock(); ThreadCond.Wait(mutex); + mutex.Unlock(); } IsThreadRunning = false; diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 476219960..d70557047 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -58,9 +58,9 @@ namespace FlaxEditor.CustomEditors if (targetType.Type == typeof(object) && values.Count > 0 && values[0] != null && !values.HasDifferentTypes) return CreateEditor(TypeUtils.GetObjectType(values[0]), canUseRefPicker); - // Special case if property is interface but the value is implemented as Scripting Object that should use reference picker - if (targetType.IsInterface && canUseRefPicker && values.Count > 0 && values[0] is FlaxEngine.Object) - return new DummyEditor(); + // Special case if property is interface but the value is implemented as Scripting Object that should use reference picker (or all interface impl are by Scripting Objects) + if (canUseRefPicker && targetType.IsInterface && values.Count > 0 && values[0] is FlaxEngine.Object) + return new FlaxObjectRefEditor(); // Use editor for the property type return CreateEditor(targetType, canUseRefPicker); diff --git a/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs b/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs index 2cbf01e41..3c101453f 100644 --- a/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/NavMeshBoundsVolumeEditor.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (value is NavMeshBoundsVolume volume) { - Navigation.BuildNavMesh(volume.Box, volume.Scene); + Navigation.BuildNavMesh(volume.BoundingBox, volume.Scene); Editor.Instance.Scene.MarkSceneEdited(volume.Scene); } } diff --git a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs index 448c55443..bfcb8bc57 100644 --- a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs @@ -24,7 +24,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var patchesCount = terrain.PatchesCount; var chunkSize = terrain.ChunkSize; var resolution = terrain.Scale; - var totalSize = terrain.Box.Size; + var totalSize = terrain.BoundingBox.Size; string text = string.Format("Patches: {0}\nTotal Chunks: {1}\nChunk Size: {2}\nResolution: {3}m x {4}m\nTotal size: {5}km x {6}km", patchesCount, patchesCount * 16, diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 1fe849f09..7dfce6055 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -260,7 +260,7 @@ namespace FlaxEditor.CustomEditors.Editors var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key)); var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor); + itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values, attributes), overrideEditor); if (_readOnly && itemLayout.Children.Count > 0) GenericEditor.OnReadOnlyProperty(itemLayout); } diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 29328e318..e237b3272 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Reflection; using FlaxEditor.Content; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; @@ -48,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors public IPresenterOwner PresenterContext; /// - /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass. + /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass or a scripting interface. /// public ScriptType Type { @@ -57,11 +58,12 @@ namespace FlaxEditor.CustomEditors.Editors { if (_type == value) return; - if (value == ScriptType.Null || (value.Type != typeof(Object) && !value.IsSubclassOf(ScriptType.Object))) + if (value == ScriptType.Null || (!value.IsInterface && value.Type != typeof(Object) && !value.IsSubclassOf(ScriptType.Object))) throw new ArgumentException(string.Format("Invalid type for FlaxObjectRefEditor. Input type: {0}", value != ScriptType.Null ? value.TypeName : "null")); _type = value; - _supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) || + _supportsPickDropDown = value.IsInterface || + new ScriptType(typeof(Actor)).IsAssignableFrom(value) || new ScriptType(typeof(Script)).IsAssignableFrom(value); // Deselect value if it's not valid now @@ -149,41 +151,38 @@ namespace FlaxEditor.CustomEditors.Editors protected virtual bool IsValid(Object obj) { var type = TypeUtils.GetObjectType(obj); - return obj == null || _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type)); + return obj == null || (!_type.IsInterface || obj is SceneObject) && _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type)); } private void ShowDropDownMenu() { Focus(); - if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type)) + var pos = new Float2(0, Height); + if (_type.IsInterface) { - ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => - { - Value = actor; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + SceneObjectSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); + } + else if (new ScriptType(typeof(Actor)).IsAssignableFrom(_type)) + { + ActorSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); } else if (new ScriptType(typeof(Control)).IsAssignableFrom(_type)) { - ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor => - { - Value = actor as UIControl; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + ActorSearchPopup.Show(this, pos, IsValid, actor => { SetDropDownResult(actor as UIControl); }, PresenterContext); } else { - ScriptSearchPopup.Show(this, new Float2(0, Height), IsValid, script => - { - Value = script; - RootWindow.Focus(); - Focus(); - }, PresenterContext); + ScriptSearchPopup.Show(this, pos, IsValid, SetDropDownResult, PresenterContext); } } + private void SetDropDownResult(Object value) + { + Value = value; + RootWindow.Focus(); + Focus(); + } + /// /// Called when value gets changed. /// @@ -218,7 +217,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw info Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, Type != null ? $"Multiple Values ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Type != null ? $"Multiple Values ({Utilities.Utils.GetTypeNameUI(_type)})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } else if (isSelected) @@ -235,7 +234,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw info Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, Type != null ? $"None ({Utilities.Utils.GetPropertyNameUI(Type.ToString())})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Type != null ? $"None ({Utilities.Utils.GetTypeNameUI(_type)})" : "-", nameRect, isEnabled ? style.ForegroundGrey : style.ForegroundGrey.AlphaMultiplied(0.75f), TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } @@ -662,4 +661,58 @@ namespace FlaxEditor.CustomEditors.Editors } } } + + /// + /// Default implementation of the inspector used to edit reference to the . + /// + internal sealed class ScriptingObjectInterfaceReferenceEditor : CustomEditor + { + private CustomElement _element; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + if (!HasDifferentTypes) + { + _element = layout.Custom(); + _element.CustomControl.PresenterContext = Presenter.Owner; + _element.CustomControl.Type = new ScriptType(Values.Type.GetGenericArguments()[0]); + _element.CustomControl.ValueChanged += OnValueChanged; + } + } + + private void OnValueChanged() + { + // Set value + var obj = _element.CustomControl.Value; + var v = Values.Type.CreateInstance(); + var objectField = v.GetType().GetField("_object", BindingFlags.Instance | BindingFlags.NonPublic); + objectField.SetValue(v, obj); + SetValue(v); + } + + /// + public override void Refresh() + { + base.Refresh(); + + var differentValues = HasDifferentValues; + _element.CustomControl.DifferentValues = differentValues; + if (!differentValues) + { + // Get value + var v = Values[0]; + var obj = v as Object; + if (v != null && obj == null) + { + var objectField = v.GetType().GetField("_object", BindingFlags.Instance | BindingFlags.NonPublic); + obj = objectField.GetValue(v) as Object; + } + _element.CustomControl.Value = obj; + } + } + } } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 7181a2c01..c8f7dc3cd 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -716,19 +716,18 @@ namespace FlaxEditor.CustomEditors.Editors if (value == null) { // Check if it's an object type that can be created in editor - if (type != ScriptMemberInfo.Null) + if (type != ScriptMemberInfo.Null && CanEditValue) { ScriptType[] types = null; if (type.IsAbstract || type.IsInterface) { // Show picker with all types that implement specific class/interface but are not abstract - types = Editor.Instance.CodeEditing.All.Get().Where(x => !x.IsAbstract && x.CanCreateInstance && type.IsAssignableFrom(x)).ToArray(); + types = Editor.Instance.CodeEditing.All.Get().Where(x => !x.IsAbstract && !x.IsScriptingObject && x.CanCreateInstance && type.IsAssignableFrom(x)).ToArray(); } else if (type.CanCreateInstance) { types = [type]; } - if (types != null && types.Length != 0) { layout = layout.Space(20); @@ -759,7 +758,7 @@ namespace FlaxEditor.CustomEditors.Editors layout.Label(""); return; } - if (!type.IsArray && !type.IsStructure && !type.IsScriptingObject && (type.IsAbstract || type.IsInterface) && value.GetType() != type.Type && layout is GroupElement group) + if (!type.IsArray && !type.IsStructure && !type.IsScriptingObject && (type.IsAbstract || type.IsInterface) && value.GetType() != type.Type && layout is GroupElement group && CanEditValue) { // Add button to unset the value to null (eg. to edit it to different type) var button = group.AddHeaderButton("Reset value to null", 0, FlaxEngine.GUI.Style.Current.Cross); diff --git a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs index b143b44b1..b08d39beb 100644 --- a/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/DictionaryValueContainer.cs @@ -14,6 +14,8 @@ namespace FlaxEditor.CustomEditors [HideInEditor] public class DictionaryValueContainer : ValueContainer { + private readonly object[] _attributes; + /// /// The key in the collection. /// @@ -24,9 +26,11 @@ namespace FlaxEditor.CustomEditors /// /// Type of the collection elements. /// The key. - public DictionaryValueContainer(ScriptType elementType, object key) + /// The dictionary property attributes to inherit. + public DictionaryValueContainer(ScriptType elementType, object key, object[] attributes = null) : base(ScriptMemberInfo.Null, elementType) { + _attributes = attributes; Key = key; } @@ -36,8 +40,9 @@ namespace FlaxEditor.CustomEditors /// Type of the collection elements. /// The key. /// The collection values. - public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values) - : this(elementType, key) + /// The dictionary property attributes to inherit. + public DictionaryValueContainer(ScriptType elementType, object key, ValueContainer values, object[] attributes = null) + : this(elementType, key, attributes) { Capacity = values.Count; for (int i = 0; i < values.Count; i++) @@ -123,5 +128,11 @@ namespace FlaxEditor.CustomEditors _hasReferenceValue = true; } } + + /// + public override object[] GetAttributes() + { + return _attributes ?? base.GetAttributes(); + } } } diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index c8d44b32a..b9bd66623 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -297,6 +297,11 @@ namespace FlaxEditor.CustomEditors else if (Type.Type == typeof(long)) _defaultValue = Convert.ToInt64(_defaultValue); } + else if (_defaultValue == null && Type.IsValueType) + { + // Use zero value for value-types that have null as default value for some reason + _defaultValue = Type.CreateInstance(); + } } } if (instanceValues._hasReferenceValue) diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index ae162c714..20ecad605 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -501,8 +501,10 @@ namespace FlaxEditor.GUI.Docking { scrPanel.GetChild(i).Parent = splitterParent; } - Assert.IsTrue(scrPanel.ChildrenCount == 0); - Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount); + if (scrPanel.ChildrenCount != 0 || splitterParent.ChildrenCount == srcPanelChildrenCount) + { + Editor.LogError("Broken dock panel layout!"); + } // Delete splitter.Dispose(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 5f09342c7..95a9a2164 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.GUI public float SortScore; /// - /// Wether the query highlights should be draw. + /// Whether the query highlights should be drawn. /// public bool DrawHighlights = true; @@ -261,6 +261,11 @@ namespace FlaxEditor.GUI /// public readonly VerticalPanel ItemsPanel; + /// + /// Gets a list of panels with item categories. + /// + public IEnumerable CategoryPanels => (IEnumerable)_categoryPanels ?? Array.Empty(); + /// /// Initializes a new instance of the class. /// @@ -504,6 +509,12 @@ namespace FlaxEditor.GUI category.Visible = true; category.Close(false); } + + if (_categoryPanels.Count == 1 && items.Count == 1) + { + // Expand the only category if there are no items outside of it + _categoryPanels[0].Open(false); + } } _searchBox?.Clear(); diff --git a/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs b/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs new file mode 100644 index 000000000..4868dd049 --- /dev/null +++ b/Source/Editor/GUI/Popups/SceneObjectSearchPopup.cs @@ -0,0 +1,146 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; +using FlaxEngine; +using FlaxEngine.GUI; +using FlaxEngine.Utilities; + +namespace FlaxEditor.GUI +{ + /// + /// Popup that shows the list of scene objects to pick. Supports searching and basic type filtering. + /// + /// + public class SceneObjectSearchPopup : ItemsListContextMenu + { + /// + /// The scene object item. + /// + /// + public class SceneObjectItemView : Item + { + private SceneObject _object; + + /// + /// Gets the scene object. + /// + public SceneObject Object => _object; + + /// + /// Initializes a new instance of the class. + /// + /// The object. + public SceneObjectItemView(SceneObject obj) + { + _object = obj; + Category = obj is Actor ? "Actors" : "Scripts"; + if (obj is Script script) + { + var type = TypeUtils.GetObjectType(script); + Name = script.Actor ? $"{type.Name} ({script.Actor.Name})" : type.Name; + } + else if (obj is Actor actor) + { + Name = actor.Name; + } + else + { + Name = obj.ToString(); + } + TooltipText = Utilities.Utils.GetTooltip(obj); + } + + /// + public override void OnDestroy() + { + _object = null; + base.OnDestroy(); + } + } + + /// + /// Validates if the given scene object item can be used to pick it. + /// + /// The scene object. + /// True if is valid. + public delegate bool IsValidDelegate(SceneObject obj); + + private IsValidDelegate _isValid; + private Action _selected; + + private SceneObjectSearchPopup(IsValidDelegate isValid, Action selected, CustomEditors.IPresenterOwner context) + { + _isValid = isValid; + _selected = selected; + + ItemClicked += OnItemClicked; + + if (context is PropertiesWindow || context == null) + { + // TODO: use async thread to search scenes + for (int i = 0; i < Level.ScenesCount; i++) + { + Find(Level.GetScene(i)); + } + } + else if (context is PrefabWindow prefabWindow) + { + Find(prefabWindow.Graph.MainActor); + } + + SortItems(); + } + + private void OnItemClicked(Item item) + { + _selected(((SceneObjectItemView)item).Object); + } + + private void Find(Actor actor) + { + if (!actor) + return; + + if (_isValid(actor)) + AddItem(new SceneObjectItemView(actor)); + + for (int i = 0; i < actor.ScriptsCount; i++) + { + var script = actor.GetScript(i); + if (_isValid(script)) + AddItem(new SceneObjectItemView(script)); + } + + for (int i = 0; i < actor.ChildrenCount; i++) + { + Find(actor.GetChild(i)); + } + } + + /// + /// Shows the popup. + /// + /// The show target. + /// The show target location. + /// Event called to check if a given scene object item is valid to be used. + /// Event called on scene object item pick. + /// The presenter owner context (i.e. PrefabWindow, PropertiesWindow). + /// The dialog. + public static SceneObjectSearchPopup Show(Control showTarget, Float2 showTargetLocation, IsValidDelegate isValid, Action selected, CustomEditors.IPresenterOwner context) + { + var popup = new SceneObjectSearchPopup(isValid, selected, context); + popup.Show(showTarget, showTargetLocation); + return popup; + } + + /// + public override void OnDestroy() + { + _isValid = null; + _selected = null; + base.OnDestroy(); + } + } +} diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 91e37ca25..4b40caecd 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -128,7 +128,7 @@ namespace FlaxEditor.Gizmo { if (_selectionParents[i] is ActorNode actorNode) { - var b = actorNode.Actor.EditorBoxChildren; + var b = actorNode.Actor.EditorBoundingBoxWithChildren; BoundingBox.Merge(ref editorBounds, ref b, out editorBounds); bottomToCenter = Mathf.Min(bottomToCenter, actorNode.Actor.Position.Y - editorBounds.Minimum.Y); } @@ -299,7 +299,7 @@ namespace FlaxEditor.Gizmo { if (_selectionParents[i] is ActorNode actorNode) { - bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren); + bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoundingBoxWithChildren); navigationDirty |= actorNode.AffectsNavigationWithChildren; } } diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 66e835fac..bb174d224 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -13,7 +13,7 @@ namespace FlaxEngine.Gizmo; /// public sealed class ViewportRubberBandSelector { - private bool _isMosueCaptured; + private bool _isMouseCaptured; private bool _isRubberBandSpanning; private bool _tryStartRubberBand; private Float2 _cachedStartingMousePosition; @@ -53,9 +53,9 @@ public sealed class ViewportRubberBandSelector /// Returns true if rubber band is currently spanning public bool ReleaseRubberBandSelection() { - if (_isMosueCaptured) + if (_isMouseCaptured) { - _isMosueCaptured = false; + _isMouseCaptured = false; _owner.Viewport.EndMouseCapture(); } if (_tryStartRubberBand) @@ -99,9 +99,9 @@ public sealed class ViewportRubberBandSelector _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; if (_lastRubberBandRect != _rubberBandRect) { - if (!_isMosueCaptured) + if (!_isMouseCaptured) { - _isMosueCaptured = true; + _isMouseCaptured = true; _owner.Viewport.StartMouseCapture(); } UpdateRubberBand(); @@ -182,7 +182,7 @@ public sealed class ViewportRubberBandSelector var a = node.Actor; // Skip actor if outside of view frustum - var actorBox = a.EditorBox; + var actorBox = a.EditorBoundingBox; if (projection.FrustumCull(ref actorBox) == ContainmentType.Disjoint) continue; @@ -263,9 +263,9 @@ public sealed class ViewportRubberBandSelector /// True if rubber band was active before stopping. public bool StopRubberBand() { - if (_isMosueCaptured) + if (_isMouseCaptured) { - _isMosueCaptured = false; + _isMouseCaptured = false; _owner.Viewport.EndMouseCapture(); } var result = _tryStartRubberBand; diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 356e4145a..a105bcdac 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -333,7 +333,7 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_GetCollisionWires(CollisionData* colli DEFINE_INTERNAL_CALL(void) EditorInternal_GetEditorBoxWithChildren(Actor* obj, BoundingBox* result) { INTERNAL_CALL_CHECK(obj); - *result = obj->GetEditorBoxChildren(); + *result = obj->GetEditorBoundingBoxWithChildren(); } DEFINE_INTERNAL_CALL(void) EditorInternal_SetOptions(ManagedEditor::InternalOptions* options) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index f6643f4f2..9697a099f 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -244,7 +244,7 @@ namespace FlaxEditor.Modules // Auto NavMesh rebuild if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren) { - var bounds = actor.BoxWithChildren; + var bounds = actor.BoundingBoxWithChildren; Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs); } } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index ca0f38b7b..7c577ad4a 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -123,6 +123,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(200)] public InputBinding LockFocusSelection = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift); + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha0")] + [EditorDisplay("Common"), EditorOrder(201)] + public InputBinding ShowEditorContents = new InputBinding(KeyboardKeys.Alpha0, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "Ctrl+F")] [EditorDisplay("Common"), EditorOrder(210)] public InputBinding Search = new InputBinding(KeyboardKeys.F, KeyboardKeys.Control); diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index f25659303..67f671756 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -233,7 +233,7 @@ namespace FlaxEditor.SceneGraph /// The points to use if the actor can be selected. public virtual Vector3[] GetActorSelectionPoints() { - return Actor.EditorBox.GetCorners(); + return Actor.EditorBoundingBox.GetCorners(); } /// diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 515939526..73ef65f7c 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors var options = Editor.Instance.Options.Options.General; if (options.AutoRebuildNavMesh) { - Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs); + Navigation.BuildNavMesh(collider.BoundingBox, options.AutoRebuildNavMeshTimeoutMs); } } } @@ -572,7 +572,7 @@ namespace FlaxEditor.SceneGraph.Actors var viewBounds = sceneContext.Viewport.ViewFrustum; foreach (var s in splines) { - var contains = viewBounds.Contains(s.EditorBox); + var contains = viewBounds.Contains(s.EditorBoundingBox); if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects) result.Add(s); } diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 4cd63f05d..391222b6a 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -319,7 +319,7 @@ namespace FlaxEditor.SceneGraph.Actors private void CreateSphere(StaticModel actor, Spawner spawner, bool singleNode) { - var bounds = actor.Sphere; + var bounds = actor.BoundingSphere; var collider = new SphereCollider { Transform = actor.Transform, @@ -336,10 +336,10 @@ namespace FlaxEditor.SceneGraph.Actors var collider = new CapsuleCollider { Transform = actor.Transform, - Position = actor.Box.Center, + Position = actor.BoundingBox.Center, // Size the capsule to best fit the actor - Radius = (float)actor.Sphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f, + Radius = (float)actor.BoundingSphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f, Height = 100f, }; spawner(collider); diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 1e4765d3b..ebab808d1 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -1,15 +1,62 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using FlaxEditor.Content; +using FlaxEngine; +using FlaxEngine.TypeConverters; +using FlaxEngine.Utilities; using System; using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using FlaxEditor.Content; -using FlaxEngine; -using FlaxEngine.Utilities; + +namespace FlaxEngine.TypeConverters +{ + /// + /// Internal ITypeDescriptorContext used to pass some context for custom TypeConvert implementations. + /// Allows passing CurrentType to TypeConverter.ConvertFrom method to support custom type conversion for FlaxEngine types (simpler than PropertyDescriptor.PropertyType). + /// + internal sealed class DummyTypeDescriptorContext : ITypeDescriptorContext + { + private static DummyTypeDescriptorContext _cached; + + public Type CurrentType; + + public static object ConvertFrom(TypeConverter converter, object value, Type type) + { + if (_cached == null) + _cached = new DummyTypeDescriptorContext(); + _cached.CurrentType = type; + var result = converter.ConvertFrom(_cached, CultureInfo.CurrentUICulture, value); + _cached.CurrentType = null; + return result; + } + + public object GetService(Type serviceType) + { + return null; + } + + public void OnComponentChanged() + { + } + + public bool OnComponentChanging() + { + return false; + } + + public IContainer Container => null; + + public object Instance => null; + + public PropertyDescriptor PropertyDescriptor => null; + } +} namespace FlaxEditor.Scripting { @@ -681,7 +728,7 @@ namespace FlaxEditor.Scripting if (converter.CanConvertTo(type)) value = converter.ConvertTo(value, type); else if (converter.CanConvertFrom(valueType)) - value = converter.ConvertFrom(null, null, value); + value = DummyTypeDescriptorContext.ConvertFrom(converter, value, type); } if (_managed is PropertyInfo propertyInfo) diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index e6bc82fe3..0738c6dd5 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -212,7 +212,7 @@ namespace FlaxEditor.Surface color = Colors.Enum; else if (type.IsValueType) color = Colors.Structures; - else if (type.IsScriptingObject || type.IsInterface) + else if (type.IsScriptingObject || type.IsInterface || type.Name.StartsWith("ScriptingObjectInterfaceReference")) color = Colors.Object; else if (hint == ConnectionsHint.Vector) color = Colors.Vector; diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 81403ae0a..cadaa424b 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -589,7 +589,7 @@ namespace FlaxEditor.Surface redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo); toolStrip.AddSeparator(); toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search); - toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph."); + toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph.", ref inputOptions.ShowEditorContents); var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping); gridSnapButton.LinkTooltip("Toggle grid snapping for nodes."); gridSnapButton.AutoCheck = true; @@ -600,6 +600,7 @@ namespace FlaxEditor.Surface window.InputActions.Add(options => options.Undo, undo.PerformUndo); window.InputActions.Add(options => options.Redo, undo.PerformRedo); window.InputActions.Add(options => options.Search, showSearch); + window.InputActions.Add(options => options.ShowEditorContents, surface.ShowWholeGraph); } } } diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index e8ab67039..caaa64d93 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -231,7 +231,7 @@ namespace FlaxEngine.Tools public override bool IsControllingMouse => IsPainting; - public override BoundingSphere FocusBounds => _cloth?.Sphere ?? base.FocusBounds; + public override BoundingSphere FocusBounds => _cloth?.BoundingSphere ?? base.FocusBounds; public override void Update(float dt) { diff --git a/Source/Editor/Tools/Foliage/FoliageTools.cpp b/Source/Editor/Tools/Foliage/FoliageTools.cpp index 4a545e9d5..19a3330a8 100644 --- a/Source/Editor/Tools/Foliage/FoliageTools.cpp +++ b/Source/Editor/Tools/Foliage/FoliageTools.cpp @@ -67,7 +67,7 @@ struct GeometryLookup static bool Search(Actor* actor, GeometryLookup& lookup) { // Early out if object is not intersecting with the foliage brush bounds - if (!actor->GetIsActive() || !actor->GetBox().Intersects(lookup.Brush)) + if (!actor->GetIsActive() || !actor->GetBoundingBox().Intersects(lookup.Brush)) return true; const auto brush = lookup.Brush; diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index dea5247f4..b0a9c6fb6 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -487,7 +487,7 @@ namespace FlaxEditor.Tools public override bool IsControllingMouse => IsPainting; /// - public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds; + public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.BoundingSphere : base.FocusBounds; /// public override void Update(float dt) @@ -598,7 +598,7 @@ namespace FlaxEditor.Tools var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale)); var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale); var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); - var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; + var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.BoundingSphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1); var lodData = meshDatas[lodIndex]; if (lodData != null) diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index 19ffb1e3f..06729d5fb 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -302,7 +302,7 @@ namespace FlaxEditor.Actions { if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren) { - var bounds = node.Actor.BoxWithChildren; + var bounds = node.Actor.BoundingBoxWithChildren; Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs); } } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5955ccf60..21794412c 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -387,7 +387,7 @@ namespace FlaxEditor.Utilities transforms[i] = nodes[i].Transform; if (nodes[i] is ActorNode actorNode) { - bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren); + bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoundingBoxWithChildren); } } return transforms; @@ -833,6 +833,24 @@ namespace FlaxEditor.Utilities } } + /// + /// Gets the type name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. + /// + /// The type. + /// The result. + public static string GetTypeNameUI(Scripting.ScriptType type) + { + var name = type.ToString(); + + // Don't format interfaces to maintain code-name (eg. prefix 'I') + if (type.IsInterface) + return name; + if (type.IsGenericType && name.StartsWith("ScriptingObjectInterfaceReference", StringComparison.Ordinal)) + return type.GetGenericArguments()[0].GetTypeDisplayName(); + + return GetPropertyNameUI(name); + } + /// /// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly. /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index a2799f4a5..09fc970b3 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -423,7 +423,7 @@ namespace FlaxEditor.Viewport /// /// Gets a value indicating whether this viewport has loaded dependant assets. /// - public virtual bool HasLoadedAssets => true; + public virtual bool HasContentLoaded => true; /// /// The 'View' widget button context menu. @@ -1565,8 +1565,9 @@ namespace FlaxEditor.Viewport var center = Float2.Round(size * 0.5f); if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f) { - _viewMousePos = center; - win.MousePosition = PointToWindow(_viewMousePos); + var windowPosition = SnapMousePositionToDevicePixels(PointToWindow(center), win.DpiScale); + _viewMousePos = PointFromWindow(windowPosition); + win.MousePosition = windowPosition * win.DpiScale; } #endif } @@ -1583,6 +1584,11 @@ namespace FlaxEditor.Viewport win.MouseMoveRelative -= OnMouseMoveRelative; } + internal static Float2 SnapMousePositionToDevicePixels(Float2 position, float dpiScale) + { + return Float2.Round(position * dpiScale) / dpiScale; + } + /// /// Called when left mouse button goes down (on press). /// @@ -1863,8 +1869,9 @@ namespace FlaxEditor.Viewport // Move mouse back to the root position if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) { - var center = PointToWindow(_startPos); - win.MousePosition = center; + var windowPosition = SnapMousePositionToDevicePixels(PointToWindow(_startPos), win.Window.DpiScale); + _startPos = PointFromWindow(windowPosition); + win.MousePosition = windowPosition; } #endif diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 0a3b4b4e5..ba0fbc2c4 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -396,6 +396,22 @@ namespace FlaxEditor.Viewport ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient); } + private static bool ActorHasContentLoaded(Actor a) + { + if (!a.HasContentLoaded) + return false; + var children = a.ChildrenCount; + for (int i = 0; i < children; i++) + { + if (!ActorHasContentLoaded(a.GetChild(i))) + return false; + } + return true; + } + + /// + public override bool HasContentLoaded => base.HasContentLoaded && (Instance == null || ActorHasContentLoaded(Instance)); + /// public EditorViewport Viewport => this; diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 721b8504d..83e8917fb 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -361,7 +361,7 @@ namespace FlaxEditor.Viewport.Previews if (_showNodes) { // Draw bounding box at the node locations - var boxSize = Mathf.Min(1.0f, _previewModel.Sphere.Radius / 100.0f); + var boxSize = Mathf.Min(1.0f, _previewModel.BoundingSphere.Radius / 100.0f); var localBox = new OrientedBoundingBox(new Vector3(-boxSize), new Vector3(boxSize)); for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++) { @@ -405,7 +405,7 @@ namespace FlaxEditor.Viewport.Previews // Draw bounds if (_showBounds) { - DebugDrawBounds(_previewModel.Box); + DebugDrawBounds(_previewModel.BoundingBox); } } @@ -433,7 +433,7 @@ namespace FlaxEditor.Viewport.Previews /// public void ResetCamera() { - ViewportCamera.SetArcBallView(_previewModel.Box); + ViewportCamera.SetArcBallView(_previewModel.BoundingBox); } /// diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index b6610c880..73709248f 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -245,7 +245,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.HasContentLoaded && PostFxVolume.HasContentLoaded; + public override bool HasContentLoaded => base.HasContentLoaded && Sky.HasContentLoaded && EnvProbe.HasContentLoaded && PostFxVolume.HasContentLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs index f0b05c8e6..0a52b8e48 100644 --- a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs +++ b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs @@ -237,7 +237,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => base.HasLoadedAssets && _previewMaterial.IsLoaded && _previewMaterial.BaseMaterial.IsLoaded; + public override bool HasContentLoaded => base.HasContentLoaded && _previewMaterial.IsLoaded && _previewMaterial.BaseMaterial.IsLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index b64a4fdba..67267708d 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -175,11 +175,11 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets + public override bool HasContentLoaded { get { - if (!base.HasLoadedAssets) + if (!base.HasContentLoaded) return false; UpdateMaterial(); return true; diff --git a/Source/Editor/Viewport/Previews/ModelBasePreview.cs b/Source/Editor/Viewport/Previews/ModelBasePreview.cs index 47a2aec97..d4fab6f02 100644 --- a/Source/Editor/Viewport/Previews/ModelBasePreview.cs +++ b/Source/Editor/Viewport/Previews/ModelBasePreview.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Previews /// public void ResetCamera() { - ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box); + ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.BoundingBox : AnimatedModel.BoundingBox); } private void OnBegin(RenderTask task, GPUContext context) diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index b6306b905..026cd94ed 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -302,7 +302,7 @@ namespace FlaxEditor.Viewport.Previews // Draw bounds if (_showBounds) { - DebugDrawBounds(_previewModel.Box); + DebugDrawBounds(_previewModel.BoundingBox); } // Draw normals @@ -395,7 +395,7 @@ namespace FlaxEditor.Viewport.Previews // Based on RenderTools::ComputeModelLOD CreateProjectionMatrix(out var projectionMatrix); float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); - var sphere = PreviewActor.Sphere; + var sphere = PreviewActor.BoundingSphere; var viewOrigin = ViewPosition; var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); @@ -461,11 +461,11 @@ namespace FlaxEditor.Viewport.Previews } /// - /// Resets the camera to focus on a object. + /// Resets the camera to focus on an object. /// public void ResetCamera() { - ViewportCamera.SetArcBallView(_previewModel.Box); + ViewportCamera.SetArcBallView(_previewModel.BoundingBox); } /// diff --git a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs index 023bd121f..1bcaa015e 100644 --- a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs @@ -78,7 +78,7 @@ namespace FlaxEditor.Viewport.Previews } /// - public override bool HasLoadedAssets => (_emitter == null || _emitter.IsLoaded) && base.HasLoadedAssets; + public override bool HasContentLoaded => (_emitter == null || _emitter.IsLoaded) && base.HasContentLoaded; /// public override void OnDestroy() diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index 74c7c435b..9bfbaabab 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -207,7 +207,7 @@ namespace FlaxEditor.Viewport.Previews private void UpdateBoundsModel() { - var bounds = _previewEffect.Box; + var bounds = _previewEffect.BoundingBox; Transform t = Transform.Identity; t.Translation = bounds.Center; t.Scale = bounds.Size; @@ -221,12 +221,12 @@ namespace FlaxEditor.Viewport.Previews public void FitIntoView(float targetSize = 300.0f) { _previewEffect.Scale = Float3.One; - float maxSize = Mathf.Max(0.001f, (float)_previewEffect.Box.Size.MaxValue); + float maxSize = Mathf.Max(0.001f, (float)_previewEffect.BoundingBox.Size.MaxValue); _previewEffect.Scale = new Float3(targetSize / maxSize); } /// - public override bool HasLoadedAssets => _previewEffect.HasContentLoaded && base.HasLoadedAssets; + public override bool HasContentLoaded => _previewEffect.HasContentLoaded && base.HasContentLoaded; /// public override void Update(float deltaTime) @@ -278,7 +278,7 @@ namespace FlaxEditor.Viewport.Previews switch (key) { case KeyboardKeys.F: - ViewportCamera.SetArcBallView(_previewEffect.Box); + ViewportCamera.SetArcBallView(_previewEffect.BoundingBox); return true; case KeyboardKeys.Spacebar: PlaySimulation = !PlaySimulation; diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs index d394190ce..903326ab6 100644 --- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Viewport.Previews // Based on RenderTools::ComputeModelLOD CreateProjectionMatrix(out var projectionMatrix); float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22); - var sphere = PreviewActor.Sphere; + var sphere = PreviewActor.BoundingSphere; var viewOrigin = ViewPosition; var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin); var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr); diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 423006c31..846eeb001 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -192,7 +192,7 @@ namespace FlaxEditor.Viewport // Place the object //var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection; - var editorBounds = actor.EditorBoxChildren; + var editorBounds = actor.EditorBoundingBoxWithChildren; var bottomToCenter = actor.Position.Y - editorBounds.Minimum.Y; var location = hitLocation + new Vector3(0, bottomToCenter, 0); diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs index 4cf7f696a..4b7bcb466 100644 --- a/Source/Editor/ViewportDebugDrawData.cs +++ b/Source/Editor/ViewportDebugDrawData.cs @@ -124,7 +124,7 @@ namespace FlaxEditor if (model == null) continue; staticModel.Transform.GetWorld(out world); - var bounds = BoundingSphere.FromBox(staticModel.Box); + var bounds = BoundingSphere.FromBox(staticModel.BoundingBox); // Pick a proper LOD Float3 center = bounds.Center - renderContext.View.Origin; @@ -149,7 +149,7 @@ namespace FlaxEditor if (model == null) continue; animatedModel.Transform.GetWorld(out world); - var bounds = BoundingSphere.FromBox(animatedModel.Box); + var bounds = BoundingSphere.FromBox(animatedModel.BoundingBox); var bones = animatedModel.SkinnedMeshBones; // Pick a proper LOD diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 69da1f9e9..9ab94bcb4 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Windows.Assets { // Focus model value.WaitForLoaded(500); - Window._preview.ViewportCamera.SetArcBallView(Window._preview.PreviewActor.Sphere); + Window._preview.ViewportCamera.SetArcBallView(Window._preview.PreviewActor.BoundingSphere); } if (EnablePreviewModelCache) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 3046ea43b..89ff1ff96 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -117,9 +117,12 @@ namespace FlaxEditor.Windows.Assets public override void OnClean() { - Window._isolateIndex = -1; - Window._highlightIndex = -1; - Window._meshProxy = null; + if (Window != null) + { + Window._isolateIndex = -1; + Window._highlightIndex = -1; + Window._meshProxy = null; + } base.OnClean(); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index da41b88c5..91db9f0e0 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows.Assets } // Auto fit - if (_focusCamera && _viewport.Task.FrameCount > 1) + if (_focusCamera && _viewport.Task.FrameCount > 1 && _viewport.HasContentLoaded) { _focusCamera = false; Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); diff --git a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs index 1e6e3e863..8e5db9e08 100644 --- a/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs +++ b/Source/Editor/Windows/Assets/PreviewsCacheWindow.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.Windows.Assets }; // Toolstrip - _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); } /// diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs index 94deb18c0..b7cf16219 100644 --- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs +++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs @@ -326,7 +326,9 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.BuildLayout(); }).LinkTooltip("Add a new sprite"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); + + InputActions.Add(options => options.ShowEditorContents, _preview.CenterView); } /// diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index dcdd6e750..4aae7f674 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -262,9 +262,11 @@ namespace FlaxEditor.Windows.Assets _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save", ref inputOptions.Save); _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); + _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view", ref editor.Options.Options.Input.ShowEditorContents); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/textures/index.html")).LinkTooltip("See documentation to learn more"); + + InputActions.Add(options => options.ShowEditorContents, _preview.CenterView); } /// diff --git a/Source/Editor/Windows/ContentWindow.Navigation.cs b/Source/Editor/Windows/ContentWindow.Navigation.cs index 27826e195..548f927c5 100644 --- a/Source/Editor/Windows/ContentWindow.Navigation.cs +++ b/Source/Editor/Windows/ContentWindow.Navigation.cs @@ -229,6 +229,17 @@ namespace FlaxEditor.Windows _navigationBar.IsLayoutLocked = wasLayoutLocked; _navigationBar.PerformLayout(); UpdateNavigationBarBounds(); + + ScrollNavigationBarToCurrentFolder(); + } + + private void ScrollNavigationBarToCurrentFolder() + { + if (_navigationBar != null && _navigationBar.ChildrenCount != 0 && _navigationBar.HScrollBar.Visible) + { + _navigationBar.HScrollBar.TargetValue = _navigationBar.HScrollBar.Maximum; + _navigationBar.HScrollBar.FastScroll(); + } } /// diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 1ee602aa1..18a3213b9 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -561,7 +561,7 @@ namespace FlaxEditor.Windows } /// - /// Enables or disables vertical and horizontal scrolling on the content tree panel + /// Enables or disables vertical and horizontal scrolling on the content tree panel /// /// The state to set scrolling to public void ScrollingOnTreeView(bool enabled) @@ -573,7 +573,7 @@ namespace FlaxEditor.Windows } /// - /// Enables or disables vertical and horizontal scrolling on the content view panel + /// Enables or disables vertical and horizontal scrolling on the content view panel /// /// The state to set scrolling to public void ScrollingOnContentView(bool enabled) @@ -1570,6 +1570,7 @@ namespace FlaxEditor.Windows PerformLayout(); } UpdateViewDropdownBounds(); + ScrollNavigationBarToCurrentFolder(); } } @@ -1850,6 +1851,14 @@ namespace FlaxEditor.Windows return base.OnMouseUp(location, button); } + /// + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + + ScrollNavigationBarToCurrentFolder(); + } + /// protected override void PerformLayoutAfterChildren() { diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 8d0c146d9..44c07be21 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -189,7 +189,7 @@ namespace FlaxEditor.Windows _pilotActor = actor; _pilotStart = actor.Transform; - _pilotBounds = actor.BoxWithChildren; + _pilotBounds = actor.BoundingBoxWithChildren; Viewport.ViewTransform = _pilotStart; if (_pilotWidget == null) { diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 67d8bbf0c..580646b17 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -188,7 +188,7 @@ namespace FlaxEngine /// /// The knowledge container to access. /// The value to set. - /// True if set value value, otherwise false. + /// True if set value, otherwise false. public bool Set(BehaviorKnowledge knowledge, T value) { return knowledge != null && knowledge.Set(Path, value); diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index d62210072..21cfe4c95 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -394,8 +394,8 @@ void BehaviorTreeMoveToNode::GetAgentSize(Actor* agent, float& outRadius, float& } // Estimate actor bounds to extract capsule information - const BoundingBox box = agent->GetBox(); - const BoundingSphere sphere = agent->GetSphere(); + const BoundingBox box = agent->GetBoundingBox(); + const BoundingSphere sphere = agent->GetBoundingSphere(); outRadius = (float)sphere.Radius; outHeight = (float)box.GetSize().Y; } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h index df649f299..6a15eb4cb 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h @@ -221,7 +221,7 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Audio/AudioListener.h b/Source/Engine/Audio/AudioListener.h index 7bb4299a5..89f4ba5af 100644 --- a/Source/Engine/Audio/AudioListener.h +++ b/Source/Engine/Audio/AudioListener.h @@ -30,7 +30,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index 07762be2f..db698f3ba 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -318,7 +318,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index b2a9434c9..315a5fcfe 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -794,7 +794,9 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M // Setup node in hierarchy nodeToActor.Add(nodeIndex, nodeActor); nodeActor->SetName(node.Name); + nodeActor->SetLocalTransform(node.LocalTransform); +#if 0 // When use local origin is checked, it shifts everything over the same amount, including the root. This tries to work around that. if (!(nodeIndex == 0 && options.UseLocalOrigin)) { @@ -829,6 +831,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M nodeActor->SetLocalTransform(positionOffset); } +#endif if (nodeIndex == 0) { diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 304270b34..9648c204f 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -263,12 +263,36 @@ namespace FlaxEngine.Interop /// The output array. public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { - TDst[] dst = new TDst[src.Length]; + if (src == null) + return null; + var dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) dst[i] = convertFunc(src[i]); return dst; } + /// + /// Converts dictionary with a custom converter function for each pair of keys and values. + /// + /// Input dictionary key type. + /// Input dictionary value type. + /// Output dictionary key type. + /// Output dictionary value type. + /// The input dictionary. + /// Converter callback for keys. + /// Converter callback for values. + /// The output dictionary. + public static Dictionary ConvertDictionary(this Dictionary src, Func convertFuncKey, Func convertFuncValue) + { + if (src == null) + return null; + var dst = new Dictionary(); + dst.EnsureCapacity(src.Count); + foreach (var e in src) + dst.Add(convertFuncKey(e.Key), convertFuncValue(e.Value)); + return dst; + } + /// Find among the scripting assemblies. /// The name to find /// If true, partial names should be allowed to be resolved. diff --git a/Source/Engine/Graphics/Models/Mesh.cs b/Source/Engine/Graphics/Models/Mesh.cs index dfca198af..8b19e90a1 100644 --- a/Source/Engine/Graphics/Models/Mesh.cs +++ b/Source/Engine/Graphics/Models/Mesh.cs @@ -117,7 +117,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, int[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -153,7 +153,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -189,7 +189,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, uint[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -225,7 +225,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -261,7 +261,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, ushort[] triangles, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -297,7 +297,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) @@ -334,7 +334,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -352,7 +352,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -388,7 +388,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -406,7 +406,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] @@ -424,7 +424,7 @@ namespace FlaxEngine /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 6848096e8..59039e15b 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -90,7 +90,7 @@ public: /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. @@ -106,7 +106,7 @@ public: /// The mesh vertices positions. Cannot be null. /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cs b/Source/Engine/Graphics/Models/SkinnedMesh.cs index eb391dd76..cc132435a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cs +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cs @@ -102,7 +102,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -140,7 +140,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -178,7 +178,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) @@ -217,7 +217,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, int[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) @@ -236,7 +236,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) @@ -255,7 +255,7 @@ namespace FlaxEngine /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). - /// The tangent vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). [Obsolete("Use UpdateMesh with Float3 and Float2 parameters instead")] public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index 0c0084eaf..ade6f6dc9 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -119,7 +119,7 @@ public: /// The skeletal bones indices to use for skinning. /// The skeletal bones weights to use for skinning (matches blendIndices). /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. @@ -137,7 +137,7 @@ public: /// The skeletal bones indices to use for skinning. /// The skeletal bones weights to use for skinning (matches blendIndices). /// The normal vectors (per vertex). - /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The tangent vectors (per vertex). /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 8330aa7f8..55cef5af8 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -417,7 +417,7 @@ GeometryDrawState* RenderBuffers::GetGeometryDrawState(SceneRendering* scene, in bool RenderBuffers::TestOcclusionCulling(const Actor* actor, uint32& cullingId) const { - return TestOcclusionCulling(actor->GetSceneRendering(), actor, actor->GetBox(), cullingId); + return TestOcclusionCulling(actor->GetSceneRendering(), actor, actor->GetBoundingBox(), cullingId); } bool RenderBuffers::TestOcclusionCulling(SceneRendering* scene, const Actor* actor, const BoundingBox& objectBounds, uint32& cullingId, const void* object) const diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index 65dffc255..c38ebc547 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -137,6 +137,7 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() PROFILE_CPU(); auto context = _device->MainContext; auto cmdBufferManager = context->GetCmdBufferManager(); + // Keep commands recorded before acquire independent from the image-acquired semaphore wait below. if (cmdBufferManager->HasPendingActiveCmdBuffer()) context->Flush(); @@ -154,7 +155,6 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() acquiredBackBuffer.WaitForSubmit(); const auto backBuffer = &_backBuffers[_acquiredImageIndex].Handle; - auto cmdBuffer = cmdBufferManager->GetCmdBuffer(); // Transition to render target (typical usage in most cases when calling backbuffer getter) @@ -174,6 +174,17 @@ void GPUSwapChainVulkan::Begin(RenderTask* task) { GPUSwapChain::Begin(task); + // Wait for the backbuffer to be available + if (_currentImageIndex != -1) + { + auto& backBuffer = _backBuffers[_currentImageIndex]; + if (backBuffer.SubmitCmdBuffer) + { + backBuffer.SubmitCmdBuffer->Wait(); + backBuffer.SubmitCmdBuffer = nullptr; + } + } + // Rebuild swapchain if need to if (_vsyncPending != _vsyncCurrent) { diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 14af9ac21..7b4683017 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1428,9 +1428,14 @@ void Actor::OnLayerChanged() BoundingBox Actor::GetBoxWithChildren() const { - BoundingBox result = GetBox(); + return GetBoundingBoxWithChildren(); +} + +BoundingBox Actor::GetBoundingBoxWithChildren() const +{ + BoundingBox result = GetBoundingBox(); for (int32 i = 0; i < Children.Count(); i++) - BoundingBox::Merge(result, Children.Get()[i]->GetBoxWithChildren(), result); + BoundingBox::Merge(result, Children.Get()[i]->GetBoundingBoxWithChildren(), result); return result; } @@ -1438,14 +1443,24 @@ BoundingBox Actor::GetBoxWithChildren() const BoundingBox Actor::GetEditorBox() const { - return GetBox(); + return GetEditorBoundingBox(); +} + +BoundingBox Actor::GetEditorBoundingBox() const +{ + return GetBoundingBox(); } BoundingBox Actor::GetEditorBoxChildren() const { - BoundingBox result = GetEditorBox(); + return GetEditorBoundingBoxWithChildren(); +} + +BoundingBox Actor::GetEditorBoundingBoxWithChildren() const +{ + BoundingBox result = GetEditorBoundingBox(); for (int32 i = 0; i < Children.Count(); i++) - BoundingBox::Merge(result, Children.Get()[i]->GetEditorBoxChildren(), result); + BoundingBox::Merge(result, Children.Get()[i]->GetEditorBoundingBoxWithChildren(), result); return result; } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 4d2e930d2..ae6ef4c41 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -690,35 +690,71 @@ public: public: /// /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// [Deprecated in 1.13] /// - API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const + API_PROPERTY() DEPRECATED("Use GetBoundingSphere instead.") FORCE_INLINE const BoundingSphere& GetSphere() const + { + return _sphere; + } + + /// + /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// + API_PROPERTY() FORCE_INLINE const BoundingSphere& GetBoundingSphere() const { return _sphere; } /// /// Gets actor bounding box that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// [Deprecated in 1.13] /// - API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const + API_PROPERTY() DEPRECATED("Use GetBoundingBox instead.") FORCE_INLINE const BoundingBox& GetBox() const + { + return _box; + } + + /// + /// Gets actor bounding box that defines 3D space intersecting with the actor (for determination of the visibility for actor). + /// + API_PROPERTY() FORCE_INLINE const BoundingBox& GetBoundingBox() const { return _box; } /// /// Gets actor bounding box of the actor including all child actors (children included in recursive way) + /// [Deprecated in 1.13] /// - API_PROPERTY() BoundingBox GetBoxWithChildren() const; + API_PROPERTY() DEPRECATED("Use GetBoundingBoxWithChildren instead.") BoundingBox GetBoxWithChildren() const; + + /// + /// Gets actor bounding box of the actor including all child actors (children included in recursive way) + /// + API_PROPERTY() BoundingBox GetBoundingBoxWithChildren() const; #if USE_EDITOR /// /// Gets actor bounding box (single actor, no children included) for editor tools. + /// [Deprecated in 1.13] /// - API_PROPERTY() virtual BoundingBox GetEditorBox() const; + API_PROPERTY() DEPRECATED("Use GetEditorBoundingBox instead.") virtual BoundingBox GetEditorBox() const; + + /// + /// Gets actor bounding box (single actor, no children included) for editor tools. + /// + API_PROPERTY() virtual BoundingBox GetEditorBoundingBox() const; /// /// Gets actor bounding box of the actor including all child actors for editor tools. + /// [Deprecated in 1.13] /// - API_PROPERTY() BoundingBox GetEditorBoxChildren() const; + API_PROPERTY() DEPRECATED("Use GetEditorBoundingBoxWithChildren instead.") BoundingBox GetEditorBoxChildren() const; + + /// + /// Gets actor bounding box of the actor including all child actors for editor tools. + /// + API_PROPERTY() BoundingBox GetEditorBoundingBoxWithChildren() const; #endif /// diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ecd28316a..3cc2afc59 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -1378,7 +1378,7 @@ void AnimatedModel::OnDebugDraw() ModelInstanceActor::OnDebugDraw(); } -BoundingBox AnimatedModel::GetEditorBox() const +BoundingBox AnimatedModel::GetEditorBoundingBox() const { if (SkinnedModel) SkinnedModel->WaitForLoaded(100); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index f18bde120..b8dfac94f 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -498,7 +498,7 @@ public: #if USE_EDITOR void OnDebugDrawSelected() override; void OnDebugDraw() override; - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; void Serialize(SerializeStream& stream, const void* otherObj) override; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index a43f17232..fac6dd5e8 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -338,7 +338,7 @@ void Camera::BeginPlay(SceneBeginData* data) Actor::BeginPlay(data); } -BoundingBox Camera::GetEditorBox() const +BoundingBox Camera::GetEditorBoundingBox() const { const Vector3 size(100); const Vector3 pos = _transform.Translation + _transform.Orientation * Vector3::Forward * 30.0f; diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 56abe6599..50cb1e43b 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -264,7 +264,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; bool HasContentLoaded() const override; void Draw(RenderContextBatch& renderContextBatch) override; void OnDebugDrawSelected() override; diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index a07ad4ac6..3ea5b19c8 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -53,7 +53,7 @@ void Decal::OnDebugDrawSelected() Actor::OnDebugDrawSelected(); } -BoundingBox Decal::GetEditorBox() const +BoundingBox Decal::GetEditorBoundingBox() const { const Vector3 size(10.0f); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h index 7ec096f01..ae8981377 100644 --- a/Source/Engine/Level/Actors/Decal.h +++ b/Source/Engine/Level/Actors/Decal.h @@ -71,7 +71,7 @@ public: // [Actor] #if USE_EDITOR void OnDebugDrawSelected() override; - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void OnLayerChanged() override; void Draw(RenderContextBatch& renderContextBatch) override; diff --git a/Source/Engine/Level/Actors/EmptyActor.cpp b/Source/Engine/Level/Actors/EmptyActor.cpp index fde7531f4..477e04d0a 100644 --- a/Source/Engine/Level/Actors/EmptyActor.cpp +++ b/Source/Engine/Level/Actors/EmptyActor.cpp @@ -9,7 +9,7 @@ EmptyActor::EmptyActor(const SpawnParams& params) #if USE_EDITOR -BoundingBox EmptyActor::GetEditorBox() const +BoundingBox EmptyActor::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/EmptyActor.h b/Source/Engine/Level/Actors/EmptyActor.h index 9149b698e..6515a70e4 100644 --- a/Source/Engine/Level/Actors/EmptyActor.h +++ b/Source/Engine/Level/Actors/EmptyActor.h @@ -14,7 +14,7 @@ class FLAXENGINE_API EmptyActor : public Actor public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif protected: diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.h b/Source/Engine/Level/Actors/EnvironmentProbe.h index 0da01d13b..00207d84c 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.h +++ b/Source/Engine/Level/Actors/EnvironmentProbe.h @@ -152,7 +152,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index cd1fed743..fad0df2bb 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -141,7 +141,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h index 63b78a6b3..1a4affbdb 100644 --- a/Source/Engine/Level/Actors/Light.h +++ b/Source/Engine/Level/Actors/Light.h @@ -75,7 +75,7 @@ public: void OnEnable() override; void OnDisable() override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 8da1236e8..54fc459a4 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -63,7 +63,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index 9112ad51f..df65b1eff 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -57,7 +57,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index b45424a3d..98f1bc76c 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -44,9 +44,9 @@ BoundingBox SceneNavigation::GetNavigationBounds() if (Volumes.IsEmpty()) return BoundingBox::Empty; PROFILE_CPU_NAMED("GetNavigationBounds"); - auto box = Volumes[0]->GetBox(); + auto box = Volumes[0]->GetBoundingBox(); for (int32 i = 1; i < Volumes.Count(); i++) - BoundingBox::Merge(box, Volumes[i]->GetBox(), box); + BoundingBox::Merge(box, Volumes[i]->GetBoundingBox(), box); return box; } @@ -55,7 +55,7 @@ NavMeshBoundsVolume* SceneNavigation::FindNavigationBoundsOverlap(const Bounding NavMeshBoundsVolume* result = nullptr; for (int32 i = 0; i < Volumes.Count(); i++) { - if (Volumes[i]->GetBox().Intersects(bounds)) + if (Volumes[i]->GetBoundingBox().Intersects(bounds)) { result = Volumes[i]; break; diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 5b2b7025d..aa1017b54 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -191,7 +191,7 @@ void SceneRendering::AddActor(Actor* a, int32& key) auto& e = list[key]; e.Actor = a; e.LayerMask = a->GetLayerMask(); - e.Bounds = a->GetSphere(); + e.Bounds = a->GetBoundingSphere(); e.NoCulling = a->_drawNoCulling; for (auto* listener : _listeners) listener->OnSceneRenderingAddActor(this, key, a); @@ -214,7 +214,7 @@ void SceneRendering::UpdateActor(Actor* a, int32& key, ISceneRenderingListener:: if (flags & ISceneRenderingListener::Layer) e.LayerMask = a->GetLayerMask(); if (flags & ISceneRenderingListener::Bounds) - e.Bounds = a->GetSphere(); + e.Bounds = a->GetBoundingSphere(); } } if (lock) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 896cf4217..0c4d10589 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -251,7 +251,7 @@ struct NavSceneRasterizer return; PROFILE_CPU_NAMED("SphereCollider"); - const BoundingSphere sphere = sphereCollider->GetSphere(); + const BoundingSphere sphere = sphereCollider->GetBoundingSphere(); TriangulateSphere(VertexBuffer, IndexBuffer, sphere); RasterizeTriangles(); } @@ -261,7 +261,7 @@ struct NavSceneRasterizer return; PROFILE_CPU_NAMED("CapsuleCollider"); - const BoundingBox box = capsuleCollider->GetBox(); + const BoundingBox box = capsuleCollider->GetBoundingBox(); TriangulateBox(VertexBuffer, IndexBuffer, box); RasterizeTriangles(); } @@ -411,7 +411,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B for (Actor* actor : scene->Navigation.Actors) { BoundingBox actorBoxNavMesh; - BoundingBox::Transform(actor->GetBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh); + BoundingBox::Transform(actor->GetBoundingBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh); if (actorBoxNavMesh.Intersects(rasterizer.TileBoundsNavMesh) && actor->IsActiveInHierarchy() && EnumHasAllFlags(actor->GetStaticFlags(), StaticFlags::Navigation)) @@ -963,10 +963,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo for (const NavMeshBoundsVolume* volume : scene->Navigation.Volumes) { if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties) || - !volume->GetBox().Intersects(dirtyBoundsNavMesh)) + !volume->GetBoundingBox().Intersects(dirtyBoundsNavMesh)) continue; auto& bounds = volumes.AddOne(); - BoundingBox::Transform(volume->GetBox(), worldToNavMesh, bounds); + BoundingBox::Transform(volume->GetBoundingBox(), worldToNavMesh, bounds); } Array unusedTiles; diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 4317cd41e..0fcb4670b 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -423,7 +423,7 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 415dd91da..cb420cb08 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1144,7 +1144,7 @@ void Particles::DrawParticles(RenderContextBatch& renderContextBatch, ParticleEf // Drawing assumes that all views within a batch have the same Origin const Vector3& viewOrigin = renderContextBatch.GetMainContext().View.Origin; - BoundingSphere bounds = effect->GetSphere(); + BoundingSphere bounds = effect->GetBoundingSphere(); bounds.Center -= viewOrigin; // Cull particles against all views diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index 96126c037..a211e3855 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -34,13 +34,13 @@ void BoxCollider::AutoResize(bool globalOrientation = true) // Hacky way to get unrotated bounded box of parent const Quaternion parentOrientation = parent->GetOrientation(); parent->SetOrientation(Quaternion::Identity); - BoundingBox parentBox = parent->GetBox(); + BoundingBox parentBox = parent->GetBoundingBox(); parent->SetOrientation(parentOrientation); for (const Actor* sibling : parent->Children) { if (sibling != this) - BoundingBox::Merge(parentBox, sibling->GetBoxWithChildren(), parentBox); + BoundingBox::Merge(parentBox, sibling->GetBoundingBoxWithChildren(), parentBox); } const Vector3 parentSize = parentBox.GetSize(); const Vector3 parentCenter = parentBox.GetCenter() - parent->GetPosition(); diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 367b87df1..1a8847819 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -209,9 +209,9 @@ public: public: /// - /// Moves the character with the given speed. Gravity is automatically applied. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move. + /// Moves the character with the given speed. Gravity is automatically applied. The speed is multipled by Time.DeltaTime internally. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move. /// - /// The movement speed (in units/s). + /// The movement speed (in units/s). The speed is multipled by Time.DeltaTime internally. /// The collision flags. It can be used to trigger various character animations. API_FUNCTION() CollisionFlags SimpleMove(const Vector3& speed); diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 630d058b4..ee3a80488 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -3,6 +3,7 @@ #include "MeshCollider.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Core/ScopeExit.h" #include "Engine/Physics/Physics.h" #include "Engine/Physics/PhysicsScene.h" #if USE_EDITOR || !BUILD_RELEASE @@ -22,19 +23,29 @@ void MeshCollider::OnCollisionDataChanged() if (CollisionData) { + _isChangingCollisionData = true; + SCOPE_EXIT { _isChangingCollisionData = false; }; + // Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin) - CollisionData->WaitForLoaded(); + // OnSet sends Loaded after Changed returns, so let that notification update the collider once. + if (!CollisionData->WaitForLoaded()) + return; } + // Clearing the reference or failing to load won't send Loaded, so clear the old geometry here. UpdateGeometry(); UpdateBounds(); } void MeshCollider::OnCollisionDataLoaded() { - // Not needed as OnCollisionDataChanged waits for it to be loaded - //UpdateGeometry(); - //UpdateBounds(); + // WaitForLoaded can dispatch Loaded while Changed is still waiting. OnSet will send it again afterwards. + if (_isChangingCollisionData) + return; + + // Virtual collision data can be recooked without changing the asset reference. + UpdateGeometry(); + UpdateBounds(); } bool MeshCollider::CanAttach(RigidBody* rigidBody) const diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 2e1ce533a..bc04575b0 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -23,6 +23,8 @@ public: AssetReference CollisionData; private: + bool _isChangingCollisionData = false; + void OnCollisionDataChanged(); void OnCollisionDataLoaded(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 18ed6effd..f2f6d3968 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -216,8 +216,8 @@ struct ClothSettings clothTrans.LocalToWorld(c, c); // Setup bounds - BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBox())); - BoundingSphere::FromBox(Actor->GetBox(), const_cast(Actor->GetSphere())); + BoundingBox::FromPoints(boundsCorners, 8, const_cast(Actor->GetBoundingBox())); + BoundingSphere::FromBox(Actor->GetBoundingBox(), const_cast(Actor->GetBoundingSphere())); return false; } }; diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp index ab7f96d8e..3ea87d9f5 100644 --- a/Source/Engine/Platform/Base/StringUtilsBase.cpp +++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp @@ -458,7 +458,16 @@ bool ParseFloat(const C* str, T* ret) if (*str == 'e' || *str == 'E') { str++; - T powerer = *str == '-' ? str++, (T)0.1 : (T)10; + T powerer = (T)10; + if (*str == '-') + { + powerer = (T)0.1; + str++; + } + else if (*str == '+') + { + str++; + } T power = 0; while (*str >= '0' && *str <= '9') { diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 5a133f64e..14eda7cfc 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -39,6 +39,62 @@ inline bool IsWindowInvalid(Window* win) return !hasWindow || !win; } +inline Window* GetEngineWindow(NSWindow* nativeWindow) +{ + WindowsManager::WindowsLocker.Lock(); + for (auto* window : WindowsManager::Windows) + { + if (window && window->GetNativePtr() == nativeWindow) + { + WindowsManager::WindowsLocker.Unlock(); + return window; + } + } + WindowsManager::WindowsLocker.Unlock(); + return nullptr; +} + +inline bool ShouldAttachToParent(const CreateWindowSettings& settings) +{ + // Cocoa child windows inherit parent z-order and minimize behavior. + // Minimizable regular windows need independent top-level window semantics. + return settings.Parent && (settings.Type != WindowType::Regular || !settings.AllowMinimize); +} + +inline bool CanMakeKeyWindow(NSWindow* window) +{ + return window && [window isVisible] && ![window isMiniaturized] && [window canBecomeKeyWindow]; +} + +inline bool IsRegularWindowWithParent(Window* window, Window* parent) +{ + return window && + !window->IsClosed() && + window->GetSettings().Type == WindowType::Regular && + window->GetSettings().Parent == parent; +} + +inline void MakeNextParentedWindowKey(NSWindow* hiddenWindow, Window* fallbackParent) +{ + if (!fallbackParent || IsWindowInvalid(fallbackParent)) + return; + + for (NSWindow* candidate in [NSApp orderedWindows]) + { + if (candidate == hiddenWindow || !CanMakeKeyWindow(candidate)) + continue; + Window* candidateWindow = GetEngineWindow(candidate); + if (!IsRegularWindowWithParent(candidateWindow, fallbackParent)) + continue; + [candidate makeKeyAndOrderFront:nil]; + return; + } + + NSWindow* parent = (NSWindow*)fallbackParent->GetNativePtr(); + if (parent != hiddenWindow && CanMakeKeyWindow(parent)) + [parent makeKeyAndOrderFront:nil]; +} + KeyboardKeys GetKey(NSEvent* event) { switch ([event keyCode]) @@ -963,7 +1019,7 @@ void MacWindow::Show() // Show NSWindow* window = (NSWindow*)_window; - if (_settings.Parent) + if (ShouldAttachToParent(_settings)) { NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); [parent addChildWindow:window ordered:NSWindowAbove]; @@ -989,18 +1045,17 @@ void MacWindow::Hide() // Hide (order out doesn't work for miniaturized windows) NSWindow* window = (NSWindow*)_window; - const BOOL wasKey = [window isKeyWindow]; + const BOOL wasKey = [window isKeyWindow] || [NSApp keyWindow] == window; if ([window isMiniaturized]) [window close]; else [window orderOut:nil]; - - // Transfer focus back to the parent when hiding popup - if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip) - { - NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); - [parent makeKeyAndOrderFront:nil]; - } + const bool shouldRestoreFocus = _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + (wasKey || [NSApp keyWindow] == nil); + if (!IsClosed() && shouldRestoreFocus) + MakeNextParentedWindowKey(window, _settings.Parent); // Base WindowBase::Hide(); @@ -1009,23 +1064,27 @@ void MacWindow::Hide() void MacWindow::Close(ClosingReason reason) { - const BOOL wasKey = _window && [(NSWindow*)_window isKeyWindow]; + NSWindow* window = (NSWindow*)_window; + const BOOL shouldRestoreFocus = window && + _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + ([window isKeyWindow] || [NSApp keyWindow] == window); + WindowBase::Close(reason); // Closing can be cancelled by managed Window.Closing handlers. if (!IsClosed()) return; - if (NSWindow* window = (NSWindow*)_window) - { + if (window) [window close]; - } - - if (_settings.Parent && wasKey && _settings.Type != WindowType::Popup && _settings.Type != WindowType::Tooltip) - { - NSWindow* parent = (NSWindow*)_settings.Parent->GetNativePtr(); - [parent makeKeyAndOrderFront:nil]; - } + const bool hasNoKeyWindow = _settings.Parent && + _settings.Type == WindowType::Regular && + [NSApp isActive] && + [NSApp keyWindow] == nil; + if (shouldRestoreFocus || hasNoKeyWindow) + MakeNextParentedWindowKey(window, _settings.Parent); } void MacWindow::Minimize() diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index dec54dd61..770b53805 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -149,7 +149,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) const HMODULE user32Dll = LoadLibraryW(L"user32.dll"); if (user32Dll) { - typedef UINT (STDAPICALLTYPE*GetDpiForWindowProc)(HWND hwnd); + typedef UINT(STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd); const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow"); if (getDpiForWindowProc) { @@ -848,7 +848,7 @@ void WindowsWindow::UpdateCursor() if (!_lastCursorHidden) { _lastCursorHidden = true; - while(::ShowCursor(FALSE) >= 0) + while (::ShowCursor(FALSE) >= 0) { if (_cursorHiddenSafetyCount >= 100) { diff --git a/Source/Engine/Renderer/Lighting/ShadowsPass.cpp b/Source/Engine/Renderer/Lighting/ShadowsPass.cpp index ea755fabf..5e7522409 100644 --- a/Source/Engine/Renderer/Lighting/ShadowsPass.cpp +++ b/Source/Engine/Renderer/Lighting/ShadowsPass.cpp @@ -412,7 +412,7 @@ public: void OnSceneRenderingAddActor(SceneRendering* scene, int32 key, Actor* a) override { if (a->HasStaticFlag(StaticFlags::Shadow)) - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } void OnSceneRenderingUpdateActor(SceneRendering* scene, int32 key, Actor* a, const BoundingSphere& prevBounds, UpdateFlags flags) override @@ -421,7 +421,7 @@ public: if (a->HasStaticFlag(StaticFlags::Shadow)) { // TODO: skip actors that don't cast shadows (eg. particles) - BoundingSphere bounds = a->GetSphere(); + BoundingSphere bounds = a->GetBoundingSphere(); if (bounds != prevBounds) { // Avoid dirtying twice when bounds are close to each other @@ -434,14 +434,14 @@ public: } else if (flags & StaticFlags) { - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } } void OnSceneRenderingRemoveActor(SceneRendering* scene, int32 key, Actor* a) override { if (a->HasStaticFlag(StaticFlags::Shadow)) - DirtyStaticBounds(a->GetSphere()); + DirtyStaticBounds(a->GetBoundingSphere()); } void OnSceneRenderingClear(SceneRendering* scene) override diff --git a/Source/Engine/Renderer/Reflections/ProbesRenderer.cpp b/Source/Engine/Renderer/Reflections/ProbesRenderer.cpp index 13610d4b1..e824c751b 100644 --- a/Source/Engine/Renderer/Reflections/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/Reflections/ProbesRenderer.cpp @@ -491,7 +491,7 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) { auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); Vector3 position = envProbe->GetTransform().LocalToWorld(envProbe->CaptureOffset); - float radius = (float)envProbe->GetSphere().Radius; + float radius = (float)envProbe->GetBoundingSphere().Radius; float nearPlane = Math::Max(METERS_TO_UNITS(0.001f), envProbe->CaptureNearPlane); // Adjust far plane distance diff --git a/Source/Engine/Renderer/Utils/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/Utils/GlobalSignDistanceFieldPass.cpp index 59fff8bfb..17bb5feb6 100644 --- a/Source/Engine/Renderer/Utils/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/Utils/GlobalSignDistanceFieldPass.cpp @@ -464,7 +464,7 @@ public: if (!ObjectTypes.Contains(a->GetTypeHandle())) return; - BoundingBox bounds = a->GetBox(); + BoundingBox bounds = a->GetBoundingBox(); bool isStatic = GLOBAL_SDF_ACTOR_IS_STATIC(a); // TODO: early out if the bounds are outside cascade 0 for dynamic objects if (prevBounds && flags != DrawModes && flags != Layer && flags != StaticFlags) diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp index 2bd53fa98..c971dd8a2 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -1,8 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "ManagedDictionary.h" - #if USE_CSHARP +#include "Engine/Scripting/BinaryModule.h" +#include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Scripting/ManagedCLR/MMethod.h" +#include "Engine/Scripting/ManagedCLR/MAssembly.h" +#include "Engine/Scripting/ManagedCLR/MException.h" +#include "Engine/Scripting/Internal/StdTypesContainer.h" + Dictionary ManagedDictionary::CachedTypes; #if !USE_MONO_AOT ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType; @@ -15,4 +21,157 @@ MMethod* ManagedDictionary::CreateInstance; MMethod* ManagedDictionary::AddDictionaryItem; MMethod* ManagedDictionary::GetDictionaryKeys; #endif + +ManagedDictionary::ManagedDictionary(MObject* instance) +{ + Instance = instance; + +#if !USE_MONO_AOT + // Cache the thunks of the dictionary helper methods + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(makeGenericTypeMethod); + MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk(); + + MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(createInstanceMethod); + CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk(); + + MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(addDictionaryItemMethod); + AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk(); + + MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(getDictionaryKeysItemMethod); + GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk(); + } +#else + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(MakeGenericType); + + CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(CreateInstance); + + AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(AddDictionaryItem); + + GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(GetDictionaryKeys); + } +#endif +} + +MType* ManagedDictionary::GetClass(MType* keyType, MType* valueType) +{ + // Check if the generic type was generated earlier + KeyValueType cacheKey = { keyType, valueType }; + MType* dictionaryType; + if (CachedTypes.TryGet(cacheKey, dictionaryType)) + return dictionaryType; + + MType* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); + MArray* genericArgs = MCore::Array::New(MCore::TypeCache::IntPtr, 2); + MType** genericArgsPtr = MCore::Array::GetAddress(genericArgs); + genericArgsPtr[0] = keyType; + genericArgsPtr[1] = valueType; + + MObject* exception = nullptr; +#if !USE_MONO_AOT + dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception); +#else + void* params[2]; + params[0] = genericType; + params[1] = genericArgs; + dictionaryType = (MType*)MakeGenericType->Invoke(nullptr, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + return nullptr; + } + CachedTypes.Add(cacheKey, dictionaryType); + return dictionaryType; +} + +ManagedDictionary ManagedDictionary::New(MType* keyType, MType* valueType) +{ + ManagedDictionary result; + MType* dictionaryType = GetClass(keyType, valueType); + if (!dictionaryType) + return result; + + MObject* exception = nullptr; +#if !USE_MONO_AOT + MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception); +#else + void* params[2]; + params[0] = dictionaryType; + params[1] = nullptr; + MObject* instance = CreateInstance->Invoke(nullptr, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + return result; + } + + result.Instance = instance; + return result; +} + +void ManagedDictionary::Add(MObject* key, MObject* value) +{ + CHECK(Instance); + + MObject* exception = nullptr; +#if !USE_MONO_AOT + AddDictionaryItem(nullptr, Instance, key, value, &exception); +#else + void* params[3]; + params[0] = Instance; + params[1] = key; + params[2] = value; + AddDictionaryItem->Invoke(Instance, params, &exception); +#endif + if (exception) + { + MException ex(exception); + ex.Log(LogType::Error, TEXT("")); + } +} + +MArray* ManagedDictionary::GetKeys() const +{ + CHECK_RETURN(Instance, nullptr); +#if !USE_MONO_AOT + return GetDictionaryKeys(nullptr, Instance, nullptr); +#else + void* params[1]; + params[0] = Instance; + return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr); +#endif +} + +MObject* ManagedDictionary::GetValue(MObject* key) const +{ + CHECK_RETURN(Instance, nullptr); + MClass* klass = MCore::Object::GetClass(Instance); + MMethod* getItemMethod = klass->GetMethod("System.Collections.IDictionary.get_Item", 1); + CHECK_RETURN(getItemMethod, nullptr); + void* params[1]; + params[0] = key; + return getItemMethod->Invoke(Instance, params, nullptr); +} + #endif diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.h b/Source/Engine/Scripting/Internal/ManagedDictionary.h index f26dddef8..b4bc01dae 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.h +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h @@ -5,13 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Scripting/Scripting.h" #if USE_CSHARP -#include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" -#include "Engine/Scripting/ManagedCLR/MClass.h" -#include "Engine/Scripting/ManagedCLR/MMethod.h" -#include "Engine/Scripting/ManagedCLR/MAssembly.h" -#include "Engine/Scripting/ManagedCLR/MException.h" -#include "Engine/Scripting/Internal/StdTypesContainer.h" #include "Engine/Core/Collections/Dictionary.h" /// @@ -57,53 +51,7 @@ private: public: MObject* Instance; - ManagedDictionary(MObject* instance = nullptr) - { - Instance = instance; - -#if !USE_MONO_AOT - // Cache the thunks of the dictionary helper methods - if (MakeGenericType == nullptr) - { - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK(scriptingClass); - - MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK(makeGenericTypeMethod); - MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk(); - - MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK(createInstanceMethod); - CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk(); - - MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); - CHECK(addDictionaryItemMethod); - AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk(); - - MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); - CHECK(getDictionaryKeysItemMethod); - GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk(); - } -#else - if (MakeGenericType == nullptr) - { - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK(scriptingClass); - - MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK(MakeGenericType); - - CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK(CreateInstance); - - AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3); - CHECK(AddDictionaryItem); - - GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1); - CHECK(GetDictionaryKeys); - } -#endif - } + ManagedDictionary(MObject* instance = nullptr); template static MObject* ToManaged(const Dictionary& data, MType* keyType, MType* valueType) @@ -154,109 +102,15 @@ public: return result; } - static MType* GetClass(MType* keyType, MType* valueType) - { - // Check if the generic type was generated earlier - KeyValueType cacheKey = { keyType, valueType }; - MType* dictionaryType; - if (CachedTypes.TryGet(cacheKey, dictionaryType)) - return dictionaryType; + static MType* GetClass(MType* keyType, MType* valueType); - MType* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); - MArray* genericArgs = MCore::Array::New(MCore::TypeCache::IntPtr, 2); - MType** genericArgsPtr = MCore::Array::GetAddress(genericArgs); - genericArgsPtr[0] = keyType; - genericArgsPtr[1] = valueType; + static ManagedDictionary New(MType* keyType, MType* valueType); - MObject* exception = nullptr; -#if !USE_MONO_AOT - dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception); -#else - void* params[2]; - params[0] = genericType; - params[1] = genericArgs; - dictionaryType = (MType*)MakeGenericType->Invoke(nullptr, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - return nullptr; - } - CachedTypes.Add(cacheKey, dictionaryType); - return dictionaryType; - } + void Add(MObject* key, MObject* value); - static ManagedDictionary New(MType* keyType, MType* valueType) - { - ManagedDictionary result; - MType* dictionaryType = GetClass(keyType, valueType); - if (!dictionaryType) - return result; + MArray* GetKeys() const; - MObject* exception = nullptr; -#if !USE_MONO_AOT - MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception); -#else - void* params[2]; - params[0] = dictionaryType; - params[1] = nullptr; - MObject* instance = CreateInstance->Invoke(nullptr, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - return result; - } - - result.Instance = instance; - return result; - } - - void Add(MObject* key, MObject* value) - { - CHECK(Instance); - - MObject* exception = nullptr; -#if !USE_MONO_AOT - AddDictionaryItem(nullptr, Instance, key, value, &exception); -#else - void* params[3]; - params[0] = Instance; - params[1] = key; - params[2] = value; - AddDictionaryItem->Invoke(Instance, params, &exception); -#endif - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("")); - } - } - - MArray* GetKeys() const - { - CHECK_RETURN(Instance, nullptr); -#if !USE_MONO_AOT - return GetDictionaryKeys(nullptr, Instance, nullptr); -#else - void* params[1]; - params[0] = Instance; - return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr); -#endif - } - - MObject* GetValue(MObject* key) const - { - CHECK_RETURN(Instance, nullptr); - MClass* klass = MCore::Object::GetClass(Instance); - MMethod* getItemMethod = klass->GetMethod("System.Collections.IDictionary.get_Item", 1); - CHECK_RETURN(getItemMethod, nullptr); - void* params[1]; - params[0] = key; - return getItemMethod->Invoke(Instance, params, nullptr); - } + MObject* GetValue(MObject* key) const; }; inline uint32 GetHash(const ManagedDictionary::KeyValueType& other) diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index 4bc2045a4..f035a26e9 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -3,6 +3,8 @@ #include "MUtils.h" #include "MClass.h" #include "MCore.h" +#include "MMethod.h" +#include "MAssembly.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Core/Types/Version.h" @@ -19,6 +21,7 @@ #include "Engine/Core/Math/Ray.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/Internal/StdTypesContainer.h" #include "Engine/Scripting/Internal/ManagedDictionary.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 0fee7a0d9..9d6e88986 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -278,57 +278,25 @@ struct MConverter::Val // Converter for ScriptingObject References. template class ScriptingObjectReference; - template -struct MConverter> -{ - MObject* Box(const ScriptingObjectReference& data, const MClass* klass) - { - return data.GetManagedInstance(); - } - - void Unbox(ScriptingObjectReference& result, MObject* data) - { - result = (T*)ScriptingObject::ToNative(data); - } - - void ToManagedArray(MArray* result, const Span>& data) - { - if (data.Length() == 0) - return; - MObject** objects = (MObject**)Allocator::Allocate(data.Length() * sizeof(MObject*)); - for (int32 i = 0; i < data.Length(); i++) - objects[i] = data[i].GetManagedInstance(); - MCore::GC::WriteArrayRef(result, Span(objects, data.Length())); - Allocator::Free(objects); - } - - void ToNativeArray(Span>& result, const MArray* data) - { - MObject** dataPtr = MCore::Array::GetAddress(data); - for (int32 i = 0; i < result.Length(); i++) - result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); - } -}; - -// Converter for Asset References. +class ScriptingObjectInterfaceReference; template class AssetReference; -template -struct MConverter> +template +struct MObjectReferenceConverter { - MObject* Box(const AssetReference& data, const MClass* klass) + MObject* Box(const Reference& data, const MClass* klass) { return data.GetManagedInstance(); } - void Unbox(AssetReference& result, MObject* data) + void Unbox(Reference& result, MObject* data) { - result = (T*)ScriptingObject::ToNative(data); + result = (Object*)ScriptingObject::ToNative(data); } - void ToManagedArray(MArray* result, const Span>& data) + void ToManagedArray(MArray* result, const Span& data) { if (data.Length() == 0) return; @@ -339,14 +307,29 @@ struct MConverter> Allocator::Free(objects); } - void ToNativeArray(Span>& result, const MArray* data) + void ToNativeArray(Span& result, const MArray* data) { MObject** dataPtr = MCore::Array::GetAddress(data); for (int32 i = 0; i < result.Length(); i++) - result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); + result.Get()[i] = (Object*)ScriptingObject::ToNative(dataPtr[i]); } }; +template +struct MConverter> : MObjectReferenceConverter, T> +{ +}; + +template +struct MConverter> : MObjectReferenceConverter, ScriptingObject> +{ +}; + +template +struct MConverter> : MObjectReferenceConverter, T> +{ +}; + // TODO: use MarshalAs=Guid on SoftAssetReference to pass guid over bindings and not load asset in glue code template class SoftAssetReference; diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index f82fb2722..b036e72f5 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -712,6 +712,12 @@ DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj) return MUtils::ToString(obj->GetType().Fullname); } +FORCE_INLINE bool ObjectInternal_MatchesType(ScriptingObject* obj, MClass* klass) +{ + return !klass || + (klass->IsInterface() ? obj->GetClass()->HasInterface(klass) : obj->Is(klass)); +} + DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MType* type, bool skipLog = false) { if (!id->IsValid()) @@ -727,7 +733,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MType* type, } if (obj) { - if (klass && !obj->Is(klass)) + if (!ObjectInternal_MatchesType(obj, klass)) { if (!skipLog) { @@ -757,7 +763,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_FindObject(Guid* id, MType* type, DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_TryFindObject(Guid* id, MType* type) { ScriptingObject* obj = Scripting::TryFindObject(*id); - if (obj && !obj->Is(MUtils::GetClass(type))) + if (obj && !ObjectInternal_MatchesType(obj, MUtils::GetClass(type))) obj = nullptr; return obj ? obj->GetOrCreateManagedInstance() : nullptr; } diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs new file mode 100644 index 000000000..46cbbef24 --- /dev/null +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.cs @@ -0,0 +1,188 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; +#if FLAX_EDITOR +using System.Globalization; +using System.ComponentModel; +#endif + +namespace FlaxEngine +{ + /// + /// The scripting object reference with interface. + /// + /// The type of the scripting interface. +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.ScriptingObjectInterfaceReferenceEditor))] + [TypeConverter(typeof(TypeConverters.ScriptingObjectInterfaceReferenceConverter))] +#endif + public struct ScriptingObjectInterfaceReference : IComparable, IComparable> where T : class + { + private Object _object; + + /// + /// Gets or sets the referenced object that implements the interface. + /// + public Object Object + { + get => _object; + set => _object = value != null && value is T ? value : null; + } + + /// + /// Gets or sets the referenced object that implements the interface. + /// + [NoSerialize] + public T Interface + { + get => _object as T; + set + { + var obj = value as Object; + if (value == null || obj != null) + _object = obj; + else + throw new InvalidCastException($"Cannot use object of type {value.GetType().FullName} for ScriptingObjectInterfaceReference<{typeof(T).FullName}>. It needs to inherit from {typeof(Object).FullName}."); + } + } + + /// + /// Initializes a new instance of the structure. + /// + /// The object to link. + public ScriptingObjectInterfaceReference(Object obj) + { + Object = obj; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The interface object to link. + public ScriptingObjectInterfaceReference(T interfaceObj) + { + Interface = interfaceObj; + } + + /// + /// Implicit cast operator to typed interface. + /// + /// Reference + /// Interface + public static explicit operator T(ScriptingObjectInterfaceReference value) + { + return value._object as T; + } + + /// + /// Implicit cast operator from object to reference. + /// + /// The object to link. + /// Reference + public static explicit operator ScriptingObjectInterfaceReference(T obj) + { + return new ScriptingObjectInterfaceReference(obj); + } + + /// + /// Implicit cast operator to object. + /// + /// Reference + /// Object + public static implicit operator Object(ScriptingObjectInterfaceReference value) + { + return value._object; + } + + /// + /// Implicit cast operator from object to reference. + /// + /// Object + /// Reference + public static implicit operator ScriptingObjectInterfaceReference(Object obj) + { + return new ScriptingObjectInterfaceReference(obj); + } + + /// + public override string ToString() + { + return _object?.ToString() ?? ""; + } + + /// + public override int GetHashCode() + { + return Object.GetUnmanagedPtr(_object).GetHashCode(); + } + + /// + public int CompareTo(object obj) + { + if (obj is ScriptingObjectInterfaceReference other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(ScriptingObjectInterfaceReference other) + { + return Object.GetUnmanagedPtr(_object).CompareTo(Object.GetUnmanagedPtr(other._object)); + } + } +} + +#if FLAX_EDITOR +namespace FlaxEngine.TypeConverters +{ + internal class ScriptingObjectInterfaceReferenceConverter : TypeConverter + { + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return base.CanConvertFrom(context, sourceType); + } + + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return false; + return base.CanConvertTo(context, destinationType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string str && context is DummyTypeDescriptorContext internalContext) + { + var type = internalContext.CurrentType; + Json.JsonSerializer.ParseID(str, out var id); + var obj = Object.Find(ref id, type.GetGenericArguments()[0]); + var objectField = type.GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + value = Activator.CreateInstance(type); + objectField.SetValue(value, obj); + return value; + } + return base.ConvertFrom(context, culture, value); + } + + /// + public override unsafe object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + var objectField = value.GetType().GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var obj = objectField.GetValue(value) as Object; + if (obj == null) + return string.Empty; + var id = obj.ID; + return Json.JsonSerializer.GetStringID(&id); + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} +#endif diff --git a/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h new file mode 100644 index 000000000..46687074a --- /dev/null +++ b/Source/Engine/Scripting/ScriptingObjectInterfaceReference.h @@ -0,0 +1,193 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "ScriptingObjectReference.h" + +/// +/// The scripting object reference with interface. +/// +/// The type of the scripting interface. +template +API_CLASS(Template, MarshalAs=ScriptingObject*) class ScriptingObjectInterfaceReference : public ScriptingObjectReferenceBase +{ +public: + typedef ScriptingObjectInterfaceReference Type; + +public: + /// + /// Initializes a new instance of the class. + /// + ScriptingObjectInterfaceReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + ScriptingObjectInterfaceReference(ScriptingObject* obj) + : ScriptingObjectReferenceBase(IsValid(obj) ? obj : nullptr) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The interface object to link. + ScriptingObjectInterfaceReference(T* interfaceObj) + : ScriptingObjectReferenceBase(ScriptingObject::FromInterface(interfaceObj)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + ScriptingObjectInterfaceReference(const ScriptingObjectInterfaceReference& other) + : ScriptingObjectReferenceBase(other._object) + { + } + + ScriptingObjectInterfaceReference(ScriptingObjectInterfaceReference&& other) noexcept + : ScriptingObjectReferenceBase(MoveTemp(other)) + { + } + + /// + /// Finalizes an instance of the class. + /// + ~ScriptingObjectInterfaceReference() + { + } + +public: + FORCE_INLINE bool operator==(ScriptingObject* other) const + { + return _object == other; + } + + FORCE_INLINE bool operator!=(ScriptingObject* other) const + { + return _object != other; + } + + FORCE_INLINE bool operator==(T* other) const + { + return Get() == other; + } + + FORCE_INLINE bool operator!=(T* other) const + { + return Get() != other; + } + + FORCE_INLINE bool operator==(const ScriptingObjectInterfaceReference& other) const + { + return _object == other._object; + } + + FORCE_INLINE bool operator!=(const ScriptingObjectInterfaceReference& other) const + { + return _object != other._object; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(ScriptingObject* other) + { + OnSet(IsValid(other) ? other : nullptr); + return *this; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(T* other) + { + OnSet(ScriptingObject::FromInterface(other)); + return *this; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(const ScriptingObjectInterfaceReference& other) + { + OnSet(other._object); + return *this; + } + + FORCE_INLINE ScriptingObjectInterfaceReference& operator=(ScriptingObjectInterfaceReference&& other) noexcept + { + ScriptingObjectReferenceBase::operator=(MoveTemp(other)); + return *this; + } + + ScriptingObjectInterfaceReference& operator=(const Guid& id) + { + ScriptingObject* obj = FindObject(id, ScriptingObject::GetStaticClass()); + OnSet(IsValid(obj) ? obj : nullptr); + return *this; + } + + /// + /// Implicit conversion to the interface. + /// + FORCE_INLINE operator T*() const + { + return Get(); + } + + /// + /// Implicit conversion to the object. + /// + FORCE_INLINE operator ScriptingObject*() const + { + return _object; + } + + /// + /// Implicit conversion to boolean value. + /// + FORCE_INLINE operator bool() const + { + return _object != nullptr; + } + + /// + /// Interface accessor. + /// + FORCE_INLINE T* operator->() const + { + return Get(); + } + + /// + /// Gets the interface pointer. + /// + FORCE_INLINE T* Get() const + { + return ScriptingObject::ToInterface(_object); + } + + /// + /// Gets the referenced object. + /// + FORCE_INLINE ScriptingObject* GetObject() const + { + return _object; + } + + /// + /// Gets managed instance object. + /// + FORCE_INLINE MObject* GetManagedInstance() const + { + return _object ? _object->GetOrCreateManagedInstance() : nullptr; + } + +private: + FORCE_INLINE static bool IsValid(const ScriptingObject* obj) + { + return !obj || obj->GetType().GetInterface(T::TypeInitializer); + } +}; + +template +uint32 GetHash(const ScriptingObjectInterfaceReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index 58fed7668..21e4ecd66 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -72,7 +72,7 @@ public: } /// - /// Gets managed instance object (or null if no object linked). + /// Gets managed instance object. /// FORCE_INLINE MObject* GetManagedInstance() const { diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index 3fd85200e..b07702e44 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -233,7 +233,7 @@ public: } /// - /// Gets managed instance object (or null if no object linked). + /// Gets managed instance object. /// MObject* GetManagedInstance() const { diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index a45ae558a..71094b127 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -1,8 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System; using FlaxEngine.GUI; using Newtonsoft.Json; +using System; namespace FlaxEngine.Json { @@ -138,6 +138,52 @@ namespace FlaxEngine.Json } } + /// + /// Serialize as path string in internal format. + /// + /// + internal class ScriptingObjectInterfaceReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + { + var objectField = value.GetType().GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var obj = objectField.GetValue(value) as Object; + if (obj == null) + { + writer.WriteNull(); + return; + } + var id = obj.ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = existingValue ?? Activator.CreateInstance(objectType); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + var obj = Object.Find(ref id, objectType.GetGenericArguments()[0]); + var objectField = objectType.GetField("_object", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + objectField.SetValue(result, obj); + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(ScriptingObjectInterfaceReference<>); + } + } + /// /// Serialize as path string in internal format. /// diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index 4f2690863..5a1a7c92d 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -34,6 +34,15 @@ namespace FlaxEngine.Json.JsonCustomSerializers _attributesIgnoreList = isManagedOnly ? AttributesIgnoreListManaged : AttributesIgnoreList; } + private void SetupProperty(JsonProperty jsonProperty, Type type, IEnumerable attributes) + { + if (_flaxType.IsAssignableFrom(type)) + { + jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + jsonProperty.Converter = JsonSerializer.ObjectConverter; + } + } + /// protected override JsonContract CreateContract(Type objectType) { @@ -116,11 +125,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = true; - if (_flaxType.IsAssignableFrom(f.FieldType)) - { - jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; - jsonProperty.Converter = JsonSerializer.ObjectConverter; - } + SetupProperty(jsonProperty, f.FieldType, attributes); result.Add(jsonProperty); } @@ -159,11 +164,7 @@ namespace FlaxEngine.Json.JsonCustomSerializers jsonProperty.Writable = true; jsonProperty.Readable = !isObsolete; - if (_flaxType.IsAssignableFrom(p.PropertyType)) - { - jsonProperty.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; - jsonProperty.Converter = JsonSerializer.ObjectConverter; - } + SetupProperty(jsonProperty, p.PropertyType, attributes); result.Add(jsonProperty); } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 179adf364..42f44815e 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -198,6 +198,7 @@ namespace FlaxEngine.Json settings.Converters.Add(new SceneReferenceConverter()); settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new SoftTypeReferenceConverter()); + settings.Converters.Add(new ScriptingObjectInterfaceReferenceConverter()); settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter()); settings.Converters.Add(new ControlReferenceConverter()); settings.Converters.Add(new MarginConverter()); @@ -625,84 +626,34 @@ namespace FlaxEngine.Json } /// - /// Parses the given object identifier represented in the internal serialization format. + /// Tries to parse the given object identifier represented in the internal serialization format. /// /// The ID string. /// The identifier. - public static unsafe void ParseID(string str, out Guid id) + /// True if cannot parse text, otherwise false + public static unsafe bool ParseID(string str, out Guid id) { + bool result = true; GuidInterop g; - - // Broken after VS 15.5 - /*fixed (char* a = str) + if (str != null && str.Length == 32) { - char* b = a + 8; - char* c = b + 8; - char* d = c + 8; - - ParseHex(a, 8, out g.A); - ParseHex(b, 8, out g.B); - ParseHex(c, 8, out g.C); - ParseHex(d, 8, out g.D); - }*/ - - // Temporary fix (not using raw char* pointer) - ParseHex(str, 0, 8, out g.A); - ParseHex(str, 8, 8, out g.B); - ParseHex(str, 16, 8, out g.C); - ParseHex(str, 24, 8, out g.D); - - id = *(Guid*)&g; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void ParseHex(char* str, int length, out uint result) - { - uint sum = 0; - char* p = str; - char* end = str + length; - - if (*p == '0' && *(p + 1) == 'x') - p += 2; - - while (p < end && *p != 0) - { - int c = *p - '0'; - - if (c < 0 || c > 9) - { - c = char.ToLower(*p) - 'a' + 10; - if (c < 10 || c > 15) - { - result = 0; - return; - } - } - - sum = 16 * sum + (uint)c; - - p++; + // Matches Flax Guid parsing of FormatType::N + result = ParseHex(str, 0, 8, out g.A) || + ParseHex(str, 8, 8, out g.B) || + ParseHex(str, 16, 8, out g.C) || + ParseHex(str, 24, 8, out g.D); } - - result = sum; + id = *(Guid*)&g; + return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ParseHex(string str, int start, int length, out uint result) + internal static bool ParseHex(string str, int start, int length, out uint result) { uint sum = 0; int p = start; int end = start + length; - if (str.Length < end) - { - result = 0; - return; - } - - if (str[p] == '0' && str[p + 1] == 'x') - p += 2; - while (p < end && str[p] != 0) { int c = str[p] - '0'; @@ -713,16 +664,16 @@ namespace FlaxEngine.Json if (c < 10 || c > 15) { result = 0; - return; + return true; } } sum = 16 * sum + (uint)c; - p++; } result = sum; + return p != end; } } } diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index cc2b7e73b..06c52b64f 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -133,6 +133,14 @@ public: v = ptr; } + template + FORCE_INLINE void Read(ScriptingObjectInterfaceReference& v) + { + uint32 id[4]; + ReadBytes(id, sizeof(id)); + v = *(Guid*)id; + } + template FORCE_INLINE void Read(SoftObjectReference& v) { diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 41ae4898a..b588a5d78 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -14,6 +14,8 @@ struct VariantType; template class ScriptingObjectReference; template +class ScriptingObjectInterfaceReference; +template class SoftObjectReference; template class AssetReference; @@ -458,7 +460,6 @@ namespace Serialization } FLAXENGINE_API bool ShouldSerializeRef(const SceneObject* v, const SceneObject* other); - template inline typename TEnableIf, TNot>>::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj) { @@ -474,7 +475,7 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); v = (T*)::FindObject(id, T::GetStaticClass()); } @@ -501,7 +502,28 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + + // Scripting Interface Reference + + template + inline bool ShouldSerialize(const ScriptingObjectInterfaceReference& v, const void* otherObj) + { + return !otherObj || ShouldSerializeRef(v.GetObject(), ((ScriptingObjectInterfaceReference*)otherObj)->GetObject()); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectInterfaceReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, ScriptingObjectInterfaceReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); v = id; } @@ -522,7 +544,7 @@ namespace Serialization { Guid id; Deserialize(stream, id, modifier); - modifier->IdsMapping.TryGet(id, id); + modifier->IdsMapping.TryGet(id, id); v = id; } diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index 7e82467f5..4e517effe 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -17,6 +17,8 @@ class ScriptingObject; template class ScriptingObjectReference; template +class ScriptingObjectInterfaceReference; +template class SoftObjectReference; template class AssetReference; diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index f027deabb..a5130ed8e 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -156,11 +156,19 @@ public: { Write(v.Get()); } + + template + FORCE_INLINE void Write(const ScriptingObjectInterfaceReference& v) + { + Write(v.GetObject()); + } + template FORCE_INLINE void Write(const SoftObjectReference& v) { Write(v.Get()); } + template FORCE_INLINE void Write(const AssetReference& v) { diff --git a/Source/Engine/Tests/TestEditorViewport.cs b/Source/Engine/Tests/TestEditorViewport.cs new file mode 100644 index 000000000..9123cfd20 --- /dev/null +++ b/Source/Engine/Tests/TestEditorViewport.cs @@ -0,0 +1,28 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#if FLAX_TESTS +using FlaxEditor.Viewport; +using FlaxEngine; +using NUnit.Framework; + +namespace FlaxEditor.Tests +{ + [TestFixture] + public class TestEditorViewport + { + [Test] + public void TestMouseAnchorSnapsToDevicePixels() + { + const float dpiScale = 1.25f; + var logicalPosition = new Float2(984.7999f, 580.7999f); + + var snapped = EditorViewport.SnapMousePositionToDevicePixels(logicalPosition, dpiScale); + var physicalPosition = snapped * dpiScale; + + Assert.AreEqual(1231.0f, physicalPosition.X, 0.0001f); + Assert.AreEqual(726.0f, physicalPosition.Y, 0.0001f); + Assert.AreEqual(snapped, EditorViewport.SnapMousePositionToDevicePixels(snapped, dpiScale)); + } + } +} +#endif diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 506291b36..8a3541def 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -37,9 +37,7 @@ TEST_CASE("Scripting") MMethod* method = klass->GetMethod("TestLibraryImports"); CHECK(method); MObject* result = method->Invoke(nullptr, nullptr, nullptr); - CHECK(result); - int32 resultValue = MUtils::Unbox(result); - CHECK(resultValue == 0); + CHECK(MUtils::Unbox(result) == 0); } SECTION("Test Class") @@ -167,4 +165,15 @@ TEST_CASE("Scripting") CHECK(interfaceObject); CHECK(interfaceObject == object); } + + SECTION("Test Interface Reference") + { + // Test native interface implementation + MClass* klass = Scripting::FindClass("FlaxEngine.Tests.TestScripting"); + CHECK(klass); + MMethod* method = klass->GetMethod("TestInterfaceReference"); + CHECK(method); + MObject* result = method->Invoke(nullptr, nullptr, nullptr); + CHECK(MUtils::Unbox(result) == 0); + } } diff --git a/Source/Engine/Tests/TestScripting.cs b/Source/Engine/Tests/TestScripting.cs index edbe18016..5846b569d 100644 --- a/Source/Engine/Tests/TestScripting.cs +++ b/Source/Engine/Tests/TestScripting.cs @@ -2,6 +2,7 @@ #if FLAX_TESTS using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; @@ -42,6 +43,33 @@ namespace FlaxEngine.Tests NativeLibrary.Free(library); return result; } + + /// + /// Tests usage with marshalling. + /// + public static int TestInterfaceReference() + { + var native = new TestClassNative(); + native.InterfaceRef = native; + var returned = native.InterfaceRef; + if (returned != native) + return 1; + returned = native.TestPassInterface(native); + if (returned != native) + return 2; + returned = native.TestPassInterfaceArray(new ScriptingObjectInterfaceReference[1] { native })[0]; + if (returned != native) + return 3; + var dic = new Dictionary>(); + dic.Add("key", native); + returned = native.TestPassInterfaceDictionary(dic)["key"]; + if (returned != native) + return 4; + var res = returned.Interface.TestInterfaceMethod("123"); + if (res != 3) + return 5; + return 0; + } } } diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index ca13ac307..e9440bb03 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -5,7 +5,9 @@ #include "Engine/Core/ISerializable.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/ScriptingObjectInterfaceReference.h" #include "Engine/Scripting/SerializableScriptingObject.h" #include "Engine/Scripting/SoftTypeReference.h" #include "Engine/Content/SceneReference.h" @@ -178,6 +180,9 @@ public: // Test struct API_FIELD() TestStruct SimpleStruct; + // Test interface reference + API_FIELD() ScriptingObjectInterfaceReference InterfaceRef; + // Test event API_EVENT() Delegate&, Array&> SimpleEvent; @@ -200,6 +205,24 @@ public: // Test nameless arguments API_FUNCTION() void TestNamelessArguments(int32, float, bool){} + // Test pass interface ref in function + API_FUNCTION() ScriptingObjectInterfaceReference TestPassInterface(ScriptingObjectInterfaceReference param1) const + { + return param1; + } + + // Test pass interface ref array in function + API_FUNCTION() Array> TestPassInterfaceArray(Array> param1) const + { + return param1; + } + + // Test pass interface ref dictionary in function + API_FUNCTION() Dictionary> TestPassInterfaceDictionary(Dictionary> param1) const + { + return param1; + } + int32 TestInterfaceMethod(const String& str) override { return str.Length(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 1166a1985..aa6d3623f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1622,6 +1622,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } +#if 0 if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && options.Type == ModelType::Prefab) { // Apply just the scale and rotations. @@ -1654,6 +1655,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } +#endif if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations)) { for (auto& animation : data.Animations) diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index aa0078c19..0caca6509 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -171,6 +171,12 @@ namespace FlaxEngine.GUI /// public virtual void DisposeChildren() { + // Steal focus from children to maintain it in a hierarchy + if (ContainsFocus) + { + Focus(); + } + bool wasLayoutLocked = _isLayoutLocked; _isLayoutLocked = true; diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index ea3c27658..1882045ba 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -243,6 +243,13 @@ namespace FlaxEngine.GUI _window.Position = mousePos + TooltipOffset; } + var mouse = Input.Mouse; + if (mouse.IsAnyButtonDown || Math.Abs(mouse.ScrollDelta) > Mathf.Epsilon) + { + // Hide popup when buttons are pressed + Hide(); + } + base.Update(deltaTime); } diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index bf87fd676..be54260da 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -63,7 +63,7 @@ UICanvas::UICanvas(const SpawnParams& params) #if USE_EDITOR -BoundingBox UICanvas::GetEditorBox() const +BoundingBox UICanvas::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/UI/UICanvas.h b/Source/Engine/UI/UICanvas.h index d654991bb..e8c757a3e 100644 --- a/Source/Engine/UI/UICanvas.h +++ b/Source/Engine/UI/UICanvas.h @@ -14,7 +14,7 @@ class FLAXENGINE_API UICanvas : public Actor public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index d16c582e9..77e363c7f 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -60,7 +60,7 @@ UIControl::UIControl(const SpawnParams& params) #if USE_EDITOR -BoundingBox UIControl::GetEditorBox() const +BoundingBox UIControl::GetEditorBoundingBox() const { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); diff --git a/Source/Engine/UI/UIControl.h b/Source/Engine/UI/UIControl.h index e7739781d..141753104 100644 --- a/Source/Engine/UI/UIControl.h +++ b/Source/Engine/UI/UIControl.h @@ -18,7 +18,7 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override; + BoundingBox GetEditorBoundingBox() const override; #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Video/VideoPlayer.h b/Source/Engine/Video/VideoPlayer.h index ecae37e61..b754969eb 100644 --- a/Source/Engine/Video/VideoPlayer.h +++ b/Source/Engine/Video/VideoPlayer.h @@ -241,12 +241,11 @@ private: public: // [Actor] #if USE_EDITOR - BoundingBox GetEditorBox() const override + BoundingBox GetEditorBoundingBox() const override { const Vector3 size(50); return BoundingBox(_transform.Translation - size, _transform.Translation + size); } - void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 2f2962d2d..3ba5b3d8f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -104,8 +104,6 @@ namespace Flax.Build.Bindings { if (attribute && valueType != null && !valueType.IsArray) { - //if (valueType.Type == "") - //ScriptingObjectReference apiType = FindApiTypeInfo(buildData, valueType, caller); // Object reference @@ -364,7 +362,7 @@ namespace Flax.Build.Bindings if (arrayApiType != null && arrayApiType.MarshalAs != null) arrayTypeInfo = arrayApiType.MarshalAs; } - return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; + return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller, marshalling) + "[]"; } // Dictionary @@ -533,6 +531,21 @@ namespace Flax.Build.Bindings return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; } return string.Empty; + case "Dictionary": + if (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count == 2) + { + // Convert dictionary that uses different type for marshalling + var keyApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); + var valueApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[1], caller); + if ((keyApiType != null && keyApiType.MarshalAs != null) || (valueApiType != null && valueApiType.MarshalAs != null)) + { + var keyConverter = keyApiType != null && keyApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, keyApiType.MarshalAs, caller)})" : ""; + var valueConverter = valueApiType != null && valueApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, valueApiType.MarshalAs, caller)})" : ""; + //return $"{{0}} != null ? System.Linq.Enumerable.ToDictionary({{0}}, x => {keyConverter}, x => {valueConverter}) : null"; + return $"{{0}}.ConvertDictionary(key => {keyConverter}key, value => {valueConverter}value)"; + } + } + return string.Empty; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -744,10 +757,12 @@ namespace Flax.Build.Bindings contents.Append($"var __{parameterInfo.Name}Count = {(isSetter ? "value" : parameterInfo.Name)}?.Length ?? 0; "); } } + var returnType = functionInfo.ReturnType; + if (functionInfo.Glue.UseReferenceForResult) { } - else if (!functionInfo.ReturnType.IsVoid) + else if (!returnType.IsVoid) { contents.Append("return "); } @@ -817,14 +832,27 @@ namespace Flax.Build.Bindings } contents.Append(')'); - if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + if ((returnType.Type == "Array" || returnType.Type == "Span" || returnType.Type == "DataContainer") && returnType.GenericArgs != null) { // Convert array that uses different type for marshalling - var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var arrayTypeInfo = returnType.GenericArgs[0]; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); } + else if (returnType.Type == "Dictionary" && returnType.GenericArgs != null && returnType.GenericArgs.Count == 2) + { + // Convert dictionary that uses different type for marshalling + var keyApiType = FindApiTypeInfo(buildData, returnType.GenericArgs[0], caller); + var valueApiType = FindApiTypeInfo(buildData, returnType.GenericArgs[1], caller); + if ((keyApiType != null && keyApiType.MarshalAs != null) || (valueApiType != null && valueApiType.MarshalAs != null)) + { + var keyConverter = keyApiType != null && keyApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, returnType.GenericArgs[0], caller)})" : ""; + var valueConverter = valueApiType != null && valueApiType.MarshalAs != null ? $"({GenerateCSharpNativeToManaged(buildData, returnType.GenericArgs[1], caller)})" : ""; + contents.Append($".ConvertDictionary(key => {keyConverter}key, value => {valueConverter}value)"); + } + } + contents.Append(';'); // Return result diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 5f8e47706..dc76580b2 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -171,6 +171,8 @@ namespace Flax.Build.Bindings return $"Variant({value}.Get())"; if (typeInfo.Type == "SoftTypeReference") return $"Variant::Typename(StringAnsiView({value}))"; + if (typeInfo.Type == "ScriptingObjectInterfaceReference") + return $"Variant({value}.GetObject())"; if (typeInfo.IsArray) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); @@ -305,12 +307,12 @@ namespace Flax.Build.Bindings return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsObjectRef) - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; + return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.Type == "SoftTypeReference") return $"(StringAnsiView){value}"; + if (typeInfo.Type == "ScriptingObjectInterfaceReference") + return $"ScriptingObjectInterfaceReference<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index f2d82cc6e..36c47138e 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -18,6 +18,7 @@ namespace Flax.Build.Bindings "ManagedScriptingObject", "PersistentScriptingObject", "ScriptingObjectReference", + "ScriptingObjectInterfaceReference", "AssetReference", "BinaryAsset", "SceneObject", diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 6f3abf755..e723f904c 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -521,7 +521,7 @@ namespace Flax.Build { var doc = System.Xml.Linq.XDocument.Load(nuspecFile); var root = (System.Xml.Linq.XElement)doc.FirstNode; - var metadataNode = root.Descendants().First(x => x.Name.LocalName== "metadata"); + var metadataNode = root.Descendants().First(x => x.Name.LocalName == "metadata"); var dependenciesNode = metadataNode.Descendants().First(x => x.Name.LocalName == "dependencies"); var groupNode = dependenciesNode.Descendants().FirstOrDefault(x => x.Attribute("targetFramework")?.Value == package.Framework); if (groupNode == null) @@ -540,6 +540,10 @@ namespace Flax.Build DeployNuGetPackage(nugetPath, targetBuildOptions, nugetFiles, new NugetPackage { Name = dependencyId, Version = dependencyVersion, Framework = package.Framework } ); } } + else + { + Log.Verbose($"Missing nuspec file '{nuspecFile}' for NuGet package '{package}'"); + } } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index f158078f5..9369578b5 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -130,6 +130,21 @@ namespace Flax.Build.NativeCpp } } + // Fallback to netcore or netstandard if original directory + var fallbacks = new[] + { + "netcoreapp3.1", + "netcoreapp3.0", + "netstandard2.1", + "netstandard2.0", + }; + foreach (var fallback in fallbacks) + { + libFolder = Path.Combine(nugetPath, Name, Version, "lib", fallback); + if (Directory.Exists(libFolder)) + return libFolder; + } + Log.Error($"Missing NuGet package \"{Name}, {Version}, {Framework}\" (nuget: {nugetPath})"); return string.Empty; } @@ -157,6 +172,11 @@ namespace Flax.Build.NativeCpp } return dlls[0]; } + + public override string ToString() + { + return $"{Name}, {Version}, {Framework}"; + } } /// diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 0ed967c6c..41c177f6b 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -386,7 +386,9 @@ namespace Flax.Build.Projects.VisualStudio // Override MSBuild .targets file with one that runs NMake commands (workaround for Rider not finding "Microsoft.Cpp.Default.props" file) var cppTargetsFileContent = new StringBuilder(); + cppTargetsFileContent.AppendLine(""); cppTargetsFileContent.AppendLine(""); + cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(" "); @@ -408,10 +410,48 @@ namespace Flax.Build.Projects.VisualStudio cppTargetsFileContent.AppendLine(string.Format(" {0}", debuggerWorkingDirectory)); cppTargetsFileContent.AppendLine(" "); cppTargetsFileContent.AppendLine(""); + + // Workarounds for Rider language server to not show errors over well known symbols + Dictionary preprocessor = new Dictionary() + { + {"__attribute__(x)", ""}, + {"__null", "0"}, + {"__extension__", ""}, + {"__asm__", "__asm"}, + {"_SIZE_T", "1"}, + }; + // Undefine hardcoded MSVC definitions + HashSet undefine = new HashSet() + { + "_MSC_VER", + "_MSC_EXTENSIONS", + "_M_IX86", + "_WIN32", + }; + + // Expose all known preprocessor definitions known by the compiler to language server + var clangDefinitions = Utilities.ReadProcessOutput("clang++", "-dM -E -x c++ /dev/null"); + foreach (var (def, val) in clangDefinitions.Split('\n').Select(x => (x.Split(' ')[1], x.Split(' ')[2]))) + preprocessor.Add(def, val); + + var workaroundsFileContent = new StringBuilder(); + workaroundsFileContent.AppendLine($""); + workaroundsFileContent.AppendLine($""); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" {string.Join(' ', undefine.Select(x => $"/U {x}"))} %(AdditionalOptions)"); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($" /usr/include/c++/16/aarch64-redhat-linux/;$(NMakeIncludeSearchPath)"); + workaroundsFileContent.AppendLine($" {string.Join(';', preprocessor.Select(x => $"{x.Key}={x.Value}"))};$(NMakePreprocessorDefinitions)"); + workaroundsFileContent.AppendLine($" "); + workaroundsFileContent.AppendLine($""); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.targets"), cppTargetsFileContent.ToString()); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.Default.props"), vcUserFileContent.ToString()); Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Microsoft.Cpp.props"), vcUserFileContent.ToString()); + Utilities.WriteFileIfChanged(Path.Combine(projectDirectory, "Rider.Workarounds.Cpp.props"), workaroundsFileContent.ToString()); } // Save the files