Merge branch 'master' into 1.2

# Conflicts:
#	Source/Engine/Platform/Network.h
This commit is contained in:
Wojtek Figat
2021-03-22 11:26:36 +01:00
116 changed files with 1782 additions and 584 deletions

View File

@@ -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.",

View File

@@ -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();
}

View File

@@ -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<Actor> list, Actor a)

View File

@@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors
/// <summary>
/// Gets the values type.
/// </summary>
public ScriptType Type { get; }
public ScriptType Type { get; private set; }
/// <summary>
/// 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;
}
/// <summary>
/// Sets the type. Use with caution.
/// </summary>
/// <param name="type">The type.</param>
public void SetType(ScriptType type)
{
Type = type;
}
/// <summary>
/// Gets the custom attributes defined for the values source member.
/// </summary>

View File

@@ -816,9 +816,16 @@ namespace FlaxEditor
/// <param name="sphere">The bounding sphere.</param>
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;
}
}
/// <summary>
@@ -828,7 +835,14 @@ namespace FlaxEditor
/// <param name="box">The bounding box.</param>
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;
}
}
/// <summary>
@@ -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));
}

View File

@@ -47,6 +47,11 @@ namespace FlaxEditor.GUI.ContextMenu
private Window _window;
private Control _previouslyFocused;
/// <summary>
/// Gets a value indicating whether use automatic popup direction fix based on the screen dimensions.
/// </summary>
protected virtual bool UseAutomaticDirectionFix => true;
/// <summary>
/// Returns true if context menu is opened
/// </summary>
@@ -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

View File

@@ -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);
}
}

View File

@@ -42,9 +42,6 @@ namespace FlaxEditor.GUI
/// <summary>
/// Gets or sets the initial value.
/// </summary>
/// <value>
/// The initial value.
/// </value>
public string InitialValue
{
get => _startValue;
@@ -54,9 +51,6 @@ namespace FlaxEditor.GUI
/// <summary>
/// Gets or sets the input field text.
/// </summary>
/// <value>
/// The text.
/// </value>
public string Text
{
get => _inputField.Text;
@@ -138,6 +132,9 @@ namespace FlaxEditor.GUI
Hide();
}
/// <inheritdoc />
protected override bool UseAutomaticDirectionFix => false;
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{

View File

@@ -1438,6 +1438,7 @@ namespace FlaxEditor.GUI.Timeline
ArrangeTracks();
PerformLayout(true);
UnlockChildrenRecursive();
PerformLayout(true);
Profiler.EndEvent();
ClearEditedFlag();

View File

@@ -50,6 +50,41 @@ namespace FlaxEditor.Gizmo
{
}
/// <summary>
/// Helper function, recursively finds the Prefab Root of node or null.
/// </summary>
/// <param name="node">The node from which to start.</param>
/// <returns>The prefab root or null.</returns>
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;
}
/// <summary>
/// Recursively walks up from the node up to ceiling node(inclusive) or selection(exclusive).
/// </summary>
/// <param name="node">The node from which to start</param>
/// <param name="ceiling">The ceiling(inclusive)</param>
/// <returns>The node to select.</returns>
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;
}
/// <inheritdoc />
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);

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -263,6 +263,12 @@ namespace FlaxEditor.SceneGraph
return _actor.IntersectsItself(ray.Ray, out distance, out normal);
}
/// <inheritdoc />
public override void GetEditorSphere(out BoundingSphere sphere)
{
Editor.GetActorEditorSphere(_actor, out sphere);
}
/// <inheritdoc />
public override void OnDebugDraw(ViewportDebugDrawData data)
{
@@ -315,5 +321,11 @@ namespace FlaxEditor.SceneGraph
base.Dispose();
}
/// <inheritdoc />
public override string ToString()
{
return _actor ? _actor.ToString() : base.ToString();
}
}
}

View File

@@ -309,6 +309,20 @@ namespace FlaxEditor.SceneGraph
return false;
}
/// <summary>
/// Gets the object bounding sphere (including child actors).
/// </summary>
/// <param name="sphere">The bounding sphere.</param>
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);
}
}
/// <summary>
/// Called when selected nodes should draw debug shapes using <see cref="DebugDraw"/> interface.
/// </summary>

View File

