diff --git a/Flax.flaxproj b/Flax.flaxproj index 0c2683afb..cc643346f 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,8 +2,8 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 0, - "Build": 6216 + "Minor": 1, + "Build": 6217 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 2cea89f08..7f19de195 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -250,6 +250,7 @@ namespace FlaxEditor.CustomEditors Selection.Clear(); Selection.Add(obj); + Selection.SetType(new ScriptType(obj.GetType())); OnSelectionChanged(); } @@ -271,6 +272,7 @@ namespace FlaxEditor.CustomEditors Selection.Clear(); Selection.AddRange(objectsArray); + Selection.SetType(new ScriptType(objectsArray.GetType())); OnSelectionChanged(); } @@ -284,6 +286,7 @@ namespace FlaxEditor.CustomEditors return; Selection.Clear(); + Selection.SetType(ScriptType.Null); OnSelectionChanged(); } diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs index a6aa34880..555ebc870 100644 --- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs @@ -22,10 +22,9 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { element = layout.ComboBox(); - element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; - - // Set layer names element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentLayers()); + element.ComboBox.SelectedIndex = (int)Values[0]; + element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; } private void GetActorsTree(List list, Actor a) diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index f8cecb801..49d3b5591 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors /// /// Gets the values type. /// - public ScriptType Type { get; } + public ScriptType Type { get; private set; } /// /// Gets a value indicating whether single object is selected. @@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors { if (_hasReferenceValue) { - if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject && referenceSceneObject.HasPrefabLink) { for (int i = 0; i < Count; i++) { if (this[i] == referenceSceneObject) continue; - if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) + if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) return true; } } @@ -251,6 +251,10 @@ namespace FlaxEditor.CustomEditors } if (instanceValues._hasReferenceValue) { + // If the reference value is set for the parent values but it's null object then skip it + if (instanceValues._referenceValue == null && !instanceValues.Type.IsValueType) + return; + _referenceValue = Info.GetValue(instanceValues._referenceValue); _hasReferenceValue = true; } @@ -280,6 +284,15 @@ namespace FlaxEditor.CustomEditors Type = type; } + /// + /// Sets the type. Use with caution. + /// + /// The type. + public void SetType(ScriptType type) + { + Type = type; + } + /// /// Gets the custom attributes defined for the values source member. /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 408dddbd6..5cada5d87 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -816,9 +816,16 @@ namespace FlaxEditor /// The bounding sphere. public static void GetActorEditorSphere(Actor actor, out BoundingSphere sphere) { - Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box); - BoundingSphere.FromBox(ref box, out sphere); - sphere.Radius = Math.Max(sphere.Radius, 15.0f); + if (actor) + { + Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box); + BoundingSphere.FromBox(ref box, out sphere); + sphere.Radius = Math.Max(sphere.Radius, 15.0f); + } + else + { + sphere = BoundingSphere.Empty; + } } /// @@ -828,7 +835,14 @@ namespace FlaxEditor /// The bounding box. public static void GetActorEditorBox(Actor actor, out BoundingBox box) { - Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box); + if (actor) + { + Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box); + } + else + { + box = BoundingBox.Zero; + } } /// @@ -1166,7 +1180,7 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { return true; } @@ -1179,7 +1193,7 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { pos = Vector2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale); } @@ -1199,7 +1213,7 @@ namespace FlaxEditor if (Windows.GameWin != null && Windows.GameWin.ContainsFocus) { var win = Windows.GameWin.Root; - if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused) + if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused) { pos = Vector2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale)); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 247844659..402374a8e 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -47,6 +47,11 @@ namespace FlaxEditor.GUI.ContextMenu private Window _window; private Control _previouslyFocused; + /// + /// Gets a value indicating whether use automatic popup direction fix based on the screen dimensions. + /// + protected virtual bool UseAutomaticDirectionFix => true; + /// /// Returns true if context menu is opened /// @@ -132,21 +137,24 @@ namespace FlaxEditor.GUI.ContextMenu Rectangle monitorBounds = Platform.GetMonitorBounds(locationSS); Vector2 rightBottomLocationSS = locationSS + dpiSize; bool isUp = false, isLeft = false; - if (monitorBounds.Bottom < rightBottomLocationSS.Y) + if (UseAutomaticDirectionFix) { - // Direction: up - isUp = true; - locationSS.Y -= dpiSize.Y; + if (monitorBounds.Bottom < rightBottomLocationSS.Y) + { + // Direction: up + isUp = true; + locationSS.Y -= dpiSize.Y; - // Offset to fix sub-menu location - if (parent is ContextMenu menu && menu._childCM != null) - locationSS.Y += 30.0f * dpiScale; - } - if (monitorBounds.Right < rightBottomLocationSS.X) - { - // Direction: left - isLeft = true; - locationSS.X -= dpiSize.X; + // Offset to fix sub-menu location + if (parent is ContextMenu menu && menu._childCM != null) + locationSS.Y += 30.0f * dpiScale; + } + if (monitorBounds.Right < rightBottomLocationSS.X) + { + // Direction: left + isLeft = true; + locationSS.X -= dpiSize.X; + } } // Update direction flag diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index 74f2d5ab0..f968c2501 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -124,7 +124,7 @@ namespace FlaxEditor.GUI.Docking throw new InvalidOperationException("Missing parent window."); var control = _tabsProxy != null ? (Control)_tabsProxy : this; var clientPos = control.PointToWindow(Vector2.Zero); - return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * RootWindow.DpiScale); + return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * DpiScale); } } diff --git a/Source/Editor/GUI/Popups/RenamePopup.cs b/Source/Editor/GUI/Popups/RenamePopup.cs index 834e620d9..c1da5dc3e 100644 --- a/Source/Editor/GUI/Popups/RenamePopup.cs +++ b/Source/Editor/GUI/Popups/RenamePopup.cs @@ -42,9 +42,6 @@ namespace FlaxEditor.GUI /// /// Gets or sets the initial value. /// - /// - /// The initial value. - /// public string InitialValue { get => _startValue; @@ -54,9 +51,6 @@ namespace FlaxEditor.GUI /// /// Gets or sets the input field text. /// - /// - /// The text. - /// public string Text { get => _inputField.Text; @@ -138,6 +132,9 @@ namespace FlaxEditor.GUI Hide(); } + /// + protected override bool UseAutomaticDirectionFix => false; + /// public override bool OnKeyDown(KeyboardKeys key) { diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index e0e4083c1..56f8620c6 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -1438,6 +1438,7 @@ namespace FlaxEditor.GUI.Timeline ArrangeTracks(); PerformLayout(true); UnlockChildrenRecursive(); + PerformLayout(true); Profiler.EndEvent(); ClearEditedFlag(); diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index afda8484e..f490de430 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -50,6 +50,41 @@ namespace FlaxEditor.Gizmo { } + /// + /// Helper function, recursively finds the Prefab Root of node or null. + /// + /// The node from which to start. + /// The prefab root or null. + public ActorNode GetPrefabRootInParent(ActorNode node) + { + if (!node.HasPrefabLink) + return null; + if (node.Actor.IsPrefabRoot) + return node; + if (node.ParentNode is ActorNode parAct) + return GetPrefabRootInParent(parAct); + return null; + } + + /// + /// Recursively walks up from the node up to ceiling node(inclusive) or selection(exclusive). + /// + /// The node from which to start + /// The ceiling(inclusive) + /// The node to select. + public ActorNode WalkUpAndFindActorNodeBeforeSelection(ActorNode node, ActorNode ceiling) + { + if (node == ceiling || _selection.Contains(node)) + return node; + if (node.ParentNode is ActorNode parentNode) + { + if (_selection.Contains(node.ParentNode)) + return node; + return WalkUpAndFindActorNodeBeforeSelection(parentNode, ceiling); + } + return node; + } + /// public override void Pick() { @@ -100,6 +135,16 @@ namespace FlaxEditor.Gizmo } } + // Select prefab root and then go down until you find the actual item in which case select the prefab root again + if (hit is ActorNode actorNode) + { + ActorNode prefabRoot = GetPrefabRootInParent(actorNode); + if (prefabRoot != null && actorNode != prefabRoot) + { + hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot); + } + } + bool addRemove = Owner.IsControlDown; bool isSelected = sceneEditing.Selection.Contains(hit); diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs index c1f7d1d0d..f8277999e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.Gizmo // Return arithmetic average or whatever it means return center / count; } - + private bool IntersectsRotateCircle(Vector3 normal, ref Ray ray, out float distance) { var plane = new Plane(Vector3.Zero, normal); @@ -51,7 +51,7 @@ namespace FlaxEditor.Gizmo Vector3.Transform(ref ray.Position, ref invGizmoWorld, out localRay.Position); // Find gizmo collisions with mouse - float closestintersection = float.MaxValue; + float closestIntersection = float.MaxValue; float intersection; _activeAxis = Axis.None; switch (_activeMode) @@ -59,42 +59,42 @@ namespace FlaxEditor.Gizmo case Mode.Translate: { // Axis boxes collision - if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection) + if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Quad planes collision - if (closestintersection >= float.MaxValue) - closestintersection = float.MinValue; - if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (closestIntersection >= float.MaxValue) + closestIntersection = float.MinValue; + if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.XY; - closestintersection = intersection; + closestIntersection = intersection; } - if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.ZX; - closestintersection = intersection; + closestIntersection = intersection; } - if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection) + if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection) { _activeAxis = Axis.YZ; - closestintersection = intersection; + closestIntersection = intersection; } break; @@ -103,20 +103,20 @@ namespace FlaxEditor.Gizmo case Mode.Rotate: { // Circles - if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestintersection) + if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Center @@ -132,27 +132,27 @@ namespace FlaxEditor.Gizmo case Mode.Scale: { // Spheres collision - if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.X; - closestintersection = intersection; + closestIntersection = intersection; } - if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Y; - closestintersection = intersection; + closestIntersection = intersection; } - if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Z; - closestintersection = intersection; + closestIntersection = intersection; } // Center - if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestintersection) + if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestIntersection) { _activeAxis = Axis.Center; - closestintersection = intersection; + closestIntersection = intersection; } break; diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 8c997bc2c..e123bd3f6 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -174,7 +174,7 @@ namespace FlaxEditor.Gizmo _axisAlignedWorld = _screenScaleMatrix * Matrix.CreateWorld(Position, Vector3.Backward, Vector3.Up); // Assign world - if (_activeTransformSpace == TransformSpace.World) + if (_activeTransformSpace == TransformSpace.World && _activeMode != Mode.Scale) { _gizmoWorld = _axisAlignedWorld; @@ -297,29 +297,6 @@ namespace FlaxEditor.Gizmo else if (_activeMode == Mode.Scale) { // Scale - if (_activeTransformSpace == TransformSpace.World && _activeAxis != Axis.Center) - { - var deltaLocal = delta; - Quaternion orientation = GetSelectedObject(0).Orientation; - delta = Vector3.Transform(delta, orientation); - - // Fix axis sign of delta movement for rotated object in some cases (eg. rotated object by 90 deg on Y axis and scale in world space with Red/X axis) - switch (_activeAxis) - { - case Axis.X: - if (deltaLocal.X < 0) - delta *= -1; - break; - case Axis.Y: - if (deltaLocal.Y < 0) - delta *= -1; - break; - case Axis.Z: - if (deltaLocal.Z < 0) - delta *= -1; - break; - } - } _scaleDelta = delta; } } diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 318d8379b..017cff3bf 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -263,6 +263,12 @@ namespace FlaxEditor.SceneGraph return _actor.IntersectsItself(ray.Ray, out distance, out normal); } + /// + public override void GetEditorSphere(out BoundingSphere sphere) + { + Editor.GetActorEditorSphere(_actor, out sphere); + } + /// public override void OnDebugDraw(ViewportDebugDrawData data) { @@ -315,5 +321,11 @@ namespace FlaxEditor.SceneGraph base.Dispose(); } + + /// + public override string ToString() + { + return _actor ? _actor.ToString() : base.ToString(); + } } } diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 01a832c5a..11c3b7709 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -309,6 +309,20 @@ namespace FlaxEditor.SceneGraph return false; } + /// + /// Gets the object bounding sphere (including child actors). + /// + /// The bounding sphere. + public virtual void GetEditorSphere(out BoundingSphere sphere) + { + sphere = new BoundingSphere(Transform.Translation, 15.0f); + for (int i = 0; i < ChildNodes.Count; i++) + { + ChildNodes[i].GetEditorSphere(out var childSphere); + BoundingSphere.Merge(ref sphere, ref childSphere, out sphere); + } + } + /// /// Called when selected nodes should draw debug shapes using interface. /// diff --git a/Source/Editor/Surface/ContextMenu/ContentFinder.cs b/Source/Editor/Surface/ContextMenu/ContentFinder.cs index 6089c2ca7..fc0f43472 100644 --- a/Source/Editor/Surface/ContextMenu/ContentFinder.cs +++ b/Source/Editor/Surface/ContextMenu/ContentFinder.cs @@ -108,7 +108,7 @@ namespace FlaxEditor.Surface.ContextMenu { _resultPanel.DisposeChildren(); - var dpiScale = RootWindow.DpiScale; + var dpiScale = DpiScale; if (items.Count == 0) { diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 2a460013c..6a8745fa6 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -578,6 +578,7 @@ namespace FlaxEditor.Surface.Elements BreakConnection(connectedBox); action.End(); Surface.Undo.AddAction(action); + Surface.MarkAsEdited(); } else { diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 996f9d2e0..9b96aa165 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -298,7 +298,6 @@ namespace FlaxEditor.Surface rerouteNode.GetBoxes().First(b => b.IsOutput).CreateConnection(inputBox); addConnectionsAction.End(); - Undo.AddAction(new MultiUndoAction(spawnNodeAction, disconnectBoxesAction, addConnectionsAction)); } else @@ -565,11 +564,10 @@ namespace FlaxEditor.Surface if (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; - Box toSelect = (key == KeyboardKeys.ArrowUp) ? - selectedBox?.ParentNode.GetPreviousBox(selectedBox) : - selectedBox?.ParentNode.GetNextBox(selectedBox); + Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox); if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput) { @@ -581,10 +579,12 @@ namespace FlaxEditor.Surface if (key == KeyboardKeys.Tab) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; int connectionCount = selectedBox.Connections.Count; - if (connectionCount == 0) return true; + if (connectionCount == 0) + return true; if (Root.GetKey(KeyboardKeys.Shift)) { @@ -596,11 +596,11 @@ namespace FlaxEditor.Surface } } - if (key == KeyboardKeys.ArrowRight || key == KeyboardKeys.ArrowLeft) { Box selectedBox = GetSelectedBox(SelectedNodes); - if (selectedBox == null) return true; + if (selectedBox == null) + return true; Box toSelect = null; @@ -633,7 +633,6 @@ namespace FlaxEditor.Surface { Select(toSelect.ParentNode); toSelect.ParentNode.SelectBox(toSelect); - } return true; } @@ -825,10 +824,7 @@ namespace FlaxEditor.Surface xLocation += -120 - distanceBetweenNodes.X; } - return new Vector2( - xLocation, - yLocation - ); + return new Vector2(xLocation, yLocation); } private bool IntersectsConnection(Vector2 mousePosition, out InputBox inputBox, out OutputBox outputBox) diff --git a/Source/Editor/Viewport/Cameras/ArcBallCamera.cs b/Source/Editor/Viewport/Cameras/ArcBallCamera.cs index 876adc796..e6a72c8b4 100644 --- a/Source/Editor/Viewport/Cameras/ArcBallCamera.cs +++ b/Source/Editor/Viewport/Cameras/ArcBallCamera.cs @@ -94,6 +94,15 @@ namespace FlaxEditor.Viewport.Cameras Viewport.ViewPosition = _orbitCenter + localPosition; } + /// + public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + { + base.SetArcBallView(orientation, orbitCenter, orbitRadius); + + _orbitCenter = orbitCenter; + _orbitRadius = orbitRadius; + } + /// public override void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse) { diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index e60def838..22abccf69 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -88,7 +88,7 @@ namespace FlaxEditor.Viewport.Cameras Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); ShowSphere(ref sphere); } - + /// /// Moves the viewport to visualize selected actors. /// @@ -103,48 +103,46 @@ namespace FlaxEditor.Viewport.Cameras /// /// Moves the viewport to visualize selected actors. /// - /// The actors to show. - public void ShowActors(List actors) + /// The actors to show. + public void ShowActors(List selection) { - if (actors.Count == 0) + if (selection.Count == 0) return; BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < actors.Count; i++) + for (int i = 0; i < selection.Count; i++) { - if (actors[i] is ActorNode actor) - { - Editor.GetActorEditorSphere(actor.Actor, out BoundingSphere sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); } + if (mergesSphere == BoundingSphere.Empty) + return; ShowSphere(ref mergesSphere); } /// /// Moves the viewport to visualize selected actors. /// - /// The actors to show. + /// The actors to show. /// The used orientation. - public void ShowActors(List actors, ref Quaternion orientation) + public void ShowActors(List selection, ref Quaternion orientation) { - if (actors.Count == 0) + if (selection.Count == 0) return; BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < actors.Count; i++) + for (int i = 0; i < selection.Count; i++) { - if (actors[i] is ActorNode actor) - { - Editor.GetActorEditorSphere(actor.Actor, out BoundingSphere sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); } + if (mergesSphere == BoundingSphere.Empty) + return; ShowSphere(ref mergesSphere, ref orientation); } - + private void ShowSphere(ref BoundingSphere sphere) { var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); @@ -154,18 +152,27 @@ namespace FlaxEditor.Viewport.Cameras private void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) { Vector3 position; - if (Viewport.UseOrthographicProjection) { position = sphere.Center + Vector3.Backward * orientation * (sphere.Radius * 5.0f); Viewport.OrthographicScale = Vector3.Distance(position, sphere.Center) / 1000; } else + { position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); - TargetPoint = position; + } + TargetPoint = sphere.Center; MoveViewport(position, orientation); } - + + /// + public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + { + base.SetArcBallView(orientation, orbitCenter, orbitRadius); + + TargetPoint = orbitCenter; + } + /// public override void Update(float deltaTime) { @@ -204,12 +211,12 @@ namespace FlaxEditor.Viewport.Cameras Viewport.GetInput(out var input); Viewport.GetPrevInput(out var prevInput); - var mainViewport = Viewport as MainEditorGizmoViewport; - bool isUsingGizmo = mainViewport != null && mainViewport.TransformGizmo.ActiveAxis != TransformGizmoBase.Axis.None; + var transformGizmo = (Viewport as EditorGizmoViewport)?.Gizmos.Active as TransformGizmoBase; + var isUsingGizmo = transformGizmo != null && transformGizmo.ActiveAxis != TransformGizmoBase.Axis.None; // Get current view properties - float yaw = Viewport.Yaw; - float pitch = Viewport.Pitch; + var yaw = Viewport.Yaw; + var pitch = Viewport.Pitch; var position = Viewport.ViewPosition; var rotation = Viewport.ViewOrientation; @@ -263,7 +270,7 @@ namespace FlaxEditor.Viewport.Cameras position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f); if (input.IsAltDown) { - position += forward * (Viewport.MouseSpeed * 40 * Viewport.MouseDeltaRight.ValuesSum); + position += forward * (Viewport.MouseSpeed * 40 * Viewport.MousePositionDelta.ValuesSum); } } @@ -271,7 +278,7 @@ namespace FlaxEditor.Viewport.Cameras if (input.IsOrbiting && isUsingGizmo) { centerMouse = false; - Viewport.ViewPosition += mainViewport.TransformGizmo.LastDelta.Translation; + Viewport.ViewPosition += transformGizmo.LastDelta.Translation; return; } @@ -280,7 +287,7 @@ namespace FlaxEditor.Viewport.Cameras Viewport.Pitch = pitch; if (input.IsOrbiting) { - float orbitRadius = Vector3.Distance(ref position, ref TargetPoint); + float orbitRadius = Mathf.Max(Vector3.Distance(ref position, ref TargetPoint), 0.0001f); Vector3 localPosition = Viewport.ViewDirection * (-1 * orbitRadius); Viewport.ViewPosition = TargetPoint + localPosition; } diff --git a/Source/Editor/Viewport/Cameras/ViewportCamera.cs b/Source/Editor/Viewport/Cameras/ViewportCamera.cs index 85b7a917c..6564d16d0 100644 --- a/Source/Editor/Viewport/Cameras/ViewportCamera.cs +++ b/Source/Editor/Viewport/Cameras/ViewportCamera.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Cameras /// The view rotation. /// The orbit center location. /// The orbit radius. - public void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + public virtual void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) { // Rotate Viewport.ViewOrientation = orientation; diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index ae2db1e9b..93b2c7fbe 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root); /// - public Vector2 MouseDelta => _mouseDeltaLeft * 1000; + public Vector2 MouseDelta => _mouseDelta * 1000; /// public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 562381805..7e1c3d887 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -140,7 +140,7 @@ namespace FlaxEditor.Viewport private bool _isControllingMouse; private int _deltaFilteringStep; private Vector2 _startPos; - private Vector2 _mouseDeltaRightLast; + private Vector2 _mouseDeltaLast; private Vector2[] _deltaFilteringBuffer = new Vector2[FpsCameraFilteringFrames]; /// @@ -159,14 +159,9 @@ namespace FlaxEditor.Viewport protected Vector2 _viewMousePos; /// - /// The mouse delta (right button down). + /// The mouse position delta. /// - protected Vector2 _mouseDeltaRight; - - /// - /// The mouse delta (left button down). - /// - protected Vector2 _mouseDeltaLeft; + protected Vector2 _mouseDelta; // Camera @@ -213,14 +208,9 @@ namespace FlaxEditor.Viewport } /// - /// Gets the mouse movement delta for the right button (user press and move). + /// Gets the mouse movement position delta (user press and move). /// - public Vector2 MouseDeltaRight => _mouseDeltaRight; - - /// - /// Gets the mouse movement delta for the left button (user press and move). - /// - public Vector2 MouseDeltaLeft => _mouseDeltaLeft; + public Vector2 MousePositionDelta => _mouseDelta; /// /// Camera's pitch angle clamp range (in degrees). @@ -875,9 +865,14 @@ namespace FlaxEditor.Viewport win.StartTrackingMouse(false); win.Cursor = CursorType.Hidden; - // Center mouse position - //_viewMousePos = Center; - //win.MousePosition = PointToWindow(_viewMousePos); + // Center mouse position if it's too close to the edge + var size = Size; + var center = size * 0.5f; + if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f) + { + _viewMousePos = center; + win.MousePosition = PointToWindow(_viewMousePos); + } } /// @@ -1018,6 +1013,7 @@ namespace FlaxEditor.Viewport if (_isControllingMouse) { var rmbWheel = false; + // Gather input { bool isAltDown = _input.IsAltDown; @@ -1099,23 +1095,21 @@ namespace FlaxEditor.Viewport moveDelta *= 0.3f; // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - _startPos; if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel) { offset = Vector2.Zero; } - offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); - _mouseDeltaRight = offset / size; - _mouseDeltaRight.Y *= size.Y / size.X; + _mouseDelta = offset / size; + _mouseDelta.Y *= size.Y / size.X; Vector2 mouseDelta = Vector2.Zero; if (_useMouseFiltering) { // Update delta filtering buffer - _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDeltaRight; + _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta; _deltaFilteringStep++; // If the step is too far, zero @@ -1129,14 +1123,16 @@ namespace FlaxEditor.Viewport mouseDelta /= FpsCameraFilteringFrames; } else - mouseDelta = _mouseDeltaRight; + { + mouseDelta = _mouseDelta; + } if (_useMouseAcceleration) { // Accelerate the delta var currentDelta = mouseDelta; - mouseDelta += _mouseDeltaRightLast * _mouseAccelerationScale; - _mouseDeltaRightLast = currentDelta; + mouseDelta += _mouseDeltaLast * _mouseAccelerationScale; + _mouseDeltaLast = currentDelta; } // Update @@ -1161,7 +1157,20 @@ namespace FlaxEditor.Viewport } else { - _mouseDeltaRight = _mouseDeltaRightLast = Vector2.Zero; + if (_input.IsMouseLeftDown || _input.IsMouseRightDown) + { + // Calculate smooth mouse delta not dependant on viewport size + Vector2 offset = _viewMousePos - _startPos; + offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); + offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); + _mouseDelta = offset / size; + _startPos = _viewMousePos; + } + else + { + _mouseDelta = Vector2.Zero; + } + _mouseDeltaLast = Vector2.Zero; if (ContainsFocus) { @@ -1198,19 +1207,6 @@ namespace FlaxEditor.Viewport UpdateView(dt, ref moveDelta, ref mouseDelta, out _); } } - if (_input.IsMouseLeftDown) - { - // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - _startPos; - offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); - offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); - _mouseDeltaLeft = offset / size; - _startPos = _viewMousePos; - } - else - { - _mouseDeltaLeft = Vector2.Zero; - } _input.MouseWheelDelta = 0; } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a0f2eb044..e79b01313 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -797,6 +797,8 @@ namespace FlaxEditor.Viewport return true; if (assetItem.IsOfType()) return true; + if (assetItem.IsOfType()) + return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) @@ -860,6 +862,13 @@ namespace FlaxEditor.Viewport return location; } + private void Spawn(Actor actor, ref Vector3 hitLocation) + { + actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); + Editor.Instance.SceneEditing.Spawn(actor); + Focus(); + } + private void Spawn(AssetItem item, SceneGraphNode hit, ref Vector2 location, ref Vector3 hitLocation) { if (item is AssetItem assetItem) @@ -872,8 +881,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, ParticleSystem = asset }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -884,8 +892,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Animation = asset }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -921,8 +928,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, SkinnedModel = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -933,8 +939,18 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Model = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); + return; + } + if (assetItem.IsOfType()) + { + var collisionData = FlaxEngine.Content.LoadAsync(item.ID); + var actor = new MeshCollider + { + Name = item.ShortName, + CollisionData = collisionData + }; + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -945,8 +961,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Clip = clip }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -954,8 +969,7 @@ namespace FlaxEditor.Viewport var prefab = FlaxEngine.Content.LoadAsync(item.ID); var actor = PrefabManager.SpawnPrefab(prefab, null); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (assetItem.IsOfType()) @@ -967,8 +981,7 @@ namespace FlaxEditor.Viewport { var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); return; } } @@ -983,8 +996,7 @@ namespace FlaxEditor.Viewport return; } actor.Name = item.Name; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Editor.Instance.SceneEditing.Spawn(actor); + Spawn(actor, ref hitLocation); } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 89c42dc95..a9ee39b8c 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -342,7 +342,7 @@ namespace FlaxEditor.Viewport public bool SnapToGround => false; /// - public Vector2 MouseDelta => _mouseDeltaLeft * 1000; + public Vector2 MouseDelta => _mouseDelta * 1000; /// public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); @@ -576,7 +576,7 @@ namespace FlaxEditor.Viewport // Selected UI controls outline for (var i = 0; i < _window.Selection.Count; i++) { - if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(this, Vector2.Zero), control.PointToParent(this, control.Size)); @@ -682,10 +682,14 @@ namespace FlaxEditor.Viewport return true; if (assetItem.IsOfType()) return true; + if (assetItem.IsOfType()) + return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; + if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) + return true; } return false; @@ -746,8 +750,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, ParticleSystem = particleSystem }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(MaterialBase).IsAssignableFrom(binaryAssetItem.Type)) @@ -773,8 +776,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, SkinnedModel = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(Model).IsAssignableFrom(binaryAssetItem.Type)) @@ -785,8 +787,18 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Model = model }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); + return; + } + if (binaryAssetItem.IsOfType()) + { + var collisionData = FlaxEngine.Content.LoadAsync(item.ID); + var actor = new MeshCollider + { + Name = item.ShortName, + CollisionData = collisionData + }; + Spawn(actor, ref hitLocation); return; } if (typeof(AudioClip).IsAssignableFrom(binaryAssetItem.Type)) @@ -797,8 +809,7 @@ namespace FlaxEditor.Viewport Name = item.ShortName, Clip = clip }; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } if (typeof(Prefab).IsAssignableFrom(binaryAssetItem.Type)) @@ -806,16 +817,24 @@ namespace FlaxEditor.Viewport var prefab = FlaxEngine.Content.LoadAsync(item.ID); var actor = PrefabManager.SpawnPrefab(prefab, null); actor.Name = item.ShortName; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); return; } } + if (item is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) + { + var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); + actor.Name = item.ShortName; + Spawn(actor, ref hitLocation); + return; + } } - private void Spawn(Actor actor) + private void Spawn(Actor actor, ref Vector3 hitLocation) { + actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); _window.Spawn(actor); + Focus(); } private void Spawn(ScriptType item, SceneGraphNode hit, ref Vector3 hitLocation) @@ -827,8 +846,7 @@ namespace FlaxEditor.Viewport return; } actor.Name = item.Name; - actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation); - Spawn(actor); + Spawn(actor, ref hitLocation); } /// diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 6f6103a5d..697a7c1c1 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -249,6 +249,19 @@ namespace FlaxEditor.Viewport.Previews } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.F: + // Pay respect.. + ViewportCamera.SetArcBallView(_previewModel.Box); + break; + } + return base.OnKeyDown(key); + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index 7055edbe9..3f285ddcd 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -85,6 +85,19 @@ namespace FlaxEditor.Viewport.Previews } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.F: + // Pay respect.. + ViewportCamera.SetArcBallView(_previewModel.Box); + break; + } + return base.OnKeyDown(key); + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index 066e98456..09ff34d0d 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -121,6 +121,18 @@ namespace FlaxEditor.Viewport.Previews { } + /// + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (_instance != null) + { + // Link UI canvases to the preview (eg. after canvas added to the prefab) + LinkCanvas(_instance); + } + } + /// public override void OnDestroy() { diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 929e81b61..b601f3786 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -107,7 +107,7 @@ namespace FlaxEditor.Windows.Assets { private readonly ParticleSystemWindow _window; - [EditorDisplay("Particle System"), EditorOrder(100), Limit(1), Tooltip("The timeline animation duration in frames.")] + [EditorDisplay("Particle System"), EditorOrder(-100), Limit(1), Tooltip("The timeline animation duration in frames.")] public int TimelineDurationFrames { get => _window.Timeline.DurationFrames; @@ -124,7 +124,7 @@ namespace FlaxEditor.Windows.Assets /// The proxy object for editing particle system track properties. /// [CustomEditor(typeof(EmitterTrackProxyEditor))] - private class EmitterTrackProxy + private class EmitterTrackProxy : GeneralProxy { private readonly ParticleSystemWindow _window; private readonly ParticleEffect _effect; @@ -171,6 +171,7 @@ namespace FlaxEditor.Windows.Assets } public EmitterTrackProxy(ParticleSystemWindow window, ParticleEffect effect, ParticleEmitterTrack track, int emitterIndex) + : base(window) { _window = window; _effect = effect; @@ -238,7 +239,7 @@ namespace FlaxEditor.Windows.Assets /// /// The proxy object for editing folder track properties. /// - private class FolderTrackProxy + private class FolderTrackProxy : GeneralProxy { private readonly FolderTrack _track; @@ -265,7 +266,8 @@ namespace FlaxEditor.Windows.Assets set => _track.IconColor = value; } - public FolderTrackProxy(FolderTrack track) + public FolderTrackProxy(ParticleSystemWindow window, FolderTrack track) + : base(window) { _track = track; } @@ -275,8 +277,7 @@ namespace FlaxEditor.Windows.Assets private readonly SplitPanel _split2; private ParticleSystemTimeline _timeline; private readonly ParticleSystemPreview _preview; - private readonly CustomEditorPresenter _propertiesEditor1; - private readonly CustomEditorPresenter _propertiesEditor2; + private readonly CustomEditorPresenter _propertiesEditor; private ToolStripButton _saveButton; private ToolStripButton _undoButton; private ToolStripButton _redoButton; @@ -347,18 +348,12 @@ namespace FlaxEditor.Windows.Assets _timeline.Modified += OnTimelineModified; _timeline.SelectionChanged += OnTimelineSelectionChanged; - // Properties editor (general) - var propertiesEditor1 = new CustomEditorPresenter(null, string.Empty); - propertiesEditor1.Panel.Parent = _split2.Panel2; - propertiesEditor1.Modified += OnParticleSystemPropertyEdited; - _propertiesEditor1 = propertiesEditor1; - propertiesEditor1.Select(new GeneralProxy(this)); - - // Properties editor (selection) - var propertiesEditor2 = new CustomEditorPresenter(null, string.Empty); - propertiesEditor2.Panel.Parent = _split2.Panel2; - propertiesEditor2.Modified += OnParticleSystemPropertyEdited; - _propertiesEditor2 = propertiesEditor2; + // Properties editor + var propertiesEditor = new CustomEditorPresenter(_undo, string.Empty); + propertiesEditor.Panel.Parent = _split2.Panel2; + propertiesEditor.Modified += OnParticleSystemPropertyEdited; + _propertiesEditor = propertiesEditor; + propertiesEditor.Select(new GeneralProxy(this)); // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save"); @@ -380,8 +375,7 @@ namespace FlaxEditor.Windows.Assets if (!_isEditingInstancedParameterValue) { - _propertiesEditor1.BuildLayoutOnUpdate(); - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); } } @@ -399,7 +393,7 @@ namespace FlaxEditor.Windows.Assets { if (_timeline.SelectedTracks.Count == 0) { - _propertiesEditor2.Deselect(); + _propertiesEditor.Select(new GeneralProxy(this)); return; } @@ -414,14 +408,14 @@ namespace FlaxEditor.Windows.Assets } else if (track is FolderTrack folderTrack) { - tracks[i] = new FolderTrackProxy(folderTrack); + tracks[i] = new FolderTrackProxy(this, folderTrack); } else { throw new NotImplementedException("Invalid track type."); } } - _propertiesEditor2.Select(tracks); + _propertiesEditor.Select(tracks); } private void OnParticleSystemPropertyEdited() @@ -443,8 +437,7 @@ namespace FlaxEditor.Windows.Assets if (_timeline.IsModified) { - _propertiesEditor1.BuildLayoutOnUpdate(); - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); _timeline.Save(_asset); } @@ -484,7 +477,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - _propertiesEditor2.Deselect(); + _propertiesEditor.Deselect(); _preview.System = null; _isWaitingForTimelineLoad = false; @@ -507,7 +500,7 @@ namespace FlaxEditor.Windows.Assets if (_parametersVersion != _preview.PreviewActor.ParametersVersion) { _parametersVersion = _preview.PreviewActor.ParametersVersion; - _propertiesEditor2.BuildLayoutOnUpdate(); + _propertiesEditor.BuildLayoutOnUpdate(); } base.Update(deltaTime); @@ -534,8 +527,7 @@ namespace FlaxEditor.Windows.Assets // Setup _undo.Clear(); _timeline.Enabled = true; - _propertiesEditor1.BuildLayout(); - _propertiesEditor2.Deselect(); + _propertiesEditor.Select(new GeneralProxy(this)); ClearEditedFlag(); } @@ -580,8 +572,7 @@ namespace FlaxEditor.Windows.Assets { if (_undo != null) _undo.Enabled = false; - _propertiesEditor1?.Deselect(); - _propertiesEditor2?.Deselect(); + _propertiesEditor?.Deselect(); _undo?.Clear(); _undo = null; diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 2968b0645..5478cfbef 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -296,7 +296,7 @@ namespace FlaxEditor.Windows // Selected UI controls outline for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++) { - if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Vector2.Zero), control.PointToParent(_viewport, control.Size)); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 1a26318a1..2497dfe3e 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -451,7 +451,69 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu // Get parameter int32 paramIndex; const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex); - value = param ? _data->Parameters[paramIndex].Value : Value::Null; + if (param) + { + value = _data->Parameters[paramIndex].Value; + switch (param->Type.Type) + { + case VariantType::Vector2: + switch (box->ID) + { + case 1: + case 2: + value = value.AsVector2().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Vector3: + switch (box->ID) + { + case 1: + case 2: + case 3: + value = value.AsVector3().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Vector4: + case VariantType::Color: + switch (box->ID) + { + case 1: + case 2: + case 3: + case 4: + value = value.AsVector4().Raw[box->ID - 1]; + break; + } + break; + case VariantType::Matrix: + { + auto& matrix = value.Type.Type == VariantType::Matrix && value.AsBlob.Data ? *(Matrix*)value.AsBlob.Data : Matrix::Identity; + switch (box->ID) + { + case 0: + value = matrix.GetRow1(); + break; + case 1: + value = matrix.GetRow2(); + break; + case 2: + value = matrix.GetRow3(); + break; + case 3: + value = matrix.GetRow4(); + break; + } + break; + } + } + } + else + { + // TODO: add warning that no parameter selected + value = Value::Zero; + } break; } default: diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index ea24d126f..784eebb28 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -1132,6 +1132,6 @@ void SceneAnimationPlayer::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Audio/AudioListener.cpp b/Source/Engine/Audio/AudioListener.cpp index 40e7bac3e..e9b1a11ab 100644 --- a/Source/Engine/Audio/AudioListener.cpp +++ b/Source/Engine/Audio/AudioListener.cpp @@ -63,7 +63,7 @@ void AudioListener::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (IsActiveInHierarchy()) diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index b07ea95ee..8969bc4d3 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -461,7 +461,7 @@ void AudioSource::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (IsActiveInHierarchy() && SourceIDs.HasItems()) diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index d7932e1b1..439b4341f 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -6,7 +6,7 @@ #include "../Types/String.h" const BoundingBox BoundingBox::Empty(Vector3(MAX_float), Vector3(MIN_float)); -const BoundingBox BoundingBox::Zero(Vector3(0.0f), Vector3(0.0f)); +const BoundingBox BoundingBox::Zero(Vector3(0.0f)); String BoundingBox::ToString() const { diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h index f669370ff..8a349e022 100644 --- a/Source/Engine/Core/Math/BoundingBox.h +++ b/Source/Engine/Core/Math/BoundingBox.h @@ -45,6 +45,16 @@ public: { } + /// + /// Initializes a new instance of the struct. + /// + /// The location of the empty bounding box. + BoundingBox(const Vector3& point) + : Minimum(point) + , Maximum(point) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Math/CollisionsHelper.cs b/Source/Engine/Core/Math/CollisionsHelper.cs index 7dd660587..d6e0392c8 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cs +++ b/Source/Engine/Core/Math/CollisionsHelper.cs @@ -125,7 +125,7 @@ namespace FlaxEngine public static class CollisionsHelper { /// - /// Determines the closest point between a point and a line. + /// Determines the closest point between a point and a line segment. /// /// The point to test. /// The line first point. diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index c0b84ca04..bc193a034 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -197,6 +197,16 @@ namespace FlaxEngine /// public float ValuesSum => X + Y; + /// + /// Gets a vector with values being absolute values of that vector. + /// + public Vector2 Absolute => new Vector2(Mathf.Abs(X), Mathf.Abs(Y)); + + /// + /// Gets a vector with values being opposite to values of that vector. + /// + public Vector2 Negative => new Vector2(-X, -Y); + /// /// Gets or sets the component at the specified index. /// diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 99430072d..2a62df067 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -227,6 +227,16 @@ namespace FlaxEngine /// public float ValuesSum => X + Y + Z + W; + /// + /// Gets a vector with values being absolute values of that vector. + /// + public Vector4 Absolute => new Vector4(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W)); + + /// + /// Gets a vector with values being opposite to values of that vector. + /// + public Vector4 Negative => new Vector4(-X, -Y, -Z, -W); + /// /// Gets or sets the component at the specified index. /// diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 709997eac..979a2547b 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -413,24 +413,6 @@ bool Engine::HasGameViewportFocus() #endif } -Vector2 Engine::ScreenToGameViewport(const Vector2& screenPos) -{ -#if USE_EDITOR - return Editor::Managed->ScreenToGameViewport(screenPos); -#else - return MainWindow ? MainWindow->ScreenToClient(screenPos) : Vector2::Minimum; -#endif -} - -Vector2 Engine::GameViewportToScreen(const Vector2& viewportPos) -{ -#if USE_EDITOR - return Editor::Managed->GameViewportToScreen(viewportPos); -#else - return MainWindow ? MainWindow->ClientToScreen(viewportPos) : Vector2::Minimum; -#endif -} - void Engine::OnPause() { LOG(Info, "App paused"); diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 22aeac9f0..13f3e1fe8 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -153,20 +153,6 @@ public: /// True if game viewport is focused, otherwise false. static bool HasGameViewportFocus(); - /// - /// Converts the screen-space position to the game viewport position. - /// - /// The screen-space position. - /// The game viewport position. - static Vector2 ScreenToGameViewport(const Vector2& screenPos); - - /// - /// Converts the game viewport position to the screen-space position. - /// - /// The game viewport position. - /// The screen-space position. - static Vector2 GameViewportToScreen(const Vector2& viewportPos); - private: static void OnPause(); diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 999dc28cb..11db5dd42 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -66,6 +66,26 @@ void Screen::SetSize(const Vector2& value) Size = value; } +Vector2 Screen::ScreenToGameViewport(const Vector2& screenPos) +{ +#if USE_EDITOR + return Editor::Managed->ScreenToGameViewport(screenPos); +#else + auto win = Engine::MainWindow; + return win ? win->ScreenToClient(screenPos) : Vector2::Minimum; +#endif +} + +Vector2 Screen::GameViewportToScreen(const Vector2& viewportPos) +{ +#if USE_EDITOR + return Editor::Managed->GameViewportToScreen(viewportPos); +#else + auto win = Engine::MainWindow; + return win ? win->ClientToScreen(viewportPos) : Vector2::Minimum; +#endif +} + bool Screen::GetCursorVisible() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 88a44da43..4fa39a324 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -33,6 +33,20 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// The value API_PROPERTY() static Vector2 GetSize(); + /// + /// Converts the screen-space position to the game viewport position. + /// + /// The screen-space position. + /// The game viewport position. + API_FUNCTION() static Vector2 ScreenToGameViewport(const Vector2& screenPos); + + /// + /// Converts the game viewport position to the screen-space position. + /// + /// The game viewport position. + /// The screen-space position. + API_FUNCTION() static Vector2 GameViewportToScreen(const Vector2& viewportPos); + /// /// Sets the window size. /// diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 485d5a891..d7796691b 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -339,14 +339,14 @@ bool Input::GetKeyUp(const KeyboardKeys key) Vector2 Input::GetMousePosition() { - return Mouse ? Engine::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum; + return Mouse ? Screen::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum; } void Input::SetMousePosition(const Vector2& position) { if (Mouse && Engine::HasGameViewportFocus()) { - const auto pos = Engine::GameViewportToScreen(position); + const auto pos = Screen::GameViewportToScreen(position); if (pos > Vector2::Minimum) Mouse->SetMousePosition(pos); } diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 3753f6140..8ab195ef1 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -203,6 +203,13 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa LOG(Error, "Editing scene hierarchy is only allowed on a main thread."); return; } +#if USE_EDITOR || !BUILD_RELEASE + if (Is()) + { + LOG(Error, "Cannot change parent of the Scene. Use Level to manage scenes."); + return; + } +#endif // Peek the previous state const Transform prevTransform = _transform; @@ -945,28 +952,34 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE_MEMBER(Name, _name); DESERIALIZE_MEMBER(Transform, _localTransform); - Guid parentId = Guid::Empty; - DESERIALIZE_MEMBER(ParentID, parentId); - const auto parent = Scripting::FindObject(parentId); - if (_parent != parent) { - if (IsDuringPlay()) + const auto member = SERIALIZE_FIND_MEMBER(stream, "ParentID"); + if (member != stream.MemberEnd()) { - SetParent(parent, false, false); + Guid parentId; + Serialization::Deserialize(member->value, parentId, modifier); + const auto parent = Scripting::FindObject(parentId); + if (_parent != parent) + { + if (IsDuringPlay()) + { + SetParent(parent, false, false); + } + else + { + if (_parent) + _parent->Children.RemoveKeepOrder(this); + _parent = parent; + if (_parent) + _parent->Children.Add(this); + OnParentChanged(); + } + } + else if (!parent && parentId.IsValid()) + { + LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); + } } - else - { - if (_parent) - _parent->Children.RemoveKeepOrder(this); - _parent = parent; - if (_parent) - _parent->Children.Add(this); - OnParentChanged(); - } - } - else if (!parent && parentId.IsValid()) - { - LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); } // StaticFlags update - added StaticFlags::Navigation @@ -1133,20 +1146,28 @@ BoundingBox Actor::GetBoxWithChildren() const #if USE_EDITOR +BoundingBox Actor::GetEditorBox() const +{ + return GetBox(); +} + BoundingBox Actor::GetEditorBoxChildren() const { BoundingBox result = GetEditorBox(); - for (int32 i = 0; i < Children.Count(); i++) { BoundingBox::Merge(result, Children[i]->GetEditorBoxChildren(), result); } - return result; } #endif +bool Actor::HasContentLoaded() const +{ + return true; +} + void Actor::UnregisterObjectHierarchy() { if (IsRegistered()) @@ -1164,6 +1185,10 @@ void Actor::UnregisterObjectHierarchy() } } +void Actor::Draw(RenderContext& renderContext) +{ +} + void Actor::DrawGeneric(RenderContext& renderContext) { // Generic drawing uses only GBuffer Fill Pass and simple frustum culling (see SceneRendering for more optimized drawing) diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 69d4c8681..233ebe04c 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -629,10 +629,7 @@ public: /// /// Gets actor bounding box (single actor, no children included) for editor tools. /// - API_PROPERTY() virtual BoundingBox GetEditorBox() const - { - return GetBox(); - } + API_PROPERTY() virtual BoundingBox GetEditorBox() const; /// /// Gets actor bounding box of the actor including all child actors for editor tools. @@ -644,10 +641,7 @@ public: /// /// Returns true if actor has loaded content. /// - API_PROPERTY() virtual bool HasContentLoaded() const - { - return true; - } + API_PROPERTY() virtual bool HasContentLoaded() const; /// /// Calls UnregisterObject for all objects in the actor hierarchy. @@ -660,9 +654,7 @@ public: /// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. models are rendered during all passed but other actors are invoked only during GBufferFill pass). /// /// The rendering context. - virtual void Draw(RenderContext& renderContext) - { - } + virtual void Draw(RenderContext& renderContext); /// /// Draws this actor. Called during custom actor rendering or any other generic rendering from code. diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 85ee6d333..f0ae7017e 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -383,7 +383,7 @@ void AnimatedModel::UpdateLocalBounds() } else { - box = BoundingBox(Vector3::Zero, Vector3::Zero); + box = BoundingBox(Vector3::Zero); } // Scale bounds diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index 028b1c507..5efe8deed 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -96,7 +96,7 @@ void BoneSocket::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index 95eebefc1..1f9de6d41 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -101,12 +101,12 @@ void Camera::SetOrthographicScale(float value) } } -void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation) const +void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& gameWindowSpaceLocation) const { - ProjectPoint(worldSpaceLocation, screenSpaceLocation, GetViewport()); + ProjectPoint(worldSpaceLocation, gameWindowSpaceLocation, GetViewport()); } -void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation, const Viewport& viewport) const +void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& cameraViewportSpaceLocation, const Viewport& viewport) const { Matrix v, p, vp; GetMatrices(v, p, viewport); @@ -114,7 +114,7 @@ void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpac Vector3 clipSpaceLocation; Vector3::Transform(worldSpaceLocation, vp, clipSpaceLocation); viewport.Project(worldSpaceLocation, vp, clipSpaceLocation); - screenSpaceLocation = Vector2(clipSpaceLocation); + cameraViewportSpaceLocation = Vector2(clipSpaceLocation); } Ray Camera::ConvertMouseToRay(const Vector2& mousePosition) const diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 7f40617dd..4bf277279 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -174,19 +174,19 @@ public: public: /// - /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for default viewport calculated from ). + /// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from ). /// /// The input world-space location (XYZ in world). - /// The output screen-space location (XY in screen pixels). - API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation) const; + /// The output game window coordinates (XY in screen pixels). + API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& gameWindowSpaceLocation) const; /// - /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for given viewport). + /// Projects the point from 3D world-space to the camera viewport-space (in screen pixels for given viewport). /// /// The input world-space location (XYZ in world). - /// The output screen-space location (XY in screen pixels). + /// The output camera viewport-space location (XY in screen pixels). /// The viewport. - API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const; + API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& cameraViewportSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const; /// /// Converts the mouse position to 3D ray. diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 240344fc6..8810b0de6 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -92,6 +92,6 @@ void DirectionalLight::OnTransformChanged() // Base LightWithShadow::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/EmptyActor.cpp b/Source/Engine/Level/Actors/EmptyActor.cpp index 309f63652..dfb64824d 100644 --- a/Source/Engine/Level/Actors/EmptyActor.cpp +++ b/Source/Engine/Level/Actors/EmptyActor.cpp @@ -22,6 +22,6 @@ void EmptyActor::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 77f8e16e7..366ab7de4 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -228,6 +228,6 @@ void ExponentialHeightFog::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 8aaf5fdd3..49f46e9b9 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -259,6 +259,6 @@ void Sky::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index e8ea08c89..4edd596b7 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -146,6 +146,6 @@ void Skybox::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 76ecb9653..4c0d21cd7 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -3,10 +3,12 @@ #include "Spline.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Animations/CurveSerialization.h" +#include "Engine/Core/Math/Matrix.h" #include Spline::Spline(const SpawnParams& params) : Actor(params) + , _localBounds(Vector3::Zero, Vector3::Zero) { } @@ -411,17 +413,27 @@ void Spline::SetTangentsSmooth() void Spline::UpdateSpline() { + auto& keyframes = Curve.GetKeyframes(); + const int32 count = keyframes.Count(); + // Always keep last point in the loop - const int32 count = Curve.GetKeyframes().Count(); if (_loop && count > 1) { - auto& first = Curve[0]; - auto& last = Curve[count - 1]; + auto& first = keyframes[0]; + auto& last = keyframes[count - 1]; last.Value = first.Value; last.TangentIn = first.TangentIn; last.TangentOut = first.TangentOut; } + // Update bounds + _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero); + for (int32 i = 1; i < count; i++) + _localBounds.Merge(keyframes[i].Value.Translation); + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); + SplineUpdated(); } @@ -485,6 +497,34 @@ void Spline::OnDebugDrawSelected() #endif +void Spline::OnTransformChanged() +{ + // Base + Actor::OnTransformChanged(); + + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); + BoundingSphere::FromBox(_box, _sphere); +} + +void Spline::PostLoad() +{ + // Base + Actor::PostLoad(); + + auto& keyframes = Curve.GetKeyframes(); + const int32 count = keyframes.Count(); + + // Update bounds + _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero); + for (int32 i = 1; i < count; i++) + _localBounds.Merge(keyframes[i].Value.Translation); + Matrix world; + _transform.GetWorld(world); + BoundingBox::Transform(_localBounds, world, _box); +} + void Spline::Serialize(SerializeStream& stream, const void* otherObj) { // Base diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index f19f9a443..79fce685d 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -15,6 +15,7 @@ DECLARE_SCENE_OBJECT(Spline); private: bool _loop = false; + BoundingBox _localBounds; public: @@ -377,6 +378,8 @@ public: void OnDebugDraw() override; void OnDebugDrawSelected() override; #endif + void OnTransformChanged() override; + void PostLoad() override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 84306716b..794658afb 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -110,7 +110,7 @@ void SplineModel::OnSplineUpdated() // Skip updates when actor is disabled or something is missing if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); return; } diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index a884c9707..3f81b22c3 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -187,7 +187,7 @@ void StaticModel::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } BoundingSphere::FromBox(_box, _sphere); } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 049e9ebf3..8b8d4ae07 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1037,9 +1037,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); // Delete objects without parent for (int32 i = 1; i < objectsCount; i++) diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index c0029aa93..a6a0edd4b 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -358,27 +358,47 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p { obj->Deserialize(instance.Data[dataIndex], modifier.Value); - // Send events because some properties may be modified during prefab changes apply - // TODO: maybe send only valid events (need to track changes for before-after state) - Actor* actor = dynamic_cast(obj); - if (actor && actor->IsDuringPlay()) + // Preserve order in parent (values from prefab are used) + if (i != 0) { - Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr); - Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr); - Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr); + auto prefab = Content::Load(prefabId); + const auto defaultInstance = prefab ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } } } } Scripting::ObjectsLookupIdMapping.Set(nullptr); - // Setup objects after deserialization + // Setup new objects after deserialization for (int32 i = existingObjectsCount; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects.Value->At(i); obj->PostLoad(); } + // Synchronize existing objects logic with deserialized state (fire events) + for (int32 i = 0; i < existingObjectsCount; i++) + { + SceneObject* obj = sceneObjects->At(i); + Actor* actor = dynamic_cast(obj); + if (actor) + { + const bool shouldBeActiveInHierarchy = actor->GetIsActive() && (!actor->GetParent() || actor->GetParent()->IsActiveInHierarchy()); + if (shouldBeActiveInHierarchy != actor->IsActiveInHierarchy()) + { + actor->_isActiveInHierarchy = shouldBeActiveInHierarchy; + actor->OnActiveInTreeChanged(); + Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr); + } + Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr); + Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr); + } + } + // Restore order in parent instance.TargetActor->SetOrderInParent(instance.OrderInParent); @@ -784,6 +804,20 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr obj->Deserialize(diffDataDocument[dataIndex], modifier.Value); sceneObjects->Add(obj); + + // Synchronize order of the scene objects with the serialized data (eg. user reordered actors in prefab editor and applied changes) + if (i != 0) + { + for (int32 j = 0; j < targetObjects->Count(); j++) + { + SceneObject* targetObject = targetObjects->At(j); + if (targetObject->GetPrefabObjectID() == obj->GetID()) + { + obj->SetOrderInParent(targetObject->GetOrderInParent()); + break; + } + } + } } else { @@ -800,7 +834,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr sceneObjects->RemoveAtKeepOrder(i); } - // Deserialize new prefab objects (add new objects) + // Deserialize new prefab objects int32 newPrefabInstanceIdToDataIndexCounter = 0; int32 newPrefabInstanceIdToDataIndexStart = sceneObjects->Count(); sceneObjects->Resize(sceneObjects->Count() + newPrefabInstanceIdToDataIndex.Count()); @@ -822,9 +856,27 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr { const int32 dataIndex = i->Value; SceneObject* obj = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++); - if (obj) + if (!obj) + continue; + SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value); + } + for (int32 j = 0; j < targetObjects->Count(); j++) + { + auto obj = targetObjects->At(j); + Guid prefabObjectId; + if (newPrefabInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId)) { - SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value); + newPrefabInstanceIdToDataIndexCounter = 0; + for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) + { + SceneObject* e = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++); + if (e->GetID() == prefabObjectId) + { + // Synchronize order of new objects with the order in target instance + e->SetOrderInParent(obj->GetOrderInParent()); + break; + } + } } } Scripting::ObjectsLookupIdMapping.Set(nullptr); @@ -861,7 +913,10 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr for (int32 i = 0; i < sceneObjects->Count(); i++) { auto obj = sceneObjects.Value->At(i); - obj->PostLoad(); + if (obj) + { + obj->PostLoad(); + } } // Update transformations diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 512d6e9b7..77698e739 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -16,6 +16,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Scripting/Script.h" #include "Engine/Scripting/Scripting.h" #if USE_EDITOR @@ -216,21 +217,57 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryIdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); } - // Delete objects without parent + // Delete objects without parent or with invalid linkage to the prefab for (int32 i = 1; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - if (obj && obj->GetParent() == nullptr) + if (!obj) + continue; + + // Check for missing parent (eg. parent object has been deleted) + if (obj->GetParent() == nullptr) { sceneObjects->At(i) = nullptr; LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); + continue; } + +#if USE_EDITOR && !BUILD_RELEASE + // Check for not being added to the parent (eg. invalid setup events fault on registration) + auto actor = dynamic_cast(obj); + auto script = dynamic_cast(obj); + if (obj->GetParent() == obj || (actor && !actor->GetParent()->Children.Contains(actor)) || (script && !script->GetParent()->Scripts.Contains(script))) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object linkage after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif + +#if USE_EDITOR && BUILD_DEBUG + // Check for being added to parent not from spawned prefab (eg. invalid parentId linkage fault) + bool hasParentInInstance = false; + for (int32 j = 0; j < sceneObjects->Count(); j++) + { + if (sceneObjects->At(j) == obj->GetParent()) + { + hasParentInInstance = true; + break; + } + } + if (!hasParentInInstance) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif } // Link objects to prefab (only deserialized from prefab data) diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index d38bd4225..94de9d713 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -362,6 +362,6 @@ void Scene::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index a5cb27712..5eca3230e 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -204,6 +204,8 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO { PROFILE_CPU_NAMED("SynchronizePrefabInstances"); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); + // Check all objects with prefab linkage for moving to a proper parent const int32 objectsToCheckCount = sceneObjects.Count(); for (int32 i = 0; i < objectsToCheckCount; i++) @@ -257,6 +259,16 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO // Reparent obj->SetParent(actualParent, false); } + + // Preserve order in parent (values from prefab are used) + if (i != 0) + { + const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + } } // Check all actors with prefab linkage for adding missing objects @@ -305,6 +317,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO continue; // Create instance (including all children) + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); SynchronizeNewPrefabInstance(prefab, actor, prefabObjectId, sceneObjects, modifier); } } @@ -313,8 +326,19 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO for (int32 i = objectsToCheckCount; i < sceneObjects.Count(); i++) { SceneObject* obj = sceneObjects[i]; + + // Preserve order in parent (values from prefab are used) + auto prefab = Content::LoadAsync(obj->GetPrefabID()); + const auto defaultInstance = prefab && prefab->IsLoaded() ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + obj->PostLoad(); } + + Scripting::ObjectsLookupIdMapping.Set(nullptr); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) @@ -414,6 +438,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* ac // Map prefab object ID to the new prefab object instance modifier->IdsMapping[prefabObjectId] = Guid::New(); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); // Create prefab instance (recursive prefab loading to support nested prefabs) auto child = Spawn(*(ISerializable::DeserializeStream*)prefabData, modifier); diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 55319bb9e..755f4179b 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -1016,6 +1016,8 @@ void NavMeshBuilder::Update() { NavBuildQueue.RemoveAt(i--); const auto scene = req.Scene.Get(); + if (!scene) + continue; // Early out if scene has no bounds volumes to define nav mesh area if (scene->NavigationVolumes.IsEmpty()) diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 9a7f92080..019dc41f3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -72,7 +72,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod case 3: value = matrix.GetRow4(); break; - default: CRASH; + default: break; } break; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 40594904d..f23aff28c 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -306,7 +306,7 @@ void ParticleEffect::UpdateBounds() // Empty bounds if there is no particle system to play or it has been never played if (bounds == BoundingBox::Empty) { - bounds = BoundingBox(_transform.Translation, _transform.Translation); + bounds = BoundingBox(_transform.Translation); } _box = bounds; diff --git a/Source/Engine/Physics/Actors/PhysicsActor.cpp b/Source/Engine/Physics/Actors/PhysicsActor.cpp index 79c4ccf7f..8e92ea902 100644 --- a/Source/Engine/Physics/Actors/PhysicsActor.cpp +++ b/Source/Engine/Physics/Actors/PhysicsActor.cpp @@ -61,7 +61,7 @@ void PhysicsActor::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } } else @@ -71,7 +71,7 @@ void PhysicsActor::UpdateBounds() } else { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); } BoundingSphere::FromBox(_box, _sphere); } diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index a610e76ce..b341f7008 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -178,6 +178,6 @@ void SplineRopeBody::OnTransformChanged() { Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 7657c7594..4315ce8ba 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -220,7 +220,7 @@ void CharacterController::UpdateBounds() if (actor) _box = P2C(actor->getWorldBounds(boundsScale)); else - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); } @@ -345,7 +345,7 @@ void CharacterController::OnTransformChanged() } else if (!_controller) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); } } diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index e44d986cc..7bff077e0 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -64,7 +64,7 @@ void SplineCollider::OnSplineUpdated() { if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) { - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); return; } @@ -178,7 +178,7 @@ void SplineCollider::UpdateBounds() void SplineCollider::GetGeometry(PxGeometryHolder& geometry) { // Reset bounds - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); BoundingSphere::FromBox(_box, _sphere); // Skip if sth is missing diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index 56c5c1b32..2a8cc57ca 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -327,7 +327,7 @@ void Joint::OnTransformChanged() // TODO: this could track only local transform changed - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); if (_joint) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 4060bfee6..7767d68eb 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -57,7 +57,7 @@ bool NetworkBase::IsReadable(NetworkSocket& socket) return true; } -bool NetworkBase::IsWriteable(NetworkSocket& socket) +bool NetworkBase::IsWritable(NetworkSocket& socket) { return true; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 5d382ab02..1e65fbe7f 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -183,11 +183,11 @@ public: /// /// Accepts a pending connection. /// - /// The socket. - /// The newly connected socket. + /// The socket. + /// The newly connected socket. /// The end point of the new socket. /// Returns true on error, otherwise false. - static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); /// /// Checks for socket readability. @@ -201,7 +201,7 @@ public: /// /// The socket. /// Returns true when data can be written. Otherwise false. - static bool IsWriteable(NetworkSocket& socket); + static bool IsWritable(NetworkSocket& socket); /// /// Creates a socket group. It allocate memory based on the desired capacity. diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index ec91013de..b1f5746ac 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -7,13 +7,13 @@ #elif PLATFORM_UWP #include "Win32/Win32Network.h" #elif PLATFORM_LINUX -#include "Base/NetworkBase.h" +#include "Unix/UnixNetwork.h" #elif PLATFORM_PS4 #include "Base/NetworkBase.h" #elif PLATFORM_XBOX_SCARLETT #include "Win32/Win32Network.h" #elif PLATFORM_ANDROID -#include "Base/NetworkBase.h" +#include "Unix/UnixNetwork.h" #elif PLATFORM_SWITCH #include "Base/NetworkBase.h" #else diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 2eadf565b..2fff3089d 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -68,8 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; -class NetworkBase; -typedef NetworkBase Network; +class UnixNetwork; +typedef UnixNetwork Network; #elif PLATFORM_PS4 @@ -137,8 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; -class NetworkBase; -typedef NetworkBase Network; +class UnixNetwork; +typedef UnixNetwork Network; #elif PLATFORM_SWITCH diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp new file mode 100644 index 000000000..139695119 --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp @@ -0,0 +1,405 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_UNIX + +#include "UnixNetwork.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Utilities/StringConverter.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct UnixSocketData +{ + int sockfd; +}; +static_assert(sizeof(NetworkSocket::Data) >= sizeof(UnixSocketData), "NetworkSocket::Data is not big enough to contains UnixSocketData !"); +static_assert(sizeof(NetworkEndPoint::Data) >= sizeof(sockaddr_in6), "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; +} + +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) +#ifdef SO_USELOOPBACK + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) +#endif + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) +#ifdef TCP_NODELAY + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) +#endif + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP, IP_MTU) + SOCKOPT(NetworkSocketOption::Type, SOL_SOCKET, SO_TYPE) +#undef SOCKOPT + default: + *level = 0; + *name = 0; + break; + } +} +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint32 size = GetAddrSize(*addr); + uint16 port; + void* paddr; + if (addr->sa_family == AF_INET6) + { + paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } + else if (addr->sa_family == AF_INET) + { + paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); + return true; + } + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr!"); + LOG_UNIX_LAST_ERROR; + return true; + } + char strPort[6]; + sprintf(strPort, "%d", port); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +bool UnixNetwork::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + socket.Protocol = proto; + socket.IPVersion = ipv; + const int domain = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const int type = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const int protocol = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + auto& sock = *(UnixSocketData*)&socket.Data; + sock.sockfd = ::socket(domain, type, protocol); + if (sock.sockfd < 0) + { + LOG(Error, "Can't create native socket"); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::DestroySocket(NetworkSocket& socket) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + ::close(sock.sockfd); + return false; +} + +bool UnixNetwork::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + const int32 v = value; + return SetSocketOption(socket, option, v); +} + +bool UnixNetwork::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + auto& sock = *(UnixSocketData*)&socket.Data; + if (setsockopt(sock.sockfd, optlvl, optnme, (char*)&value, sizeof(value)) == -1) + { + LOG(Warning, "Unable to set socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + int32 v; + const bool status = GetSocketOption(socket, option, &v); + *value = v == 1 ? true : false; + return status; +} + +bool UnixNetwork::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + socklen_t size; + auto& sock = *(UnixSocketData*)&socket.Data; + if (getsockopt(sock.sockfd, optlvl, optnme, (char*)value, &size) == -1) + { + LOG(Warning, "Unable to get socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + auto& sock = *(UnixSocketData*)&socket.Data; + if (connect(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to connect socket to address! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return true; + } + const uint16 size = GetAddrSizeFromEP(endPoint); + if (bind(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to bind socket! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::Listen(NetworkSocket& socket, uint16 queueSize) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (listen(sock.sockfd, (int32)queueSize) == -1) + { + LOG(Error, "Unable to listen ! Socket : {0}", sock.sockfd); + return true; + } + return false; +} + +bool UnixNetwork::Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint) +{ + auto& serverSock = *(UnixSocketData*)&serverSocket.Data; + if (serverSocket.Protocol != NetworkProtocol::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", serverSock.sockfd); + return true; + } + sockaddr_in6 addr; + socklen_t size = sizeof(sockaddr_in6); + int sock = accept(serverSock.sockfd, (sockaddr*)&addr, &size); + if (sock < 0) + { + LOG(Warning, "Unable to accept incoming connection! Socket : {0}", serverSock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + auto& newSock = *(UnixSocketData*)&newSocket.Data; + newSock.sockfd = sock; + memcpy(newEndPoint.Data, &addr, size); + newSocket.Protocol = serverSocket.Protocol; + newSocket.IPVersion = serverSocket.IPVersion; + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) + return true; + return false; +} + +bool UnixNetwork::IsReadable(NetworkSocket& socket) +{ + return NetworkBase::IsReadable(socket); // TODO: impl this +} + +bool UnixNetwork::IsWritable(NetworkSocket& socket) +{ + return NetworkBase::IsWritable(socket); // TODO: impl this +} + +bool UnixNetwork::CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group) +{ + return NetworkBase::CreateSocketGroup(capacity, group); // TODO: impl this +} + +bool UnixNetwork::DestroySocketGroup(NetworkSocketGroup& group) +{ + return NetworkBase::DestroySocketGroup(group); // TODO: impl this +} + +int32 UnixNetwork::Poll(NetworkSocketGroup& group) +{ + return NetworkBase::Poll(group); // TODO: impl this +} + +bool UnixNetwork::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + return NetworkBase::GetSocketState(group, index, state); // TODO: impl this +} + +int32 UnixNetwork::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return NetworkBase::AddSocketToGroup(group, socket); // TODO: impl this +} + +bool UnixNetwork::GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, NetworkSocket* socket) +{ + return NetworkBase::GetSocketFromGroup(group, index, socket); // TODO: impl this +} + +void UnixNetwork::RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index) +{ + NetworkBase::RemoveSocketFromGroup(group, index); // TODO: impl this +} + +bool UnixNetwork::RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return NetworkBase::RemoveSocketFromGroup(group, socket); // TODO: impl this +} + +void UnixNetwork::ClearGroup(NetworkSocketGroup& group) +{ + NetworkBase::ClearGroup(group); // TODO: impl this +} + +int32 UnixNetwork::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return -1; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) + { + if ((size = send(sock.sockfd, (const char*)data, length, 0)) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) + { + if ((size = sendto(sock.sockfd, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else + { + // TODO: better explanation + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + return size; +} + +int32 UnixNetwork::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + uint32 size; + if (endPoint == nullptr) + { + if ((size = recv(sock.sockfd, (char*)buffer, bufferSize, 0)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + LOG_UNIX_LAST_ERROR; + return -1; + } + } + else + { + socklen_t addrsize = sizeof(sockaddr_in6); + sockaddr_in6 addr; + if ((size = recvfrom(sock.sockfd, (void*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +bool UnixNetwork::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + int status; + addrinfo hints; + addrinfo* info; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (bindable) + hints.ai_flags = AI_PASSIVE; + + // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + const StringAsANSI<60> addressAnsi(*address.Address, address.Address.Length()); + const StringAsANSI<10> portAnsi(*address.Port, address.Port.Length()); + if ((status = getaddrinfo(address.Address == String::Empty ? nullptr : addressAnsi.Get(), address.Port == String::Empty ? nullptr : portAnsi.Get(), &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1}", address.Address != String::Empty ? *address.Address : TEXT("ANY"), String(gai_strerror(status))); + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address! Address : {0}", address.Address != String::Empty ? *address.Address : TEXT("ANY")); + return true; + } + + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + freeaddrinfo(info); + return true; + } + freeaddrinfo(info); + return false; +} + +NetworkEndPoint UnixNetwork::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + return NetworkBase::RemapEndPointToIPv6(endPoint); // TODO: impl this +} + +#endif diff --git a/Source/Engine/Platform/Unix/UnixNetwork.h b/Source/Engine/Platform/Unix/UnixNetwork.h new file mode 100644 index 000000000..58c27c453 --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_UNIX + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API UnixNetwork : public NetworkBase +{ +public: + + // [NetworkBase] + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, uint16 queueSize); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket); + static bool IsWritable(NetworkSocket& socket); + static bool CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group); + static bool DestroySocketGroup(NetworkSocketGroup& group); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static bool GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, NetworkSocket* socket); + static void RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index); + static bool RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 64e29467e..a5edd68e3 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +#if PLATFORM_WIN32 + #include "Win32Network.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" @@ -7,10 +9,8 @@ #include #include -#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; - -static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); -static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); +static_assert(sizeof(NetworkSocket::Data) >= sizeof(SOCKET), "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof(NetworkEndPoint::Data) >= sizeof(sockaddr_in6), "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); static_assert(SOCKGROUP_ITEMSIZE >= sizeof(pollfd), "SOCKGROUP_ITEMSIZE macro is not big enough to contains pollfd !"); // @formatter:off @@ -44,12 +44,12 @@ static String GetLastErrorMessage() static int GetAddrSize(const sockaddr& addr) { - return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + return addr.sa_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); } static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) { - return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); } static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) @@ -118,6 +118,7 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i { switch (option) { +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) @@ -135,6 +136,11 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP, IP_MTU) SOCKOPT(NetworkSocketOption::Type, SOL_SOCKET, SO_TYPE) +#undef SOCKOPT + default: + *level = 0; + *name = 0; + break; } } @@ -152,7 +158,7 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, Ne LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage()); return true; } - memcpy(socket.Data, &sock, sizeof sock); + memcpy(socket.Data, &sock, sizeof(sock)); unsigned long value = 1; if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { @@ -187,7 +193,7 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption op TranslateSockOptToNative(option, &optlvl, &optnme); - if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof(value)) == SOCKET_ERROR) { LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); return true; @@ -241,7 +247,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) return true; } - const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + const uint16 size = GetAddrSizeFromEP(endPoint); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { LOG(Error, "Unable to bind socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); @@ -260,28 +266,28 @@ bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) return false; } -bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +bool Win32Network::Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint) { - if (serverSock.Protocol != NetworkProtocol::Tcp) + if (serverSocket.Protocol != NetworkProtocol::Tcp) { - LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSocket.Data); return true; } SOCKET sock; sockaddr_in6 addr; - int32 size = sizeof sockaddr_in6; - if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) + int32 size = sizeof(sockaddr_in6); + if ((sock = accept(*(SOCKET*)serverSocket.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) { int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error)); + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSocket.Data, GetErrorMessage(error)); return true; } - memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newSocket.Data, &sock, sizeof(sock)); memcpy(newEndPoint.Data, &addr, size); - newSock.Protocol = serverSock.Protocol; - newSock.IPVersion = serverSock.IPVersion; + newSocket.Protocol = serverSocket.Protocol; + newSocket.IPVersion = serverSocket.IPVersion; if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) return true; return false; @@ -305,7 +311,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket) return false; } -bool Win32Network::IsWriteable(NetworkSocket& socket) +bool Win32Network::IsWritable(NetworkSocket& socket) { pollfd entry; entry.fd = *(SOCKET*)socket.Data; @@ -358,7 +364,7 @@ bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, Netwo if (index >= group.Capacity) return true; pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; - memset(&state, 0, sizeof state); + memset(&state, 0, sizeof(state)); if (pollptr->revents & POLLERR) state.Error = true; if (pollptr->revents & POLLHUP) @@ -398,7 +404,7 @@ bool Win32Network::GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, N if (index >= group.Capacity) return true; SOCKET s = ((pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE])->fd; - memcpy(socket->Data, &s, sizeof s); + memcpy(socket->Data, &s, sizeof(s)); int32 value; if (GetSocketOption(*socket, NetworkSocketOption::Type, &value)) return true; @@ -490,7 +496,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer } else { - int32 addrsize = sizeof sockaddr_in6; + int32 addrsize = sizeof(sockaddr_in6); sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { @@ -508,7 +514,7 @@ bool Win32Network::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, int status; addrinfoW hints; addrinfoW* info; - memset(&hints, 0, sizeof hints); + memset(&hints, 0, sizeof(hints)); hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; hints.ai_flags |= AI_ADDRCONFIG; hints.ai_flags |= AI_V4MAPPED; @@ -518,13 +524,13 @@ bool Win32Network::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) if ((status = GetAddrInfoW(address.Address == String::Empty ? nullptr : address.Address.Get(), address.Port == String::Empty ? nullptr : address.Port.Get(), &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1}", address.Address != String::Empty ? address.Address : String("ANY"), gai_strerror(status)); + LOG(Error, "Unable to query info for address : {0} Error : {1}", address.Address != String::Empty ? *address.Address : TEXT("ANY"), gai_strerror(status)); return true; } if (info == nullptr) { - LOG(Error, "Unable to resolve address! Address : {0}", address.Address != String::Empty ? address.Address : String("ANY")); + LOG(Error, "Unable to resolve address! Address : {0}", address.Address != String::Empty ? *address.Address : TEXT("ANY")); return true; } @@ -534,7 +540,6 @@ bool Win32Network::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, return true; } FreeAddrInfoW(info); - return false; } @@ -552,7 +557,7 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() - memset(addr6, 0, sizeof sockaddr_in6); + memset(addr6, 0, sizeof(sockaddr_in6)); addr6->sin6_family = AF_INET6; addr6->sin6_scope_struct = scope; addr6->sin6_addr = v4MappedPrefix; @@ -562,3 +567,5 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) return pv6; } + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index ce4e59718..4949b8a38 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -9,6 +9,7 @@ class FLAXENGINE_API Win32Network : public NetworkBase { public: + // [NetworkBase] static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); @@ -19,9 +20,9 @@ public: static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); - static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket); - static bool IsWriteable(NetworkSocket& socket); + static bool IsWritable(NetworkSocket& socket); static bool CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group); static bool DestroySocketGroup(NetworkSocketGroup& group); static int32 Poll(NetworkSocketGroup& group); diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp index 1bec8868a..a6dc90a1f 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp @@ -10,6 +10,8 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Engine.h" #include "Engine/Platform/IGuiData.h" +#include "Engine/Input/Input.h" +#include "Engine/Input/Mouse.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/ThreadPool.h" #include "Engine/Scripting/Scripting.h" @@ -630,6 +632,10 @@ DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) dropSource->Release(); ReleaseStgMedium(&stgmed); + // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop) + if (Input::GetMouseButton(MouseButton::Left)) + Input::Mouse->OnMouseUp(Input::Mouse->GetPosition(), MouseButton::Left, this); + return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None; } diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 127fa4d87..b3bd51d6f 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; +using FlaxEngine.GUI; using FlaxEngine.Json.JsonCustomSerializers; using FlaxEngine.Utilities; using Newtonsoft.Json; @@ -119,6 +120,115 @@ namespace FlaxEngine.Json } } + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class MarginConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var valueMargin = (Margin)value; + + writer.WriteStartObject(); + { + writer.WritePropertyName("Left"); + writer.WriteValue(valueMargin.Left); + writer.WritePropertyName("Right"); + writer.WriteValue(valueMargin.Right); + writer.WritePropertyName("Top"); + writer.WriteValue(valueMargin.Top); + writer.WritePropertyName("Bottom"); + writer.WriteValue(valueMargin.Bottom); + } + writer.WriteEndObject(); + } + + /// + public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer) + { + var valueMargin = (Margin)value; + var otherMargin = (Margin)other; + writer.WriteStartObject(); + if (!Mathf.NearEqual(valueMargin.Left, otherMargin.Left)) + { + writer.WritePropertyName("Left"); + writer.WriteValue(valueMargin.Left); + } + if (!Mathf.NearEqual(valueMargin.Right, otherMargin.Right)) + { + writer.WritePropertyName("Right"); + writer.WriteValue(valueMargin.Right); + } + if (!Mathf.NearEqual(valueMargin.Top, otherMargin.Top)) + { + writer.WritePropertyName("Top"); + writer.WriteValue(valueMargin.Top); + } + if (!Mathf.NearEqual(valueMargin.Bottom, otherMargin.Bottom)) + { + writer.WritePropertyName("Bottom"); + writer.WriteValue(valueMargin.Bottom); + } + writer.WriteEndObject(); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var value = (Margin?)existingValue ?? new Margin(); + if (reader.TokenType == JsonToken.StartObject) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + { + var propertyName = (string)reader.Value; + var propertyValue = (float)reader.ReadAsDouble(); + switch (propertyName) + { + case "Left": + value.Left = propertyValue; + break; + case "Right": + value.Right = propertyValue; + break; + case "Top": + value.Top = propertyValue; + break; + case "Bottom": + value.Bottom = propertyValue; + break; + } + break; + } + case JsonToken.Comment: break; + default: return value; + } + } + } + return value; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Margin); + } + + /// + public override bool CanRead => true; + + /// + public override bool CanWrite => true; + + /// + public override bool CanWriteDiff => true; + } + /* /// /// Serialize Guid values using `N` format @@ -219,6 +329,7 @@ namespace FlaxEngine.Json settings.Converters.Add(ObjectConverter); settings.Converters.Add(new SceneReferenceConverter()); settings.Converters.Add(new SoftObjectReferenceConverter()); + settings.Converters.Add(new MarginConverter()); settings.Converters.Add(new VersionConverter()); //settings.Converters.Add(new GuidConverter()); return settings; diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index fad907f45..1a8da00eb 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -100,6 +100,8 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c void JsonTools::ChangeIds(Document& doc, const Dictionary& mapping) { + if (mapping.IsEmpty()) + return; ::ChangeIds(doc, doc, mapping); } diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index e87de12a3..502b5c4a5 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -37,7 +37,7 @@ Terrain::~Terrain() void Terrain::UpdateBounds() { PROFILE_CPU(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); for (int32 i = 0; i < _patches.Count(); i++) { auto patch = _patches[i]; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index b6b5306e2..fa0ae58c5 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -54,6 +54,7 @@ namespace { // Loaded and parsed features data cache Dictionary Features; + CriticalSection FeaturesLock; } bool FeatureData::Init() @@ -174,6 +175,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo features.Add(typeName); \ if (!Features.ContainsKey(typeName)) \ { \ + ScopeLock lock(FeaturesLock); \ auto& feature = Features[typeName]; \ type::Generate(feature.Data); \ if (feature.Init()) \ @@ -388,7 +390,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; -#define WRITE_FEATURES(input) for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); +#define WRITE_FEATURES(input) FeaturesLock.Lock(); for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); FeaturesLock.Unlock(); // Defines { _writer.Write(TEXT("#define MATERIAL_MASK_THRESHOLD ({0})\n"), baseLayer->MaskThreshold); diff --git a/Source/Engine/UI/GUI/Common/Border.cs b/Source/Engine/UI/GUI/Common/Border.cs index 543735e99..fb699b132 100644 --- a/Source/Engine/UI/GUI/Common/Border.cs +++ b/Source/Engine/UI/GUI/Common/Border.cs @@ -5,7 +5,7 @@ namespace FlaxEngine.GUI /// /// Border control that draws the border around the control edges (inner and outer sides). /// - public class Border : Control + public class Border : ContainerControl { /// /// Gets or sets the color used to draw border lines. @@ -30,9 +30,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); Render2D.DrawRectangle(new Rectangle(Vector2.Zero, Size), BorderColor, BorderWidth); } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 9d56a4a36..639552ec3 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -7,7 +7,7 @@ namespace FlaxEngine.GUI /// /// Button control /// - public class Button : Control + public class Button : ContainerControl { /// /// The default height fro the buttons. @@ -171,7 +171,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data Rectangle clientRect = new Rectangle(Vector2.Zero, Size); diff --git a/Source/Engine/UI/GUI/Common/Image.cs b/Source/Engine/UI/GUI/Common/Image.cs index b94b285b6..264fdf306 100644 --- a/Source/Engine/UI/GUI/Common/Image.cs +++ b/Source/Engine/UI/GUI/Common/Image.cs @@ -80,9 +80,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); if (Brush == null) return; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 0889bad9d..d549a9d85 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -200,8 +200,8 @@ namespace FlaxEngine.GUI color *= 0.6f; var scale = 1.0f; - var hAlignment = _autoWidth ? TextAlignment.Near : HorizontalAlignment; - var wAlignment = _autoHeight ? TextAlignment.Near : VerticalAlignment; + var hAlignment = HorizontalAlignment; + var wAlignment = VerticalAlignment; if (_autoFitText) { hAlignment = TextAlignment.Center; @@ -239,7 +239,13 @@ namespace FlaxEngine.GUI if (font) { // Calculate text size - _textSize = font.MeasureText(_text); + var layout = TextLayoutOptions.Default; + layout.TextWrapping = Wrapping; + if (_autoHeight && !_autoWidth) + layout.Bounds.Size.X = Width - Margin.Width; + else if (_autoWidth && !_autoHeight) + layout.Bounds.Size.Y = Height - Margin.Height; + _textSize = font.MeasureText(_text, ref layout); // Check if size is controlled via text if (_autoWidth || _autoHeight) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index f68336d24..21cce7faa 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -8,7 +8,7 @@ namespace FlaxEngine.GUI /// Progress bar control shows visual progress of the action or set of actions. /// /// - public class ProgressBar : Control + public class ProgressBar : ContainerControl { /// /// The value. @@ -160,9 +160,9 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { - base.Draw(); + base.DrawSelf(); float progressNormalized = (_current - _minimum) / _maximum; if (progressNormalized > 0.001f) diff --git a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs index 526390dac..3894c3027 100644 --- a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs +++ b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs @@ -105,7 +105,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Draw cached texture if (_texture && !_invalid && !_isDuringTextureDraw) @@ -119,7 +119,7 @@ namespace FlaxEngine.GUI } // Draw default UI directly - base.Draw(); + base.DrawSelf(); } /// diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 5dcb25419..1a57061e1 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -124,7 +124,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.Height / Platform.DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperLeft; } } @@ -136,7 +136,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.Height / Platform.DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -151,7 +151,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.Height / Platform.DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -166,7 +166,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.Height / Platform.DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -220,7 +220,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data var rect = new Rectangle(Vector2.Zero, Size); @@ -280,7 +280,7 @@ namespace FlaxEngine.GUI { Vector2 leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); Vector2 rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); - float height = font.Height / Platform.DpiScale; + float height = font.Height / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = Color.White * alpha; @@ -330,7 +330,7 @@ namespace FlaxEngine.GUI if (textBlock.Style.UnderlineBrush != null) { var underLineHeight = 2.0f; - var height = font.Height / Platform.DpiScale; + var height = font.Height / DpiScale; var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index b1b873f34..0ed5c731c 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -107,7 +107,7 @@ namespace FlaxEngine.GUI return Vector2.Zero; } - height = font.Height / Platform.DpiScale; + height = font.Height / DpiScale; return font.GetCharPosition(_text, index, ref _layout); } @@ -132,7 +132,7 @@ namespace FlaxEngine.GUI } /// - public override void Draw() + public override void DrawSelf() { // Cache data var rect = new Rectangle(Vector2.Zero, Size); @@ -159,7 +159,7 @@ namespace FlaxEngine.GUI { Vector2 leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); Vector2 rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.Height / Platform.DpiScale; + float fontHeight = font.Height / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 1df079b48..5541ee3e5 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -9,7 +9,7 @@ namespace FlaxEngine.GUI /// /// Base class for all text box controls which can gather text input from the user. /// - public abstract class TextBoxBase : Control + public abstract class TextBoxBase : ContainerControl { /// /// The text separators (used for words skipping). @@ -539,6 +539,12 @@ namespace FlaxEngine.GUI return; } + // If it's not selected + if (_selectionStart == -1 && _selectionEnd == -1) + { + return; + } + Rectangle caretBounds = CaretBounds; Rectangle textArea = TextRectangle; diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index fb11f2eff..0a4114205 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -262,6 +262,8 @@ namespace FlaxEngine.GUI // Change order _children.Insert(newIndex, child); } + + PerformLayout(); } /// @@ -614,25 +616,32 @@ namespace FlaxEngine.GUI } } - /// + /// + /// Draw the control and the children. + /// public override void Draw() { - base.Draw(); + DrawSelf(); - // Push clipping mask if (ClipChildren) { GetDesireClientArea(out var clientArea); Render2D.PushClip(ref clientArea); - } - - DrawChildren(); - - // Pop clipping mask - if (ClipChildren) - { + DrawChildren(); Render2D.PopClip(); } + else + { + DrawChildren(); + } + } + + /// + /// Draws the control. + /// + public virtual void DrawSelf() + { + base.Draw(); } /// diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index 11b5e677a..06bd0499d 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -223,57 +223,60 @@ namespace FlaxEngine.GUI set { if (!_bounds.Equals(ref value)) - { - // Calculate anchors based on the parent container client area - Margin anchors; - if (_parent != null) - { - _parent.GetDesireClientArea(out var parentBounds); - anchors = new Margin - ( - _anchorMin.X * parentBounds.Size.X + parentBounds.Location.X, - _anchorMax.X * parentBounds.Size.X, - _anchorMin.Y * parentBounds.Size.Y + parentBounds.Location.Y, - _anchorMax.Y * parentBounds.Size.Y - ); - } - else - { - anchors = Margin.Zero; - } - - // Calculate offsets on X axis - _offsets.Left = value.Location.X - anchors.Left; - if (_anchorMin.X != _anchorMax.X) - { - _offsets.Right = anchors.Right - value.Location.X - value.Size.X; - } - else - { - _offsets.Right = value.Size.X; - } - - // Calculate offsets on Y axis - _offsets.Top = value.Location.Y - anchors.Top; - if (_anchorMin.Y != _anchorMax.Y) - { - _offsets.Bottom = anchors.Bottom - value.Location.Y - value.Size.Y; - } - else - { - _offsets.Bottom = value.Size.Y; - } - - // Flush the control bounds - UpdateBounds(); - } + SetBounds(ref value); } } + private void SetBounds(ref Rectangle value) + { + // Calculate anchors based on the parent container client area + Margin anchors; + if (_parent != null) + { + _parent.GetDesireClientArea(out var parentBounds); + anchors = new Margin + ( + _anchorMin.X * parentBounds.Size.X + parentBounds.Location.X, + _anchorMax.X * parentBounds.Size.X, + _anchorMin.Y * parentBounds.Size.Y + parentBounds.Location.Y, + _anchorMax.Y * parentBounds.Size.Y + ); + } + else + { + anchors = Margin.Zero; + } + + // Calculate offsets on X axis + _offsets.Left = value.Location.X - anchors.Left; + if (_anchorMin.X != _anchorMax.X) + { + _offsets.Right = anchors.Right - value.Location.X - value.Size.X; + } + else + { + _offsets.Right = value.Size.X; + } + + // Calculate offsets on Y axis + _offsets.Top = value.Location.Y - anchors.Top; + if (_anchorMin.Y != _anchorMax.Y) + { + _offsets.Bottom = anchors.Bottom - value.Location.Y - value.Size.Y; + } + else + { + _offsets.Bottom = value.Size.Y; + } + + // Flush the control bounds + UpdateBounds(); + } + /// - /// Gets or sets the scale. + /// Gets or sets the scale. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner. /// - [ExpandGroups, EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter.")] + [ExpandGroups, EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner.")] public Vector2 Scale { get => _scale; @@ -303,9 +306,9 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the shear transform angles (x, y). Defined in degrees. + /// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point. /// - [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees.")] + [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")] public Vector2 Shear { get => _shear; @@ -319,9 +322,9 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the rotation angle (in degrees). + /// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default). /// - [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")] + [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")] public float Rotation { get => _rotation; @@ -553,7 +556,7 @@ namespace FlaxEngine.GUI } bounds.Location += parentBounds.Location; } - Bounds = bounds; + SetBounds(ref bounds); } return; } diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 3062c3088..96b7bf22b 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -209,7 +209,6 @@ namespace FlaxEngine.GUI public bool Enabled { get => _isEnabled; - set { if (_isEnabled != value) @@ -255,7 +254,6 @@ namespace FlaxEngine.GUI public bool Visible { get => _isVisible; - set { if (_isVisible != value) @@ -312,6 +310,11 @@ namespace FlaxEngine.GUI /// public virtual WindowRootControl RootWindow => _root?.RootWindow; + /// + /// Gets the control DPI scale factor (1 is default). Includes custom DPI scale. + /// + public float DpiScale => _root?.RootWindow?.Window.DpiScale ?? Platform.DpiScale; + /// /// Gets screen position of the control (upper left corner). /// diff --git a/Source/Engine/UI/GUI/RenderOutputControl.cs b/Source/Engine/UI/GUI/RenderOutputControl.cs index c12b1844e..9a7f6dfb1 100644 --- a/Source/Engine/UI/GUI/RenderOutputControl.cs +++ b/Source/Engine/UI/GUI/RenderOutputControl.cs @@ -224,7 +224,7 @@ namespace FlaxEngine.GUI /// public void SyncBackbufferSize() { - float scale = ResolutionScale * (RootWindow?.DpiScale ?? Platform.DpiScale); + float scale = ResolutionScale * DpiScale; int width = Mathf.CeilToInt(Width * scale); int height = Mathf.CeilToInt(Height * scale); if (_customResolution.HasValue) diff --git a/Source/Engine/UI/GUI/WindowRootControl.cs b/Source/Engine/UI/GUI/WindowRootControl.cs index a8028ebe8..1e9785195 100644 --- a/Source/Engine/UI/GUI/WindowRootControl.cs +++ b/Source/Engine/UI/GUI/WindowRootControl.cs @@ -55,11 +55,6 @@ namespace FlaxEngine.GUI /// public bool IsMaximized => _window.IsMaximized; - /// - /// Gets the window DPI scale factor (1 is default). Includes custom DPI scale - /// - public float DpiScale => _window.DpiScale; - internal WindowRootControl(Window window) { _window = window; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index a26d3d169..df75bbb87 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -31,7 +31,7 @@ TextRender::TextRender(const SpawnParams& params) { _world = Matrix::Identity; _color = Color::White; - _localBox = BoundingBox(Vector3::Zero, Vector3::Zero); + _localBox = BoundingBox(Vector3::Zero); _layoutOptions.Bounds = Rectangle(-100, -100, 200, 200); _layoutOptions.HorizontalAlignment = TextAlignment::Center; _layoutOptions.VerticalAlignment = TextAlignment::Center; @@ -92,7 +92,7 @@ void TextRender::UpdateLayout() _vb0.Clear(); _vb1.Clear(); _vb2.Clear(); - _localBox = BoundingBox(Vector3::Zero, Vector3::Zero); + _localBox = BoundingBox(Vector3::Zero); BoundingBox::Transform(_localBox, _world, _box); BoundingSphere::FromBox(_box, _sphere); #if USE_PRECISE_MESH_INTERSECTS @@ -291,7 +291,7 @@ void TextRender::UpdateLayout() if (_ib.Data.IsEmpty()) { // Empty - box = BoundingBox(_transform.Translation, _transform.Translation); + box = BoundingBox(_transform.Translation); } _localBox = box; BoundingBox::Transform(_localBox, _world, _box); diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index ccd0561ad..c21281c67 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -15,6 +15,7 @@ MMethod* UICanvas_PostDeserialize = nullptr; MMethod* UICanvas_OnEnable = nullptr; MMethod* UICanvas_OnDisable = nullptr; MMethod* UICanvas_EndPlay = nullptr; +MMethod* UICanvas_ParentChanged = nullptr; #define UICANVAS_INVOKE(event) \ auto instance = GetManagedInstance(); \ @@ -43,6 +44,7 @@ UICanvas::UICanvas(const SpawnParams& params) UICanvas_OnEnable = mclass->GetMethod("OnEnable"); UICanvas_OnDisable = mclass->GetMethod("OnDisable"); UICanvas_EndPlay = mclass->GetMethod("EndPlay"); + UICanvas_ParentChanged = mclass->GetMethod("ParentChanged"); } } @@ -133,6 +135,14 @@ void UICanvas::EndPlay() Actor::EndPlay(); } +void UICanvas::OnParentChanged() +{ + // Base + Actor::OnParentChanged(); + + UICANVAS_INVOKE(ParentChanged); +} + void UICanvas::OnEnable() { UICANVAS_INVOKE(OnEnable); @@ -154,6 +164,6 @@ void UICanvas::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); } diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 99bc35672..cbd554775 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -31,6 +31,12 @@ namespace FlaxEngine /// [Tooltip("The world space rendering mode that places Canvas as any other object in the scene. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'.")] WorldSpace = 2, + + /// + /// The world space rendering mode that places Canvas as any other object in the scene and orients it to face the camera. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'. + /// + [Tooltip("The world space rendering mode that places Canvas as any other object in the scene and orients canvas to face the camera. The size of the Canvas can be set manually using its Transform, and UI elements will render in front of or behind other objects in the scene based on 3D placement. This is useful for UIs that are meant to be a part of the world. This is also known as a 'diegetic interface'.")] + WorldSpaceFaceCamera = 3, } /// @@ -57,7 +63,8 @@ namespace FlaxEngine /// public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) { - // TODO: apply frustum culling to skip rendering if canvas is not in a viewport + if (renderContext.View.Frustum.Contains(Canvas.Bounds.GetBoundingBox()) == ContainmentType.Disjoint) + return; Profiler.BeginEventGPU("UI Canvas"); @@ -81,7 +88,7 @@ namespace FlaxEngine private CanvasRenderMode _renderMode; private readonly CanvasRootControl _guiRoot; private CanvasRenderer _renderer; - private bool _isLoading; + private bool _isLoading, _isRegisteredForTick; /// /// Gets or sets the canvas rendering mode. @@ -101,16 +108,16 @@ namespace FlaxEngine Setup(); // Reset size - if (previous == CanvasRenderMode.ScreenSpace && _renderMode == CanvasRenderMode.WorldSpace) + if (previous == CanvasRenderMode.ScreenSpace || (_renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera)) Size = new Vector2(500, 500); } } } /// - /// Gets or sets the canvas rendering location within rendering pipeline. Used only in or . + /// Gets or sets the canvas rendering location within rendering pipeline. Used only in or or . /// - [EditorOrder(13), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_Is3D)), Tooltip("Canvas rendering location within the rendering pipeline. Change this if you want GUI to affect the lighting or post processing effects like bloom.")] + [EditorOrder(13), EditorDisplay("Canvas"), VisibleIf("Editor_Is3D"), Tooltip("Canvas rendering location within the rendering pipeline. Change this if you want GUI to affect the lighting or post processing effects like bloom.")] public PostProcessEffectLocation RenderLocation { get; set; } = PostProcessEffectLocation.Default; private int _order; @@ -138,22 +145,26 @@ namespace FlaxEngine [EditorOrder(15), EditorDisplay("Canvas"), Tooltip("If checked, canvas can receive the input events.")] public bool ReceivesEvents { get; set; } = true; +#if FLAX_EDITOR private bool Editor_Is3D => _renderMode != CanvasRenderMode.ScreenSpace; - private bool Editor_IsWorldSpace => _renderMode == CanvasRenderMode.WorldSpace; + private bool Editor_IsWorldSpace => _renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera; private bool Editor_IsCameraSpace => _renderMode == CanvasRenderMode.CameraSpace; + private bool Editor_UseRenderCamera => _renderMode == CanvasRenderMode.CameraSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera; +#endif + /// - /// Gets or sets the size of the canvas. Used only in or . + /// Gets or sets the size of the canvas. Used only in or . /// - [EditorOrder(20), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsWorldSpace)), Tooltip("Canvas size.")] + [EditorOrder(20), EditorDisplay("Canvas"), VisibleIf("Editor_IsWorldSpace"), Tooltip("Canvas size.")] public Vector2 Size { get => _guiRoot.Size; set { - if (_renderMode == CanvasRenderMode.WorldSpace || _isLoading) + if (_renderMode == CanvasRenderMode.WorldSpace || _renderMode == CanvasRenderMode.WorldSpaceFaceCamera || _isLoading) { _guiRoot.Size = value; } @@ -163,19 +174,19 @@ namespace FlaxEngine /// /// Gets or sets a value indicating whether ignore scene depth when rendering the GUI (scene objects won't cover the interface). /// - [EditorOrder(30), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_Is3D)), Tooltip("If checked, scene depth will be ignored when rendering the GUI (scene objects won't cover the interface).")] + [EditorOrder(30), EditorDisplay("Canvas"), VisibleIf("Editor_Is3D"), Tooltip("If checked, scene depth will be ignored when rendering the GUI (scene objects won't cover the interface).")] public bool IgnoreDepth { get; set; } = false; /// - /// Gets or sets the camera used to place the GUI when render mode is set to . + /// Gets or sets the camera used to place the GUI when render mode is set to or . /// - [EditorOrder(50), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsCameraSpace)), Tooltip("Camera used to place the GUI when RenderMode is set to CameraSpace")] + [EditorOrder(50), EditorDisplay("Canvas"), VisibleIf("Editor_UseRenderCamera"), Tooltip("Camera used to place the GUI when RenderMode is set to CameraSpace or WorldSpaceFaceCamera.")] public Camera RenderCamera { get; set; } /// /// Gets or sets the distance from the to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well. /// - [EditorOrder(60), Limit(0.01f), EditorDisplay("Canvas"), VisibleIf(nameof(Editor_IsCameraSpace)), Tooltip("Distance from the RenderCamera to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well.")] + [EditorOrder(60), Limit(0.01f), EditorDisplay("Canvas"), VisibleIf("Editor_IsCameraSpace"), Tooltip("Distance from the RenderCamera to place the plane with GUI. If the screen is resized, changes resolution, or the camera frustum changes, the Canvas will automatically change size to match as well.")] public float Distance { get; set; } = 500; /// @@ -218,7 +229,7 @@ namespace FlaxEngine var camera = Camera.MainCamera; if (camera) { - ray = camera.ConvertMouseToRay(location); + ray = camera.ConvertMouseToRay(location * Platform.DpiScale); } else { @@ -237,6 +248,18 @@ namespace FlaxEngine }; } + /// + /// Finalizes an instance of the class. + /// + ~UICanvas() + { + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } + } + /// /// Gets the world-space oriented bounding box that contains a 3D canvas. /// @@ -271,24 +294,32 @@ namespace FlaxEngine /// The world. public void GetWorldMatrix(out Matrix world) { - if (_renderMode == CanvasRenderMode.WorldSpace) - { - // In 3D world - GetLocalToWorldMatrix(out world); - } - else if (_renderMode == CanvasRenderMode.CameraSpace) - { - Matrix tmp1, tmp2; - Vector3 viewPos, viewUp, viewForward, pos; - Quaternion viewRot; - - // Use default camera is not specified - var camera = RenderCamera ?? Camera.MainCamera; - #if FLAX_EDITOR - if (_editorTask) + // Override projection for editor preview + if (_editorTask) + { + if (_renderMode == CanvasRenderMode.WorldSpace) + { + GetLocalToWorldMatrix(out world); + } + else if (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + var view = _editorTask.View; + var transform = Transform; + var cameraPosition = view.Position; + var cameraDirection = view.Direction; + var up = Vector3.Up; + Matrix.Translation(_guiRoot.Width * -0.5f, _guiRoot.Height * -0.5f, 0, out var m1); + Matrix.Scaling(ref transform.Scale, out var m2); + Matrix.Multiply(ref m1, ref m2, out var m3); + Quaternion.Euler(180, 180, 0, out var quat); + Matrix.RotationQuaternion(ref quat, out m2); + Matrix.Multiply(ref m3, ref m2, out m1); + Matrix.Billboard(ref transform.Translation, ref cameraPosition, ref up, ref cameraDirection, out m2); + Matrix.Multiply(ref m1, ref m2, out world); + } + else if (_renderMode == CanvasRenderMode.CameraSpace) { - // Use editor viewport task to override Camera Space placement var view = _editorTask.View; var frustum = view.Frustum; if (!frustum.IsOrthographic) @@ -296,19 +327,53 @@ namespace FlaxEngine else _guiRoot.Size = _editorTask.Viewport.Size; Matrix.Translation(_guiRoot.Width / -2.0f, _guiRoot.Height / -2.0f, 0, out world); - Matrix.RotationYawPitchRoll(Mathf.Pi, Mathf.Pi, 0, out tmp2); - Matrix.Multiply(ref world, ref tmp2, out tmp1); - viewPos = view.Position; - viewRot = view.Direction != Vector3.Up ? Quaternion.LookRotation(view.Direction, Vector3.Up) : Quaternion.LookRotation(view.Direction, Vector3.Right); - viewUp = Vector3.Up * viewRot; - viewForward = view.Direction; - pos = view.Position + view.Direction * Distance; + Matrix.RotationYawPitchRoll(Mathf.Pi, Mathf.Pi, 0, out var tmp2); + Matrix.Multiply(ref world, ref tmp2, out var tmp1); + var viewPos = view.Position; + var viewRot = view.Direction != Vector3.Up ? Quaternion.LookRotation(view.Direction, Vector3.Up) : Quaternion.LookRotation(view.Direction, Vector3.Right); + var viewUp = Vector3.Up * viewRot; + var viewForward = view.Direction; + var pos = view.Position + view.Direction * Distance; Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); Matrix.Multiply(ref tmp1, ref tmp2, out world); return; } + else + { + world = Matrix.Identity; + } + return; + } #endif + // Use default camera is not specified + var camera = RenderCamera ?? Camera.MainCamera; + + if (_renderMode == CanvasRenderMode.WorldSpace || (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera && !camera)) + { + // In 3D world + GetLocalToWorldMatrix(out world); + } + else if (_renderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + // In 3D world face camera + var transform = Transform; + var cameraPosition = camera.Position; + var cameraDirection = camera.Direction; + var up = Vector3.Up; + Matrix.Translation(_guiRoot.Width * -0.5f, _guiRoot.Height * -0.5f, 0, out var m1); + Matrix.Scaling(ref transform.Scale, out var m2); + Matrix.Multiply(ref m1, ref m2, out var m3); + Quaternion.Euler(180, 180, 0, out var quat); + Matrix.RotationQuaternion(ref quat, out m2); + Matrix.Multiply(ref m3, ref m2, out m1); + Matrix.Billboard(ref transform.Translation, ref cameraPosition, ref up, ref cameraDirection, out m2); + Matrix.Multiply(ref m1, ref m2, out world); + } + else if (_renderMode == CanvasRenderMode.CameraSpace && camera) + { + Matrix tmp1, tmp2; + // Adjust GUI size to the viewport size at the given distance form the camera var viewport = camera.Viewport; if (camera.UsePerspective) @@ -329,11 +394,11 @@ namespace FlaxEngine Matrix.Multiply(ref world, ref tmp2, out tmp1); // In front of the camera - viewPos = camera.Position; - viewRot = camera.Orientation; - viewUp = Vector3.Up * viewRot; - viewForward = Vector3.Forward * viewRot; - pos = viewPos + viewForward * Distance; + var viewPos = camera.Position; + var viewRot = camera.Orientation; + var viewUp = Vector3.Up * viewRot; + var viewForward = Vector3.Forward * viewRot; + var pos = viewPos + viewForward * Distance; Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); Matrix.Multiply(ref tmp1, ref tmp2, out world); @@ -368,13 +433,19 @@ namespace FlaxEngine _renderer = null; } #if FLAX_EDITOR - if (_editorRoot != null) + if (_editorRoot != null && IsActiveInHierarchy) _guiRoot.Parent = _editorRoot; #endif + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } break; } case CanvasRenderMode.CameraSpace: case CanvasRenderMode.WorldSpace: + case CanvasRenderMode.WorldSpaceFaceCamera: { // Render canvas manually _guiRoot.AnchorPreset = AnchorPresets.TopLeft; @@ -404,11 +475,32 @@ namespace FlaxEngine } #endif } + if (!_isRegisteredForTick) + { + _isRegisteredForTick = true; + Scripting.Update += OnUpdate; + } break; } } } + private void OnUpdate() + { + if (this && IsActiveInHierarchy && _renderMode != CanvasRenderMode.ScreenSpace) + { + try + { + Profiler.BeginEvent(Name); + _guiRoot.Update(Time.UnscaledDeltaTime); + } + finally + { + Profiler.EndEvent(); + } + } + } + internal string Serialize() { StringBuilder sb = new StringBuilder(256); @@ -442,13 +534,16 @@ namespace FlaxEngine jsonWriter.WritePropertyName("Distance"); jsonWriter.WriteValue(Distance); - jsonWriter.WritePropertyName("Size"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("X"); - jsonWriter.WriteValue(Size.X); - jsonWriter.WritePropertyName("Y"); - jsonWriter.WriteValue(Size.Y); - jsonWriter.WriteEndObject(); + if (RenderMode == CanvasRenderMode.WorldSpace || RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) + { + jsonWriter.WritePropertyName("Size"); + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName("X"); + jsonWriter.WriteValue(Size.X); + jsonWriter.WritePropertyName("Y"); + jsonWriter.WriteValue(Size.Y); + jsonWriter.WriteEndObject(); + } jsonWriter.WriteEndObject(); } @@ -510,7 +605,10 @@ namespace FlaxEngine jsonWriter.WriteValue(Distance); } - if (Size != other.Size) + if ((RenderMode == CanvasRenderMode.WorldSpace || + RenderMode == CanvasRenderMode.WorldSpaceFaceCamera || + other.RenderMode == CanvasRenderMode.WorldSpace || + other.RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) && Size != other.Size) { jsonWriter.WritePropertyName("Size"); jsonWriter.WriteStartObject(); @@ -539,6 +637,16 @@ namespace FlaxEngine Setup(); } + internal void ParentChanged() + { +#if FLAX_EDITOR + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + { + _guiRoot.Parent = HasParent && IsActiveInHierarchy ? _editorRoot : null; + } +#endif + } + internal void OnEnable() { #if FLAX_EDITOR @@ -572,6 +680,12 @@ namespace FlaxEngine internal void EndPlay() { + if (_isRegisteredForTick) + { + _isRegisteredForTick = false; + Scripting.Update -= OnUpdate; + } + if (_renderer) { SceneRenderTask.GlobalCustomPostFx.Remove(_renderer); @@ -587,6 +701,8 @@ namespace FlaxEngine internal void EditorOverride(SceneRenderTask task, ContainerControl root) { + if (_editorTask == task && _editorRoot == root) + return; if (_editorTask != null && _renderer != null) _editorTask.CustomPostFx.Remove(_renderer); if (_editorRoot != null && _guiRoot != null) @@ -596,7 +712,7 @@ namespace FlaxEngine _editorRoot = root; Setup(); - if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null && IsActiveInHierarchy) _guiRoot.Parent = _editorRoot; } #endif diff --git a/Source/Engine/UI/UICanvas.h b/Source/Engine/UI/UICanvas.h index 8e8b9b0d1..cb049c684 100644 --- a/Source/Engine/UI/UICanvas.h +++ b/Source/Engine/UI/UICanvas.h @@ -24,6 +24,7 @@ protected: // [Actor] void BeginPlay(SceneBeginData* data) final override; void EndPlay() final override; + void OnParentChanged() override; void OnEnable() override; void OnDisable() override; void OnTransformChanged() final override; diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 51312ae15..b9fef02f8 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -155,9 +155,6 @@ void UIControl::OnParentChanged() // Base Actor::OnParentChanged(); - if (!IsDuringPlay()) - return; - UICONTROL_INVOKE(ParentChanged); } @@ -166,7 +163,7 @@ void UIControl::OnTransformChanged() // Base Actor::OnTransformChanged(); - _box = BoundingBox(_transform.Translation, _transform.Translation); + _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); UICONTROL_INVOKE(TransformChanged); @@ -198,8 +195,8 @@ void UIControl::OnOrderInParentChanged() void UIControl::OnActiveInTreeChanged() { + UICONTROL_INVOKE(ActiveInTreeChanged); + // Base Actor::OnActiveInTreeChanged(); - - UICONTROL_INVOKE(ActiveInTreeChanged); } diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index 8fd4f8f72..b8f8b00dd 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -199,6 +199,11 @@ namespace FlaxEngine // Don't link disabled actors if (!IsActiveInHierarchy) return null; +#if FLAX_EDITOR + // Prefab editor doesn't fire BeginPlay so for disabled actors we don't unlink them so do it here + if (!IsActive) + return null; +#endif var parent = Parent; if (parent is UIControl uiControl && uiControl.Control is ContainerControl uiContainerControl) @@ -296,6 +301,9 @@ namespace FlaxEngine if (_control != null) { Json.JsonSerializer.Deserialize(_control, json); + + // Synchronize actor with control location + OnControlLocationChanged(_control); } } @@ -320,6 +328,12 @@ namespace FlaxEngine { if (_control != null && !_blockEvents) { + // Skip if this control is inactive and it's parent too (parent will unlink from hierarchy but children will stay connected while being inactive) + if (!IsActiveInHierarchy && Parent && !Parent.IsActive) + { + return; + } + // Link or unlink control (won't modify Enable/Visible state) _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 3a9feca06..6f8878d25 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.0.6216")] -[assembly: AssemblyFileVersion("1.0.6216")] +[assembly: AssemblyVersion("1.1.6217")] +[assembly: AssemblyFileVersion("1.1.6217")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 94eb7c467..7a888ae1f 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 0, 6216) -#define FLAXENGINE_VERSION_TEXT "1.0.6216" +#define FLAXENGINE_VERSION Version(1, 1, 6217) +#define FLAXENGINE_VERSION_TEXT "1.1.6217" #define FLAXENGINE_VERSION_MAJOR 1 -#define FLAXENGINE_VERSION_MINOR 0 -#define FLAXENGINE_VERSION_BUILD 6216 +#define FLAXENGINE_VERSION_MINOR 1 +#define FLAXENGINE_VERSION_BUILD 6217 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved." diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.dll b/Source/Platforms/DotNet/Newtonsoft.Json.dll index 17df31d86..b08068dd3 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.dll +++ b/Source/Platforms/DotNet/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:616780417730a6371909713a2d8150347870e67ed52803642caef37e8bda1891 -size 630272 +oid sha256:7ed0cb43a692d86f2d47bfc93475e554ee8a70b33187b1e1d2356a7d4f25fffa +size 629760 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.pdb b/Source/Platforms/DotNet/Newtonsoft.Json.pdb index 68e8489b6..9d8502e2c 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.pdb +++ b/Source/Platforms/DotNet/Newtonsoft.Json.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d4fc76d2ef9b0cd7b88da95f4894d9cc924703af0b02e052f8e78446f75e2d9 -size 239080 +oid sha256:5e5b77e2864fee56c380d85c8d1488713c5ba5deacb9005d125d9e47629014e8 +size 249884 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.xml b/Source/Platforms/DotNet/Newtonsoft.Json.xml index fb9404249..c1d3cfd3d 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.xml +++ b/Source/Platforms/DotNet/Newtonsoft.Json.xml @@ -1691,6 +1691,15 @@ The value. The calling serializer. + + + Writes the JSON representation of the object diff compared to other instance of the object (the same type). + + The to write to. + The value. + The other value (the same type). + The calling serializer. + Reads the JSON representation of the object. @@ -1722,6 +1731,12 @@ true if this can write JSON; otherwise, false. + + + Gets a value indicating whether this can write JSON for object difference. + + true if this can write JSON diff; otherwise, false. + Converts an object to and from JSON. diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 84236cfd2..44d3b457c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1092,7 +1092,7 @@ namespace Flax.Build.Bindings private static unsafe void GenerateCSharp(BuildData buildData, IGrouping binaryModule) { // Skip generating C# bindings code for native-only modules - if (binaryModule.Any(x => !x.BuildCSharp)) + if (binaryModule.All(x => !x.BuildCSharp)) return; var contents = new StringBuilder(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 8413917bb..27dedb6b4 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -18,7 +18,8 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { - private static readonly Dictionary _typeCache = new Dictionary(); + private static readonly Dictionary TypeCache = new Dictionary(); + private const int CacheVersion = 7; internal static void Write(BinaryWriter writer, string e) { @@ -160,7 +161,7 @@ namespace Flax.Build.Bindings var typename = reader.ReadString(); if (string.IsNullOrEmpty(typename)) return e; - if (!_typeCache.TryGetValue(typename, out var type)) + if (!TypeCache.TryGetValue(typename, out var type)) { type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); if (type == null) @@ -169,7 +170,7 @@ namespace Flax.Build.Bindings Log.Error(msg); throw new Exception(msg); } - _typeCache.Add(typename, type); + TypeCache.Add(typename, type); } e = (T)Activator.CreateInstance(type); e.Read(reader); @@ -185,7 +186,7 @@ namespace Flax.Build.Bindings for (int i = 0; i < count; i++) { var typename = reader.ReadString(); - if (!_typeCache.TryGetValue(typename, out var type)) + if (!TypeCache.TryGetValue(typename, out var type)) { type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); if (type == null) @@ -194,7 +195,7 @@ namespace Flax.Build.Bindings Log.Error(msg); throw new Exception(msg); } - _typeCache.Add(typename, type); + TypeCache.Add(typename, type); } var e = (T)Activator.CreateInstance(type); e.Read(reader); @@ -218,7 +219,7 @@ namespace Flax.Build.Bindings using (var writer = new BinaryWriter(stream, Encoding.UTF8)) { // Version - writer.Write(5); + writer.Write(CacheVersion); writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks); // Build options @@ -254,7 +255,7 @@ namespace Flax.Build.Bindings { // Version var version = reader.ReadInt32(); - if (version != 5) + if (version != CacheVersion) return false; if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) return false; diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 75ff73720..0769d6ce6 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -34,6 +34,8 @@ namespace Flax.Build.Bindings BindingsGenerator.Write(writer, Module.BinaryModuleName); writer.Write(Module.BuildNativeCode); writer.Write(Module.BuildCSharp); + writer.Write(Globals.Project.Name); + writer.Write(Globals.Project.Version.ToString()); base.Write(writer); } @@ -44,7 +46,9 @@ namespace Flax.Build.Bindings reader.ReadString() != Module.FilePath || BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName || reader.ReadBoolean() != Module.BuildNativeCode || - reader.ReadBoolean() != Module.BuildCSharp + reader.ReadBoolean() != Module.BuildCSharp || + reader.ReadString() != Globals.Project.Name || + reader.ReadString() != Globals.Project.Version.ToString() ) throw new Exception(); diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index 8164214cc..b98af5dc1 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -193,9 +193,14 @@ namespace Flax.Deploy DeployFile(src, dst, "Newtonsoft.Json.pdb"); DeployFiles(src, dst, "*.dll"); DeployFiles(src, dst, "*.so"); - DeployFile(src, dst, "libmonosgen-2.0.so.1"); - DeployFile(src, dst, "libmonosgen-2.0.so.1.0.0"); DeployFile(src, dst, "Logo.png"); + + // Optimize package size + Utilities.Run("strip", "FlaxEditor", null, dst, Utilities.RunOptions.None); + Utilities.Run("strip", "libFlaxEditor.so", null, dst, Utilities.RunOptions.None); + Utilities.Run("strip", "libmonosgen-2.0.so", null, dst, Utilities.RunOptions.None); + Utilities.Run("ln", "-s libmonosgen-2.0.so libmonosgen-2.0.so.1", null, dst, Utilities.RunOptions.None); + Utilities.Run("ln", "-s libmonosgen-2.0.so libmonosgen-2.0.so.1.0.0", null, dst, Utilities.RunOptions.None); } else { diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs index 221483536..bec8b583c 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs @@ -20,6 +20,13 @@ namespace Flax.Build.Platforms /// public UWPPlatform() { + // Skip if running on non-Windows system + if (Platform.BuildTargetPlatform != TargetPlatform.Windows) + { + _hasRequiredSDKsInstalled = false; + return; + } + // Visual Studio 2017+ supported only var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019); if (visualStudio == null) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index fca112e9c..7c1a78c81 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -243,9 +243,14 @@ namespace Flax.Build.Platforms { if (_toolsets != null) return _toolsets; - var vsInstances = VisualStudioInstance.GetInstances(); _toolsets = new Dictionary(); + // Skip if running on non-Windows system + if (BuildTargetPlatform != TargetPlatform.Windows) + return _toolsets; + + var vsInstances = VisualStudioInstance.GetInstances(); + // Visual Studio 2015 - single instance var vs2015 = vsInstances.FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2015); if (vs2015 != null) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs index 4c4250aab..6d39625f9 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs @@ -97,9 +97,13 @@ namespace Flax.Build.Projects.VisualStudio { _installDirs = new List(); + // Skip if running on non-Windows system + if (Platform.BuildTargetPlatform != TargetPlatform.Windows) + return _installDirs; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - // Visual Studio 2017-2020 + // Visual Studio 2017-2019 List preReleaseInstallDirs = null; try {