diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax
index 3318b811b..ae46fcf06 100644
--- a/Content/Editor/IconsAtlas.flax
+++ b/Content/Editor/IconsAtlas.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d867a994701f09964335ddf51cd4060a54728b9de4420ad3f0bfbd8847bc7602
-size 5611659
+oid sha256:745ce418c493445abde98aea91a34592b00c5c46b68cd7e61604a05b3043c76c
+size 5608587
diff --git a/Source/Editor/Content/GUI/ContentView.DragDrop.cs b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
index 3c0993b93..348e2b443 100644
--- a/Source/Editor/Content/GUI/ContentView.DragDrop.cs
+++ b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
@@ -45,8 +45,14 @@ namespace FlaxEditor.Content.GUI
private void ImportActors(DragActors actors, ContentFolder location)
{
- // Use only the first actor
- Editor.Instance.Prefabs.CreatePrefab(actors.Objects[0].Actor);
+ foreach (var actorNode in actors.Objects)
+ {
+ var actor = actorNode.Actor;
+ if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
+ continue;
+
+ Editor.Instance.Prefabs.CreatePrefab(actor, false);
+ }
}
///
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index 17999d772..cfba940c2 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -33,7 +33,12 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Asset)), DefaultEditor]
public class AssetRefEditor : CustomEditor
{
- private AssetPicker _picker;
+ ///
+ /// The asset picker used to get a reference to an asset.
+ ///
+ public AssetPicker Picker;
+
+ private bool _isRefreshing;
private ScriptType _valueType;
///
@@ -44,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (HasDifferentTypes)
return;
- _picker = layout.Custom().CustomControl;
+ Picker = layout.Custom().CustomControl;
_valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
var assetType = _valueType;
@@ -66,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
- _picker.FileExtension = assetReference.TypeName;
+ Picker.FileExtension = assetReference.TypeName;
}
else
{
@@ -75,26 +80,30 @@ namespace FlaxEditor.CustomEditors.Editors
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
+ else
+ assetType = ScriptType.Void;
}
}
- _picker.AssetType = assetType;
- _picker.Height = height;
- _picker.SelectedItemChanged += OnSelectedItemChanged;
+ Picker.AssetType = assetType;
+ Picker.Height = height;
+ Picker.SelectedItemChanged += OnSelectedItemChanged;
}
private void OnSelectedItemChanged()
{
+ if (_isRefreshing)
+ return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
- SetValue(_picker.SelectedItem);
+ SetValue(Picker.SelectedItem);
else if (_valueType.Type == typeof(Guid))
- SetValue(_picker.SelectedID);
+ SetValue(Picker.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
- SetValue(new SceneReference(_picker.SelectedID));
+ SetValue(new SceneReference(Picker.SelectedID));
else if (_valueType.Type == typeof(string))
- SetValue(_picker.SelectedPath);
+ SetValue(Picker.SelectedPath);
else
- SetValue(_picker.SelectedAsset);
+ SetValue(Picker.SelectedAsset);
}
///
@@ -104,16 +113,18 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues)
{
+ _isRefreshing = true;
if (Values[0] is AssetItem assetItem)
- _picker.SelectedItem = assetItem;
+ Picker.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
- _picker.SelectedID = guid;
+ Picker.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
- _picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
+ Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
- _picker.SelectedPath = path;
+ Picker.SelectedPath = path;
else
- _picker.SelectedAsset = Values[0] as Asset;
+ Picker.SelectedAsset = Values[0] as Asset;
+ _isRefreshing = false;
}
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
index a08a254d4..277f9ecb0 100644
--- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.CustomEditors.GUI;
+using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
@@ -13,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Editors
{
private GroupElement _group;
private bool _updateName;
+ private int _entryIndex;
+ private bool _isRefreshing;
+ private MaterialBase _material;
+ private ModelInstanceActor _modelInstance;
+ private AssetRefEditor _materialEditor;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -21,47 +28,122 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
+ if (ParentEditor == null)
+ return;
+ var entry = (ModelInstanceEntry)Values[0];
+ var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
+ var materialLabel = new PropertyNameLabel("Material");
+ materialLabel.TooltipText = "The mesh surface material used for the rendering.";
+ if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
+ {
+ _entryIndex = entryIndex;
+ _modelInstance = modelInstance;
+ var slots = modelInstance.MaterialSlots;
+ if (entry.Material == slots[entryIndex].Material)
+ {
+ // Ensure that entry with default material set is set back to null
+ modelInstance.SetMaterial(entryIndex, null);
+ }
+ _material = modelInstance.GetMaterial(entryIndex);
+ var defaultValue = GPUDevice.Instance.DefaultMaterial;
+ if (slots[entryIndex].Material)
+ {
+ // Use default value set on asset (eg. Model Asset)
+ defaultValue = slots[entryIndex].Material;
+ }
+
+ // Create material picker
+ var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
+ var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
+ materialEditor.Values.SetDefaultValue(defaultValue);
+ materialEditor.RefreshDefaultValue();
+ materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
+ _materialEditor = materialEditor;
+ }
+
base.Initialize(group);
}
+ private void OnSelectedMaterialChanged()
+ {
+ if (_isRefreshing)
+ return;
+ _isRefreshing = true;
+ var slots = _modelInstance.MaterialSlots;
+ var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
+ var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
+ var value = (ModelInstanceEntry)Values[0];
+ var prevMaterial = value.Material;
+ if (!material)
+ {
+ // Fallback to default material
+ _materialEditor.Picker.SelectedAsset = defaultMaterial;
+ value.Material = defaultMaterial;
+ }
+ else if (material == slots[_entryIndex].Material)
+ {
+ // Asset default material
+ value.Material = null;
+ }
+ else if (material == defaultMaterial && !slots[_entryIndex].Material)
+ {
+ // Default material while asset has no set as well
+ value.Material = null;
+ }
+ else
+ {
+ // Custom material
+ value.Material = material;
+ }
+ if (prevMaterial != value.Material)
+ SetValue(value);
+ _isRefreshing = false;
+ }
+
+ ///
+ protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
+ {
+ // Skip material member as it is overridden
+ if (item.Info.Name == "Material" && _materialEditor != null)
+ return;
+ base.SpawnProperty(itemLayout, itemValues, item);
+ }
+
///
public override void Refresh()
{
+ // Update panel title to match material slot name
if (_updateName &&
_group != null &&
ParentEditor?.ParentEditor != null &&
ParentEditor.ParentEditor.Values.Count > 0)
{
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
- if (ParentEditor.ParentEditor.Values[0] is StaticModel staticModel)
+ if (ParentEditor.ParentEditor.Values[0] is ModelInstanceActor modelInstance)
{
- var model = staticModel.Model;
- if (model && model.IsLoaded)
+ var slots = modelInstance.MaterialSlots;
+ if (slots != null && slots.Length > entryIndex)
{
- var slots = model.MaterialSlots;
- if (slots != null && slots.Length > entryIndex)
- {
- _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
- _updateName = false;
- }
- }
- }
- else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel)
- {
- var model = animatedModel.SkinnedModel;
- if (model && model.IsLoaded)
- {
- var slots = model.MaterialSlots;
- if (slots != null && slots.Length > entryIndex)
- {
- _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
- _updateName = false;
- }
+ _updateName = false;
+ _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
}
}
}
+ // Refresh currently selected material
+ _material = _modelInstance.GetMaterial(_entryIndex);
+
base.Refresh();
}
+
+ ///
+ protected override void Deinitialize()
+ {
+ _material = null;
+ _modelInstance = null;
+ _materialEditor = null;
+
+ base.Deinitialize();
+ }
}
}
diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs
index 127ba3f6f..fb3c46a41 100644
--- a/Source/Editor/EditorIcons.cs
+++ b/Source/Editor/EditorIcons.cs
@@ -96,6 +96,7 @@ namespace FlaxEditor
public SpriteHandle Link64;
public SpriteHandle Build64;
public SpriteHandle Add64;
+ public SpriteHandle ShipIt64;
// 96px
public SpriteHandle Toolbox96;
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index bfded5380..3e5d22eb0 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -475,22 +475,31 @@ namespace FlaxEditor.GUI
if (_type != ScriptType.Null)
{
// Show asset picker popup
- AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
+ if (_selected != null)
+ {
+ var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ popup.ScrollToAndHighlightItemByName(selectedAssetName);
+ }
}
else
{
// Show content item picker popup
- ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
+ if (_selectedItem != null)
+ {
+ popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
+ }
}
}
else if (_selected != null || _selectedItem != null)
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs b/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs
new file mode 100644
index 000000000..5abb52b4a
--- /dev/null
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs
@@ -0,0 +1,93 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using FlaxEngine;
+
+namespace FlaxEditor.GUI.ContextMenu
+{
+ ///
+ /// Context menu for a single selectable option from range of values (eg. enum).
+ ///
+ [HideInEditor]
+ class ContextMenuSingleSelectGroup
+ {
+ private struct SingleSelectGroupItem
+ {
+ public string Text;
+ public string Tooltip;
+ public T Value;
+ public Action Selected;
+ public List Buttons;
+ }
+
+ private List _menus = new List();
+ private List _items = new List();
+ private SingleSelectGroupItem _selectedItem;
+
+ public T Selected
+ {
+ get => _selectedItem.Value;
+ set
+ {
+ var index = _items.FindIndex(x => x.Value.Equals(value));
+ if (index != -1 && !_selectedItem.Value.Equals(value))
+ {
+ SetSelected(_items[index]);
+ }
+ }
+ }
+
+ public Action SelectedChanged;
+
+ public ContextMenuSingleSelectGroup AddItem(string text, T value, Action selected = null, string tooltip = null)
+ {
+ var item = new SingleSelectGroupItem
+ {
+ Text = text,
+ Tooltip = tooltip,
+ Value = value,
+ Selected = selected,
+ Buttons = new List()
+ };
+ _items.Add(item);
+ foreach (var contextMenu in _menus)
+ AddItemToContextMenu(contextMenu, item);
+ return this;
+ }
+
+ public ContextMenuSingleSelectGroup AddItemsToContextMenu(ContextMenu contextMenu)
+ {
+ _menus.Add(contextMenu);
+ for (int i = 0; i < _items.Count; i++)
+ AddItemToContextMenu(contextMenu, _items[i]);
+ return this;
+ }
+
+ private void AddItemToContextMenu(ContextMenu contextMenu, SingleSelectGroupItem item)
+ {
+ var btn = contextMenu.AddButton(item.Text, () => { SetSelected(item); });
+ if (item.Tooltip != null)
+ btn.TooltipText = item.Tooltip;
+ item.Buttons.Add(btn);
+ if (item.Equals(_selectedItem))
+ btn.Checked = true;
+ }
+
+ private void SetSelected(SingleSelectGroupItem item)
+ {
+ foreach (var e in _items)
+ {
+ foreach (var btn in e.Buttons)
+ btn.Checked = false;
+ }
+ _selectedItem = item;
+
+ SelectedChanged?.Invoke(item.Value);
+ item.Selected?.Invoke();
+
+ foreach (var btn in item.Buttons)
+ btn.Checked = true;
+ }
+ }
+}
diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs
index ae470235d..42d236991 100644
--- a/Source/Editor/GUI/ItemsListContextMenu.cs
+++ b/Source/Editor/GUI/ItemsListContextMenu.cs
@@ -265,6 +265,35 @@ namespace FlaxEditor.GUI
_searchBox.Focus();
}
+ ///
+ /// Scrolls the scroll panel to a specific Item
+ ///
+ /// The item to scroll to.
+ public void ScrollViewTo(Item item)
+ {
+ _scrollPanel.ScrollViewTo(item, true);
+ }
+
+ ///
+ /// Scrolls to the item and focuses it by name.
+ ///
+ /// The item name.
+ public void ScrollToAndHighlightItemByName(string itemName)
+ {
+ foreach (var child in ItemsPanel.Children)
+ {
+ if (child is not ItemsListContextMenu.Item item)
+ continue;
+ if (string.Equals(item.Name, itemName, StringComparison.Ordinal))
+ {
+ // Highlight and scroll to item
+ item.Focus();
+ ScrollViewTo(item);
+ break;
+ }
+ }
+ }
+
///
/// Sorts the items list (by item name by default).
///
diff --git a/Source/Editor/GUI/ToolStrip.cs b/Source/Editor/GUI/ToolStrip.cs
index 6a881281f..0dac241ed 100644
--- a/Source/Editor/GUI/ToolStrip.cs
+++ b/Source/Editor/GUI/ToolStrip.cs
@@ -23,10 +23,15 @@ namespace FlaxEditor.GUI
public const int DefaultMarginH = 2;
///
- /// Event fired when button gets clicked.
+ /// Event fired when button gets clicked with the primary mouse button.
///
public Action ButtonClicked;
+ ///
+ /// Event fired when button gets clicked with the secondary mouse button.
+ ///
+ public Action SecondaryButtonClicked;
+
///
/// Tries to get the last button.
///
@@ -146,6 +151,11 @@ namespace FlaxEditor.GUI
ButtonClicked?.Invoke(button);
}
+ internal void OnSecondaryButtonClicked(ToolStripButton button)
+ {
+ SecondaryButtonClicked?.Invoke(button);
+ }
+
///
protected override void PerformLayoutBeforeChildren()
{
diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs
index 09a26b940..e839a7356 100644
--- a/Source/Editor/GUI/ToolStripButton.cs
+++ b/Source/Editor/GUI/ToolStripButton.cs
@@ -20,13 +20,19 @@ namespace FlaxEditor.GUI
private SpriteHandle _icon;
private string _text;
- private bool _mouseDown;
+ private bool _primaryMouseDown;
+ private bool _secondaryMouseDown;
///
/// Event fired when user clicks the button.
///
public Action Clicked;
+ ///
+ /// Event fired when user clicks the button.
+ ///
+ public Action SecondaryClicked;
+
///
/// The checked state.
///
@@ -63,6 +69,11 @@ namespace FlaxEditor.GUI
}
}
+ ///
+ /// A reference to a context menu to raise when the secondary mouse button is pressed.
+ ///
+ public ContextMenu.ContextMenu ContextMenu;
+
///
/// Initializes a new instance of the class.
///
@@ -108,10 +119,11 @@ namespace FlaxEditor.GUI
var iconRect = new Rectangle(DefaultMargin, DefaultMargin, iconSize, iconSize);
var textRect = new Rectangle(DefaultMargin, 0, 0, Height);
bool enabled = EnabledInHierarchy;
+ bool mouseButtonDown = _primaryMouseDown || _secondaryMouseDown;
// Draw background
if (enabled && (IsMouseOver || IsNavFocused || Checked))
- Render2D.FillRectangle(clientRect, Checked ? style.BackgroundSelected : _mouseDown ? style.BackgroundHighlighted : (style.LightBackground * 1.3f));
+ Render2D.FillRectangle(clientRect, Checked ? style.BackgroundSelected : mouseButtonDown ? style.BackgroundHighlighted : (style.LightBackground * 1.3f));
// Draw icon
if (_icon.IsValid)
@@ -124,13 +136,7 @@ namespace FlaxEditor.GUI
if (!string.IsNullOrEmpty(_text))
{
textRect.Size.X = Width - DefaultMargin - textRect.Left;
- Render2D.DrawText(
- style.FontMedium,
- _text,
- textRect,
- enabled ? style.Foreground : style.ForegroundDisabled,
- TextAlignment.Near,
- TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
}
}
@@ -155,9 +161,13 @@ namespace FlaxEditor.GUI
{
if (button == MouseButton.Left)
{
- // Set flag
- _mouseDown = true;
-
+ _primaryMouseDown = true;
+ Focus();
+ return true;
+ }
+ if (button == MouseButton.Right)
+ {
+ _secondaryMouseDown = true;
Focus();
return true;
}
@@ -168,12 +178,9 @@ namespace FlaxEditor.GUI
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
- if (button == MouseButton.Left && _mouseDown)
+ if (button == MouseButton.Left && _primaryMouseDown)
{
- // Clear flag
- _mouseDown = false;
-
- // Fire events
+ _primaryMouseDown = false;
if (AutoCheck)
Checked = !Checked;
Clicked?.Invoke();
@@ -181,6 +188,14 @@ namespace FlaxEditor.GUI
return true;
}
+ if (button == MouseButton.Right && _secondaryMouseDown)
+ {
+ _secondaryMouseDown = false;
+ SecondaryClicked?.Invoke();
+ (Parent as ToolStrip)?.OnSecondaryButtonClicked(this);
+ ContextMenu?.Show(this, new Float2(0, Height));
+ return true;
+ }
return base.OnMouseUp(location, button);
}
@@ -188,8 +203,8 @@ namespace FlaxEditor.GUI
///
public override void OnMouseLeave()
{
- // Clear flag
- _mouseDown = false;
+ _primaryMouseDown = false;
+ _secondaryMouseDown = false;
base.OnMouseLeave();
}
@@ -197,8 +212,8 @@ namespace FlaxEditor.GUI
///
public override void OnLostFocus()
{
- // Clear flag
- _mouseDown = false;
+ _primaryMouseDown = false;
+ _secondaryMouseDown = false;
base.OnLostFocus();
}
diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs
index 8b4f49986..3174d23c1 100644
--- a/Source/Editor/Gizmo/GridGizmo.cs
+++ b/Source/Editor/Gizmo/GridGizmo.cs
@@ -46,20 +46,19 @@ namespace FlaxEditor.Gizmo
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
- float space, size;
+ float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size;
if (dst <= 500.0f)
{
- space = 50;
size = 8000;
}
else if (dst <= 2000.0f)
{
- space = 100;
+ space *= 2;
size = 8000;
}
else
{
- space = 1000;
+ space *= 20;
size = 100000;
}
diff --git a/Source/Editor/Gizmo/SelectionOutline.cs b/Source/Editor/Gizmo/SelectionOutline.cs
index ccc4bdd27..1374c7247 100644
--- a/Source/Editor/Gizmo/SelectionOutline.cs
+++ b/Source/Editor/Gizmo/SelectionOutline.cs
@@ -178,6 +178,8 @@ namespace FlaxEditor.Gizmo
if (selection[i] is ActorNode actorNode && actorNode.Actor != null)
CollectActors(actorNode.Actor);
}
+ if (_actors.Count == 0)
+ return;
// Render selected objects depth
Renderer.DrawSceneDepth(context, task, customDepth, _actors);
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index d17913dfd..dd5cc6c76 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -469,7 +469,7 @@ namespace FlaxEditor.Gizmo
}
// Apply transformation (but to the parents, not whole selection pool)
- if (anyValid || (!_isTransforming && Owner.UseDuplicate))
+ if (anyValid || (_isTransforming && Owner.UseDuplicate))
{
StartTransforming();
LastDelta = new Transform(translationDelta, rotationDelta, scaleDelta);
diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs
index ed422e991..7b3ffebb3 100644
--- a/Source/Editor/Modules/PrefabsModule.cs
+++ b/Source/Editor/Modules/PrefabsModule.cs
@@ -37,6 +37,11 @@ namespace FlaxEditor.Modules
///
public event Action PrefabApplied;
+ ///
+ /// Locally cached actor for prefab creation.
+ ///
+ private Actor _prefabCreationActor;
+
internal PrefabsModule(Editor editor)
: base(editor)
{
@@ -78,6 +83,16 @@ namespace FlaxEditor.Modules
///
/// The root prefab actor.
public void CreatePrefab(Actor actor)
+ {
+ CreatePrefab(actor, true);
+ }
+
+ ///
+ /// Starts the creating prefab for the given actor by showing the new item creation dialog in .
+ ///
+ /// The root prefab actor.
+ /// Allow renaming or not
+ public void CreatePrefab(Actor actor, bool rename)
{
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditContent)
@@ -90,7 +105,8 @@ namespace FlaxEditor.Modules
PrefabCreating?.Invoke(actor);
var proxy = Editor.ContentDatabase.GetProxy();
- Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name);
+ _prefabCreationActor = actor;
+ Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename);
}
private void OnPrefabCreated(ContentItem contentItem)
@@ -107,25 +123,21 @@ namespace FlaxEditor.Modules
// Record undo for prefab creating (backend links the target instance with the prefab)
if (Editor.Undo.Enabled)
{
- var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode).ToList().BuildNodesParents();
- if (selection.Count == 0)
+ if (!_prefabCreationActor)
return;
- if (selection.Count == 1)
+ var actorsList = new List();
+ Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor);
+
+ var actions = new IUndoAction[actorsList.Count];
+ for (int i = 0; i < actorsList.Count; i++)
{
- var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[0]).Actor);
- Undo.AddAction(action);
- }
- else
- {
- var actions = new IUndoAction[selection.Count];
- for (int i = 0; i < selection.Count; i++)
- {
- var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[i]).Actor);
- actions[i] = action;
- }
- Undo.AddAction(new MultiUndoAction(actions));
+ var action = BreakPrefabLinkAction.Linked(actorsList[i]);
+ actions[i] = action;
}
+ Undo.AddAction(new MultiUndoAction(actions));
+
+ _prefabCreationActor = null;
}
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 858ef2b1a..38c61942a 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -20,6 +20,8 @@ using FlaxEngine.GUI;
using FlaxEngine.Json;
using DockHintWindow = FlaxEditor.GUI.Docking.DockHintWindow;
using MasterDockPanel = FlaxEditor.GUI.Docking.MasterDockPanel;
+using FlaxEditor.Content.Settings;
+using FlaxEditor.Options;
namespace FlaxEditor.Modules
{
@@ -36,6 +38,9 @@ namespace FlaxEditor.Modules
private ContentStats _contentStats;
private bool _progressFailed;
+ ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
+ private Scene[] _scenesToReload;
+
private ContextMenuButton _menuFileSaveScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -54,7 +59,9 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuSceneAlignViewportWithActor;
private ContextMenuButton _menuScenePilotActor;
private ContextMenuButton _menuSceneCreateTerrain;
- private ContextMenuButton _menuGamePlay;
+ private ContextMenuButton _menuGamePlayGame;
+ private ContextMenuButton _menuGamePlayCurrentScenes;
+ private ContextMenuButton _menuGameStop;
private ContextMenuButton _menuGamePause;
private ContextMenuButton _menuToolsBuildScenes;
private ContextMenuButton _menuToolsBakeLightmaps;
@@ -74,6 +81,7 @@ namespace FlaxEditor.Modules
private ToolStripButton _toolStripRotate;
private ToolStripButton _toolStripScale;
private ToolStripButton _toolStripBuildScenes;
+ private ToolStripButton _toolStripCook;
private ToolStripButton _toolStripPlay;
private ToolStripButton _toolStripPause;
private ToolStripButton _toolStripStep;
@@ -195,6 +203,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
//
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
+ _toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
//
var play = _toolStripPlay;
var pause = _toolStripPause;
@@ -351,6 +360,7 @@ namespace FlaxEditor.Modules
// Update window background
mainWindow.BackgroundColor = Style.Current.Background;
+ InitSharedMenus();
InitMainMenu(mainWindow);
InitToolstrip(mainWindow);
InitStatusBar(mainWindow);
@@ -409,6 +419,7 @@ namespace FlaxEditor.Modules
Editor.Undo.UndoDone += OnUndoEvent;
Editor.Undo.RedoDone += OnUndoEvent;
Editor.Undo.ActionDone += OnUndoEvent;
+ GameCooker.Event += OnGameCookerEvent;
UpdateToolstrip();
}
@@ -424,6 +435,11 @@ namespace FlaxEditor.Modules
UpdateStatusBar();
}
+ private void OnGameCookerEvent(GameCooker.EventType type)
+ {
+ UpdateToolstrip();
+ }
+
///
public override void OnExit()
{
@@ -466,6 +482,22 @@ namespace FlaxEditor.Modules
return dialog;
}
+ private void InitSharedMenus()
+ {
+ for (int i = 1; i <= 4; i++)
+ _numberOfClientsGroup.AddItem(i.ToString(), i);
+
+ _numberOfClientsGroup.Selected = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
+ _numberOfClientsGroup.SelectedChanged = value =>
+ {
+ var options = Editor.Options.Options;
+ options.Interface.NumberOfGameClientsToLaunch = value;
+ Editor.Options.Apply(options);
+ };
+
+ Editor.Options.OptionsChanged += options => { _numberOfClientsGroup.Selected = options.Interface.NumberOfGameClientsToLaunch; };
+ }
+
private void InitMainMenu(RootControl mainWindow)
{
MainMenu = new MainMenu(mainWindow)
@@ -523,11 +555,19 @@ namespace FlaxEditor.Modules
MenuGame = MainMenu.AddButton("Game");
cm = MenuGame.ContextMenu;
cm.VisibleChanged += OnMenuGameShowHide;
- _menuGamePlay = cm.AddButton("Play", inputOptions.Play.ToString(), Editor.Simulation.RequestStartPlay);
+
+ _menuGamePlayGame = cm.AddButton("Play Game", PlayGame);
+ _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.Play.ToString(), PlayScenes);
+ _menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay);
_menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay);
+
cm.AddSeparator();
- cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
- cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
+ var numberOfClientsMenu = cm.AddChildMenu("Number of game clients");
+ _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
+
+ cm.AddSeparator();
+ cm.AddButton("Cook & Run", CookAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
+ cm.AddButton("Run cooked game", RunCookedGame).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
// Tools
MenuTools = MainMenu.AddButton("Tools");
@@ -603,7 +643,7 @@ namespace FlaxEditor.Modules
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
- _menuGamePlay.ShortKeys = inputOptions.Play.ToString();
+ _menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString();
_menuGamePause.ShortKeys = inputOptions.Pause.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
@@ -611,6 +651,8 @@ namespace FlaxEditor.Modules
private void InitToolstrip(RootControl mainWindow)
{
+ var inputOptions = Editor.Options.Options.Input;
+
ToolStrip = new ToolStrip(34.0f, MainMenu.Bottom)
{
Parent = mainWindow,
@@ -625,10 +667,33 @@ namespace FlaxEditor.Modules
_toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
ToolStrip.AddSeparator();
+
+ // Cook scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
+
+ // Cook and run
+ _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, CookAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
+ _toolStripCook.ContextMenu = new ContextMenu();
+ _toolStripCook.ContextMenu.AddButton("Run cooked game", RunCookedGame);
+ _toolStripCook.ContextMenu.AddSeparator();
+ var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
+ _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
+
ToolStrip.AddSeparator();
- _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)");
- _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)");
+
+ // Play
+ _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, OnPlayPressed).LinkTooltip("Play Game");
+ _toolStripPlay.ContextMenu = new ContextMenu();
+ var playSubMenu = _toolStripPlay.ContextMenu.AddChildMenu("Play button action");
+ var playActionGroup = new ContextMenuSingleSelectGroup();
+ playActionGroup.AddItem("Play Game", InterfaceOptions.PlayAction.PlayGame, null, "Launches the game from the First Scene defined in the project settings.");
+ playActionGroup.AddItem("Play Scenes", InterfaceOptions.PlayAction.PlayScenes, null, "Launches the game using the scenes currently loaded in the editor.");
+ playActionGroup.AddItemsToContextMenu(playSubMenu.ContextMenu);
+ playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
+ playActionGroup.SelectedChanged = SetPlayAction;
+ Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
+
+ _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
UpdateToolstrip();
@@ -663,10 +728,7 @@ namespace FlaxEditor.Modules
var defaultTextColor = StatusBar.TextColor;
_outputLogButton.HoverBegin += () => StatusBar.TextColor = Style.Current.BackgroundSelected;
_outputLogButton.HoverEnd += () => StatusBar.TextColor = defaultTextColor;
- _outputLogButton.Clicked += () =>
- {
- Editor.Windows.OutputLogWin.FocusOrShow();
- };
+ _outputLogButton.Clicked += () => { Editor.Windows.OutputLogWin.FocusOrShow(); };
// Progress bar with label
const float progressBarWidth = 120.0f;
@@ -786,7 +848,9 @@ namespace FlaxEditor.Modules
var isPlayMode = Editor.StateMachine.IsPlayMode;
var canPlay = Level.IsAnySceneLoaded;
- _menuGamePlay.Enabled = !isPlayMode && canPlay;
+ _menuGamePlayGame.Enabled = !isPlayMode && canPlay;
+ _menuGamePlayCurrentScenes.Enabled = !isPlayMode && canPlay;
+ _menuGameStop.Enabled = isPlayMode && canPlay;
_menuGamePause.Enabled = isPlayMode && canPlay;
c.PerformLayout();
@@ -968,6 +1032,72 @@ namespace FlaxEditor.Modules
projectInfo.Save();
}
+ private void SetPlayAction(InterfaceOptions.PlayAction newPlayAction)
+ {
+ var options = Editor.Options.Options;
+ options.Interface.PlayButtonAction = newPlayAction;
+ Editor.Options.Apply(options);
+ }
+
+ private void OnPlayPressed()
+ {
+ switch (Editor.Options.Options.Interface.PlayButtonAction)
+ {
+ case InterfaceOptions.PlayAction.PlayGame:
+ if (Editor.IsPlayMode)
+ Editor.Simulation.RequestStopPlay();
+ else
+ PlayGame();
+ return;
+ case InterfaceOptions.PlayAction.PlayScenes:
+ PlayScenes();
+ return;
+ }
+ }
+
+ private void PlayGame()
+ {
+ var firstScene = GameSettings.Load().FirstScene;
+ if (firstScene == Guid.Empty)
+ {
+ if (Level.IsAnySceneLoaded)
+ Editor.Simulation.RequestStartPlay();
+ return;
+ }
+
+ _scenesToReload = Level.Scenes;
+ Level.UnloadAllScenes();
+ Level.LoadScene(firstScene);
+
+ Editor.PlayModeEnd += OnPlayGameSceneEnding;
+ Editor.Simulation.RequestPlayOrStopPlay();
+ }
+
+ private void OnPlayGameSceneEnding()
+ {
+ Editor.PlayModeEnd -= OnPlayGameSceneEnding;
+
+ Level.UnloadAllScenes();
+
+ foreach (var scene in _scenesToReload)
+ Level.LoadScene(scene.ID);
+ }
+
+ private void PlayScenes()
+ {
+ Editor.Simulation.RequestPlayOrStopPlay();
+ }
+
+ private void CookAndRun()
+ {
+ Editor.Windows.GameCookerWin.BuildAndRun();
+ }
+
+ private void RunCookedGame()
+ {
+ Editor.Windows.GameCookerWin.RunCooked();
+ }
+
private void OnMainWindowClosing()
{
// Clear UI references (GUI cannot be used after window closing)
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index acfb7f5e8..5fd8c31c7 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -74,6 +74,22 @@ namespace FlaxEditor.Options
DockBottom = DockState.DockBottom
}
+ ///
+ /// Options for the action taken by the play button.
+ ///
+ public enum PlayAction
+ {
+ ///
+ /// Launches the game from the First Scene defined in the project settings.
+ ///
+ PlayGame,
+
+ ///
+ /// Launches the game using the scenes currently loaded in the editor.
+ ///
+ PlayScenes,
+ }
+
///
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
///
@@ -82,14 +98,12 @@ namespace FlaxEditor.Options
public float InterfaceScale { get; set; } = 1.0f;
#if PLATFORM_WINDOWS
-
///
/// Gets or sets a value indicating whether use native window title bar. Editor restart required.
///
[DefaultValue(false)]
[EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")]
public bool UseNativeWindowSystem { get; set; } = false;
-
#endif
///
@@ -196,30 +210,44 @@ namespace FlaxEditor.Options
[EditorDisplay("Play In-Editor", "Focus Game Window On Play"), EditorOrder(400), Tooltip("Determines whether auto-focus game window on play mode start.")]
public bool FocusGameWinOnPlay { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating what action should be taken upon pressing the play button.
+ ///
+ [DefaultValue(PlayAction.PlayScenes)]
+ [EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
+ public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes;
+
+ ///
+ /// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
+ ///
+ [DefaultValue(1), Range(1, 4)]
+ [EditorDisplay("Cook & Run"), EditorOrder(500)]
+ public int NumberOfGameClientsToLaunch = 1;
+
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
///
/// Gets or sets the title font for editor UI.
///
- [EditorDisplay("Fonts"), EditorOrder(500), Tooltip("The title font for editor UI.")]
+ [EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18);
///
/// Gets or sets the large font for editor UI.
///
- [EditorDisplay("Fonts"), EditorOrder(510), Tooltip("The large font for editor UI.")]
+ [EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14);
///
/// Gets or sets the medium font for editor UI.
///
- [EditorDisplay("Fonts"), EditorOrder(520), Tooltip("The medium font for editor UI.")]
+ [EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9);
///
/// Gets or sets the small font for editor UI.
///
- [EditorDisplay("Fonts"), EditorOrder(530), Tooltip("The small font for editor UI.")]
+ [EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9);
}
}
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index 495aa85d0..cee63a562 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -59,5 +59,12 @@ namespace FlaxEditor.Options
[DefaultValue(false)]
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
public bool DefaultInvertPanning { get; set; } = false;
+
+ ///
+ /// Scales editor viewport grid.
+ ///
+ [DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
+ public float ViewportGridScale { get; set; } = 50.0f;
}
}
diff --git a/Source/Editor/Tools/Foliage/FoliageTools.cpp b/Source/Editor/Tools/Foliage/FoliageTools.cpp
index b24bcdf00..7d0de7d41 100644
--- a/Source/Editor/Tools/Foliage/FoliageTools.cpp
+++ b/Source/Editor/Tools/Foliage/FoliageTools.cpp
@@ -292,7 +292,6 @@ void FoliageTools::Paint(Foliage* foliage, Span foliageTypesIndices, cons
{
PROFILE_CPU_NAMED("Place Instances");
- Matrix matrix;
FoliageInstance instance;
Quaternion tmp;
Matrix world;
diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index b02f63f87..49b03ede1 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -80,6 +80,7 @@ namespace FlaxEditor.Windows
public LogGroup Group;
public LogEntryDescription Desc;
public SpriteHandle Icon;
+ public int LogCount = 1;
public LogEntry(DebugLogWindow window, ref LogEntryDescription desc)
: base(0, 0, 120, DefaultHeight)
@@ -137,7 +138,14 @@ namespace FlaxEditor.Windows
// Title
var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10);
Render2D.PushClip(ref clientRect);
- Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
+ if (LogCount == 1)
+ {
+ Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
+ }
+ else if (LogCount > 1)
+ {
+ Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground);
+ }
Render2D.PopClip();
}
@@ -289,6 +297,7 @@ namespace FlaxEditor.Windows
private readonly List _pendingEntries = new List(32);
private readonly ToolStripButton _clearOnPlayButton;
+ private readonly ToolStripButton _collapseLogsButton;
private readonly ToolStripButton _pauseOnErrorButton;
private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3];
@@ -316,6 +325,7 @@ namespace FlaxEditor.Windows
};
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode");
+ _collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse").SetAutoCheck(true).SetChecked(true).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages");
@@ -612,6 +622,30 @@ namespace FlaxEditor.Windows
var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top;
for (int i = 0; i < _pendingEntries.Count; i++)
{
+ if (_collapseLogsButton.Checked)
+ {
+ bool logExists = false;
+ foreach (var child in _entriesPanel.Children)
+ {
+ if (child is LogEntry entry)
+ {
+ var pendingEntry = _pendingEntries[i];
+ if (string.Equals(entry.Desc.Title, pendingEntry.Desc.Title, StringComparison.Ordinal) &&
+ string.Equals(entry.Desc.LocationFile, pendingEntry.Desc.LocationFile, StringComparison.Ordinal) &&
+ entry.Desc.Level == pendingEntry.Desc.Level &&
+ string.Equals(entry.Desc.Description, pendingEntry.Desc.Description, StringComparison.Ordinal) &&
+ entry.Desc.LocationLine == pendingEntry.Desc.LocationLine)
+ {
+ entry.LogCount += 1;
+ newEntry = entry;
+ logExists = true;
+ break;
+ }
+ }
+ }
+ if (logExists)
+ continue;
+ }
newEntry = _pendingEntries[i];
newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked;
anyVisible |= newEntry.Visible;
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index f3fd753d1..5ece067a0 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -238,6 +238,7 @@ namespace FlaxEditor.Windows
{
[EditorDisplay(null, "arm64")]
ARM64,
+
[EditorDisplay(null, "x64")]
x64,
}
@@ -614,6 +615,18 @@ namespace FlaxEditor.Windows
_exitOnBuildEnd = true;
}
+ ///
+ /// Returns true if can build for the given platform (both supported and available).
+ ///
+ /// The platform.
+ /// True if can build, otherwise false.
+ public bool CanBuild(PlatformType platformType)
+ {
+ if (_buildTabProxy.PerPlatformOptions.TryGetValue(platformType, out var platform))
+ return platform.IsAvailable && platform.IsSupported;
+ return false;
+ }
+
///
/// Builds all the targets from the given preset.
///
@@ -659,16 +672,24 @@ namespace FlaxEditor.Windows
{
Editor.Log("Building and running");
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
- _buildingQueue.Enqueue(new QueueItem
+ var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
+ for (int i = 0; i < numberOfClients; i++)
{
- Target = new BuildTarget
+ var buildOptions = BuildOptions.AutoRun;
+ if (i > 0)
+ buildOptions |= BuildOptions.NoCook;
+
+ _buildingQueue.Enqueue(new QueueItem
{
- Output = _buildTabProxy.PerPlatformOptions[platform].Output,
- Platform = buildPlatform,
- Mode = buildConfiguration,
- },
- Options = BuildOptions.AutoRun,
- });
+ Target = new BuildTarget
+ {
+ Output = _buildTabProxy.PerPlatformOptions[platform].Output,
+ Platform = buildPlatform,
+ Mode = buildConfiguration,
+ },
+ Options = buildOptions,
+ });
+ }
}
///
@@ -678,16 +699,20 @@ namespace FlaxEditor.Windows
{
Editor.Log("Running cooked build");
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
- _buildingQueue.Enqueue(new QueueItem
+ var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
+ for (int i = 0; i < numberOfClients; i++)
{
- Target = new BuildTarget
+ _buildingQueue.Enqueue(new QueueItem
{
- Output = _buildTabProxy.PerPlatformOptions[platform].Output,
- Platform = buildPlatform,
- Mode = buildConfiguration,
- },
- Options = BuildOptions.AutoRun | BuildOptions.NoCook,
- });
+ Target = new BuildTarget
+ {
+ Output = _buildTabProxy.PerPlatformOptions[platform].Output,
+ Platform = buildPlatform,
+ Mode = buildConfiguration,
+ },
+ Options = BuildOptions.AutoRun | BuildOptions.NoCook,
+ });
+ }
}
private void BuildTarget()
diff --git a/Source/Engine/Animations/AnimationGraph.cs b/Source/Engine/Animations/AnimationGraph.cs
index 46b3acfc1..2e26b8370 100644
--- a/Source/Engine/Animations/AnimationGraph.cs
+++ b/Source/Engine/Animations/AnimationGraph.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
@@ -94,9 +93,11 @@ namespace FlaxEngine
public AnimatedModel Instance;
}
+ [HideInEditor]
[CustomMarshaller(typeof(Context), MarshalMode.Default, typeof(ContextMarshaller))]
internal static class ContextMarshaller
{
+ [HideInEditor]
[StructLayout(LayoutKind.Sequential)]
public struct ContextNative
{
@@ -116,7 +117,7 @@ namespace FlaxEngine
internal static Context ToManaged(ContextNative managed)
{
- return new Context()
+ return new Context
{
Graph = managed.Graph,
GraphExecutor = managed.GraphExecutor,
@@ -129,9 +130,10 @@ namespace FlaxEngine
Instance = AnimatedModelMarshaller.ConvertToManaged(managed.Instance),
};
}
+
internal static ContextNative ToNative(Context managed)
{
- return new ContextNative()
+ return new ContextNative
{
Graph = managed.Graph,
GraphExecutor = managed.GraphExecutor,
@@ -144,6 +146,7 @@ namespace FlaxEngine
Instance = AnimatedModelMarshaller.ConvertToUnmanaged(managed.Instance),
};
}
+
internal static void Free(ContextNative unmanaged)
{
}
@@ -243,6 +246,12 @@ namespace FlaxEngine
throw new ArgumentNullException(nameof(source));
if (destination == null)
throw new ArgumentNullException(nameof(destination));
+ if (source->NodesCount <= 0 || source->NodesCount > 4096)
+ throw new ArgumentOutOfRangeException(nameof(source));
+ if (destination->NodesCount <= 0 || destination->NodesCount > 4096)
+ throw new ArgumentOutOfRangeException(nameof(destination));
+ if (source->NodesCount != destination->NodesCount)
+ throw new ArgumentOutOfRangeException();
destination->NodesCount = source->NodesCount;
destination->Unused = source->Unused;
Utils.MemoryCopy(new IntPtr(destination->Nodes), new IntPtr(source->Nodes), (ulong)(source->NodesCount * sizeof(Transform)));
diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
index d74c523f1..cdd5b03eb 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
+++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
@@ -5,7 +5,6 @@
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
-#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
@@ -189,12 +188,8 @@ bool AnimGraph::InitCustomNode(Node* node)
return false;
}
- // Create node values managed array
+ // Initialization can happen on Content Thread so ensure to have runtime attached
MCore::Thread::Attach();
- MArray* values = MCore::Array::New( MCore::TypeCache::Object, node->Values.Count());
- MObject** valuesPtr = MCore::Array::GetAddress(values);
- for (int32 i = 0; i < node->Values.Count(); i++)
- valuesPtr[i] = MUtils::BoxVariant(node->Values[i]);
// Allocate managed node object (create GC handle to prevent destruction)
MObject* obj = type->CreateInstance();
@@ -202,7 +197,7 @@ bool AnimGraph::InitCustomNode(Node* node)
// Initialize node
InternalInitData initData;
- initData.Values = values;
+ initData.Values = MUtils::ToArray(node->Values, MCore::TypeCache::Object);
initData.BaseModel = BaseModel.GetManagedInstance();
void* params[1];
params[0] = &initData;
@@ -211,7 +206,6 @@ bool AnimGraph::InitCustomNode(Node* node)
if (exception)
{
MCore::GCHandle::Free(handleGC);
-
MException ex(exception);
ex.Log(LogType::Warning, TEXT("AnimGraph"));
return false;
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 31b97fb52..82842376e 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -235,8 +235,9 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const float nestedAnimLength = nestedAnim.Anim->GetLength();
const float nestedAnimDuration = nestedAnim.Anim->GetDuration();
const float nestedAnimSpeed = nestedAnim.Speed * speed;
- nestedAnimPos = nestedAnimPos / nestedAnimDuration * nestedAnimSpeed;
- nestedAnimPrevPos = nestedAnimPrevPos / nestedAnimDuration * nestedAnimSpeed;
+ const float frameRateMatchScale = (float)nestedAnim.Anim->Data.FramesPerSecond / (float)anim->Data.FramesPerSecond;
+ nestedAnimPos = nestedAnimPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale;
+ nestedAnimPrevPos = nestedAnimPrevPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
diff --git a/Source/Engine/Content/Assets/MaterialBase.h b/Source/Engine/Content/Assets/MaterialBase.h
index bca70b6da..070347348 100644
--- a/Source/Engine/Content/Assets/MaterialBase.h
+++ b/Source/Engine/Content/Assets/MaterialBase.h
@@ -27,7 +27,6 @@ public:
///
/// Returns true if material is an material instance.
///
- /// True if it's a material instance, otherwise false.
virtual bool IsMaterialInstance() const = 0;
public:
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index 52d7c34b5..d530e5456 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -205,9 +205,6 @@ FlaxStorage::~FlaxStorage()
{
// Validate if has been disposed
ASSERT(IsDisposed());
-
- // Validate other fields
- // Note: disposed storage has no open files
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
ASSERT(_chunks.IsEmpty());
@@ -216,7 +213,6 @@ FlaxStorage::~FlaxStorage()
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
_file.DeleteAll();
#endif
-
}
FlaxStorage::LockData FlaxStorage::LockSafe()
@@ -550,11 +546,9 @@ bool FlaxStorage::Load()
}
break;
default:
- {
LOG(Warning, "Unsupported storage format version: {1}. {0}", ToString(), _version);
return true;
}
- }
// Mark as loaded (version number describes 'isLoaded' state)
_version = version;
@@ -573,7 +567,7 @@ bool FlaxStorage::Reload()
// Perform clean reloading
Dispose();
- bool failed = Load();
+ const bool failed = Load();
OnReloaded(this, failed);
@@ -1434,10 +1428,8 @@ void FlaxFile::GetEntries(Array& output) const
void FlaxFile::Dispose()
{
- // Base
FlaxStorage::Dispose();
- // Clean
_asset.ID = Guid::Empty;
}
@@ -1482,7 +1474,7 @@ bool FlaxPackage::HasAsset(const Guid& id) const
bool FlaxPackage::HasAsset(const AssetInfo& info) const
{
ASSERT(_path == info.Path);
- Entry* e = _entries.TryGet(info.ID);
+ const Entry* e = _entries.TryGet(info.ID);
return e && e->TypeName == info.TypeName;
}
@@ -1511,10 +1503,8 @@ void FlaxPackage::GetEntries(Array& output) const
void FlaxPackage::Dispose()
{
- // Base
FlaxStorage::Dispose();
- // Clean
_entries.Clear();
}
diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h
index 0ee4fc1a9..d06ef0b3f 100644
--- a/Source/Engine/Core/Math/BoundingBox.h
+++ b/Source/Engine/Core/Math/BoundingBox.h
@@ -38,9 +38,7 @@ public:
///
/// Empty constructor.
///
- BoundingBox()
- {
- }
+ BoundingBox() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h
index 287d33c2f..1502b6302 100644
--- a/Source/Engine/Core/Math/BoundingFrustum.h
+++ b/Source/Engine/Core/Math/BoundingFrustum.h
@@ -34,9 +34,7 @@ public:
///
/// Empty constructor.
///
- BoundingFrustum()
- {
- }
+ BoundingFrustum() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/BoundingSphere.h b/Source/Engine/Core/Math/BoundingSphere.h
index 08f5df7f1..da303e8d5 100644
--- a/Source/Engine/Core/Math/BoundingSphere.h
+++ b/Source/Engine/Core/Math/BoundingSphere.h
@@ -34,9 +34,7 @@ public:
///
/// Empty constructor.
///
- BoundingSphere()
- {
- }
+ BoundingSphere() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h
index 997ed9c17..af93fc0f2 100644
--- a/Source/Engine/Core/Math/Color.h
+++ b/Source/Engine/Core/Math/Color.h
@@ -50,9 +50,7 @@ public:
///
/// Empty constructor.
///
- Color()
- {
- }
+ Color() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h
index 36da3d41d..6c2b9648d 100644
--- a/Source/Engine/Core/Math/Color32.h
+++ b/Source/Engine/Core/Math/Color32.h
@@ -53,9 +53,7 @@ public:
///
/// Empty constructor.
///
- Color32()
- {
- }
+ Color32() = default;
///
/// Constructs a new Color32 with given r, g, b, a components.
diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h
index 06b03895d..346b5d6b3 100644
--- a/Source/Engine/Core/Math/Half.h
+++ b/Source/Engine/Core/Math/Half.h
@@ -121,9 +121,7 @@ public:
///
/// Default constructor
///
- Half2()
- {
- }
+ Half2() = default;
///
/// Init
@@ -185,9 +183,7 @@ public:
Half Z;
public:
- Half3()
- {
- }
+ Half3() = default;
Half3(Half x, Half y, Half z)
: X(x)
@@ -242,9 +238,7 @@ public:
Half W;
public:
- Half4()
- {
- }
+ Half4() = default;
Half4(Half x, Half y, Half z, Half w)
: X(x)
diff --git a/Source/Engine/Core/Math/Mathd.cs b/Source/Engine/Core/Math/Mathd.cs
index 29bcc9420..48db547df 100644
--- a/Source/Engine/Core/Math/Mathd.cs
+++ b/Source/Engine/Core/Math/Mathd.cs
@@ -876,38 +876,54 @@ namespace FlaxEngine
///
/// Given a heading which may be outside the +/- PI range, 'unwind' it back into that range.
///
+ /// Optimized version of that is it faster and has fixed cost but with large angle values (100 for example) starts to lose accuracy floating point problem.
/// Angle in radians to unwind.
/// Valid angle in radians.
public static double UnwindRadians(double angle)
{
- // TODO: make it faster?
+ var a = angle - Math.Floor(angle / TwoPi) * TwoPi; // Loop function between 0 and TwoPi
+ return a > Pi ? a - TwoPi : a; // Change range so it become Pi and -Pi
+ }
+
+ ///
+ /// The same as but is more computation intensive with large and has better accuracy with large .
+ ///
cost of this function is %
+ ///
+ /// Angle in radians to unwind.
+ /// Valid angle in radians.
+ public static double UnwindRadiansAccurate(double angle)
+ {
while (angle > Pi)
- {
angle -= TwoPi;
- }
while (angle < -Pi)
- {
angle += TwoPi;
- }
return angle;
}
///
- /// Utility to ensure angle is between +/- 180 degrees by unwinding
+ /// Utility to ensure angle is between +/- 180 degrees by unwinding.
///
+ /// Optimized version of that is it faster and has fixed cost but with large angle values (100 for example) starts to lose accuracy floating point problem.
/// Angle in degrees to unwind.
/// Valid angle in degrees.
public static double UnwindDegrees(double angle)
{
- // TODO: make it faster?
- while (angle > 180.0f)
- {
- angle -= 360.0f;
- }
- while (angle < -180.0f)
- {
- angle += 360.0f;
- }
+ var a = angle - Math.Floor(angle / 360.0) * 360.0; // Loop function between 0 and 360
+ return a > 180 ? a - 360.0 : a; // Change range so it become 180 and -180
+ }
+
+ ///
+ /// The same as but is more computation intensive with large and has better accuracy with large .
+ ///
cost of this function is % 180.0f
+ ///
+ /// Angle in radians to unwind.
+ /// Valid angle in radians.
+ public static double UnwindDegreesAccurate(double angle)
+ {
+ while (angle > 180.0)
+ angle -= 360.0;
+ while (angle < -180.0)
+ angle += 360.0;
return angle;
}
@@ -927,8 +943,9 @@ namespace FlaxEngine
/// Interpolates between two values using a linear function by a given amount.
///
///
- /// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and
- /// http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
+ /// See:
+ ///
+ ///
///
/// Value to interpolate from.
/// Value to interpolate to.
@@ -944,7 +961,8 @@ namespace FlaxEngine
/// Performs smooth (cubic Hermite) interpolation between 0 and 1.
///
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ /// See:
+ ///
///
/// Value between 0 and 1 indicating interpolation amount.
public static double SmoothStep(double amount)
@@ -956,7 +974,8 @@ namespace FlaxEngine
/// Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints.
///
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ /// See:
+ ///
///
/// Value between 0 and 1 indicating interpolation amount.
public static double SmootherStep(double amount)
@@ -1013,7 +1032,7 @@ namespace FlaxEngine
///
/// Gauss function.
- /// http://en.wikipedia.org/wiki/Gaussian_function#Two-dimensional_Gaussian_function
+ ///
///
/// Curve amplitude.
/// Position X.
diff --git a/Source/Engine/Core/Math/Mathf.cs b/Source/Engine/Core/Math/Mathf.cs
index 9463c13cb..cea81a779 100644
--- a/Source/Engine/Core/Math/Mathf.cs
+++ b/Source/Engine/Core/Math/Mathf.cs
@@ -1176,38 +1176,54 @@ namespace FlaxEngine
///
/// Given a heading which may be outside the +/- PI range, 'unwind' it back into that range.
///
+ /// Optimized version of that is it faster and has fixed cost but with large angle values (100 for example) starts to lose accuracy floating point problem.
/// Angle in radians to unwind.
/// Valid angle in radians.
public static float UnwindRadians(float angle)
{
- // TODO: make it faster?
+ var a = angle - (float)Math.Floor(angle / TwoPi) * TwoPi; // Loop function between 0 and TwoPi
+ return a > Pi ? a - TwoPi : a; // Change range so it become Pi and -Pi
+ }
+
+ ///
+ /// The same as but is more computation intensive with large and has better accuracy with large .
+ ///
cost of this function is %
+ ///
+ /// Angle in radians to unwind.
+ /// Valid angle in radians.
+ public static float UnwindRadiansAccurate(float angle)
+ {
while (angle > Pi)
- {
angle -= TwoPi;
- }
while (angle < -Pi)
- {
angle += TwoPi;
- }
return angle;
}
///
- /// Utility to ensure angle is between +/- 180 degrees by unwinding
+ /// Utility to ensure angle is between +/- 180 degrees by unwinding.
///
+ /// Optimized version of that is it faster and has fixed cost but with large angle values (100 for example) starts to lose accuracy floating point problem.
/// Angle in degrees to unwind.
/// Valid angle in degrees.
public static float UnwindDegrees(float angle)
{
- // TODO: make it faster?
+ var a = angle - (float)Math.Floor(angle / 360.0f) * 360.0f; // Loop function between 0 and 360
+ return a > 180 ? a - 360.0f : a; // Change range so it become 180 and -180
+ }
+
+ ///
+ /// The same as but is more computation intensive with large and has better accuracy with large .
+ ///
cost of this function is % 180.0f
+ ///
+ /// Angle in radians to unwind.
+ /// Valid angle in radians.
+ public static float UnwindDegreesAccurate(float angle)
+ {
while (angle > 180.0f)
- {
angle -= 360.0f;
- }
while (angle < -180.0f)
- {
angle += 360.0f;
- }
return angle;
}
@@ -1299,8 +1315,12 @@ namespace FlaxEngine
///
/// Interpolates between two values using a linear function by a given amount.
///
- /// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
- /// Value to interpolate from.
+ ///
+ /// See:
+ ///
+ ///
+ ///
+ /// /// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
@@ -1312,8 +1332,12 @@ namespace FlaxEngine
///
/// Interpolates between two values using a linear function by a given amount.
///
- /// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
- /// Value to interpolate from.
+ ///
+ /// See:
+ ///
+ ///
+ ///
+ /// /// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
@@ -1325,8 +1349,12 @@ namespace FlaxEngine
///
/// Interpolates between two values using a linear function by a given amount.
///
- /// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
- /// Value to interpolate from.
+ ///
+ /// See:
+ ///
+ ///
+ ///
+ /// /// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
@@ -1338,7 +1366,10 @@ namespace FlaxEngine
///
/// Performs smooth (cubic Hermite) interpolation between 0 and 1.
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ ///
+ /// See:
+ ///
+ ///
/// Value between 0 and 1 indicating interpolation amount.
public static float SmoothStep(float amount)
{
@@ -1348,7 +1379,10 @@ namespace FlaxEngine
///
/// Performs smooth (cubic Hermite) interpolation between 0 and 1.
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ ///
+ /// See:
+ ///
+ ///
/// Value between 0 and 1 indicating interpolation amount.
public static double SmoothStep(double amount)
{
@@ -1358,7 +1392,10 @@ namespace FlaxEngine
///
/// Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints.
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ ///
+ /// See:
+ ///
+ ///
/// Value between 0 and 1 indicating interpolation amount.
public static float SmootherStep(float amount)
{
@@ -1368,7 +1405,10 @@ namespace FlaxEngine
///
/// Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints.
///
- /// See https://en.wikipedia.org/wiki/Smoothstep
+ ///
+ /// See:
+ ///
+ ///
/// Value between 0 and 1 indicating interpolation amount.
public static double SmootherStep(double amount)
{
@@ -1446,7 +1486,7 @@ namespace FlaxEngine
///
/// Gauss function.
- /// http://en.wikipedia.org/wiki/Gaussian_function#Two-dimensional_Gaussian_function
+ ///
///
/// Curve amplitude.
/// Position X.
@@ -1463,7 +1503,7 @@ namespace FlaxEngine
///
/// Gauss function.
- /// http://en.wikipedia.org/wiki/Gaussian_function#Two-dimensional_Gaussian_function
+ ///
///
/// Curve amplitude.
/// Position X.
diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h
index 7d6d57bac..aed2ead8d 100644
--- a/Source/Engine/Core/Math/Matrix.h
+++ b/Source/Engine/Core/Math/Matrix.h
@@ -83,9 +83,7 @@ public:
///
/// Empty constructor.
///
- Matrix()
- {
- }
+ Matrix() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h
index 1e29be85a..344f68455 100644
--- a/Source/Engine/Core/Math/Matrix3x3.h
+++ b/Source/Engine/Core/Math/Matrix3x3.h
@@ -63,9 +63,7 @@ public:
///
/// Empty constructor.
///
- Matrix3x3()
- {
- }
+ Matrix3x3() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Plane.h b/Source/Engine/Core/Math/Plane.h
index 57a2be344..8c3d4129b 100644
--- a/Source/Engine/Core/Math/Plane.h
+++ b/Source/Engine/Core/Math/Plane.h
@@ -30,9 +30,7 @@ public:
///
/// Empty constructor.
///
- Plane()
- {
- }
+ Plane() = default;
///
/// Init
diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h
index 5b164f979..e6425f7ab 100644
--- a/Source/Engine/Core/Math/Quaternion.h
+++ b/Source/Engine/Core/Math/Quaternion.h
@@ -67,9 +67,7 @@ public:
///
/// Empty constructor.
///
- Quaternion()
- {
- }
+ Quaternion() = default;
///
/// Init
diff --git a/Source/Engine/Core/Math/Ray.h b/Source/Engine/Core/Math/Ray.h
index c8486f39e..f2f9081e5 100644
--- a/Source/Engine/Core/Math/Ray.h
+++ b/Source/Engine/Core/Math/Ray.h
@@ -35,9 +35,7 @@ public:
///
/// Empty constructor.
///
- Ray()
- {
- }
+ Ray() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Rectangle.h b/Source/Engine/Core/Math/Rectangle.h
index c59905fd6..bf282b6e3 100644
--- a/Source/Engine/Core/Math/Rectangle.h
+++ b/Source/Engine/Core/Math/Rectangle.h
@@ -31,9 +31,7 @@ public:
///
/// Empty constructor.
///
- Rectangle()
- {
- }
+ Rectangle() = default;
// Init
// @param x Rectangle location X coordinate
diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h
index ef644f08a..1079b4c23 100644
--- a/Source/Engine/Core/Math/Transform.h
+++ b/Source/Engine/Core/Math/Transform.h
@@ -40,9 +40,7 @@ public:
///
/// Empty constructor.
///
- Transform()
- {
- }
+ Transform() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Triangle.h b/Source/Engine/Core/Math/Triangle.h
index a92919cfc..63f0e34ec 100644
--- a/Source/Engine/Core/Math/Triangle.h
+++ b/Source/Engine/Core/Math/Triangle.h
@@ -30,9 +30,7 @@ public:
///
/// Empty constructor.
///
- Triangle()
- {
- }
+ Triangle() = default;
///
/// Initializes a new instance of the struct.
diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h
index 87ced1de6..83deb009a 100644
--- a/Source/Engine/Core/Math/Vector2.h
+++ b/Source/Engine/Core/Math/Vector2.h
@@ -60,9 +60,7 @@ public:
///
/// Empty constructor.
///
- Vector2Base()
- {
- }
+ Vector2Base() = default;
FORCE_INLINE Vector2Base(T xy)
: X(xy)
diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h
index 33be7f2d6..cad5250b7 100644
--- a/Source/Engine/Core/Math/Vector3.h
+++ b/Source/Engine/Core/Math/Vector3.h
@@ -89,9 +89,7 @@ public:
///
/// Empty constructor.
///
- Vector3Base()
- {
- }
+ Vector3Base() = default;
FORCE_INLINE Vector3Base(T xyz)
: X(xyz)
diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h
index 213f05815..5c7b24c4a 100644
--- a/Source/Engine/Core/Math/Vector4.h
+++ b/Source/Engine/Core/Math/Vector4.h
@@ -76,9 +76,7 @@ public:
///
/// Empty constructor.
///
- Vector4Base()
- {
- }
+ Vector4Base() = default;
FORCE_INLINE Vector4Base(T xyzw)
: X(xyzw)
diff --git a/Source/Engine/Core/Math/Viewport.h b/Source/Engine/Core/Math/Viewport.h
index bb4ec8f8b..d478eb7cc 100644
--- a/Source/Engine/Core/Math/Viewport.h
+++ b/Source/Engine/Core/Math/Viewport.h
@@ -52,9 +52,7 @@ public:
///
/// Empty constructor.
///
- Viewport()
- {
- }
+ Viewport() = default;
// Init
// @param x The x coordinate of the upper-left corner of the viewport in pixels
diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h
index e7bea5fbd..e0518ec77 100644
--- a/Source/Engine/Core/Types/Span.h
+++ b/Source/Engine/Core/Types/Span.h
@@ -115,6 +115,12 @@ inline Span ToSpan(const T* ptr, int32 length)
return Span(ptr, length);
}
+template
+inline Span ToSpan(const Array& data)
+{
+ return Span((U*)data.Get(), data.Count());
+}
+
template
inline bool SpanContains(const Span span, const T& value)
{
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index 8232d415f..973e494f1 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -2796,6 +2796,122 @@ String Variant::ToString() const
}
}
+void Variant::Inline()
+{
+ VariantType::Types type = VariantType::Null;
+ byte data[sizeof(Matrix)];
+ if (Type.Type == VariantType::Structure && AsBlob.Data && AsBlob.Length <= sizeof(Matrix))
+ {
+ for (int32 i = 2; i < VariantType::MAX; i++)
+ {
+ if (StringUtils::Compare(Type.TypeName, InBuiltTypesTypeNames[i]) == 0)
+ {
+ type = (VariantType::Types)i;
+ break;
+ }
+ }
+ if (type == VariantType::Null)
+ {
+ // Aliases
+ if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector2") == 0)
+ type = VariantType::Types::Vector2;
+ else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector3") == 0)
+ type = VariantType::Types::Vector3;
+ else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector4") == 0)
+ type = VariantType::Types::Vector4;
+ }
+ if (type != VariantType::Null)
+ Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
+ }
+ if (type != VariantType::Null)
+ {
+ switch (type)
+ {
+ case VariantType::Bool:
+ *this = *(bool*)data;
+ break;
+ case VariantType::Int:
+ *this = *(int32*)data;
+ break;
+ case VariantType::Uint:
+ *this = *(uint32*)data;
+ break;
+ case VariantType::Int64:
+ *this = *(int64*)data;
+ break;
+ case VariantType::Uint64:
+ *this = *(uint64*)data;
+ break;
+ case VariantType::Float:
+ *this = *(float*)data;
+ break;
+ case VariantType::Double:
+ *this = *(double*)data;
+ break;
+ case VariantType::Float2:
+ *this = *(Float2*)data;
+ break;
+ case VariantType::Float3:
+ *this = *(Float3*)data;
+ break;
+ case VariantType::Float4:
+ *this = *(Float4*)data;
+ break;
+ case VariantType::Color:
+ *this = *(Color*)data;
+ break;
+ case VariantType::Guid:
+ *this = *(Guid*)data;
+ break;
+ case VariantType::BoundingBox:
+ *this = Variant(*(BoundingBox*)data);
+ break;
+ case VariantType::BoundingSphere:
+ *this = *(BoundingSphere*)data;
+ break;
+ case VariantType::Quaternion:
+ *this = *(Quaternion*)data;
+ break;
+ case VariantType::Transform:
+ *this = Variant(*(Transform*)data);
+ break;
+ case VariantType::Rectangle:
+ *this = *(Rectangle*)data;
+ break;
+ case VariantType::Ray:
+ *this = Variant(*(Ray*)data);
+ break;
+ case VariantType::Matrix:
+ *this = Variant(*(Matrix*)data);
+ break;
+ case VariantType::Int2:
+ *this = *(Int2*)data;
+ break;
+ case VariantType::Int3:
+ *this = *(Int3*)data;
+ break;
+ case VariantType::Int4:
+ *this = *(Int4*)data;
+ break;
+ case VariantType::Int16:
+ *this = *(int16*)data;
+ break;
+ case VariantType::Uint16:
+ *this = *(uint16*)data;
+ break;
+ case VariantType::Double2:
+ *this = *(Double2*)data;
+ break;
+ case VariantType::Double3:
+ *this = *(Double3*)data;
+ break;
+ case VariantType::Double4:
+ *this = *(Double4*)data;
+ break;
+ }
+ }
+}
+
bool Variant::CanCast(const Variant& v, const VariantType& to)
{
if (v.Type == to)
@@ -3682,6 +3798,7 @@ void Variant::AllocStructure()
const ScriptingType& type = typeHandle.GetType();
AsBlob.Length = type.Size;
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
+ Platform::MemoryClear(AsBlob.Data, AsBlob.Length);
type.Struct.Ctor(AsBlob.Data);
}
else if (typeName == "System.Int16" || typeName == "System.UInt16")
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index 7745307c3..9776c8e0b 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -359,6 +359,9 @@ public:
void SetAsset(Asset* asset);
String ToString() const;
+ // Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
+ void Inline();
+
FORCE_INLINE Variant Cast(const VariantType& to) const
{
return Cast(*this, to);
diff --git a/Source/Engine/Core/Types/Version.h b/Source/Engine/Core/Types/Version.h
index 57c6e10fe..1dc36ea38 100644
--- a/Source/Engine/Core/Types/Version.h
+++ b/Source/Engine/Core/Types/Version.h
@@ -72,15 +72,6 @@ public:
return _major;
}
- ///
- /// Gets the high 16 bits of the revision number.
- ///
- /// A 16-bit signed integer.
- FORCE_INLINE int16 MajorRevision() const
- {
- return static_cast(_revision >> 16);
- }
-
///
/// Gets the value of the minor component of the version number for the current Version object.
///
@@ -90,15 +81,6 @@ public:
return _minor;
}
- ///
- /// Gets the low 16 bits of the revision number.
- ///
- /// A 16-bit signed integer.
- FORCE_INLINE int16 MinorRevision() const
- {
- return static_cast(_revision & 65535);
- }
-
///
/// Gets the value of the revision component of the version number for the current Version object.
///
@@ -126,61 +108,26 @@ public:
return _major == obj._major && _minor == obj._minor && _build == obj._build && _revision == obj._revision;
}
- ///
- /// Determines whether two specified Version objects are equal.
- ///
- /// The other Version object.
- /// True if equals ; otherwise, false.
FORCE_INLINE bool operator==(const Version& other) const
{
return Equals(other);
}
-
- ///
- /// Determines whether the first specified Version object is greater than the second specified Version object.
- ///
- /// The first Version object.
- /// True if is greater than ; otherwise, false.
- FORCE_INLINE bool operator >(const Version& other) const
+ FORCE_INLINE bool operator>(const Version& other) const
{
return other < *this;
}
-
- ///
- /// Determines whether the first specified Version object is greater than or equal to the second specified Version object.
- /// /summary>
- /// The other Version object.
- /// True if is greater than or equal to ; otherwise, false.
- FORCE_INLINE bool operator >=(const Version& other) const
+ FORCE_INLINE bool operator>=(const Version& other) const
{
return other <= *this;
}
-
- ///
- /// Determines whether two specified Version objects are not equal.
- ///
- /// The other Version object.
- /// True if does not equal ; otherwise, false.
FORCE_INLINE bool operator!=(const Version& other) const
{
return !(*this == other);
}
-
- ///
- /// Determines whether the first specified Version object is less than the second specified Version object.
- ///
- /// The first other object.
- /// True if is less than ; otherwise, false.
FORCE_INLINE bool operator<(const Version& other) const
{
return CompareTo(other) < 0;
}
-
- ///
- /// Determines whether the first specified Version object is less than or equal to the second Version object.
- ///
- /// The other Version object.
- /// True if is less than or equal to ; otherwise, false.
FORCE_INLINE bool operator<=(const Version& other) const
{
return CompareTo(other) <= 0;
diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs
index 797b02d5b..95146a95c 100644
--- a/Source/Engine/Engine/NativeInterop.Managed.cs
+++ b/Source/Engine/Engine/NativeInterop.Managed.cs
@@ -274,12 +274,12 @@ namespace FlaxEngine.Interop
#if FLAX_EDITOR
[HideInEditor]
#endif
- internal static class ManagedString
+ public static class ManagedString
{
internal static ManagedHandle EmptyStringHandle = ManagedHandle.Alloc(string.Empty);
[System.Diagnostics.DebuggerStepThrough]
- internal static unsafe IntPtr ToNative(string str)
+ public static unsafe IntPtr ToNative(string str)
{
if (str == null)
return IntPtr.Zero;
@@ -290,7 +290,7 @@ namespace FlaxEngine.Interop
}
[System.Diagnostics.DebuggerStepThrough]
- internal static unsafe IntPtr ToNativeWeak(string str)
+ public static unsafe IntPtr ToNativeWeak(string str)
{
if (str == null)
return IntPtr.Zero;
@@ -301,7 +301,7 @@ namespace FlaxEngine.Interop
}
[System.Diagnostics.DebuggerStepThrough]
- internal static string ToManaged(IntPtr ptr)
+ public static string ToManaged(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
return null;
@@ -309,7 +309,7 @@ namespace FlaxEngine.Interop
}
[System.Diagnostics.DebuggerStepThrough]
- internal static void Free(IntPtr ptr)
+ public static void Free(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
return;
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index 17b8b73af..57454ad1e 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -244,7 +244,25 @@ namespace FlaxEngine.Interop
@namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
typeAttributes = (uint)type.Attributes,
};
- *assemblyHandle = GetAssemblyHandle(type.Assembly);
+
+ Assembly assembly = null;
+ if (type.IsGenericType && !type.Assembly.IsCollectible)
+ {
+ // The owning assembly of a generic type with type arguments referencing
+ // collectible assemblies must be one of the collectible assemblies.
+ foreach (var genericType in type.GetGenericArguments())
+ {
+ if (genericType.Assembly.IsCollectible)
+ {
+ assembly = genericType.Assembly;
+ break;
+ }
+ }
+ }
+ if (assembly == null)
+ assembly = type.Assembly;
+
+ *assemblyHandle = GetAssemblyHandle(assembly);
}
[UnmanagedCallersOnly]
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index acfd7181b..da372ac90 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -444,7 +444,7 @@ namespace FlaxEngine.Interop
}
else
{
- toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedField), bindingFlags);
+ toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArray), bindingFlags);
toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags);
}
}
@@ -663,6 +663,17 @@ namespace FlaxEngine.Interop
MarshalHelperReferenceType.ToManaged(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false);
}
+ internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset)
+ {
+ fieldOffset = Unsafe.SizeOf();
+ IntPtr fieldStartPtr = fieldPtr;
+ fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size);
+ fieldOffset += (fieldPtr - fieldStartPtr).ToInt32();
+
+ ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner);
+ MarshalHelperReferenceType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false);
+ }
+
internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset)
{
fieldOffset = Unsafe.SizeOf();
@@ -691,14 +702,15 @@ namespace FlaxEngine.Interop
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
{
Type type = typeof(T);
- if (type.IsByRef || byRef)
+ byRef |= type.IsByRef;
+ if (byRef)
{
if (type.IsByRef)
type = type.GetElementType();
Assert.IsTrue(type.IsValueType);
}
- if (type == typeof(IntPtr))
+ if (type == typeof(IntPtr) && byRef)
managedValue = (T)(object)nativePtr;
else if (type == typeof(ManagedHandle))
managedValue = (T)(object)ManagedHandle.FromIntPtr(nativePtr);
@@ -778,10 +790,12 @@ namespace FlaxEngine.Interop
if (type == typeof(string))
managedValue = Unsafe.As(ManagedString.ToManaged(nativePtr));
+ else if (nativePtr == IntPtr.Zero)
+ managedValue = null;
else if (type.IsClass)
- managedValue = nativePtr != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target) : null;
+ managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target);
else if (type.IsInterface) // Dictionary
- managedValue = nativePtr != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target) : null;
+ managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target);
else
throw new NotImplementedException();
}
diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h
index 4dbcf6d06..422e554a0 100644
--- a/Source/Engine/Graphics/GPUDevice.h
+++ b/Source/Engine/Graphics/GPUDevice.h
@@ -238,7 +238,7 @@ public:
///
/// Gets the default material.
///
- MaterialBase* GetDefaultMaterial() const;
+ API_PROPERTY() MaterialBase* GetDefaultMaterial() const;
///
/// Gets the default material (Deformable domain).
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 08fb77223..8185f3b9d 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -788,7 +788,9 @@ void Actor::BreakPrefabLink()
void Actor::Initialize()
{
- ASSERT(!IsDuringPlay());
+#if ENABLE_ASSERTION
+ CHECK(!IsDuringPlay());
+#endif
// Cache
if (_parent)
@@ -802,7 +804,9 @@ void Actor::Initialize()
void Actor::BeginPlay(SceneBeginData* data)
{
- ASSERT(!IsDuringPlay());
+#if ENABLE_ASSERTION
+ CHECK(!IsDuringPlay());
+#endif
// Set flag
Flags |= ObjectFlags::IsDuringPlay;
@@ -832,7 +836,9 @@ void Actor::BeginPlay(SceneBeginData* data)
void Actor::EndPlay()
{
- ASSERT(IsDuringPlay());
+#if ENABLE_ASSERTION
+ CHECK(IsDuringPlay());
+#endif
// Fire event for scripting
if (IsActiveInHierarchy() && GetScene())
@@ -1059,7 +1065,9 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
void Actor::OnEnable()
{
- ASSERT(!_isEnabled);
+#if ENABLE_ASSERTION
+ CHECK(!_isEnabled);
+#endif
_isEnabled = true;
for (int32 i = 0; i < Scripts.Count(); i++)
@@ -1079,7 +1087,9 @@ void Actor::OnEnable()
void Actor::OnDisable()
{
- ASSERT(_isEnabled);
+#if ENABLE_ASSERTION
+ CHECK(_isEnabled);
+#endif
_isEnabled = false;
for (int32 i = Scripts.Count() - 1; i >= 0; i--)
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 373278885..dff527924 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -898,6 +898,14 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
+const Span AnimatedModel::GetMaterialSlots() const
+{
+ const auto model = SkinnedModel.Get();
+ if (model && !model->WaitForLoaded())
+ return ToSpan(model->MaterialSlots);
+ return Span();
+}
+
MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex)
{
if (SkinnedModel)
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index 2693f573d..763629e93 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -373,6 +373,7 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
+ const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h
index 1dfd5c0e0..3faad35b3 100644
--- a/Source/Engine/Level/Actors/ModelInstanceActor.h
+++ b/Source/Engine/Level/Actors/ModelInstanceActor.h
@@ -35,6 +35,11 @@ public:
///
API_PROPERTY() void SetEntries(const Array& value);
+ ///
+ /// Gets the material slots array set on the asset (eg. model or skinned model asset).
+ ///
+ API_PROPERTY(Sealed) virtual const Span GetMaterialSlots() const = 0;
+
///
/// Gets the material used to draw the meshes which are assigned to that slot (set in Entries or model's default).
///
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index 843bf6076..6ca841b41 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -341,6 +341,14 @@ void SplineModel::OnParentChanged()
OnSplineUpdated();
}
+const Span SplineModel::GetMaterialSlots() const
+{
+ const auto model = Model.Get();
+ if (model && !model->WaitForLoaded())
+ return ToSpan(model->MaterialSlots);
+ return Span();
+}
+
MaterialBase* SplineModel::GetMaterial(int32 entryIndex)
{
if (Model)
diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h
index 87f39699c..2a3d45a27 100644
--- a/Source/Engine/Level/Actors/SplineModel.h
+++ b/Source/Engine/Level/Actors/SplineModel.h
@@ -115,6 +115,7 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnParentChanged() override;
+ const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
protected:
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index 778f24d65..93eef3056 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -535,6 +535,14 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
}
}
+const Span StaticModel::GetMaterialSlots() const
+{
+ const auto model = Model.Get();
+ if (model && !model->WaitForLoaded())
+ return ToSpan(model->MaterialSlots);
+ return Span();
+}
+
MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{
if (Model)
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index 06fbdd874..600f174c9 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -166,6 +166,7 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
+ const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
index 649d01d85..9ef996869 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
@@ -189,7 +189,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va
const Matrix viewProjection = context.ViewTask ? context.ViewTask->View.PrevViewProjection : Matrix::Identity;
const Float3 position = (Float3)TryGetValue(node->GetBox(0), Value::Zero);
Float4 projPos;
- Float3::Transform(position, viewProjection);
+ Float3::Transform(position, viewProjection, projPos);
projPos /= projPos.W;
value = Float2(projPos.X * 0.5f + 0.5f, projPos.Y * 0.5f + 0.5f);
break;
diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp
index 04f928bba..46b978e1d 100644
--- a/Source/Engine/Platform/Mac/MacPlatform.cpp
+++ b/Source/Engine/Platform/Mac/MacPlatform.cpp
@@ -56,36 +56,94 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
{
if (CommandLine::Options.Headless)
return DialogResult::None;
- CFStringRef textRef = AppleUtils::ToString(text);
- CFStringRef captionRef = AppleUtils::ToString(caption);
- CFOptionFlags flags = 0;
+ NSAlert* alert = [[NSAlert alloc] init];
+ ASSERT(alert);
switch (buttons)
{
case MessageBoxButtons::AbortRetryIgnore:
+ [alert addButtonWithTitle:@"Abort"];
+ [alert addButtonWithTitle:@"Retry"];
+ [alert addButtonWithTitle:@"Ignore"];
+ break;
+ case MessageBoxButtons::OK:
+ [alert addButtonWithTitle:@"OK"];
+ break;
case MessageBoxButtons::OKCancel:
+ [alert addButtonWithTitle:@"OK"];
+ [alert addButtonWithTitle:@"Cancel"];
+ break;
case MessageBoxButtons::RetryCancel:
+ [alert addButtonWithTitle:@"Retry"];
+ [alert addButtonWithTitle:@"Cancel"];
+ break;
case MessageBoxButtons::YesNo:
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ break;
case MessageBoxButtons::YesNoCancel:
- flags |= kCFUserNotificationCancelResponse;
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ [alert addButtonWithTitle:@"Cancel"];
break;
}
switch (icon)
{
case MessageBoxIcon::Information:
- flags |= kCFUserNotificationNoteAlertLevel;
+ [alert setAlertStyle:NSAlertStyleCritical];
break;
case MessageBoxIcon::Error:
case MessageBoxIcon::Stop:
- flags |= kCFUserNotificationStopAlertLevel;
+ [alert setAlertStyle:NSAlertStyleInformational];
break;
case MessageBoxIcon::Warning:
- flags |= kCFUserNotificationCautionAlertLevel;
+ [alert setAlertStyle:NSAlertStyleWarning];
break;
}
- SInt32 result = CFUserNotificationDisplayNotice(0, flags, nullptr, nullptr, nullptr, captionRef, textRef, nullptr);
- CFRelease(captionRef);
- CFRelease(textRef);
- return DialogResult::OK;
+ [alert setMessageText:(NSString*)AppleUtils::ToString(caption)];
+ [alert setInformativeText:(NSString*)AppleUtils::ToString(text)];
+ NSInteger button = [alert runModal];
+ DialogResult result = DialogResult::OK;
+ switch (buttons)
+ {
+ case MessageBoxButtons::AbortRetryIgnore:
+ if (button == NSAlertFirstButtonReturn)
+ result = DialogResult::Abort;
+ else if (button == NSAlertSecondButtonReturn)
+ result = DialogResult::Retry;
+ else
+ result = DialogResult::Ignore;
+ break;
+ case MessageBoxButtons::OK:
+ result = DialogResult::OK;
+ break;
+ case MessageBoxButtons::OKCancel:
+ if (button == NSAlertFirstButtonReturn)
+ result = DialogResult::OK;
+ else
+ result = DialogResult::Cancel;
+ break;
+ case MessageBoxButtons::RetryCancel:
+ if (button == NSAlertFirstButtonReturn)
+ result = DialogResult::Retry;
+ else
+ result = DialogResult::Cancel;
+ break;
+ case MessageBoxButtons::YesNo:
+ if (button == NSAlertFirstButtonReturn)
+ result = DialogResult::Yes;
+ else
+ result = DialogResult::No;
+ break;
+ case MessageBoxButtons::YesNoCancel:
+ if (button == NSAlertFirstButtonReturn)
+ result = DialogResult::Yes;
+ else if (button == NSAlertSecondButtonReturn)
+ result = DialogResult::No;
+ else
+ result = DialogResult::Cancel;
+ break;
+ }
+ return result;
}
Float2 AppleUtils::PosToCoca(const Float2& pos)
@@ -301,15 +359,16 @@ Float2 MacPlatform::GetMousePosition()
CGEventRef event = CGEventCreate(nullptr);
CGPoint cursor = CGEventGetLocation(event);
CFRelease(event);
- return Float2((float)cursor.x, (float)cursor.y);
+ return Float2((float)cursor.x, (float)cursor.y) * MacPlatform::ScreenScale;
}
void MacPlatform::SetMousePosition(const Float2& pos)
{
CGPoint cursor;
- cursor.x = (CGFloat)pos.X;
- cursor.y = (CGFloat)pos.Y;
+ cursor.x = (CGFloat)(pos.X / MacPlatform::ScreenScale);
+ cursor.y = (CGFloat)(pos.Y / MacPlatform::ScreenScale);
CGWarpMouseCursorPosition(cursor);
+ CGAssociateMouseAndMouseCursorPosition(true);
}
Float2 MacPlatform::GetDesktopSize()
diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp
index 90bb07279..aeee93e37 100644
--- a/Source/Engine/Platform/Mac/MacWindow.cpp
+++ b/Source/Engine/Platform/Mac/MacWindow.cpp
@@ -72,11 +72,11 @@ KeyboardKeys GetKey(NSEvent* event)
case 0x30: return KeyboardKeys::Tab;
case 0x31: return KeyboardKeys::Spacebar;
case 0x32: return KeyboardKeys::BackQuote;
- case 0x33: return KeyboardKeys::Delete;
+ case 0x33: return KeyboardKeys::Backspace;
//case 0x34:
case 0x35: return KeyboardKeys::Escape;
- //case 0x36:
- //case 0x37: Command
+ case 0x36: return KeyboardKeys::Control; // Command (right)
+ case 0x37: return KeyboardKeys::Control; // Command (left)
case 0x38: return KeyboardKeys::Shift;
case 0x39: return KeyboardKeys::Capital;
case 0x3A: return KeyboardKeys::Alt;
@@ -378,7 +378,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
{
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
- Input::Keyboard->OnKeyDown(key);
+ Input::Keyboard->OnKeyDown(key, Window);
// Send a text input event
switch (key)
@@ -400,7 +400,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
if (length >= 16)
length = 15;
[text getCharacters:buffer range:NSMakeRange(0, length)];
- Input::Keyboard->OnCharInput((Char)buffer[0]);
+ Input::Keyboard->OnCharInput((Char)buffer[0], Window);
}
}
@@ -408,7 +408,32 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
{
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
- Input::Keyboard->OnKeyUp(key);
+ Input::Keyboard->OnKeyUp(key, Window);
+}
+
+- (void)flagsChanged:(NSEvent*)event
+{
+ int32 modMask;
+ int32 keyCode = [event keyCode];
+ if (keyCode == 0x36 || keyCode == 0x37)
+ modMask = NSEventModifierFlagCommand;
+ else if (keyCode == 0x38 || keyCode == 0x3c)
+ modMask = NSEventModifierFlagShift;
+ else if (keyCode == 0x3a || keyCode == 0x3d)
+ modMask = NSEventModifierFlagOption;
+ else if (keyCode == 0x3b || keyCode == 0x3e)
+ modMask = NSEventModifierFlagControl;
+ else
+ return;
+ KeyboardKeys key = GetKey(event);
+ if (key != KeyboardKeys::None)
+ {
+ int32 modifierFlags = [event modifierFlags];
+ if ((modifierFlags & modMask) == modMask)
+ Input::Keyboard->OnKeyDown(key, Window);
+ else
+ Input::Keyboard->OnKeyUp(key, Window);
+ }
}
- (void)scrollWheel:(NSEvent*)event
@@ -643,7 +668,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
// TODO: impl StartPosition for MacWindow
// TODO: impl Fullscreen for MacWindow
// TODO: impl ShowInTaskbar for MacWindow
- // TODO: impl AllowInput for MacWindow
// TODO: impl IsTopmost for MacWindow
}
diff --git a/Source/Engine/Renderer/Renderer.cs b/Source/Engine/Renderer/Renderer.cs
index bfb81a84c..71dca424b 100644
--- a/Source/Engine/Renderer/Renderer.cs
+++ b/Source/Engine/Renderer/Renderer.cs
@@ -17,10 +17,13 @@ namespace FlaxEngine
[Unmanaged]
public static void DrawSceneDepth(GPUContext context, SceneRenderTask task, GPUTexture output, List customActors)
{
- if (customActors.Count == 0)
- return;
- var temp = CollectionsMarshal.AsSpan(customActors).ToArray(); // FIXME
- var tempCount = temp.Length;
+ Actor[] temp = null;
+ int tempCount = 0;
+ if (customActors != null && customActors.Count != 0)
+ {
+ temp = CollectionsMarshal.AsSpan(customActors).ToArray(); // FIXME
+ tempCount = temp.Length;
+ }
Internal_DrawSceneDepth(FlaxEngine.Object.GetUnmanagedPtr(context), FlaxEngine.Object.GetUnmanagedPtr(task), FlaxEngine.Object.GetUnmanagedPtr(output), temp, ref tempCount);
}
@@ -32,7 +35,14 @@ namespace FlaxEngine
[Unmanaged]
public static void DrawActors(ref RenderContext renderContext, List customActors)
{
- DrawActors(ref renderContext, Utils.ExtractArrayFromList(customActors));
+ Actor[] temp = null;
+ int tempCount = 0;
+ if (customActors != null && customActors.Count != 0)
+ {
+ temp = CollectionsMarshal.AsSpan(customActors).ToArray(); // FIXME
+ tempCount = temp.Length;
+ }
+ Internal_DrawActors(ref renderContext, temp, ref tempCount);
}
}
}
diff --git a/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs
index 62040d224..5bb79b81b 100644
--- a/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs
+++ b/Source/Engine/Scripting/Attributes/Editor/AssetReferenceAttribute.cs
@@ -53,4 +53,23 @@ namespace FlaxEngine
UseSmallPicker = useSmallPicker;
}
}
+
+#if USE_NETCORE
+ ///
+ /// Specifies a options for an asset reference picker in the editor. Allows to customize view or provide custom value assign policy.
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public class AssetReferenceAttribute : AssetReferenceAttribute
+ {
+ ///
+ /// Initializes a new instance of the class for generic type T.
+ ///
+ /// True if use asset picker with a smaller height (single line), otherwise will use with full icon.
+ public AssetReferenceAttribute(bool useSmallPicker = false)
+ : base(typeof(T), useSmallPicker)
+ {
+ }
+ }
+#endif
}
diff --git a/Source/Engine/Scripting/Internal/ManagedSerialization.cpp b/Source/Engine/Scripting/Internal/ManagedSerialization.cpp
index f1543a326..f5ea46b5e 100644
--- a/Source/Engine/Scripting/Internal/ManagedSerialization.cpp
+++ b/Source/Engine/Scripting/Internal/ManagedSerialization.cpp
@@ -108,7 +108,7 @@ void ManagedSerialization::Deserialize(const StringAnsiView& data, MObject* obje
// Prepare arguments
void* args[3];
args[0] = object;
- args[1] = (void*)str;
+ args[1] = (void*)&str;
args[2] = (void*)&len;
// Call serialization tool
diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
index d97ff92cd..be8e10fab 100644
--- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
+++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp
@@ -3,7 +3,6 @@
#include "MUtils.h"
#include "MClass.h"
#include "MCore.h"
-#include "MDomain.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Types/Version.h"
@@ -449,7 +448,7 @@ Variant MUtils::UnboxVariant(MObject* value)
{
auto& a = array[i];
a.SetType(elementType);
- Platform::MemoryCopy(&a.AsData,(byte*)ptr + elementSize * i, elementSize);
+ Platform::MemoryCopy(&a.AsData, (byte*)ptr + elementSize * i, elementSize);
}
break;
case VariantType::Transform:
@@ -1004,11 +1003,11 @@ MClass* MUtils::GetClass(const Variant& value)
case VariantType::Enum:
return Scripting::FindClass(StringAnsiView(value.Type.TypeName));
case VariantType::ManagedObject:
- {
- MObject* obj = (MObject*)value;
- if (obj)
- return MCore::Object::GetClass(obj);
- }
+ {
+ MObject* obj = (MObject*)value;
+ if (obj)
+ return MCore::Object::GetClass(obj);
+ }
default: ;
}
return GetClass(value.Type);
@@ -1224,10 +1223,10 @@ MObject* MUtils::ToManaged(const Version& value)
auto versionToManaged = scriptingClass->GetMethod("VersionToManaged", 4);
CHECK_RETURN(versionToManaged, nullptr);
- int major = value.Major();
- int minor = value.Minor();
- int build = value.Build();
- int revision = value.Revision();
+ int32 major = value.Major();
+ int32 minor = value.Minor();
+ int32 build = value.Build();
+ int32 revision = value.Revision();
void* params[4];
params[0] = &major;
@@ -1244,26 +1243,29 @@ MObject* MUtils::ToManaged(const Version& value)
Version MUtils::ToNative(MObject* value)
{
+ Version result;
if (value)
#if USE_NETCORE
{
- auto ver = Version();
-
auto scriptingClass = Scripting::GetStaticClass();
- CHECK_RETURN(scriptingClass, ver);
- auto versionToNative = scriptingClass->GetMethod("VersionToNative", 2);
- CHECK_RETURN(versionToNative, ver);
+ CHECK_RETURN(scriptingClass, result);
+ auto versionToNative = scriptingClass->GetMethod("VersionToNative", 5);
+ CHECK_RETURN(versionToNative, result);
- void* params[2];
+ void* params[5];
params[0] = value;
- params[1] = &ver;
+ params[1] = (byte*)&result;
+ params[2] = (byte*)&result + sizeof(int32);
+ params[3] = (byte*)&result + sizeof(int32) * 2;
+ params[4] = (byte*)&result + sizeof(int32) * 3;
versionToNative->Invoke(nullptr, params, nullptr);
- return ver;
+
+ return result;
}
#else
return *(Version*)MCore::Object::Unbox(value);
#endif
- return Version();
+ return result;
}
#endif
diff --git a/Source/Engine/Scripting/Scripting.Build.cs b/Source/Engine/Scripting/Scripting.Build.cs
index 21f4477d0..10fc2aff9 100644
--- a/Source/Engine/Scripting/Scripting.Build.cs
+++ b/Source/Engine/Scripting/Scripting.Build.cs
@@ -17,10 +17,29 @@ public class Scripting : EngineModule
{
if (EngineConfiguration.WithDotNet(options))
{
+ void AddFrameworkDefines(string template, int major, int latestMinor)
+ {
+ for (int minor = latestMinor; minor >= 0; minor--)
+ {
+ options.ScriptingAPI.Defines.Add(string.Format(template, major, minor));
+ options.ScriptingAPI.Defines.Add(string.Format($"{template}_OR_GREATER", major, minor));
+ }
+ }
+
// .NET
options.PrivateDependencies.Add("nethost");
options.ScriptingAPI.Defines.Add("USE_NETCORE");
+ // .NET SDK
+ AddFrameworkDefines("NET{0}_{1}", 7, 0); // "NET7_0" and "NET7_0_OR_GREATER"
+ AddFrameworkDefines("NET{0}_{1}", 6, 0);
+ AddFrameworkDefines("NET{0}_{1}", 5, 0);
+ options.ScriptingAPI.Defines.Add("NET");
+ AddFrameworkDefines("NETCOREAPP{0}_{1}", 3, 1); // "NETCOREAPP3_1" and "NETCOREAPP3_1_OR_GREATER"
+ AddFrameworkDefines("NETCOREAPP{0}_{1}", 2, 2);
+ AddFrameworkDefines("NETCOREAPP{0}_{1}", 1, 1);
+ options.ScriptingAPI.Defines.Add("NETCOREAPP");
+
if (options.Target is EngineTarget engineTarget && engineTarget.UseSeparateMainExecutable(options))
{
// Build target doesn't support linking again main executable (eg. Linux) thus additional shared library is used for the engine (eg. libFlaxEditor.so)
diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp
index e2d709653..bbfa802f1 100644
--- a/Source/Engine/Scripting/Scripting.cpp
+++ b/Source/Engine/Scripting/Scripting.cpp
@@ -476,12 +476,28 @@ bool Scripting::Load()
// Load FlaxEngine
const String flaxEnginePath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
- if (((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->Load(flaxEnginePath))
+ auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
+ if (flaxEngineModule->Assembly->Load(flaxEnginePath))
{
LOG(Error, "Failed to load FlaxEngine C# assembly.");
return true;
}
+ // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
+ // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
+#if USE_LARGE_WORLDS
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"];
+#else
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"];
+#endif
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"];
+
#if USE_EDITOR
// Skip loading game modules in Editor on startup - Editor loads them later during splash screen (eg. after first compilation)
static bool SkipFirstLoad = true;
diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs
index 7a91b2d96..2b49d97d4 100644
--- a/Source/Engine/Scripting/Scripting.cs
+++ b/Source/Engine/Scripting/Scripting.cs
@@ -229,22 +229,24 @@ namespace FlaxEngine
return ManagedHandle.Alloc(new CultureInfo(lcid));
}
- internal static void VersionToNative(ManagedHandle versionHandle, IntPtr nativePtr)
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct VersionNative
+ {
+ public int Major;
+ public int Minor;
+ public int Build;
+ public int Revision;
+ }
+
+ internal static void VersionToNative(ManagedHandle versionHandle, ref int major, ref int minor, ref int build, ref int revision)
{
Version version = Unsafe.As(versionHandle.Target);
if (version != null)
{
- Marshal.WriteInt32(nativePtr, 0, version.Major);
- Marshal.WriteInt32(nativePtr, 4, version.Minor);
- Marshal.WriteInt32(nativePtr, 8, version.Build);
- Marshal.WriteInt32(nativePtr, 12, version.Revision);
- }
- else
- {
- Marshal.WriteInt32(nativePtr, 0, 0);
- Marshal.WriteInt32(nativePtr, 4, 0);
- Marshal.WriteInt32(nativePtr, 8, -1);
- Marshal.WriteInt32(nativePtr, 12, -1);
+ major = version.Major;
+ minor = version.Minor;
+ build = version.Build;
+ revision = version.Revision;
}
}
diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
index 59704800a..11d136d62 100644
--- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
+++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp
@@ -151,6 +151,13 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context)
auto patch = terrain->GetPatch(entry.AsTerrain.PatchIndex);
auto chunk = &patch->Chunks[entry.AsTerrain.ChunkIndex];
auto chunkSize = terrain->GetChunkSize();
+ if (!patch->Heightmap)
+ {
+ LOG(Error, "Terrain actor {0} is missing heightmap for baking, skipping baking stage.", terrain->GetName());
+ _wasStageDone = true;
+ scene->EntriesLocker.Unlock();
+ return;
+ }
const auto heightmap = patch->Heightmap.Get()->GetTexture();
Matrix world;
@@ -171,7 +178,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context)
DrawCall drawCall;
if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, 0))
- return;
+ break;
context->UpdateCB(cb, &shaderData);
context->BindCB(0, cb);
diff --git a/Source/Engine/Tests/TestMath.cs b/Source/Engine/Tests/TestMath.cs
new file mode 100644
index 000000000..b8eda936b
--- /dev/null
+++ b/Source/Engine/Tests/TestMath.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#if FLAX_TESTS
+using System;
+using NUnit.Framework;
+
+namespace FlaxEngine.Tests
+{
+ ///
+ /// Tests for and .
+ ///
+ [TestFixture]
+ public class TestMath
+ {
+ ///
+ /// Test unwinding angles.
+ ///
+ [Test]
+ public void TestUnwind()
+ {
+ Assert.AreEqual(0.0f, Mathf.UnwindDegreesAccurate(0.0f));
+ Assert.AreEqual(45.0f, Mathf.UnwindDegreesAccurate(45.0f));
+ Assert.AreEqual(90.0f, Mathf.UnwindDegreesAccurate(90.0f));
+ Assert.AreEqual(180.0f, Mathf.UnwindDegreesAccurate(180.0f));
+ Assert.AreEqual(0.0f, Mathf.UnwindDegreesAccurate(360.0f));
+ Assert.AreEqual(0.0f, Mathf.UnwindDegrees(0.0f));
+ Assert.AreEqual(45.0f, Mathf.UnwindDegrees(45.0f));
+ Assert.AreEqual(90.0f, Mathf.UnwindDegrees(90.0f));
+ Assert.AreEqual(180.0f, Mathf.UnwindDegrees(180.0f));
+ Assert.AreEqual(0.0f, Mathf.UnwindDegrees(360.0f));
+ var fError = 0.001f;
+ var dError = 0.00001;
+ for (float f = -400.0f; f <= 400.0f; f += 0.1f)
+ {
+ var f1 = Mathf.UnwindDegreesAccurate(f);
+ var f2 = Mathf.UnwindDegrees(f);
+ if (Mathf.Abs(f1 - f2) >= fError)
+ throw new Exception($"Failed on angle={f}, {f1} != {f2}");
+ var d = (double)f;
+ var d1 = Mathd.UnwindDegreesAccurate(d);
+ var d2 = Mathd.UnwindDegrees(d);
+ if (Mathd.Abs(d1 - d2) >= dError)
+ throw new Exception($"Failed on angle={d}, {d1} != {d2}");
+ }
+ }
+ }
+}
+#endif
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 37154e09f..ca4495f67 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -675,6 +675,10 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
}
}
}
+
+ // For in-built structures try to convert it into internal format for better comparability with the scripting
+ value.Inline();
+
break;
}
// Unpack Structure
diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h
index 238823afd..0d841f3d4 100644
--- a/Source/Engine/Visject/VisjectGraph.h
+++ b/Source/Engine/Visject/VisjectGraph.h
@@ -246,6 +246,7 @@ public:
void ProcessGroupCollections(Box* box, Node* node, Value& value);
protected:
+ void InlineVariantStruct(Variant& v);
virtual Value eatBox(Node* caller, Box* box) = 0;
virtual Graph* GetCurrentGraph() const = 0;
diff --git a/Source/ThirdParty/fmt/core.h b/Source/ThirdParty/fmt/core.h
index a8b208508..31049d410 100644
--- a/Source/ThirdParty/fmt/core.h
+++ b/Source/ThirdParty/fmt/core.h
@@ -30,6 +30,7 @@ Customizations done to fmt lib:
#define FMT_USE_USER_DEFINED_LITERALS 0
#define FMT_USE_STRING 0
#define FMT_USE_LONG_DOUBLE 0
+#define FMT_USE_FLOAT128 0
#define FMT_USE_ITERATOR 0
#define FMT_USE_LOCALE_GROUPING 0
#define FMT_EXCEPTIONS 0
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
index 920e74f67..fc3e6a534 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
@@ -1203,6 +1203,8 @@ namespace Flax.Build.Bindings
contents.Append("ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
+ else if (parameterInfo.IsParams)
+ contents.Append("params ");
contents.Append(managedType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
@@ -1263,6 +1265,8 @@ namespace Flax.Build.Bindings
contents.Append("ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
+ else if (parameterInfo.IsParams)
+ contents.Append("params ");
contents.Append(managedType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
@@ -1988,6 +1992,8 @@ namespace Flax.Build.Bindings
contents.Append("ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
+ else if (parameterInfo.IsParams)
+ contents.Append("params ");
contents.Append(managedType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
@@ -2053,7 +2059,7 @@ namespace Flax.Build.Bindings
if (code.StartsWith("using"))
CSharpUsedNamespaces.Add(code.Substring(6));
else if (code.Length > 0)
- contents.Append(injectCodeInfo.Code).AppendLine(";");
+ contents.Append(code).AppendLine(";");
}
}
else
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
index 72896f186..c2a785c9c 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs
@@ -19,7 +19,7 @@ namespace Flax.Build.Bindings
partial class BindingsGenerator
{
private static readonly Dictionary TypeCache = new Dictionary();
- private const int CacheVersion = 18;
+ private const int CacheVersion = 19;
internal static void Write(BinaryWriter writer, string e)
{
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
index 1eeefaf27..d4fcd9629 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs
@@ -337,6 +337,9 @@ namespace Flax.Build.Bindings
case "this":
currentParam.IsThis = true;
break;
+ case "params":
+ currentParam.IsParams = true;
+ break;
case "attributes":
currentParam.Attributes = tag.Value;
break;
diff --git a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs
index 3676e8e76..bae49e98e 100644
--- a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs
@@ -19,6 +19,7 @@ namespace Flax.Build.Bindings
public bool IsRef;
public bool IsOut;
public bool IsThis;
+ public bool IsParams;
public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue);
@@ -37,6 +38,7 @@ namespace Flax.Build.Bindings
writer.Write(IsRef);
writer.Write(IsOut);
writer.Write(IsThis);
+ writer.Write(IsParams);
}
public void Read(BinaryReader reader)
@@ -49,6 +51,7 @@ namespace Flax.Build.Bindings
IsRef = reader.ReadBoolean();
IsOut = reader.ReadBoolean();
IsThis = reader.ReadBoolean();
+ IsParams = reader.ReadBoolean();
}
public override string ToString()
diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
index 13433f92c..d6772379f 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
@@ -159,6 +159,7 @@ namespace Flax.Build
var outputPath = Path.GetDirectoryName(buildData.Target.GetOutputFilePath(buildOptions));
var outputFile = Path.Combine(outputPath, name + ".dll");
var outputDocFile = Path.Combine(outputPath, name + ".xml");
+ var outputGeneratedFiles = Path.Combine(buildOptions.IntermediateFolder);
string cscPath, referenceAssemblies;
#if USE_NETCORE
var dotnetSdk = DotNetSdk.Instance;
@@ -263,6 +264,9 @@ namespace Flax.Build
#endif
args.Add(string.Format("/out:\"{0}\"", outputFile));
args.Add(string.Format("/doc:\"{0}\"", outputDocFile));
+#if USE_NETCORE
+ args.Add(string.Format("/generatedfilesout:\"{0}\"", outputGeneratedFiles));
+#endif
if (buildOptions.ScriptingAPI.Defines.Count != 0)
args.Add("/define:" + string.Join(";", buildOptions.ScriptingAPI.Defines));
if (buildData.Configuration == TargetConfiguration.Debug)
@@ -272,8 +276,10 @@ namespace Flax.Build
foreach (var reference in fileReferences)
args.Add(string.Format("/reference:\"{0}\"", reference));
#if USE_NETCORE
- foreach (var analyzer in buildOptions.ScriptingAPI.SystemAnalyzers)
- args.Add(string.Format("/analyzer:\"{0}{1}.dll\"", referenceAnalyzers, analyzer));
+ foreach (var systemAnalyzer in buildOptions.ScriptingAPI.SystemAnalyzers)
+ args.Add(string.Format("/analyzer:\"{0}{1}.dll\"", referenceAnalyzers, systemAnalyzer));
+ foreach (var analyzer in buildOptions.ScriptingAPI.Analyzers)
+ args.Add(string.Format("/analyzer:\"{0}\"", analyzer));
#endif
foreach (var sourceFile in sourceFiles)
args.Add("\"" + sourceFile + "\"");
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
index ef365d3b7..e612c0a95 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
@@ -200,14 +200,19 @@ namespace Flax.Build.NativeCpp
public HashSet SystemReferences;
///
- /// The .Net libraries references (dll or exe files paths).
+ /// The system analyzers/source generators.
+ ///
+ public HashSet SystemAnalyzers;
+
+ ///
+ /// The .NET libraries references (dll or exe files paths).
///
public HashSet FileReferences;
///
- /// The .Net libraries references (dll or exe files paths).
+ /// The .NET analyzers (dll or exe files paths).
///
- public HashSet SystemAnalyzers;
+ public HashSet Analyzers;
///
/// True if ignore compilation warnings due to missing code documentation comments.
@@ -232,6 +237,7 @@ namespace Flax.Build.NativeCpp
Defines.AddRange(other.Defines);
SystemReferences.AddRange(other.SystemReferences);
FileReferences.AddRange(other.FileReferences);
+ Analyzers.AddRange(other.Analyzers);
IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings;
}
}
@@ -305,6 +311,7 @@ namespace Flax.Build.NativeCpp
"Microsoft.Interop.SourceGeneration",
},
FileReferences = new HashSet(),
+ Analyzers = new HashSet(),
};
///
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
index efce04776..5fba0dc43 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
@@ -247,6 +247,14 @@ namespace Flax.Build.Projects.VisualStudio
csProjectFileContent.AppendLine(" ");
}
}
+ foreach (var analyzer in configuration.TargetBuildOptions.ScriptingAPI.Analyzers)
+ {
+ csProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
+ csProjectFileContent.AppendLine(string.Format(" ", Path.GetFileNameWithoutExtension(analyzer)));
+ csProjectFileContent.AppendLine(string.Format(" {0}", Utilities.MakePathRelativeTo(analyzer, projectDirectory).Replace('/', '\\')));
+ csProjectFileContent.AppendLine(" ");
+ csProjectFileContent.AppendLine(" ");
+ }
csProjectFileContent.AppendLine("");
}