@@ -108,7 +108,7 @@ namespace FlaxEditor.Surface.ContextMenu
{
_resultPanel.DisposeChildren();
var dpiScale = RootWindow.DpiScale;
var dpiScale = DpiScale;
if (items.Count == 0)
{

View File

@@ -578,6 +578,7 @@ namespace FlaxEditor.Surface.Elements
BreakConnection(connectedBox);
action.End();
Surface.Undo.AddAction(action);
Surface.MarkAsEdited();
}
else
{

View File

@@ -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)

View File

@@ -94,6 +94,15 @@ namespace FlaxEditor.Viewport.Cameras
Viewport.ViewPosition = _orbitCenter + localPosition;
}
/// <inheritdoc />
public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius)
{
base.SetArcBallView(orientation, orbitCenter, orbitRadius);
_orbitCenter = orbitCenter;
_orbitRadius = orbitRadius;
}
/// <inheritdoc />
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse)
{

View File

@@ -88,7 +88,7 @@ namespace FlaxEditor.Viewport.Cameras
Editor.GetActorEditorSphere(actor, out BoundingSphere sphere);
ShowSphere(ref sphere);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
@@ -103,48 +103,46 @@ namespace FlaxEditor.Viewport.Cameras
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="actors">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> actors)
/// <param name="selection">The actors to show.</param>
public void ShowActors(List<SceneGraphNode> 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);
}
/// <summary>
/// Moves the viewport to visualize selected actors.
/// </summary>
/// <param name="actors">The actors to show.</param>
/// <param name="selection">The actors to show.</param>
/// <param name="orientation">The used orientation.</param>
public void ShowActors(List<SceneGraphNode> actors, ref Quaternion orientation)
public void ShowActors(List<SceneGraphNode> 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);
}
/// <inheritdoc />
public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius)
{
base.SetArcBallView(orientation, orbitCenter, orbitRadius);
TargetPoint = orbitCenter;
}
/// <inheritdoc />
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;
}

View File

@@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Cameras
/// <param name="orientation">The view rotation.</param>
/// <param name="orbitCenter">The orbit center location.</param>
/// <param name="orbitRadius">The orbit radius.</param>
public void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius)
public virtual void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius)
{
// Rotate
Viewport.ViewOrientation = orientation;

View File

@@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport
public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
/// <inheritdoc />
public Vector2 MouseDelta => _mouseDeltaLeft * 1000;
public Vector2 MouseDelta => _mouseDelta * 1000;
/// <inheritdoc />
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);

View File

@@ -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];
/// <summary>
@@ -159,14 +159,9 @@ namespace FlaxEditor.Viewport
protected Vector2 _viewMousePos;
/// <summary>
/// The mouse delta (right button down).
/// The mouse position delta.
/// </summary>
protected Vector2 _mouseDeltaRight;
/// <summary>
/// The mouse delta (left button down).
/// </summary>
protected Vector2 _mouseDeltaLeft;
protected Vector2 _mouseDelta;
// Camera
@@ -213,14 +208,9 @@ namespace FlaxEditor.Viewport
}
/// <summary>
/// Gets the mouse movement delta for the right button (user press and move).
/// Gets the mouse movement position delta (user press and move).
/// </summary>
public Vector2 MouseDeltaRight => _mouseDeltaRight;
/// <summary>
/// Gets the mouse movement delta for the left button (user press and move).
/// </summary>
public Vector2 MouseDeltaLeft => _mouseDeltaLeft;
public Vector2 MousePositionDelta => _mouseDelta;
/// <summary>
/// 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);
}
}
/// <summary>
@@ -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;
}

View File

@@ -797,6 +797,8 @@ namespace FlaxEditor.Viewport
return true;
if (assetItem.IsOfType<ModelBase>())
return true;
if (assetItem.IsOfType<CollisionData>())
return true;
if (assetItem.IsOfType<AudioClip>())
return true;
if (assetItem.IsOfType<Prefab>())
@@ -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<SceneAnimation>())
@@ -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<MaterialBase>())
@@ -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<Model>())
@@ -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<CollisionData>())
{
var collisionData = FlaxEngine.Content.LoadAsync<CollisionData>(item.ID);
var actor = new MeshCollider
{
Name = item.ShortName,
CollisionData = collisionData
};
Spawn(actor, ref hitLocation);
return;
}
if (assetItem.IsOfType<AudioClip>())
@@ -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<Prefab>())
@@ -954,8 +969,7 @@ namespace FlaxEditor.Viewport
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(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<SceneAsset>())
@@ -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);
}
/// <inheritdoc />

View File

