diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index d517d12fd..eaaa5685b 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -467,26 +467,36 @@ namespace FlaxEditor.CustomEditors.Dedicated // Setup transform if (Presenter is LayoutElementsContainer l) { + for (int i = 0; i < l.Children.Count; i++) + { + if (l.Children[i] is GroupElement g && g.Panel.HeaderText.Equals("Transform", StringComparison.Ordinal)) + { + l.Children.Remove(g); + l.ContainerControl.Children.Remove(g.Panel); + break; + } + } + var transformGroup = l.Group("Transform"); VerticalPanelElement mainHor = VerticalPanelWithoutMargin(transformGroup); CreateTransformElements(mainHor, ValuesTypes); - + ScriptMemberInfo scaleInfo = ValuesTypes[0].GetProperty("Scale"); ItemInfo scaleItem = new ItemInfo(scaleInfo); transformGroup.Property("Scale", scaleItem.GetValues(Values)); - + ScriptMemberInfo pivotInfo = ValuesTypes[0].GetProperty("Pivot"); ItemInfo pivotItem = new ItemInfo(pivotInfo); transformGroup.Property("Pivot", pivotItem.GetValues(Values)); - + ScriptMemberInfo shearInfo = ValuesTypes[0].GetProperty("Shear"); ItemInfo shearItem = new ItemInfo(shearInfo); transformGroup.Property("Shear", shearItem.GetValues(Values)); - + ScriptMemberInfo rotationInfo = ValuesTypes[0].GetProperty("Rotation"); ItemInfo rotationItem = new ItemInfo(rotationInfo); transformGroup.Property("Rotation", rotationItem.GetValues(Values)); - + // Get position of general tab for (int i = 0; i < l.Children.Count; i++) { diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index d7d16083e..de7a5d1bf 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -57,17 +57,18 @@ namespace FlaxEditor.CustomEditors.Editors menu.ItemsContainer.RemoveChildren(); menu.AddButton("Copy", linkedEditor.Copy); - var paste = menu.AddButton("Paste", linkedEditor.Paste); - paste.Enabled = linkedEditor.CanPaste; + var b = menu.AddButton("Paste", linkedEditor.Paste); + b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; menu.AddSeparator(); - var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); - moveUpButton.Enabled = Index > 0; + b = menu.AddButton("Move up", OnMoveUpClicked); + b.Enabled = Index > 0 && !Editor._readOnly; - var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked); - moveDownButton.Enabled = Index + 1 < Editor.Count; - - menu.AddButton("Remove", OnRemoveClicked); + b = menu.AddButton("Move down", OnMoveDownClicked); + b.Enabled = Index + 1 < Editor.Count && !Editor._readOnly; + + b = menu.AddButton("Remove", OnRemoveClicked); + b.Enabled = !Editor._readOnly; } private void OnMoveUpClicked() @@ -177,6 +178,7 @@ namespace FlaxEditor.CustomEditors.Editors private IntValueBox _sizeBox; private Color _background; private int _elementsCount, _minCount, _maxCount; + private bool _readOnly; private bool _canResize; private bool _canReorderItems; private CollectionAttribute.DisplayType _displayType; @@ -209,6 +211,7 @@ namespace FlaxEditor.CustomEditors.Editors return; var size = Count; + _readOnly = false; _canResize = true; _canReorderItems = true; _minCount = 0; @@ -225,6 +228,7 @@ namespace FlaxEditor.CustomEditors.Editors if (collection != null) { _canResize = !collection.ReadOnly; + _readOnly = collection.ReadOnly; _minCount = collection.MinCount; _maxCount = collection.MaxCount; _canReorderItems = collection.CanReorderItems; @@ -235,6 +239,12 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; _displayType = collection.Display; } + if (attributes != null && attributes.Any(x => x is ReadOnlyAttribute)) + { + _readOnly = true; + _canResize = false; + _canReorderItems = false; + } if (_maxCount == 0) _maxCount = ushort.MaxValue; _canResize &= _minCount < _maxCount; @@ -243,8 +253,7 @@ namespace FlaxEditor.CustomEditors.Editors dragArea.CustomControl.Editor = this; dragArea.CustomControl.ElementType = ElementType; - // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter - // which scripts can be dragged over and dropped on this collection editor. + // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter which scripts can be dragged over and dropped on this collection editor var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); if (assetReference != null) { @@ -333,6 +342,8 @@ namespace FlaxEditor.CustomEditors.Editors var property = panel.AddPropertyItem(itemLabel); var itemLayout = (LayoutElementsContainer)property; itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + if (_readOnly && itemLayout.Children.Count > 0) + GenericEditor.OnReadOnlyProperty(itemLayout); } else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single)) { @@ -340,13 +351,15 @@ namespace FlaxEditor.CustomEditors.Editors cdp.CustomControl.Setup(this, i, _canReorderItems); var itemLayout = cdp.VerticalPanel(); cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + if (_readOnly && itemLayout.Children.Count > 0) + GenericEditor.OnReadOnlyProperty(itemLayout); } } } _elementsCount = size; // Add/Remove buttons - if (_canResize) + if (_canResize && !_readOnly) { var panel = dragArea.HorizontalPanel(); panel.Panel.Size = new Float2(0, 20); diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index da60aaefd..0bf477834 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -131,7 +131,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { - if (button == MouseButton.Left) + if (button == MouseButton.Left && _editor._canEditKeys) { OnEditClicked(null); return true; @@ -197,6 +197,11 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; _displayType = collection.Display; } + if (attributes != null && attributes.Any(x => x is ReadOnlyAttribute)) + { + _readOnly = true; + _canEditKeys = false; + } // Size if (layout.ContainerControl is DropPanel dropPanel) @@ -239,14 +244,6 @@ namespace FlaxEditor.CustomEditors.Editors var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); var valuesType = new ScriptType(valueType); - - bool single = valuesType.IsPrimitive || - valuesType.Equals(new ScriptType(typeof(string))) || - valuesType.IsEnum || - (valuesType.GetFields().Length == 1 && valuesType.GetProperties().Length == 0) || - (valuesType.GetProperties().Length == 1 && valuesType.GetFields().Length == 0) || - valuesType.Equals(new ScriptType(typeof(JsonAsset))) || - valuesType.Equals(new ScriptType(typeof(SettingsBase))); // Use separate layout cells for each collection items to improve layout updates for them in separation var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum; @@ -263,6 +260,8 @@ namespace FlaxEditor.CustomEditors.Editors var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key)); var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor); + if (_readOnly && itemLayout.Children.Count > 0) + GenericEditor.OnReadOnlyProperty(itemLayout); } } _elementsCount = size; diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 63e103daa..ec75926d9 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -581,6 +581,43 @@ namespace FlaxEditor.CustomEditors.Editors return layout; } + internal static void OnReadOnlyProperty(LayoutElementsContainer itemLayout, int labelIndex = -1) + { + PropertiesListElement list = null; + int firstChildControlIndex = 0; + bool disableSingle = true; + var control = itemLayout.Children[itemLayout.Children.Count - 1]; + if (control is GroupElement group && group.Children.Count > 0) + { + list = group.Children[0] as PropertiesListElement; + disableSingle = false; // Disable all nested editors + } + else if (control is PropertiesListElement list1 && labelIndex != -1) + { + list = list1; + firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex; + } + else if (control?.Control != null) + { + control.Control.Enabled = false; + } + + if (list != null) + { + // Disable controls added to the editor + var count = list.Properties.Children.Count; + for (int j = firstChildControlIndex; j < count; j++) + { + var child = list.Properties.Children[j]; + if (disableSingle && child is PropertyNameLabel) + break; + + if (child != null) + child.Enabled = false; + } + } + } + /// /// Evaluate the cache for a given property item. /// @@ -660,35 +697,7 @@ namespace FlaxEditor.CustomEditors.Editors if (item.IsReadOnly && itemLayout.Children.Count > 0) { - PropertiesListElement list = null; - int firstChildControlIndex = 0; - bool disableSingle = true; - var control = itemLayout.Children[itemLayout.Children.Count - 1]; - if (control is GroupElement group && group.Children.Count > 0) - { - list = group.Children[0] as PropertiesListElement; - disableSingle = false; // Disable all nested editors - } - else if (control is PropertiesListElement list1) - { - list = list1; - firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex; - } - - if (list != null) - { - // Disable controls added to the editor - var count = list.Properties.Children.Count; - for (int j = firstChildControlIndex; j < count; j++) - { - var child = list.Properties.Children[j]; - if (disableSingle && child is PropertyNameLabel) - break; - - if (child != null) - child.Enabled = false; - } - } + OnReadOnlyProperty(itemLayout, labelIndex); } EvaluateVisibleIf(itemLayout, item, labelIndex); diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 714f5a0b3..fc66f284a 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -371,9 +371,25 @@ namespace FlaxEditor.GUI.Dialogs Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); // Color difference - var newRect = new Rectangle(_cOK.X, _cHex.Bottom + PickerMargin, _cCancel.Right - _cOK.Left, 0); - newRect.Size.Y = _cValue.Bottom - newRect.Y; - Render2D.FillRectangle(newRect, _value * _value.A); + var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0); + newRect.Size.Y = 50; + Render2D.FillRectangle(newRect, Color.White); + var smallRectSize = 10; + var numHor = Mathf.FloorToInt(newRect.Width / smallRectSize); + var numVer = Mathf.FloorToInt(newRect.Height / smallRectSize); + // Draw checkerboard for background of color to help with transparency + for (int i = 0; i < numHor; i++) + { + for (int j = 0; j < numVer; j++) + { + if ((i + j) % 2 == 0 ) + { + var rect = new Rectangle(newRect.X + smallRectSize * i, newRect.Y + smallRectSize * j, new Float2(smallRectSize)); + Render2D.FillRectangle(rect, Color.Gray); + } + } + } + Render2D.FillRectangle(newRect, _value); } /// diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 9a010f733..aefc25c90 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -418,9 +418,19 @@ namespace FlaxEditor.GUI.Tabs { // If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible) + { tabsSize.Y += TabsPanel.HScrollBar.Height; + var style = Style.Current; + TabsPanel.HScrollBar.TrackColor = style.Background; + TabsPanel.HScrollBar.ThumbColor = style.ForegroundGrey; + } else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible) + { tabsSize.X += TabsPanel.VScrollBar.Width; + var style = Style.Current; + TabsPanel.VScrollBar.TrackColor = style.Background; + TabsPanel.VScrollBar.ThumbColor = style.ForegroundGrey; + } } // Fit the tabs panel diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index d5782c10f..7b94f7f30 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -698,6 +698,38 @@ namespace FlaxEditor.GUI.Tree } } + // Show tree guide lines + if (Editor.Instance.Options.Options.Interface.ShowTreeLines) + { + TreeNode parentNode = Parent as TreeNode; + bool thisNodeIsLast = false; + while (parentNode != null && parentNode != ParentTree.Children[0]) + { + float bottomOffset = 0; + float topOffset = 0; + + if (Parent == parentNode && this == Parent.Children[0]) + topOffset = 2; + + if (thisNodeIsLast && parentNode.Children.Count == 1) + bottomOffset = topOffset != 0 ? 4 : 2; + + if (Parent == parentNode && this == Parent.Children[Parent.Children.Count - 1] && !_opened) + { + thisNodeIsLast = true; + bottomOffset = topOffset != 0 ? 4 : 2; + } + + float leftOffset = 9; + // Adjust offset for icon image + if (_iconCollaped.IsValid) + leftOffset += 18; + var lineRect1 = new Rectangle(parentNode.TextRect.Left - leftOffset, parentNode.HeaderRect.Top + topOffset, 1, parentNode.HeaderRect.Height - bottomOffset); + Render2D.FillRectangle(lineRect1, isSelected ? style.ForegroundGrey : style.LightBackground); + parentNode = parentNode.Parent as TreeNode; + } + } + // Base if (_opened) { @@ -729,7 +761,7 @@ namespace FlaxEditor.GUI.Tree // Try to estimate the rough location of the first node, assuming the node height is constant var firstChildGlobalRect = GetChildGlobalRectangle(children[0], ref globalTransform); - var firstVisibleChild = Math.Clamp((int)Math.Floor((globalClipping.Y - firstChildGlobalRect.Top) / firstChildGlobalRect.Height) + 1, 0, children.Count - 1); + var firstVisibleChild = Math.Clamp((int)Math.Floor((globalClipping.Y - firstChildGlobalRect.Top) / _headerHeight) + 1, 0, children.Count - 1); if (GetChildGlobalRectangle(children[firstVisibleChild], ref globalTransform).Top > globalClipping.Top || !children[firstVisibleChild].Visible) { // Estimate overshoot, either it's partially visible or hidden in the tree diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 43461b0a6..5b7535b5d 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -499,6 +499,15 @@ namespace FlaxEditor bool drawAnySelectedControl = false; var transformGizmo = TransformGizmo; var mousePos = PointFromWindow(RootWindow.MousePosition); + if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver) + { + // Highlight control under mouse for easier selecting (except if already selected) + if (RayCastControl(ref mousePos, out var hitControl) && + (transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl))) + { + DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl); + } + } if (transformGizmo != null) { // Selected UI controls outline @@ -511,15 +520,6 @@ namespace FlaxEditor } } } - if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver) - { - // Highlight control under mouse for easier selecting (except if already selected) - if (RayCastControl(ref mousePos, out var hitControl) && - (transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl))) - { - DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl); - } - } if (drawAnySelectedControl) Render2D.PopTransform(); @@ -617,40 +617,39 @@ namespace FlaxEditor // Draw sizing widgets if (_widgets == null) _widgets = new List(); - var widgetSize = 8.0f; + var widgetSize = 10.0f; var viewScale = ViewScale; if (viewScale < 0.7f) widgetSize *= viewScale; var controlSize = control.Size.Absolute.MinValue / 50.0f; if (controlSize < 1.0f) widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f); - var cornerSize = new Float2(widgetSize); - DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE); - DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE); - var edgeSizeV = new Float2(widgetSize * 2, widgetSize); - var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X); + var widgetHandleSize = new Float2(widgetSize); + DrawControlWidget(uiControl, ref ul, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE); + DrawControlWidget(uiControl, ref ur, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref bl, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref br, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE); Float2.Lerp(ref ul, ref bl, 0.5f, out var el); Float2.Lerp(ref ur, ref br, 0.5f, out var er); Float2.Lerp(ref ul, ref ur, 0.5f, out var eu); Float2.Lerp(ref bl, ref br, 0.5f, out var eb); - DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS); - DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref el, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref er, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS); // TODO: draw anchors } } - private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor) + private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size,float scale, Float2 resizeAxis, CursorType cursor) { var style = Style.Current; - var rect = new Rectangle(pos - size * 0.5f, size); + var rect = new Rectangle((pos + resizeAxis * 10 * scale) - size * 0.5f, size); if (rect.Contains(ref mousePos)) { Render2D.FillRectangle(rect, style.Foreground); + Render2D.DrawRectangle(rect, style.SelectionBorder); } else { diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 4cf1fe050..223420191 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -210,6 +210,13 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(310)] public bool SeparateValueAndUnit { get; set; } + /// + /// Gets or sets the option to put a space between numbers and units for unit formatting. + /// + [DefaultValue(true)] + [EditorDisplay("Interface"), EditorOrder(320)] + public bool ShowTreeLines { get; set; } = true; + /// /// Gets or sets the timestamps prefix mode for output log messages. /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index a56ab7b42..44ec4a74b 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -15,6 +15,7 @@ using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.GUI { @@ -625,6 +626,7 @@ namespace FlaxEditor.SceneGraph.GUI { var item = _dragScriptItems.Objects[i]; var actorType = Editor.Instance.CodeEditing.Actors.Get(item); + var scriptType = Editor.Instance.CodeEditing.Scripts.Get(item); if (actorType != ScriptType.Null) { var actor = actorType.CreateInstance() as Actor; @@ -639,6 +641,18 @@ namespace FlaxEditor.SceneGraph.GUI ActorNode.Root.Spawn(actor, spawnParent); actor.OrderInParent = newOrder; } + else if (scriptType != ScriptType.Null) + { + if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below) + { + Editor.LogWarning("Failed to spawn script of type " + actorType.TypeName); + continue; + } + IUndoAction action = new AddRemoveScript(true, newParent, scriptType); + Select(); + ActorNode.Root.Undo?.AddAction(action); + action.Do(); + } } result = DragDropEffect.Move; } @@ -699,9 +713,9 @@ namespace FlaxEditor.SceneGraph.GUI return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType); } - private static bool ValidateDragScriptItem(ScriptItem script) + private bool ValidateDragScriptItem(ScriptItem script) { - return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; + return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null || Editor.Instance.CodeEditing.Scripts.Get(script) != ScriptType.Null; } /// diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 7d08213fa..cf39357e3 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -351,6 +351,8 @@ namespace FlaxEditor.Viewport private void OnCollectDrawCalls(ref RenderContext renderContext) { + if (renderContext.View.Pass == DrawPass.Depth) + return; DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext); if (ShowNavigation) Editor.Internal_DrawNavMesh(); @@ -620,12 +622,12 @@ namespace FlaxEditor.Viewport private static bool ValidateDragActorType(ScriptType actorType) { - return Level.IsAnySceneLoaded; + return Level.IsAnySceneLoaded && Editor.Instance.CodeEditing.Actors.Get().Contains(actorType); } private static bool ValidateDragScriptItem(ScriptItem script) { - return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; + return Level.IsAnySceneLoaded && Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index d7624892d..b1fdc1720 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -98,7 +98,6 @@ namespace FlaxEditor.Viewport ShowDebugDraw = true; ShowEditorPrimitives = true; Gizmos = new GizmosCollection(this); - var inputOptions = window.Editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -219,6 +218,8 @@ namespace FlaxEditor.Viewport private void OnCollectDrawCalls(ref RenderContext renderContext) { + if (renderContext.View.Pass == DrawPass.Depth) + return; DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext); _debugDrawData.OnDraw(ref renderContext); } @@ -498,7 +499,7 @@ namespace FlaxEditor.Viewport private static bool ValidateDragActorType(ScriptType actorType) { - return true; + return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType); } private static bool ValidateDragScriptItem(ScriptItem script) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 0c1b0a373..7538e06b5 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -102,8 +102,8 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float Swap(eventTimeMin, eventTimeMax); } } - const float eventTime = animPos / static_cast(anim->Data.FramesPerSecond); - const float eventDeltaTime = (animPos - animPrevPos) / static_cast(anim->Data.FramesPerSecond); + const float eventTime = (float)(animPos / anim->Data.FramesPerSecond); + const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond); for (const auto& track : anim->Events) { for (const auto& k : track.Second.GetKeyframes()) @@ -211,7 +211,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed) } if (animPos < 0) animPos = animLength + animPos; - animPos *= static_cast(anim->Data.FramesPerSecond); + animPos = (float)(animPos * anim->Data.FramesPerSecond); return animPos; } @@ -265,7 +265,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* float nestedAnimPrevPos = animPrevPos - nestedAnim.Time; const float nestedAnimLength = nestedAnim.Anim->GetLength(); const float nestedAnimSpeed = nestedAnim.Speed * speed; - const float frameRateMatchScale = nestedAnimSpeed / (float)anim->Data.FramesPerSecond; + const float frameRateMatchScale = (float)(nestedAnimSpeed / anim->Data.FramesPerSecond); nestedAnimPos = nestedAnimPos * frameRateMatchScale; nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale; GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); @@ -363,8 +363,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Check if animation looped if (animPos < animPrevPos) { - const float endPos = anim->GetLength() * static_cast(anim->Data.FramesPerSecond); - const float timeToEnd = endPos - animPrevPos; + const float endPos = (float)(anim->GetLength() * anim->Data.FramesPerSecond); Transform rootBegin = refPose; rootChannel.Evaluate(0, &rootBegin, false); @@ -372,16 +371,13 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* Transform rootEnd = refPose; rootChannel.Evaluate(endPos, &rootEnd, false); - //rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true); - // Complex motion calculation to preserve the looped movement // (end - before + now - begin) // It sums the motion since the last update to anim end and since the start to now if (motionPosition) srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask; if (motionRotation) - srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); - //srcNode.Orientation = Quaternion::Identity; + srcNode.Orientation = (rootBefore.Orientation.Conjugated() * rootEnd.Orientation) * (rootBegin.Orientation.Conjugated() * rootNode.Orientation); } else { @@ -1165,21 +1161,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto nodes = node->GetNodes(this); const auto basePoseNodes = static_cast(valueA.AsPointer); const auto blendPoseNodes = static_cast(valueB.AsPointer); - const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes(); - Transform t, basePoseTransform, blendPoseTransform, refrenceTransform; + const auto& refNodes = _graph.BaseModel.Get()->GetNodes(); + Transform t, basePoseTransform, blendPoseTransform, refTransform; for (int32 i = 0; i < nodes->Nodes.Count(); i++) { basePoseTransform = basePoseNodes->Nodes[i]; blendPoseTransform = blendPoseNodes->Nodes[i]; - refrenceTransform = refrenceNodes[i].LocalTransform; + refTransform = refNodes[i].LocalTransform; - // base + (blend - refrence) = transform - t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation); - auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation; + // base + (blend - reference) + t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refTransform.Translation); + auto diff = Quaternion::Invert(refTransform.Orientation) * blendPoseTransform.Orientation; t.Orientation = basePoseTransform.Orientation * diff; - t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale); + t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refTransform.Scale); - //lerp base and transform + // Lerp base and transform Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]); } Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 96c49bc05..857e288fc 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -886,7 +886,8 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& instance = eatBox(node, box->FirstConnection()); else instance.SetObject(object); - if (!instance.AsObject) + ScriptingObject* instanceObj = (ScriptingObject*)instance; + if (!instanceObj) { LOG(Error, "Cannot bind event to null object."); PrintStack(LogType::Error); @@ -928,13 +929,13 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& } eventBinding->BindedMethods.Add(method); if (eventBinding->BindedMethods.Count() == 1) - (*eventBinder)(instance.AsObject, object, true); + (*eventBinder)(instanceObj, object, true); } else if (eventBinding) { // Unbind from the event if (eventBinding->BindedMethods.Count() == 1) - (*eventBinder)(instance.AsObject, object, false); + (*eventBinder)(instanceObj, object, false); eventBinding->BindedMethods.Remove(method); } } diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 674330f70..49cd27d66 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -218,10 +218,7 @@ const Char* TypeId2TypeName(const uint32 typeId) } FlaxStorage::FlaxStorage(const StringView& path) - : _refCount(0) - , _chunksLock(0) - , _version(0) - , _path(path) + : _path(path) { } @@ -242,6 +239,7 @@ FlaxStorage::~FlaxStorage() if (stream) Delete(stream); } + Platform::AtomicStore(&_files, 0); #endif } @@ -1327,6 +1325,7 @@ FileReadStream* FlaxStorage::OpenFile() LOG(Error, "Cannot open Flax Storage file \'{0}\'.", _path); return nullptr; } + Platform::InterlockedIncrement(&_files); // Create file reading stream stream = New(file); @@ -1336,11 +1335,13 @@ FileReadStream* FlaxStorage::OpenFile() bool FlaxStorage::CloseFileHandles() { - // Early out if no handles are opened - Array streams; - _file.GetValues(streams); - if (streams.IsEmpty() && Platform::AtomicRead(&_chunksLock) == 0) + if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0) + { + Array> streams; + _file.GetValues(streams); + ASSERT(streams.Count() == 0); return false; + } PROFILE_CPU(); // Note: this is usually called by the content manager when this file is not used or on exit @@ -1372,7 +1373,7 @@ bool FlaxStorage::CloseFileHandles() return true; // Failed, someone is still accessing the file // Close file handles (from all threads) - streams.Clear(); + Array> streams; _file.GetValues(streams); for (FileReadStream* stream : streams) { @@ -1380,6 +1381,7 @@ bool FlaxStorage::CloseFileHandles() Delete(stream); } _file.Clear(); + Platform::AtomicStore(&_files, 0); return false; } diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 3b982c32e..865b3e656 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -87,8 +87,9 @@ public: protected: // State - int64 _refCount; - int64 _chunksLock; + int64 _refCount = 0; + int64 _chunksLock = 0; + int64 _files = 0; double _lastRefLostTime; CriticalSection _loadLocker; @@ -97,7 +98,7 @@ protected: Array _chunks; // Metadata - uint32 _version; + uint32 _version = 0; String _path; protected: diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 567dbeba6..39439af5c 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -404,7 +404,7 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Material = material; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue(); + drawCall.ObjectRadius = (float)_sphere.Radius * drawCall.World.GetScaleVector().GetAbsolute().MaxValue(); drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = world; drawCall.Surface.Lightmap = nullptr; @@ -472,7 +472,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? + drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; @@ -534,7 +534,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? + drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 14542ef91..06c1201ef 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -246,7 +246,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? + drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; @@ -289,7 +289,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? + drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = nullptr; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index eaf32c069..525da3f36 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -4,9 +4,9 @@ #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/ISerializable.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/SoftAssetReference.h" -#include "Engine/Core/ISerializable.h" #include "Engine/Content/Assets/Texture.h" #include "Engine/Content/Assets/MaterialBase.h" @@ -526,13 +526,13 @@ API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable ToneMappingSettingsOverride OverrideFlags = Override::None; /// - /// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color. The default value is `6500`. + /// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color. /// API_FIELD(Attributes="Limit(1500, 15000), EditorOrder(0), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTemperature)") float WhiteTemperature = 6500.0f; /// - /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. The default value is `0`. + /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. /// API_FIELD(Attributes="Limit(-1, 1, 0.001f), EditorOrder(1), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTint)") float WhiteTint = 0.0f; @@ -1079,7 +1079,7 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable CameraArtifactsSettingsOverride OverrideFlags = Override::None; /// - /// Strength of the vignette effect. Value 0 hides it. The default value is 0.4. + /// Strength of the vignette effect. Value 0 hides it. /// API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") float VignetteIntensity = 0.4f; @@ -1091,19 +1091,19 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable Float3 VignetteColor = Float3(0, 0, 0.001f); /// - /// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. The default value is 0.125. + /// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. /// API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)") float VignetteShapeFactor = 0.125f; /// - /// Intensity of the grain filter. A value of 0 hides it. The default value is 0.005. + /// Intensity of the grain filter. A value of 0 hides it. /// API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)") float GrainAmount = 0.006f; /// - /// Size of the grain particles. The default value is 1.6. + /// Size of the grain particles. /// API_FIELD(Attributes="Limit(1.0f, 3.0f, 0.01f), EditorOrder(4), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainParticleSize)") float GrainParticleSize = 1.6f; @@ -1731,49 +1731,49 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing; /// - /// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance. The default value is half. + /// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.DepthResolution)") ResolutionMode DepthResolution = ResolutionMode::Half; /// - /// The raycast resolution. Full gives better quality, but half improves performance. The default value is half. + /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RayTracePassResolution)") ResolutionMode RayTracePassResolution = ResolutionMode::Half; /// - /// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections. Default value is 0.82. + /// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(10), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.BRDFBias), EditorDisplay(null, \"BRDF Bias\")") float BRDFBias = 0.82f; /// - /// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene. The default value is 0.45. + /// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(15), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RoughnessThreshold)") float RoughnessThreshold = 0.45f; /// - /// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower. The default value is 0.1. + /// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(20), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.WorldAntiSelfOcclusionBias)") float WorldAntiSelfOcclusionBias = 0.1f; /// - /// The raycast resolution. Full gives better quality, but half improves performance. The default value is half. + /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(25), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolvePassResolution)") ResolutionMode ResolvePassResolution = ResolutionMode::Full; /// - /// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Default value is 4. Use 1 for the highest speed. + /// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Use value of 1 for the best performance at cost of quality. /// API_FIELD(Attributes="Limit(1, 8), EditorOrder(26), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolveSamples)") int32 ResolveSamples = 4; /// - /// The point at which the far edges of the reflection begin to fade. Has no effect on performance. The default value is 0.1. + /// The point at which the far edges of the reflection begin to fade. Has no effect on performance. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.02f), EditorOrder(30), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.EdgeFadeFactor)") float EdgeFadeFactor = 0.1f; @@ -1803,13 +1803,13 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl bool TemporalEffect = true; /// - /// The intensity of the temporal effect. Lower values produce reflections faster, but more noise. The default value is 8. + /// The intensity of the temporal effect. Lower values produce reflections faster, but more noise. /// API_FIELD(Attributes="Limit(0, 20.0f, 0.5f), EditorOrder(55), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalScale)") float TemporalScale = 8.0f; /// - /// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1. The default value is 0.8. + /// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1. /// API_FIELD(Attributes="Limit(0.05f, 1.0f, 0.01f), EditorOrder(60), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalResponse)") float TemporalResponse = 0.8f; @@ -2015,7 +2015,7 @@ API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable ScreenSpaceReflectionsSettings ScreenSpaceReflections; /// - /// The anti-aliasing effect settings. + /// The antialiasing effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Anti Aliasing\"), EditorOrder(1100), JsonProperty(\"AA\")") AntiAliasingSettings AntiAliasing; diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index b44810648..f2411ae2a 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -354,9 +354,9 @@ public: // Applies the render origin to the transformation instance matrix. FORCE_INLINE void GetWorldMatrix(Matrix& world) const { - world.M41 -= Origin.X; - world.M42 -= Origin.Y; - world.M43 -= Origin.Z; + world.M41 -= (float)Origin.X; + world.M42 -= (float)Origin.Y; + world.M43 -= (float)Origin.Z; } }; diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 2e1870a57..cb925a206 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -609,7 +609,10 @@ void Actor::SetIsActive(bool value) void Actor::SetStaticFlags(StaticFlags value) { + if (_staticFlags == value) + return; _staticFlags = value; + OnStaticFlagsChanged(); } void Actor::SetTransform(const Transform& value) @@ -1229,6 +1232,14 @@ void Actor::OnOrderInParentChanged() Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr); } +void Actor::OnStaticFlagsChanged() +{ +} + +void Actor::OnLayerChanged() +{ +} + BoundingBox Actor::GetBoxWithChildren() const { BoundingBox result = GetBox(); diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 98045fc85..dc77cdbee 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -886,14 +886,12 @@ public: /// Gets rotation of the actor oriented towards the specified world position with upwards direction. /// /// The world position to orient towards. - /// The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. + /// The up direction that constrains up axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp) const; public: /// /// Execute custom action on actors tree. - /// Action should returns false to stop calling deeper. - /// First action argument is current actor object. /// /// Actor to call on every actor in the tree. Returns true if keep calling deeper. /// Custom arguments for the function @@ -903,14 +901,12 @@ public: if (action(this, args...)) { for (int32 i = 0; i < Children.Count(); i++) - Children[i]->TreeExecute(action, args...); + Children.Get()[i]->TreeExecute(action, args...); } } /// /// Execute custom action on actor children tree. - /// Action should returns false to stop calling deeper. - /// First action argument is current actor object. /// /// Actor to call on every actor in the tree. Returns true if keep calling deeper. /// Custom arguments for the function @@ -918,7 +914,7 @@ public: void TreeExecuteChildren(Function& action, Params ... args) { for (int32 i = 0; i < Children.Count(); i++) - Children[i]->TreeExecute(action, args...); + Children.Get()[i]->TreeExecute(action, args...); } public: @@ -1016,12 +1012,15 @@ public: /// virtual void OnOrderInParentChanged(); + /// + /// Called when actor static flag gets changed. + /// + virtual void OnStaticFlagsChanged(); + /// /// Called when layer gets changed. /// - virtual void OnLayerChanged() - { - } + virtual void OnLayerChanged(); /// /// Called when adding object to the game. diff --git a/Source/Engine/Level/Actors/PostFxVolume.cpp b/Source/Engine/Level/Actors/PostFxVolume.cpp index cacb75404..44d5ed990 100644 --- a/Source/Engine/Level/Actors/PostFxVolume.cpp +++ b/Source/Engine/Level/Actors/PostFxVolume.cpp @@ -33,7 +33,7 @@ void PostFxVolume::Collect(RenderContext& renderContext) } } - if (weight > ZeroTolerance) + if (weight > ZeroTolerance && renderContext.View.RenderLayersMask.HasLayer(GetLayer())) { const float totalSizeSqrt = (_transform.Scale * _size).LengthSquared(); renderContext.List->AddSettingsBlend((IPostFxSettingsProvider*)this, weight, _priority, totalSizeSqrt); diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index a32cd9412..e61800d0a 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -99,7 +99,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M DrawCall drawCall; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius; + drawCall.ObjectRadius = (float)_sphere.Radius; drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index a84eca432..29c14f035 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -410,7 +410,7 @@ void SplineModel::Draw(RenderContext& renderContext) const Transform splineTransform = GetTransform(); renderContext.View.GetWorldMatrix(splineTransform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline + drawCall.ObjectRadius = (float)_sphere.Radius; // TODO: use radius for the spline chunk rather than whole spline const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); for (int32 segment = 0; segment < _instances.Count(); segment++) { diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index eae239a8c..c7e1bc511 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -30,6 +30,12 @@ bool SceneAsset::IsInternalType() const return true; } +void SceneNavigation::Clear() +{ + Volumes.Clear(); + Actors.Clear(); +} + BoundingBox SceneNavigation::GetNavigationBounds() { if (Volumes.IsEmpty()) @@ -373,6 +379,7 @@ void Scene::EndPlay() // Improve scene cleanup performance by removing all data from scene rendering and ticking containers Ticking.Clear(); Rendering.Clear(); + Navigation.Clear(); // Base Actor::EndPlay(); diff --git a/Source/Engine/Level/Scene/SceneNavigation.h b/Source/Engine/Level/Scene/SceneNavigation.h index f8b5eef50..66b7ec2d0 100644 --- a/Source/Engine/Level/Scene/SceneNavigation.h +++ b/Source/Engine/Level/Scene/SceneNavigation.h @@ -23,6 +23,17 @@ public: /// Array Meshes; + /// + /// The list of registered navigation-relevant actors (on the scene). + /// + Array Actors; + +public: + /// + /// Clears this instance data. + /// + void Clear(); + /// /// Gets the total navigation volumes bounds. /// diff --git a/Source/Engine/Level/SceneQuery.h b/Source/Engine/Level/SceneQuery.h index a352e66ef..53e01829a 100644 --- a/Source/Engine/Level/SceneQuery.h +++ b/Source/Engine/Level/SceneQuery.h @@ -18,7 +18,7 @@ class FLAXENGINE_API SceneQuery { public: /// - /// Try to find actor hit by the given ray + /// Try to find actor hit by the given ray. /// /// Ray to test /// Hit actor or nothing @@ -55,19 +55,16 @@ public: public: /// /// Execute custom action on actors tree. - /// Action should returns false to stop calling deeper. - /// First action argument is current actor object. /// /// Actor to call on every actor in the tree. Returns true if keep calling deeper. /// Custom arguments for the function template - static void TreeExecute(Function& action, Params ... args) + static void TreeExecute(Function& action, Params... args) { #if SCENE_QUERIES_WITH_LOCK ScopeLock lock(Level::ScenesLock); #endif - for (int32 i = 0; i < Level::Scenes.Count(); i++) - Level::Scenes[i]->TreeExecute(action, args...); + Level::Scenes.Get()[i]->TreeExecute(action, args...); } }; diff --git a/Source/Engine/Navigation/NavLink.cpp b/Source/Engine/Navigation/NavLink.cpp index ce85d63b6..c766cf867 100644 --- a/Source/Engine/Navigation/NavLink.cpp +++ b/Source/Engine/Navigation/NavLink.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "NavLink.h" +#include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" NavLink::NavLink(const SpawnParams& params) @@ -62,6 +63,20 @@ void NavLink::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie DESERIALIZE(BiDirectional); } +void NavLink::OnEnable() +{ + GetScene()->Navigation.Actors.Add(this); + + Actor::OnEnable(); +} + +void NavLink::OnDisable() +{ + Actor::OnDisable(); + + GetScene()->Navigation.Actors.Remove(this); +} + void NavLink::OnTransformChanged() { // Base diff --git a/Source/Engine/Navigation/NavLink.h b/Source/Engine/Navigation/NavLink.h index c372e74b9..059d25f9f 100644 --- a/Source/Engine/Navigation/NavLink.h +++ b/Source/Engine/Navigation/NavLink.h @@ -46,6 +46,8 @@ public: #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnEnable() override; + void OnDisable() override; protected: // [Actor] diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 5bc5141fa..f81932837 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -23,7 +23,6 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Level.h" -#include "Engine/Level/SceneQuery.h" #include #include #include @@ -68,7 +67,7 @@ struct Modifier NavAreaProperties* NavArea; }; -struct NavigationSceneRasterization +struct NavSceneRasterizer { NavMesh* NavMesh; BoundingBox TileBoundsNavMesh; @@ -83,7 +82,7 @@ struct NavigationSceneRasterization Array* Modifiers; const bool IsWorldToNavMeshIdentity; - NavigationSceneRasterization(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) + NavSceneRasterizer(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) : TileBoundsNavMesh(tileBoundsNavMesh) , WorldToNavMesh(worldToNavMesh) , IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity()) @@ -103,35 +102,20 @@ struct NavigationSceneRasterization auto& ib = IndexBuffer; if (vb.IsEmpty() || ib.IsEmpty()) return; + PROFILE_CPU(); // Rasterize triangles + const Float3* vbData = vb.Get(); + const int32* ibData = ib.Get(); + Float3 v0, v1, v2; if (IsWorldToNavMeshIdentity) { // Faster path for (int32 i0 = 0; i0 < ib.Count();) { - auto v0 = vb[ib[i0++]]; - auto v1 = vb[ib[i0++]]; - auto v2 = vb[ib[i0++]]; -#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY - DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true); -#endif - - auto n = Float3::Cross(v0 - v1, v0 - v2); - n.Normalize(); - const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; - rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); - } - } - else - { - // Transform vertices from world space into the navmesh space - const Matrix worldToNavMesh = WorldToNavMesh; - for (int32 i0 = 0; i0 < ib.Count();) - { - auto v0 = Float3::Transform(vb[ib[i0++]], worldToNavMesh); - auto v1 = Float3::Transform(vb[ib[i0++]], worldToNavMesh); - auto v2 = Float3::Transform(vb[ib[i0++]], worldToNavMesh); + v0 = vbData[ibData[i0++]]; + v1 = vbData[ibData[i0++]]; + v2 = vbData[ibData[i0++]]; #if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true); #endif @@ -142,6 +126,29 @@ struct NavigationSceneRasterization rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); } } + else + { + // Transform vertices from world space into the navmesh space + const Matrix worldToNavMesh = WorldToNavMesh; + for (int32 i0 = 0; i0 < ib.Count();) + { + Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v0); + Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v1); + Float3::Transform(vbData[ibData[i0++]], worldToNavMesh, v2); +#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY + DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true); +#endif + + auto n = Float3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : RC_NULL_AREA; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + + // Clear after use + vb.Clear(); + ib.Clear(); } static void TriangulateBox(Array& vb, Array& ib, const OrientedBoundingBox& box) @@ -215,88 +222,67 @@ struct NavigationSceneRasterization } } - static bool Walk(Actor* actor, NavigationSceneRasterization& e) + void Rasterize(Actor* actor) { - // Early out if object is not intersecting with the tile bounds or is not using navigation - if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation)) - return true; - BoundingBox actorBoxNavMesh; - BoundingBox::Transform(actor->GetBox(), e.WorldToNavMesh, actorBoxNavMesh); - if (!actorBoxNavMesh.Intersects(e.TileBoundsNavMesh)) - return true; - - // Prepare buffers (for triangles) - auto& vb = e.VertexBuffer; - auto& ib = e.IndexBuffer; - vb.Clear(); - ib.Clear(); - - // Extract data from the actor if (const auto* boxCollider = dynamic_cast(actor)) { if (boxCollider->GetIsTrigger()) - return true; + return; PROFILE_CPU_NAMED("BoxCollider"); const OrientedBoundingBox box = boxCollider->GetOrientedBox(); - TriangulateBox(vb, ib, box); - - e.RasterizeTriangles(); + TriangulateBox(VertexBuffer, IndexBuffer, box); + RasterizeTriangles(); } else if (const auto* sphereCollider = dynamic_cast(actor)) { if (sphereCollider->GetIsTrigger()) - return true; + return; PROFILE_CPU_NAMED("SphereCollider"); const BoundingSphere sphere = sphereCollider->GetSphere(); - TriangulateSphere(vb, ib, sphere); - - e.RasterizeTriangles(); + TriangulateSphere(VertexBuffer, IndexBuffer, sphere); + RasterizeTriangles(); } else if (const auto* capsuleCollider = dynamic_cast(actor)) { if (capsuleCollider->GetIsTrigger()) - return true; + return; PROFILE_CPU_NAMED("CapsuleCollider"); const BoundingBox box = capsuleCollider->GetBox(); - TriangulateBox(vb, ib, box); - - e.RasterizeTriangles(); + TriangulateBox(VertexBuffer, IndexBuffer, box); + RasterizeTriangles(); } else if (const auto* meshCollider = dynamic_cast(actor)) { if (meshCollider->GetIsTrigger()) - return true; + return; PROFILE_CPU_NAMED("MeshCollider"); auto collisionData = meshCollider->CollisionData.Get(); if (!collisionData || collisionData->WaitForLoaded()) - return true; - - collisionData->ExtractGeometry(vb, ib); + return; + collisionData->ExtractGeometry(VertexBuffer, IndexBuffer); Matrix meshColliderToWorld; meshCollider->GetLocalToWorldMatrix(meshColliderToWorld); - for (auto& v : vb) + for (auto& v : VertexBuffer) Float3::Transform(v, meshColliderToWorld, v); - - e.RasterizeTriangles(); + RasterizeTriangles(); } else if (const auto* splineCollider = dynamic_cast(actor)) { if (splineCollider->GetIsTrigger()) - return true; + return; PROFILE_CPU_NAMED("SplineCollider"); auto collisionData = splineCollider->CollisionData.Get(); if (!collisionData || collisionData->WaitForLoaded()) - return true; + return; - splineCollider->ExtractGeometry(vb, ib); - - e.RasterizeTriangles(); + splineCollider->ExtractGeometry(VertexBuffer, IndexBuffer); + RasterizeTriangles(); } else if (const auto* terrain = dynamic_cast(actor)) { @@ -306,13 +292,13 @@ struct NavigationSceneRasterization { const auto patch = terrain->GetPatch(patchIndex); BoundingBox patchBoundsNavMesh; - BoundingBox::Transform(patch->GetBounds(), e.WorldToNavMesh, patchBoundsNavMesh); - if (!patchBoundsNavMesh.Intersects(e.TileBoundsNavMesh)) + BoundingBox::Transform(patch->GetBounds(), WorldToNavMesh, patchBoundsNavMesh); + if (!patchBoundsNavMesh.Intersects(TileBoundsNavMesh)) continue; - patch->ExtractCollisionGeometry(vb, ib); - - e.RasterizeTriangles(); + // TODO: get collision only from tile area + patch->ExtractCollisionGeometry(VertexBuffer, IndexBuffer); + RasterizeTriangles(); } } else if (const auto* navLink = dynamic_cast(actor)) @@ -321,44 +307,33 @@ struct NavigationSceneRasterization OffMeshLink link; link.Start = navLink->GetTransform().LocalToWorld(navLink->Start); - Float3::Transform(link.Start, e.WorldToNavMesh, link.Start); + Float3::Transform(link.Start, WorldToNavMesh, link.Start); link.End = navLink->GetTransform().LocalToWorld(navLink->End); - Float3::Transform(link.End, e.WorldToNavMesh, link.End); + Float3::Transform(link.End, WorldToNavMesh, link.End); link.Radius = navLink->Radius; link.BiDir = navLink->BiDirectional; link.Id = GetHash(navLink->GetID()); - e.OffMeshLinks->Add(link); + OffMeshLinks->Add(link); } else if (const auto* navModifierVolume = dynamic_cast(actor)) { - if (navModifierVolume->AgentsMask.IsNavMeshSupported(e.NavMesh->Properties)) + if (navModifierVolume->AgentsMask.IsNavMeshSupported(NavMesh->Properties)) { PROFILE_CPU_NAMED("NavModifierVolume"); Modifier modifier; OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox(); - bounds.Transform(e.WorldToNavMesh); + bounds.Transform(WorldToNavMesh); bounds.GetBoundingBox(modifier.Bounds); modifier.NavArea = navModifierVolume->GetNavArea(); - e.Modifiers->Add(modifier); + Modifiers->Add(modifier); } } - - return true; } }; -void RasterizeGeometry(NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) -{ - PROFILE_CPU_NAMED("RasterizeGeometry"); - - NavigationSceneRasterization rasterization(navMesh, tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks, modifiers); - Function treeWalkFunction(NavigationSceneRasterization::Walk); - SceneQuery::TreeExecute(treeWalkFunction, rasterization); -} - // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) @@ -455,11 +430,44 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B Array offMeshLinks; Array modifiers; - RasterizeGeometry(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers); + { + PROFILE_CPU_NAMED("RasterizeGeometry"); + NavSceneRasterizer rasterizer(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers); - rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); - rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); - rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, *heightfield); + // Collect actors to rasterize + Array actors; + { + PROFILE_CPU_NAMED("CollectActors"); + ScopeLock lock(Level::ScenesLock); + for (Scene* scene : Level::Scenes) + { + for (Actor* actor : scene->Navigation.Actors) + { + BoundingBox actorBoxNavMesh; + BoundingBox::Transform(actor->GetBox(), rasterizer.WorldToNavMesh, actorBoxNavMesh); + if (actorBoxNavMesh.Intersects(rasterizer.TileBoundsNavMesh) && + actor->IsActiveInHierarchy() && + EnumHasAllFlags(actor->GetStaticFlags(), StaticFlags::Navigation)) + { + actors.Add(actor); + } + } + } + } + + // Rasterize actors + for (Actor* actor : actors) + { + rasterizer.Rasterize(actor); + } + } + + { + PROFILE_CPU_NAMED("FilterHeightfield"); + rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); + rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); + rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, *heightfield); + } rcCompactHeightfield* compactHeightfield = rcAllocCompactHeightfield(); if (!compactHeightfield) @@ -467,39 +475,51 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B LOG(Warning, "Could not generate navmesh: Out of memory compact heightfield."); return true; } - if (!rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, *heightfield, *compactHeightfield)) { - LOG(Warning, "Could not generate navmesh: Could not build compact data."); - return true; + PROFILE_CPU_NAMED("CompactHeightfield"); + if (!rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, *heightfield, *compactHeightfield)) + { + LOG(Warning, "Could not generate navmesh: Could not build compact data."); + return true; + } } - rcFreeHeightField(heightfield); - - if (!rcErodeWalkableArea(&context, config.walkableRadius, *compactHeightfield)) { - LOG(Warning, "Could not generate navmesh: Could not erode."); - return true; + PROFILE_CPU_NAMED("ErodeWalkableArea"); + if (!rcErodeWalkableArea(&context, config.walkableRadius, *compactHeightfield)) + { + LOG(Warning, "Could not generate navmesh: Could not erode."); + return true; + } } // Mark areas - for (auto& modifier : modifiers) { - const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA; - Float3 bMin = modifier.Bounds.Minimum; - Float3 bMax = modifier.Bounds.Maximum; - rcMarkBoxArea(&context, &bMin.X, &bMax.X, areaId, *compactHeightfield); + PROFILE_CPU_NAMED("MarkModifiers"); + for (auto& modifier : modifiers) + { + const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA; + Float3 bMin = modifier.Bounds.Minimum; + Float3 bMax = modifier.Bounds.Maximum; + rcMarkBoxArea(&context, &bMin.X, &bMax.X, areaId, *compactHeightfield); + } } - if (!rcBuildDistanceField(&context, *compactHeightfield)) { - LOG(Warning, "Could not generate navmesh: Could not build distance field."); - return true; + PROFILE_CPU_NAMED("BuildDistanceField"); + if (!rcBuildDistanceField(&context, *compactHeightfield)) + { + LOG(Warning, "Could not generate navmesh: Could not build distance field."); + return true; + } } - - if (!rcBuildRegions(&context, *compactHeightfield, config.borderSize, config.minRegionArea, config.mergeRegionArea)) { - LOG(Warning, "Could not generate navmesh: Could not build regions."); - return true; + PROFILE_CPU_NAMED("BuildRegions"); + if (!rcBuildRegions(&context, *compactHeightfield, config.borderSize, config.minRegionArea, config.mergeRegionArea)) + { + LOG(Warning, "Could not generate navmesh: Could not build regions."); + return true; + } } rcContourSet* contourSet = rcAllocContourSet(); @@ -508,10 +528,13 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B LOG(Warning, "Could not generate navmesh: Out of memory for contour set."); return true; } - if (!rcBuildContours(&context, *compactHeightfield, config.maxSimplificationError, config.maxEdgeLen, *contourSet)) { - LOG(Warning, "Could not generate navmesh: Could not create contours."); - return true; + PROFILE_CPU_NAMED("BuildContours"); + if (!rcBuildContours(&context, *compactHeightfield, config.maxSimplificationError, config.maxEdgeLen, *contourSet)) + { + LOG(Warning, "Could not generate navmesh: Could not create contours."); + return true; + } } rcPolyMesh* polyMesh = rcAllocPolyMesh(); @@ -520,10 +543,13 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B LOG(Warning, "Could not generate navmesh: Out of memory for poly mesh."); return true; } - if (!rcBuildPolyMesh(&context, *contourSet, config.maxVertsPerPoly, *polyMesh)) { - LOG(Warning, "Could not generate navmesh: Could not triangulate contours."); - return true; + PROFILE_CPU_NAMED("BuildPolyMesh"); + if (!rcBuildPolyMesh(&context, *contourSet, config.maxVertsPerPoly, *polyMesh)) + { + LOG(Warning, "Could not generate navmesh: Could not triangulate contours."); + return true; + } } rcPolyMeshDetail* detailMesh = rcAllocPolyMeshDetail(); @@ -532,20 +558,20 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B LOG(Warning, "Could not generate navmesh: Out of memory for detail mesh."); return true; } - if (!rcBuildPolyMeshDetail(&context, *polyMesh, *compactHeightfield, config.detailSampleDist, config.detailSampleMaxError, *detailMesh)) { - LOG(Warning, "Could not generate navmesh: Could not build detail mesh."); - return true; + PROFILE_CPU_NAMED("BuildPolyMeshDetail"); + if (!rcBuildPolyMeshDetail(&context, *polyMesh, *compactHeightfield, config.detailSampleDist, config.detailSampleMaxError, *detailMesh)) + { + LOG(Warning, "Could not generate navmesh: Could not build detail mesh."); + return true; + } } rcFreeCompactHeightfield(compactHeightfield); rcFreeContourSet(contourSet); for (int i = 0; i < polyMesh->npolys; i++) - { polyMesh->flags[i] = polyMesh->areas[i] != RC_NULL_AREA ? 1 : 0; - } - if (polyMesh->nverts == 0) { // Empty tile @@ -623,15 +649,18 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B // Generate navmesh tile data unsigned char* navData = nullptr; int navDataSize = 0; - if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { - LOG(Warning, "Could not build Detour navmesh."); - return true; + PROFILE_CPU_NAMED("CreateNavMeshData"); + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + LOG(Warning, "Could not build Detour navmesh."); + return true; + } } ASSERT_LOW_LAYER(navDataSize > 4 && *(uint32*)navData == DT_NAVMESH_MAGIC); // Sanity check for Detour header { - PROFILE_CPU_NAMED("Navigation.CreateTile"); + PROFILE_CPU_NAMED("CreateTiles"); ScopeLock lock(runtime->Locker); @@ -729,17 +758,13 @@ public: bool Run() override { PROFILE_CPU_NAMED("BuildNavMeshTile"); - const auto navMesh = NavMesh.Get(); if (!navMesh) - { return false; - } if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); } - return false; } @@ -776,7 +801,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId) { NavBuildTasksLocker.Unlock(); - // Cancel task but without locking queue from this thread to prevent dead-locks + // Cancel task but without locking queue from this thread to prevent deadlocks task->Cancel(); NavBuildTasksLocker.Lock(); @@ -815,7 +840,7 @@ float NavMeshBuilder::GetNavMeshBuildingProgress() return result; } -void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize) +void BuildTileAsync(NavMesh* navMesh, const int32 x, const int32 y, const rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize) { NavMeshRuntime* runtime = navMesh->GetRuntime(); NavBuildTasksLocker.Lock(); @@ -1108,7 +1133,7 @@ void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float t if (!scene) { LOG(Warning, "Could not generate navmesh without scene."); - return; + return; } // Early out if scene is not using navigation diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp index 9644b4e75..f4e6f2702 100644 --- a/Source/Engine/Navigation/NavModifierVolume.cpp +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -47,6 +47,20 @@ void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifie DESERIALIZE(AreaName); } +void NavModifierVolume::OnEnable() +{ + GetScene()->Navigation.Actors.Add(this); + + BoxVolume::OnEnable(); +} + +void NavModifierVolume::OnDisable() +{ + BoxVolume::OnDisable(); + + GetScene()->Navigation.Actors.Remove(this); +} + void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds) { #if COMPILE_WITH_NAV_MESH_BUILDER diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h index f394afa7c..b5b2163a5 100644 --- a/Source/Engine/Navigation/NavModifierVolume.h +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -34,6 +34,8 @@ public: // [BoxVolume] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnEnable() override; + void OnDisable() override; protected: // [BoxVolume] diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index e46eb6b24..66fc91cce 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -939,7 +939,7 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe DrawCall drawCall; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); drawCall.ObjectPosition = effect->GetSphere().Center - view.Origin; - drawCall.ObjectRadius = effect->GetSphere().Radius; + drawCall.ObjectRadius = (float)effect->GetSphere().Radius; // Draw all emitters for (int32 emitterIndex = 0; emitterIndex < effect->Instance.Emitters.Count(); emitterIndex++) diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index d8541a222..6175c9058 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -2,6 +2,7 @@ #include "Collider.h" #include "Engine/Core/Log.h" +#include "Engine/Level/Scene/Scene.h" #if USE_EDITOR #include "Engine/Level/Scene/SceneRendering.h" #endif @@ -35,6 +36,13 @@ void Collider::SetIsTrigger(bool value) _isTrigger = value; if (_shape) PhysicsBackend::SetShapeState(_shape, IsActiveInHierarchy(), _isTrigger && CanBeTrigger()); + if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && _isEnabled) + { + if (_isTrigger) + GetScene()->Navigation.Actors.Remove(this); + else + GetScene()->Navigation.Actors.Add(this); + } } void Collider::SetCenter(const Vector3& value) @@ -43,13 +51,9 @@ void Collider::SetCenter(const Vector3& value) return; _center = value; if (_staticActor) - { PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity); - } else if (const RigidBody* rigidBody = GetAttachedRigidBody()) - { PhysicsBackend::SetShapeLocalPose(_shape, (_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale(), _localTransform.Orientation); - } UpdateBounds(); } @@ -134,25 +138,27 @@ RigidBody* Collider::GetAttachedRigidBody() const return nullptr; } -#if USE_EDITOR - void Collider::OnEnable() { + if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) + GetScene()->Navigation.Actors.Add(this); +#if USE_EDITOR GetSceneRendering()->AddPhysicsDebug(this); +#endif - // Base - Actor::OnEnable(); + PhysicsColliderActor::OnEnable(); } void Collider::OnDisable() { - // Base - Actor::OnDisable(); + PhysicsColliderActor::OnDisable(); + if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) + GetScene()->Navigation.Actors.Remove(this); +#if USE_EDITOR GetSceneRendering()->RemovePhysicsDebug(this); -} - #endif +} void Collider::Attach(RigidBody* rigidBody) { @@ -432,6 +438,19 @@ void Collider::OnLayerChanged() UpdateLayerBits(); } +void Collider::OnStaticFlagsChanged() +{ + PhysicsColliderActor::OnStaticFlagsChanged(); + + if (!_isTrigger && _isEnabled) + { + if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation)) + GetScene()->Navigation.Actors.AddUnique(this); + else + GetScene()->Navigation.Actors.Remove(this); + } +} + void Collider::OnPhysicsSceneChanged(PhysicsScene* previous) { PhysicsColliderActor::OnPhysicsSceneChanged(previous); diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 05f2a6eb2..a1660d531 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -171,15 +171,14 @@ public: protected: // [PhysicsColliderActor] -#if USE_EDITOR void OnEnable() override; void OnDisable() override; -#endif void BeginPlay(SceneBeginData* data) override; void EndPlay() override; void OnActiveInTreeChanged() override; void OnParentChanged() override; void OnTransformChanged() override; void OnLayerChanged() override; + void OnStaticFlagsChanged() override; void OnPhysicsSceneChanged(PhysicsScene* previous) override; }; diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index 116d929cc..78a67f0b1 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -8,6 +8,7 @@ #include "Engine/Physics/PhysicsScene.h" #include "Engine/Physics/PhysicsBackend.h" #include "Engine/Physics/CollisionCooking.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", true); @@ -33,6 +34,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i LOG(Error, "Cannot cook collision data for virtual models on a main thread (virtual models data is stored on GPU only). Use thread pool or async task."); return true; } + PROFILE_CPU(); // Prepare CollisionCooking::Argument arg; @@ -61,6 +63,7 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i bool CollisionData::CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { + PROFILE_CPU(); CHECK_RETURN(vertices.Length() != 0, true); CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true); ModelData modelData; @@ -74,6 +77,7 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span& ve bool CollisionData::CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { + PROFILE_CPU(); CHECK_RETURN(vertices.Length() != 0, true); CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true); ModelData modelData; @@ -89,12 +93,12 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span& ve bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { - // Validate state if (!IsVirtual()) { LOG(Warning, "Only virtual assets can be modified at runtime."); return true; } + PROFILE_CPU(); // Prepare CollisionCooking::Argument arg; @@ -107,18 +111,14 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, SerializedOptions options; BytesContainer outputData; if (CollisionCooking::CookCollision(arg, options, outputData)) - { return true; - } // Clear state unload(true); // Load data if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok) - { return true; - } // Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation) onLoaded(); @@ -133,6 +133,7 @@ bool CollisionData::GetModelTriangle(uint32 faceIndex, MeshBase*& mesh, uint32& meshTriangleIndex = MAX_uint32; if (!IsLoaded()) return false; + PROFILE_CPU(); ScopeLock lock(Locker); if (_triangleMesh) { @@ -178,6 +179,7 @@ bool CollisionData::GetModelTriangle(uint32 faceIndex, MeshBase*& mesh, uint32& void CollisionData::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const { + PROFILE_CPU(); vertexBuffer.Clear(); indexBuffer.Clear(); @@ -194,6 +196,7 @@ const Array& CollisionData::GetDebugLines() { if (_hasMissingDebugLines && IsLoaded()) { + PROFILE_CPU(); ScopeLock lock(Locker); _hasMissingDebugLines = false; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 5d86ed8c9..8824abdfa 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -4265,6 +4265,7 @@ void* PhysicsBackend::CreateHeightField(byte* data, int32 dataSize) void PhysicsBackend::GetConvexMeshTriangles(void* contextMesh, Array& vertexBuffer, Array& indexBuffer) { + PROFILE_CPU(); auto contextMeshPhysX = (PxConvexMesh*)contextMesh; uint32 numIndices = 0; uint32 numVertices = contextMeshPhysX->getNbVertices(); @@ -4304,6 +4305,7 @@ void PhysicsBackend::GetConvexMeshTriangles(void* contextMesh, Array& vertexBuffer, Array& indexBuffer) { + PROFILE_CPU(); auto triangleMeshPhysX = (PxTriangleMesh*)triangleMesh; uint32 numVertices = triangleMeshPhysX->getNbVertices(); uint32 numIndices = triangleMeshPhysX->getNbTriangles() * 3; diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index d893d2700..dfb44d935 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -134,7 +134,7 @@ public: static ScriptingObject* ToNative(MObject* obj); - static MObject* ToManaged(ScriptingObject* obj) + FORCE_INLINE static MObject* ToManaged(const ScriptingObject* obj) { return obj ? obj->GetOrCreateManagedInstance() : nullptr; } diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index aa677cda9..66ecbd377 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -12,6 +12,7 @@ #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Level/Scene/Scene.h" #include "Engine/Physics/PhysicsScene.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" @@ -804,6 +805,7 @@ RigidBody* Terrain::GetAttachedRigidBody() const void Terrain::OnEnable() { + GetScene()->Navigation.Actors.Add(this); GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->AddPhysicsDebug(this); @@ -824,6 +826,7 @@ void Terrain::OnEnable() void Terrain::OnDisable() { + GetScene()->Navigation.Actors.Remove(this); GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG GetSceneRendering()->RemovePhysicsDebug(this); diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 4b70f876c..6902b5d8d 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -97,7 +97,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.Material = _cachedDrawMaterial; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius; + drawCall.ObjectRadius = (float)_sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z)); @@ -155,7 +155,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.Material = material; renderContext.View.GetWorldMatrix(_transform, drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius; + drawCall.ObjectRadius = (float)_sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z)); diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 34fc57e84..4272cabe8 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2474,7 +2474,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 1d9d53554..ae1c30a3c 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -7,6 +7,16 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include +TestNesting::TestNesting(const SpawnParams& params) + : SerializableScriptingObject(params) +{ +} + +TestNesting2::TestNesting2(const SpawnParams& params) + : SerializableScriptingObject(params) +{ +} + TestClassNative::TestClassNative(const SpawnParams& params) : ScriptingObject(params) { diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 49db7d85e..5cd61cbc7 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -6,6 +6,59 @@ #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/SerializableScriptingObject.h" + +// Test compilation with nested types. +API_CLASS() class TestNesting : public SerializableScriptingObject +{ + DECLARE_SCRIPTING_TYPE(TestNesting); + API_AUTO_SERIALIZATION(); + + // Structure + API_STRUCT() struct TestAttribute : public ISerializable + { + DECLARE_SCRIPTING_TYPE_MINIMAL(TestAttribute); + API_AUTO_SERIALIZATION(); + + // Enumeration + API_ENUM() enum TestEnum + { + E1, E2, + }; + + // Enum + API_FIELD() TestEnum Enum = E1; + }; + + // Attributes + API_FIELD() Array Attributes; + // Enum + API_FIELD() TestAttribute::TestEnum Enum = TestAttribute::E1; +}; + +// Test compilation with nested types. +API_CLASS() class TestNesting2 : public SerializableScriptingObject +{ + DECLARE_SCRIPTING_TYPE(TestNesting2); + API_AUTO_SERIALIZATION(); + + // Structure + API_STRUCT() struct TestAttribute + { + DECLARE_SCRIPTING_TYPE_MINIMAL(TestAttribute); + + // Enumeration + API_ENUM() enum TestEnum + { + E1, E2, + }; + }; + + // Attributes + API_FIELD() Array Attributes; + // Enum + API_FIELD() TestNesting::TestAttribute::TestEnum Enum = TestNesting::TestAttribute::E1; +}; // Test structure. API_STRUCT(NoDefault) struct TestStruct : public ISerializable diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 61282e7a5..bfdad73b0 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -4,6 +4,27 @@ using System.ComponentModel; namespace FlaxEngine.GUI { + /// + /// Options for text case + /// + public enum TextCaseOptions + { + /// + /// No text case. + /// + None, + + /// + /// Uppercase. + /// + Uppercase, + + /// + /// Lowercase + /// + Lowercase + } + /// /// The basic GUI label control. /// @@ -45,6 +66,24 @@ namespace FlaxEngine.GUI } } + /// + /// The text case. + /// + [EditorDisplay("Text Style"), EditorOrder(2000), Tooltip("The case of the text.")] + public TextCaseOptions CaseOption { get; set; } = TextCaseOptions.None; + + /// + /// Whether to bold the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2001), Tooltip("Bold the text.")] + public bool Bold { get; set; } = false; + + /// + /// Whether to italicize the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2002), Tooltip("Italicize the text.")] + public bool Italic { get; set; } = false; + /// /// Gets or sets the color of the text. /// @@ -234,18 +273,49 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Font font = GetFont(); + var text = ConvertedText(); + + Render2D.DrawText(font, Material, text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); } + private Font GetFont() + { + Font font; + if (Bold) + font = Italic ? _font.GetBold().GetItalic().GetFont() : _font.GetBold().GetFont(); + else if (Italic) + font = _font.GetItalic().GetFont(); + else + font = _font.GetFont(); + return font; + } + + private LocalizedString ConvertedText() + { + LocalizedString text = _text; + switch (CaseOption) + { + case TextCaseOptions.Uppercase: + text = text.ToString().ToUpper(); + break; + case TextCaseOptions.Lowercase: + text = text.ToString().ToLower(); + break; + } + return text; + } + /// protected override void PerformLayoutBeforeChildren() { if (_autoWidth || _autoHeight || _autoFitText) { - var font = _font.GetFont(); + Font font = GetFont(); + var text = ConvertedText(); if (font) { // Calculate text size @@ -255,7 +325,7 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = font.MeasureText(_text, ref layout); + _textSize = font.MeasureText(text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 3d0581c4e..56838b9a4 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -24,11 +24,49 @@ namespace FlaxEngine.GUI get => _watermarkText; set => _watermarkText = value; } + + /// + /// The text case. + /// + [EditorDisplay("Text Style"), EditorOrder(2000), Tooltip("The case of the text.")] + public TextCaseOptions CaseOption { get; set; } = TextCaseOptions.None; + + /// + /// Whether to bold the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2001), Tooltip("Bold the text.")] + public bool Bold { get; set; } = false; + + /// + /// Whether to italicize the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2002), Tooltip("Italicize the text.")] + public bool Italic { get; set; } = false; + + /// + /// The vertical alignment of the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The vertical alignment of the text.")] + public TextAlignment VerticalAlignment + { + get => _layout.VerticalAlignment; + set => _layout.VerticalAlignment = value; + } + + /// + /// The vertical alignment of the text. + /// + [EditorDisplay("Text Style"), EditorOrder(2024), Tooltip("The horizontal alignment of the text.")] + public TextAlignment HorizontalAlignment + { + get => _layout.HorizontalAlignment; + set => _layout.HorizontalAlignment = value; + } /// /// Gets or sets the text wrapping within the control bounds. /// - [EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The text wrapping within the control bounds.")] + [EditorDisplay("Text Style"), EditorOrder(2025), Tooltip("The text wrapping within the control bounds.")] public TextWrapping Wrapping { get => _layout.TextWrapping; @@ -38,13 +76,13 @@ namespace FlaxEngine.GUI /// /// Gets or sets the font. /// - [EditorDisplay("Text Style"), EditorOrder(2024)] + [EditorDisplay("Text Style"), EditorOrder(2026)] public FontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// - [EditorDisplay("Text Style"), EditorOrder(2025), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")] + [EditorDisplay("Text Style"), EditorOrder(2027), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")] public MaterialBase TextMaterial { get; set; } /// @@ -98,19 +136,46 @@ namespace FlaxEngine.GUI /// public override Float2 GetTextSize() { - var font = Font.GetFont(); + var font = GetFont(); if (font == null) { return Float2.Zero; } - return font.MeasureText(_text, ref _layout); + return font.MeasureText(ConvertedText(), ref _layout); + } + + private Font GetFont() + { + Font font; + if (Bold) + font = Italic ? Font.GetBold().GetItalic().GetFont() : Font.GetBold().GetFont(); + else if (Italic) + font = Font.GetItalic().GetFont(); + else + font = Font.GetFont(); + return font; + } + + private string ConvertedText() + { + string text = _text; + switch (CaseOption) + { + case TextCaseOptions.Uppercase: + text = text.ToUpper(); + break; + case TextCaseOptions.Lowercase: + text = text.ToLower(); + break; + } + return text; } /// public override Float2 GetCharPosition(int index, out float height) { - var font = Font.GetFont(); + var font = GetFont(); if (font == null) { height = Height; @@ -118,19 +183,19 @@ namespace FlaxEngine.GUI } height = font.Height / DpiScale; - return font.GetCharPosition(_text, index, ref _layout); + return font.GetCharPosition(ConvertedText(), index, ref _layout); } /// public override int HitTestText(Float2 location) { - var font = Font.GetFont(); + var font = GetFont(); if (font == null) { return 0; } - return font.HitTestText(_text, location, ref _layout); + return font.HitTestText(ConvertedText(), location, ref _layout); } /// @@ -147,7 +212,7 @@ namespace FlaxEngine.GUI // Cache data var rect = new Rectangle(Float2.Zero, Size); bool enabled = EnabledInHierarchy; - var font = Font.GetFont(); + var font = GetFont(); if (!font) return; @@ -166,11 +231,13 @@ namespace FlaxEngine.GUI if (useViewOffset) Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset)); + var text = ConvertedText(); + // Check if sth is selected to draw selection if (HasSelection) { - var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); - var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); + var leftEdge = font.GetCharPosition(text, SelectionLeft, ref _layout); + var rightEdge = font.GetCharPosition(text, SelectionRight, ref _layout); var fontHeight = font.Height; var textHeight = fontHeight / DpiScale; @@ -207,12 +274,12 @@ namespace FlaxEngine.GUI } // Text or watermark - if (_text.Length > 0) + if (text.Length > 0) { var color = TextColor; if (!enabled) color *= 0.6f; - Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); + Render2D.DrawText(font, text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText)) { @@ -224,7 +291,25 @@ namespace FlaxEngine.GUI { float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f); alpha = alpha * alpha * alpha * alpha * alpha * alpha; - Render2D.FillRectangle(CaretBounds, CaretColor * alpha); + if (CaretPosition == 0) + { + var bounds = CaretBounds; + if (_layout.VerticalAlignment == TextAlignment.Center) + bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height * 0.5f - bounds.Height * 0.5f; + else if (_layout.VerticalAlignment == TextAlignment.Far) + bounds.Y = _layout.Bounds.Y + _layout.Bounds.Height - bounds.Height; + + if (_layout.HorizontalAlignment == TextAlignment.Center) + bounds.X = _layout.Bounds.X + _layout.Bounds.Width * 0.5f - bounds.Width * 0.5f; + else if (_layout.HorizontalAlignment == TextAlignment.Far) + bounds.X = _layout.Bounds.X + _layout.Bounds.Width - bounds.Width; + Render2D.FillRectangle(bounds, CaretColor * alpha); + } + else + { + Render2D.FillRectangle(CaretBounds, CaretColor * alpha); + } + } // Restore rendering state diff --git a/Source/Engine/UI/GUI/Panels/Panel.cs b/Source/Engine/UI/GUI/Panels/Panel.cs index 8c90b6544..6549f94da 100644 --- a/Source/Engine/UI/GUI/Panels/Panel.cs +++ b/Source/Engine/UI/GUI/Panels/Panel.cs @@ -17,6 +17,9 @@ namespace FlaxEngine.GUI private ScrollBars _scrollBars; private float _scrollBarsSize = ScrollBar.DefaultSize; private Margin _scrollMargin; + private Color _scrollbarTrackColor; + private Color _scrollbarThumbColor; + private Color _scrollbarThumbSelectedColor; /// /// The cached scroll area bounds. Used to scroll contents of the panel control. Cached during performing layout. @@ -49,7 +52,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the scroll bars usage by this panel. /// - [EditorOrder(0), Tooltip("The scroll bars usage.")] + [EditorDisplay("Scrollbar Style"), EditorOrder(1500), Tooltip("The scroll bars usage.")] public ScrollBars ScrollBars { get => _scrollBars; @@ -73,6 +76,12 @@ namespace FlaxEngine.GUI //VScrollBar.X += VScrollBar.Width; VScrollBar.ValueChanged += () => SetViewOffset(Orientation.Vertical, VScrollBar.Value); } + if (VScrollBar != null) + { + VScrollBar.TrackColor = _scrollbarTrackColor; + VScrollBar.ThumbColor = _scrollbarThumbColor; + VScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor; + } } else if (VScrollBar != null) { @@ -94,6 +103,12 @@ namespace FlaxEngine.GUI //HScrollBar.Offsets += new Margin(0, 0, HScrollBar.Height * 0.5f, 0); HScrollBar.ValueChanged += () => SetViewOffset(Orientation.Horizontal, HScrollBar.Value); } + if (HScrollBar != null) + { + HScrollBar.TrackColor = _scrollbarTrackColor; + HScrollBar.ThumbColor = _scrollbarThumbColor; + HScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor; + } } else if (HScrollBar != null) { @@ -108,7 +123,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the size of the scroll bars. /// - [EditorOrder(5), Tooltip("Scroll bars size.")] + [EditorDisplay("Scrollbar Style"), EditorOrder(1501), Tooltip("Scroll bars size.")] public float ScrollBarsSize { get => _scrollBarsSize; @@ -124,7 +139,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets a value indicating whether always show scrollbars. Otherwise show them only if scrolling is available. /// - [EditorOrder(10), Tooltip("Whether always show scrollbars. Otherwise show them only if scrolling is available.")] + [EditorDisplay("Scrollbar Style"), EditorOrder(1502), Tooltip("Whether always show scrollbars. Otherwise show them only if scrolling is available.")] public bool AlwaysShowScrollbars { get => _alwaysShowScrollbars; @@ -157,7 +172,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin. /// - [EditorOrder(20), Tooltip("Scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin.")] + [EditorDisplay("Scrollbar Style"), EditorOrder(1503), Tooltip("Scroll margin applies to the child controls area. Can be used to expand the scroll area bounds by adding a margin.")] public Margin ScrollMargin { get => _scrollMargin; @@ -171,6 +186,57 @@ namespace FlaxEngine.GUI } } + /// + /// The color of the scroll bar track. + /// + [EditorDisplay("Scrollbar Style"), EditorOrder(1600), ExpandGroups] + public Color ScrollbarTrackColor + { + get => _scrollbarTrackColor; + set + { + _scrollbarTrackColor = value; + if (VScrollBar != null) + VScrollBar.TrackColor = _scrollbarTrackColor; + if (HScrollBar != null) + HScrollBar.TrackColor = _scrollbarTrackColor; + } + } + + /// + /// The color of the scroll bar thumb. + /// + [EditorDisplay("Scrollbar Style"), EditorOrder(1601)] + public Color ScrollbarThumbColor + { + get => _scrollbarThumbColor; + set + { + _scrollbarThumbColor = value; + if (VScrollBar != null) + VScrollBar.ThumbColor = _scrollbarThumbColor; + if (HScrollBar != null) + HScrollBar.ThumbColor = _scrollbarThumbColor; + } + } + + /// + /// The color of the scroll bar thumb when selected. + /// + [EditorDisplay("Scrollbar Style"), EditorOrder(1602)] + public Color ScrollbarThumbSelectedColor + { + get => _scrollbarThumbSelectedColor; + set + { + _scrollbarThumbSelectedColor = value; + if (VScrollBar != null) + VScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor; + if (HScrollBar != null) + HScrollBar.ThumbSelectedColor = _scrollbarThumbSelectedColor; + } + } + /// /// Initializes a new instance of the class. /// @@ -187,6 +253,10 @@ namespace FlaxEngine.GUI public Panel(ScrollBars scrollBars, bool autoFocus = false) { AutoFocus = autoFocus; + var style = Style.Current; + _scrollbarTrackColor = style.BackgroundHighlighted; + _scrollbarThumbColor = style.BackgroundNormal; + _scrollbarThumbSelectedColor = style.BackgroundSelected; ScrollBars = scrollBars; } diff --git a/Source/Engine/UI/GUI/Panels/ScrollBar.cs b/Source/Engine/UI/GUI/Panels/ScrollBar.cs index e6064e881..bb45accf7 100644 --- a/Source/Engine/UI/GUI/Panels/ScrollBar.cs +++ b/Source/Engine/UI/GUI/Panels/ScrollBar.cs @@ -74,6 +74,21 @@ namespace FlaxEngine.GUI /// public bool EnableSmoothing { get; set; } = true; + /// + /// The track color. + /// + public Color TrackColor; + + /// + /// The thumb color. + /// + public Color ThumbColor; + + /// + /// The selected thumb color. + /// + public Color ThumbSelectedColor; + /// /// Gets or sets the minimum value. /// @@ -209,6 +224,10 @@ namespace FlaxEngine.GUI AutoFocus = false; _orientation = orientation; + var style = Style.Current; + TrackColor = style.BackgroundHighlighted; + ThumbColor = style.BackgroundNormal; + ThumbSelectedColor = style.BackgroundSelected; } /// @@ -377,8 +396,8 @@ namespace FlaxEngine.GUI base.Draw(); var style = Style.Current; - Render2D.FillRectangle(_trackRect, style.BackgroundHighlighted * _thumbOpacity); - Render2D.FillRectangle(_thumbRect, (_thumbClicked ? style.BackgroundSelected : style.BackgroundNormal) * _thumbOpacity); + Render2D.FillRectangle(_trackRect, TrackColor * _thumbOpacity); + Render2D.FillRectangle(_thumbRect, (_thumbClicked ? ThumbSelectedColor : ThumbColor) * _thumbOpacity); } /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index ef07bfa79..a7f80e152 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -234,11 +234,16 @@ namespace FlaxEngine.GUI Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Lerp(style.BackgroundSelected, style.Background, 0.6f)); Render2D.FillRectangle(new Rectangle(1.1f, 1.1f, Width - 2, Height - 2), style.Background); + // Padding for text + var textRect = GetClientArea(); + textRect.X += 5; + textRect.Width -= 10; + // Tooltip text Render2D.DrawText( style.FontMedium, _currentText, - GetClientArea(), + textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center, diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 22d78ab99..8640791f5 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -366,7 +366,7 @@ void TextRender::Draw(RenderContext& renderContext) DrawCall drawCall; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.ObjectRadius = _sphere.Radius; + drawCall.ObjectRadius = (float)_sphere.Radius; drawCall.Surface.GeometrySize = _localBox.GetSize(); drawCall.Surface.PrevWorld = _drawState.PrevWorld; drawCall.Surface.Lightmap = nullptr; diff --git a/Source/ThirdParty/recastnavigation/Recast.h b/Source/ThirdParty/recastnavigation/Recast.h index 9def8fd2a..4515b1fc5 100644 --- a/Source/ThirdParty/recastnavigation/Recast.h +++ b/Source/ThirdParty/recastnavigation/Recast.h @@ -196,6 +196,10 @@ protected: class rcScopedTimer { public: +#if 1 + // Disable timer functionality + inline rcScopedTimer(rcContext* ctx, const rcTimerLabel label) { } +#else /// Constructs an instance and starts the timer. /// @param[in] ctx The context to use. /// @param[in] label The category of the timer. @@ -209,6 +213,7 @@ private: rcContext* const m_ctx; const rcTimerLabel m_label; +#endif }; /// Specifies a configuration to use when performing Recast builds. diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 14e2e136e..ee1004213 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -98,7 +98,7 @@ namespace Flax.Build.Bindings { var result = NativeName; if (Parent != null && !(Parent is FileInfo)) - result = Parent.FullNameNative + '_' + result; + result = Parent.FullNameNative.Replace("::", "_") + '_' + result; return result; } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 3f7dc1e3a..a1ad9880d 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -267,13 +267,21 @@ namespace Flax.Build.Bindings if (value.Contains('(') && value.Contains(')')) return "new " + value; + // Special case for non-strongly typed enums + if (valueType != null && !value.Contains('.', StringComparison.Ordinal)) + { + apiType = FindApiTypeInfo(buildData, valueType, caller); + if (apiType is EnumInfo) + return $"{apiType.Name}.{value}"; + } + return value; } private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; - if (typeInfo?.Type == null) + if (typeInfo == null || typeInfo.Type == null) throw new ArgumentNullException(); // Use dynamic array as wrapper container for fixed-size native arrays @@ -1641,8 +1649,9 @@ namespace Flax.Build.Bindings if (internalType) { // Marshal blittable array elements back to original non-blittable elements - string originalElementTypeMarshaller = originalElementType + "Marshaller"; - string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; + string originalElementTypeMarshaller = $"{originalElementType}Marshaller"; + string originalElementTypeName = originalElementType.Substring(originalElementType.LastIndexOf('.') + 1); // Strip namespace + string internalElementType = $"{originalElementTypeMarshaller}.{originalElementTypeName}Internal"; toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 3bf3a2a2a..138291177 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1991,13 +1991,8 @@ namespace Flax.Build.Bindings } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); - if (buildData.Target.IsEditor && false) - contents.Append(" MMethod* method = nullptr;").AppendLine(); // TODO: find a better way to cache event method in editor and handle C# hot-reload - else - contents.Append(" static MMethod* method = nullptr;").AppendLine(); - contents.Append(" if (!method)").AppendLine(); - contents.AppendFormat(" method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); - contents.Append(" CHECK(method);").AppendLine(); + contents.Append(" static MMethod* method = nullptr;").AppendLine(); + contents.AppendFormat(" if (!method) {{ method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2}); CHECK(method); }}", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" MObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;");