@@ -342,7 +342,7 @@ namespace FlaxEditor.Viewport
public bool SnapToGround => false;
/// <inheritdoc />
public Vector2 MouseDelta => _mouseDeltaLeft * 1000;
public Vector2 MouseDelta => _mouseDelta * 1000;
/// <inheritdoc />
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<ModelBase>())
return true;
if (assetItem.IsOfType<CollisionData>())
return true;
if (assetItem.IsOfType<AudioClip>())
return true;
if (assetItem.IsOfType<Prefab>())
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<CollisionData>())
{
var collisionData = FlaxEngine.Content.LoadAsync<CollisionData>(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<Prefab>(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);
}
/// <inheritdoc />

View File

@@ -249,6 +249,19 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
break;
}
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -85,6 +85,19 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
break;
}
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -121,6 +121,18 @@ namespace FlaxEditor.Viewport.Previews
{
}
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -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.
/// </summary>
[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
/// <summary>
/// The proxy object for editing folder track properties.
/// </summary>
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
/// <inheritdoc />
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;

View File

@@ -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));

View File

@@ -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:

View File

@@ -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);
}

View File

@@ -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())

View File

@@ -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())

View File

@@ -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
{

View File

@@ -45,6 +45,16 @@ public:
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BoundingBox"/> struct.
/// </summary>
/// <param name="point">The location of the empty bounding box.</param>
BoundingBox(const Vector3& point)
: Minimum(point)
, Maximum(point)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BoundingBox"/> struct.
/// </summary>

View File

@@ -125,7 +125,7 @@ namespace FlaxEngine
public static class CollisionsHelper
{
/// <summary>
/// Determines the closest point between a point and a line.
/// Determines the closest point between a point and a line segment.
/// </summary>
/// <param name="point">The point to test.</param>
/// <param name="p0">The line first point.</param>

View File

@@ -197,6 +197,16 @@ namespace FlaxEngine
/// </summary>
public float ValuesSum => X + Y;
/// <summary>
/// Gets a vector with values being absolute values of that vector.
/// </summary>
public Vector2 Absolute => new Vector2(Mathf.Abs(X), Mathf.Abs(Y));
/// <summary>
/// Gets a vector with values being opposite to values of that vector.
/// </summary>
public Vector2 Negative => new Vector2(-X, -Y);
/// <summary>
/// Gets or sets the component at the specified index.
/// </summary>

View File

@@ -227,6 +227,16 @@ namespace FlaxEngine
/// </summary>
public float ValuesSum => X + Y + Z + W;
/// <summary>
/// Gets a vector with values being absolute values of that vector.
/// </summary>
public Vector4 Absolute => new Vector4(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W));
/// <summary>
/// Gets a vector with values being opposite to values of that vector.
/// </summary>
public Vector4 Negative => new Vector4(-X, -Y, -Z, -W);
/// <summary>
/// Gets or sets the component at the specified index.
/// </summary>

View File

@@ -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");

View File

@@ -153,20 +153,6 @@ public:
/// <returns>True if game viewport is focused, otherwise false.</returns>
static bool HasGameViewportFocus();
/// <summary>
/// Converts the screen-space position to the game viewport position.
/// </summary>
/// <param name="screenPos">The screen-space position.</param>
/// <returns>The game viewport position.</returns>
static Vector2 ScreenToGameViewport(const Vector2& screenPos);
/// <summary>
/// Converts the game viewport position to the screen-space position.
/// </summary>
/// <param name="viewportPos">The game viewport position.</param>
/// <returns>The screen-space position.</returns>
static Vector2 GameViewportToScreen(const Vector2& viewportPos);
private:
static void OnPause();

View File

@@ -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

View File

@@ -33,6 +33,20 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen);
/// <returns>The value</returns>
API_PROPERTY() static Vector2 GetSize();
/// <summary>
/// Converts the screen-space position to the game viewport position.
/// </summary>
/// <param name="screenPos">The screen-space position.</param>
/// <returns>The game viewport position.</returns>
API_FUNCTION() static Vector2 ScreenToGameViewport(const Vector2& screenPos);
/// <summary>
/// Converts the game viewport position to the screen-space position.
/// </summary>
/// <param name="viewportPos">The game viewport position.</param>
/// <returns>The screen-space position.</returns>
API_FUNCTION() static Vector2 GameViewportToScreen(const Vector2& viewportPos);
/// <summary>
/// Sets the window size.
/// </summary>

View File

@@ -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);
}

View File

@@ -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<Scene>())
{
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<Actor>(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<Actor>(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)

View File

@@ -629,10 +629,7 @@ public:
/// <summary>
/// Gets actor bounding box (single actor, no children included) for editor tools.
/// </summary>
API_PROPERTY() virtual BoundingBox GetEditorBox() const
{
return GetBox();
}
API_PROPERTY() virtual BoundingBox GetEditorBox() const;
/// <summary>
/// Gets actor bounding box of the actor including all child actors for editor tools.
@@ -644,10 +641,7 @@ public:
/// <summary>
/// Returns true if actor has loaded content.
/// </summary>
API_PROPERTY() virtual bool HasContentLoaded() const
{
return true;
}
API_PROPERTY() virtual bool HasContentLoaded() const;
/// <summary>
/// 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).
/// </summary>
/// <param name="renderContext">The rendering context.</param>
virtual void Draw(RenderContext& renderContext)
{
}
virtual void Draw(RenderContext& renderContext);
/// <summary>
/// Draws this actor. Called during custom actor rendering or any other generic rendering from code.

View File

@@ -383,7 +383,7 @@ void AnimatedModel::UpdateLocalBounds()
}
else
{
box = BoundingBox(Vector3::Zero, Vector3::Zero);
box = BoundingBox(Vector3::Zero);
}
// Scale bounds

View File

@@ -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);
}

View File

@@ -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

View File

@@ -174,19 +174,19 @@ public:
public:
/// <summary>
/// Projects the point from 3D world-space to the camera screen-space (in screen pixels for default viewport calculated from <see cref="Viewport"/>).
/// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from <see cref="Viewport"/>).
/// </summary>
/// <param name="worldSpaceLocation">The input world-space location (XYZ in world).</param>
/// <param name="screenSpaceLocation">The output screen-space location (XY in screen pixels).</param>
API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation) const;
/// <param name="gameWindowSpaceLocation">The output game window coordinates (XY in screen pixels).</param>
API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& gameWindowSpaceLocation) const;
/// <summary>
/// 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).
/// </summary>
/// <param name="worldSpaceLocation">The input world-space location (XYZ in world).</param>
/// <param name="screenSpaceLocation">The output screen-space location (XY in screen pixels).</param>
/// <param name="cameraViewportSpaceLocation">The output camera viewport-space location (XY in screen pixels).</param>
/// <param name="viewport">The viewport.</param>
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;
/// <summary>
/// Converts the mouse position to 3D ray.

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -3,10 +3,12 @@
#include "Spline.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Animations/CurveSerialization.h"
#include "Engine/Core/Math/Matrix.h"
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
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

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -187,7 +187,7 @@ void StaticModel::UpdateBounds()
}
else
{
_box = BoundingBox(_transform.Translation, _transform.Translation);
_box = BoundingBox(_transform.Translation);
}
BoundingSphere::FromBox(_box, _sphere);
}

View File

@@ -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++)

View File

@@ -358,27 +358,47 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& 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<Actor*>(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<Prefab>(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<Actor*>(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

View File

@@ -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, Dictionary<Guid
// TODO: consider caching actorToRemovedObjectsData per prefab
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
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<Actor*>(obj);
auto script = dynamic_cast<Script*>(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)

View File

@@ -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);
}

View File

@@ -204,6 +204,8 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array<SceneObject*>& 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<SceneObject*>& 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<SceneObject*>& 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<SceneObject*>& 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<Prefab>(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);

View File

@@ -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())

View File

@@ -72,7 +72,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod
case 3:
value = matrix.GetRow4();
break;
default: CRASH;
default:
break;
}
break;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -57,7 +57,7 @@ bool NetworkBase::IsReadable(NetworkSocket& socket)
return true;
}
bool NetworkBase::IsWriteable(NetworkSocket& socket)
bool NetworkBase::IsWritable(NetworkSocket& socket)
{
return true;
}

View File

@@ -183,11 +183,11 @@ public:
/// <summary>
/// Accepts a pending connection.
/// </summary>
/// <param name="serverSock">The socket.</param>
/// <param name="newSock">The newly connected socket.</param>
/// <param name="serverSocket">The socket.</param>
/// <param name="newSocket">The newly connected socket.</param>
/// <param name="newEndPoint">The end point of the new socket.</param>
/// <returns>Returns true on error, otherwise false.</returns>
static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint);
static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint);
/// <summary>
/// Checks for socket readability.
@@ -201,7 +201,7 @@ public:
/// </summary>
/// <param name="socket">The socket.</param>
/// <returns>Returns true when data can be written. Otherwise false.</returns>
static bool IsWriteable(NetworkSocket& socket);
static bool IsWritable(NetworkSocket& socket);
/// <summary>
/// Creates a socket group. It allocate memory based on the desired capacity.

View File

@@ -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

View File

@@ -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

View File

@@ -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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <cerrno>
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

View File

@@ -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

View File

@@ -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 <WS2ipdef.h>
#include <WS2tcpip.h>
#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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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
}
}
/// <summary>
/// Serialize SoftObjectReference as Guid in internal format.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
internal class MarginConverter : JsonConverter
{
/// <inheritdoc />
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();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Margin);
}
/// <inheritdoc />
public override bool CanRead => true;
/// <inheritdoc />
public override bool CanWrite => true;
/// <inheritdoc />
public override bool CanWriteDiff => true;
}
/*
/// <summary>
/// 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;

View File

@@ -100,6 +100,8 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c
void JsonTools::ChangeIds(Document& doc, const Dictionary<Guid, Guid>& mapping)
{
if (mapping.IsEmpty())
return;
::ChangeIds(doc, doc, mapping);
}

View File

@@ -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];

View File

@@ -54,6 +54,7 @@ namespace
{
// Loaded and parsed features data cache
Dictionary<StringAnsi, FeatureData> 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);

View File

@@ -5,7 +5,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Border control that draws the border around the control edges (inner and outer sides).
/// </summary>
public class Border : Control
public class Border : ContainerControl
{
/// <summary>
/// Gets or sets the color used to draw border lines.
@@ -30,9 +30,9 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
public override void Draw()
public override void DrawSelf()
{
base.Draw();
base.DrawSelf();
Render2D.DrawRectangle(new Rectangle(Vector2.Zero, Size), BorderColor, BorderWidth);
}

View File

@@ -7,7 +7,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Button control
/// </summary>
public class Button : Control
public class Button : ContainerControl
{
/// <summary>
/// The default height fro the buttons.
@@ -171,7 +171,7 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
public override void Draw()
public override void DrawSelf()
{
// Cache data
Rectangle clientRect = new Rectangle(Vector2.Zero, Size);

View File

@@ -80,9 +80,9 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
public override void Draw()
public override void DrawSelf()
{
base.Draw();
base.DrawSelf();
if (Brush == null)
return;

View File

@@ -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)

View File

@@ -8,7 +8,7 @@ namespace FlaxEngine.GUI
/// Progress bar control shows visual progress of the action or set of actions.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" />
public class ProgressBar : Control
public class ProgressBar : ContainerControl
{
/// <summary>
/// The value.
@@ -160,9 +160,9 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
public override void Draw()
public override void DrawSelf()
{
base.Draw();
base.DrawSelf();
float progressNormalized = (_current - _minimum) / _maximum;
if (progressNormalized > 0.001f)

View File

@@ -105,7 +105,7 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
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();
}
/// <inheritdoc />

View File

@@ -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
}
/// <inheritdoc />
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);
}

View File

@@ -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
}
/// <inheritdoc />
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);

View File

@@ -9,7 +9,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Base class for all text box controls which can gather text input from the user.
/// </summary>
public abstract class TextBoxBase : Control
public abstract class TextBoxBase : ContainerControl
{
/// <summary>
/// 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;

View File

@@ -262,6 +262,8 @@ namespace FlaxEngine.GUI
// Change order
_children.Insert(newIndex, child);
}
PerformLayout();
}
/// <summary>
@@ -614,25 +616,32 @@ namespace FlaxEngine.GUI
}
}
/// <inheritdoc />
/// <summary>
/// Draw the control and the children.
/// </summary>
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();
}
}
/// <summary>
/// Draws the control.
/// </summary>
public virtual void DrawSelf()
{
base.Draw();
}
/// <summary>

View File

@@ -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();
}
/// <summary>
/// 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.
/// </summary>
[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
}
/// <summary>
/// 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.
/// </summary>
[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
}
/// <summary>
/// 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).
/// </summary>
[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;
}

View File

@@ -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
/// </summary>
public virtual WindowRootControl RootWindow => _root?.RootWindow;
/// <summary>
/// Gets the control DPI scale factor (1 is default). Includes custom DPI scale.
/// </summary>
public float DpiScale => _root?.RootWindow?.Window.DpiScale ?? Platform.DpiScale;
/// <summary>
/// Gets screen position of the control (upper left corner).
/// </summary>

View File

@@ -224,7 +224,7 @@ namespace FlaxEngine.GUI
/// </summary>
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)

View File

@@ -55,11 +55,6 @@ namespace FlaxEngine.GUI
/// </summary>
public bool IsMaximized => _window.IsMaximized;
/// <summary>
/// Gets the window DPI scale factor (1 is default). Includes custom DPI scale
/// </summary>
public float DpiScale => _window.DpiScale;
internal WindowRootControl(Window window)
{
_window = window;

View File

@@ -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);

View File

@@ -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);
}

Some files were not shown because too many files have changed in this diff Show More