diff --git a/Content/Shaders/Editor/Grid.flax b/Content/Shaders/Editor/Grid.flax
index 440d37512..5fea3f4a2 100644
--- a/Content/Shaders/Editor/Grid.flax
+++ b/Content/Shaders/Editor/Grid.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9aa0cb389296afe4ee474395f188e61f8a1e428aac1f09fc7d54825b1d6e58df
-size 4744
+oid sha256:489711fab457331f74da08325ce7129237fbcb815d0d8c8a4ffe52e85a2672f7
+size 4743
diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax
index fa75716d9..59d6caed5 100644
--- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax
+++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c87a431fc7230f3d345ce131fdb1727ffe45874b55ed062feff73aabed514f59
-size 13194
+oid sha256:2948a652d488effda7d5f14c3f603c0ae266c318aaa15cf8543cda8a10d21c0e
+size 13163
diff --git a/Content/Shaders/Quad.flax b/Content/Shaders/Quad.flax
index 6bdf6ce8e..69f7c4f31 100644
--- a/Content/Shaders/Quad.flax
+++ b/Content/Shaders/Quad.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:60680a4ce8deee4de81d8aafed454fb5aace3a6d18a11eda3b96e81ccaf801a9
-size 4443
+oid sha256:32bb06520034006149c4036a8e2ac90b483da07f5e9e7f01568b5e6696bbca59
+size 4397
diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
index 9a33d82af..7ec4147d8 100644
--- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
@@ -92,7 +92,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index))
{
- SetPointSmooth(spline, index);
+ SetPointSmooth(Editor, spline, index);
}
}
}
@@ -145,7 +145,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (!IsAlignedTangentMode(spline, index))
{
- SetPointSmooth(spline, index);
+ SetPointSmooth(Editor, spline, index);
}
}
@@ -176,7 +176,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void OnSetMode(Spline spline, int index)
{
- SetTangentSmoothIn(spline, index);
+ SetTangentSmoothIn(Editor, spline, index);
Editor.SetSelectTangentIn(spline, index);
}
}
@@ -190,7 +190,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void OnSetMode(Spline spline, int index)
{
- SetTangentSmoothOut(spline, index);
+ SetTangentSmoothOut(Editor, spline, index);
Editor.SetSelectTangentOut(spline, index);
}
}
@@ -756,14 +756,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
spline.UpdateSpline();
}
- private static void SetTangentSmoothIn(Spline spline, int index)
+ private static void SetTangentSmoothIn(SplineEditor editor, Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
// Auto smooth tangent if's linear
if (keyframe.TangentIn.Translation.Length == 0)
{
- var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
+ var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation;
+ var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f, cameraTransform);
var previousKeyframe = spline.GetSplineKeyframe(index - 1);
var tangentDirection = keyframe.Value.WorldToLocalVector(previousKeyframe.Value.Translation - keyframe.Value.Translation);
tangentDirection = tangentDirection.Normalized * smoothRange;
@@ -775,14 +776,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
spline.UpdateSpline();
}
- private static void SetTangentSmoothOut(Spline spline, int index)
+ private static void SetTangentSmoothOut(SplineEditor editor, Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
// Auto smooth tangent if's linear
if (keyframe.TangentOut.Translation.Length == 0)
{
- var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f);
+ var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation;
+ var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f, cameraTransform);
var nextKeyframe = spline.GetSplineKeyframe(index + 1);
var tangentDirection = keyframe.Value.WorldToLocalVector(nextKeyframe.Value.Translation - keyframe.Value.Translation);
tangentDirection = tangentDirection.Normalized * smoothRange;
@@ -795,7 +797,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
spline.UpdateSpline();
}
- private static void SetPointSmooth(Spline spline, int index)
+ private static void SetPointSmooth(SplineEditor editor, Spline spline, int index)
{
var keyframe = spline.GetSplineKeyframe(index);
var tangentInSize = keyframe.TangentIn.Translation.Length;
@@ -803,7 +805,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
var isLastKeyframe = index >= spline.SplinePointsCount - 1;
var isFirstKeyframe = index <= 0;
- var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f);
+ var cameraTransform = editor.Presenter.Owner.PresenterViewport.ViewTransform.Translation;
+ var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f, cameraTransform);
// Force smooth it's linear point
if (tangentInSize == 0f)
diff --git a/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs
new file mode 100644
index 000000000..65a6dc1a1
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/ControlReferenceEditor.cs
@@ -0,0 +1,84 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using Object = FlaxEngine.Object;
+
+namespace FlaxEditor.CustomEditors.Editors;
+
+///
+/// The reference picker control used for UIControls using ControlReference.
+///
+internal class UIControlRefPickerControl : FlaxObjectRefPickerControl
+{
+ ///
+ /// Type of the control to pick.
+ ///
+ public Type ControlType;
+
+ ///
+ protected override bool IsValid(Object obj)
+ {
+ return obj == null || (obj is UIControl control && control.Control.GetType() == ControlType);
+ }
+}
+
+///
+/// ControlReferenceEditor class.
+///
+[CustomEditor(typeof(ControlReference<>)), DefaultEditor]
+internal class ControlReferenceEditor : CustomEditor
+{
+ private CustomElement _element;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ if (!HasDifferentTypes)
+ {
+ _element = layout.Custom();
+ if (ValuesTypes == null || ValuesTypes[0] == null)
+ {
+ Editor.LogWarning("ControlReference needs to be assigned in code.");
+ return;
+ }
+ Type genType = ValuesTypes[0].GetGenericArguments()[0];
+ if (typeof(Control).IsAssignableFrom(genType))
+ {
+ _element.CustomControl.PresenterContext = Presenter.Owner;
+ _element.CustomControl.ControlType = genType;
+ _element.CustomControl.Type = new ScriptType(typeof(UIControl));
+ }
+ _element.CustomControl.ValueChanged += () =>
+ {
+ Type genericType = ValuesTypes[0].GetGenericArguments()[0];
+ if (typeof(Control).IsAssignableFrom(genericType))
+ {
+ Type t = typeof(ControlReference<>);
+ Type tw = t.MakeGenericType(new Type[] { genericType });
+ var instance = Activator.CreateInstance(tw);
+ ((IControlReference)instance).UIControl = (UIControl)_element.CustomControl.Value;
+ SetValue(instance);
+ }
+ };
+ }
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ if (!HasDifferentValues)
+ {
+ if (Values[0] is IControlReference cr)
+ _element.CustomControl.Value = cr.UIControl;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
index 29514c369..d53844499 100644
--- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
@@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Editors
public IPresenterOwner PresenterContext;
///
- /// Gets or sets the allowed objects type (given type and all sub classes). Must be type of any subclass.
+ /// Gets or sets the allowed objects type (given type and all subclasses). Must be type of any subclass.
///
public ScriptType Type
{
@@ -61,7 +61,8 @@ namespace FlaxEditor.CustomEditors.Editors
throw new ArgumentException(string.Format("Invalid type for FlaxObjectRefEditor. Input type: {0}", value != ScriptType.Null ? value.TypeName : "null"));
_type = value;
- _supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) || new ScriptType(typeof(Script)).IsAssignableFrom(value);
+ _supportsPickDropDown = new ScriptType(typeof(Actor)).IsAssignableFrom(value) ||
+ new ScriptType(typeof(Script)).IsAssignableFrom(value);
// Deselect value if it's not valid now
if (!IsValid(_value))
@@ -80,7 +81,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (_value == value)
return;
if (!IsValid(value))
- throw new ArgumentException("Invalid object type.");
+ value = null;
// Special case for missing objects (eg. referenced actor in script that is deleted in editor)
if (value != null && (Object.GetUnmanagedPtr(value) == IntPtr.Zero || value.ID == Guid.Empty))
@@ -91,27 +92,17 @@ namespace FlaxEditor.CustomEditors.Editors
// Get name to display
if (_value is Script script)
- {
_valueName = script.Actor ? $"{type.Name} ({script.Actor.Name})" : type.Name;
- }
else if (_value != null)
- {
_valueName = _value.ToString();
- }
else
- {
_valueName = string.Empty;
- }
// Update tooltip
if (_value is SceneObject sceneObject)
- {
TooltipText = Utilities.Utils.GetTooltip(sceneObject);
- }
else
- {
TooltipText = string.Empty;
- }
OnValueChanged();
}
@@ -150,7 +141,12 @@ namespace FlaxEditor.CustomEditors.Editors
_type = ScriptType.Object;
}
- private bool IsValid(Object obj)
+ ///
+ /// Object validation check routine.
+ ///
+ /// Input object to check.
+ /// True if it can be assigned, otherwise false.
+ protected virtual bool IsValid(Object obj)
{
var type = TypeUtils.GetObjectType(obj);
return obj == null || _type.IsAssignableFrom(type) && (CheckValid == null || CheckValid(obj, type));
@@ -168,6 +164,15 @@ namespace FlaxEditor.CustomEditors.Editors
Focus();
}, PresenterContext);
}
+ else if (new ScriptType(typeof(Control)).IsAssignableFrom(_type))
+ {
+ ActorSearchPopup.Show(this, new Float2(0, Height), IsValid, actor =>
+ {
+ Value = actor as UIControl;
+ RootWindow.Focus();
+ Focus();
+ }, PresenterContext);
+ }
else
{
ScriptSearchPopup.Show(this, new Float2(0, Height), IsValid, script =>
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index d757c13a3..d27dc3f8c 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -41,6 +41,7 @@ public class Editor : EditorModule
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
+ options.ScriptingAPI.SystemReferences.Add("System.Diagnostics.Process");
if (Profiler.Use(options))
options.ScriptingAPI.Defines.Add("USE_PROFILER");
diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs
index 966ee22e2..b61a38daa 100644
--- a/Source/Editor/Gizmo/GridGizmo.cs
+++ b/Source/Editor/Gizmo/GridGizmo.cs
@@ -89,6 +89,7 @@ namespace FlaxEditor.Gizmo
desc.CullMode = CullMode.TwoSided;
desc.VS = _shader.GPU.GetVS("VS_Grid");
desc.PS = _shader.GPU.GetPS("PS_Grid");
+ desc.DepthWriteEnable = false;
_psGrid.Init(ref desc);
}
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index a144d6f4c..850e4df3d 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -41,7 +41,7 @@ namespace FlaxEditor.Modules
public override Undo Undo => Editor.Instance.Undo;
///
- public override List Selection => _editor.SceneEditing.Selection;
+ public override ISceneEditingContext SceneContext => _editor.Windows.EditWin;
}
///
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
index 311767caf..18233c162 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
@@ -234,7 +234,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
{
if (methodGenericMap.TryGetValue(type.Name, out var methodIndex))
return "``" + methodIndex;
- return "`" + typeGenericMap[type.Name];
+ if (typeGenericMap.TryGetValue(type.Name, out var typeKey))
+ return "`" + typeKey;
+ return "`";
}
if (type.HasElementType)
{
diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs
index 411bf29a3..555e3cb55 100644
--- a/Source/Editor/SceneGraph/Actors/SplineNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs
@@ -39,7 +39,7 @@ namespace FlaxEditor.SceneGraph.Actors
public override bool CanBeSelectedDirectly => true;
- public override bool CanDuplicate => !Root?.Selection.Contains(ParentNode) ?? true;
+ public override bool CanDuplicate => !Root?.SceneContext.Selection.Contains(ParentNode) ?? true;
public override bool CanDelete => true;
@@ -176,7 +176,7 @@ namespace FlaxEditor.SceneGraph.Actors
normal = -ray.Ray.Direction;
var actor = (Spline)_node.Actor;
var pos = actor.GetSplinePoint(Index);
- var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize);
+ var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize, ray.View.Position);
return new BoundingSphere(pos, nodeSize).Intersects(ref ray.Ray, out distance);
}
@@ -186,9 +186,10 @@ namespace FlaxEditor.SceneGraph.Actors
var pos = actor.GetSplinePoint(Index);
var tangentIn = actor.GetSplineTangent(Index, true).Translation;
var tangentOut = actor.GetSplineTangent(Index, false).Translation;
- var pointSize = NodeSizeByDistance(pos, PointNodeSize);
- var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize);
- var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize);
+ var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation;
+ var pointSize = NodeSizeByDistance(pos, PointNodeSize, cameraTransform);
+ var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize, cameraTransform);
+ var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize, cameraTransform);
// Draw spline path
ParentNode.OnDebugDraw(data);
@@ -262,7 +263,7 @@ namespace FlaxEditor.SceneGraph.Actors
normal = -ray.Ray.Direction;
var actor = (Spline)_node.Actor;
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
- var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
+ var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize, ray.View.Position);
return new BoundingSphere(pos, tangentSize).Intersects(ref ray.Ray, out distance);
}
@@ -274,7 +275,8 @@ namespace FlaxEditor.SceneGraph.Actors
// Draw selected tangent highlight
var actor = (Spline)_node.Actor;
var pos = actor.GetSplineTangent(_index, _isIn).Translation;
- var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize);
+ var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation;
+ var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize, cameraTransform);
DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false);
}
@@ -306,9 +308,16 @@ namespace FlaxEditor.SceneGraph.Actors
private void OnUpdate()
{
+ // Prevent update event called when actor got deleted (incorrect state)
+ if (!Actor)
+ {
+ FlaxEngine.Scripting.Update -= OnUpdate;
+ return;
+ }
+
// If this node's point is selected
- var selection = Editor.Instance.SceneEditing.Selection;
- if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this)
+ var selection = Root?.SceneContext.Selection;
+ if (selection != null && selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this)
{
var mouse = Input.Mouse;
var keyboard = Input.Keyboard;
@@ -317,7 +326,7 @@ namespace FlaxEditor.SceneGraph.Actors
EditSplineWithSnap(selectedPoint);
var canAddSplinePoint = mouse.PositionDelta == Float2.Zero && mouse.Position != Float2.Zero;
- var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right);
+ var requestAddSplinePoint = keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right);
if (requestAddSplinePoint && canAddSplinePoint)
AddSplinePoint(selectedPoint);
}
@@ -338,9 +347,7 @@ namespace FlaxEditor.SceneGraph.Actors
while (srcCount > dstCount)
{
var node = ActorChildNodes[srcCount-- - 1];
- // TODO: support selection interface inside SceneGraph nodes (eg. on Root) so prefab editor can handle this too
- if (Editor.Instance.SceneEditing.Selection.Contains(node))
- Editor.Instance.SceneEditing.Deselect();
+ Root?.SceneContext.Deselect(node);
node.Dispose();
}
@@ -356,22 +363,23 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
- private unsafe void AddSplinePoint(SplinePointNode selectedPoint)
+ private void AddSplinePoint(SplinePointNode selectedPoint)
{
// Check mouse hit on scene
+ var root = Root;
var spline = (Spline)Actor;
- var viewport = Editor.Instance.Windows.EditWin.Viewport;
+ var viewport = root.SceneContext.Viewport;
var mouseRay = viewport.MouseRay;
var viewRay = viewport.ViewRay;
var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives;
- var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags);
+ var hit = root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags);
if (hit == null)
return;
// Undo data
var oldSpline = spline.SplineKeyframes;
var editAction = new EditSplineAction(spline, oldSpline);
- Root.Undo.AddAction(editAction);
+ root.Undo.AddAction(editAction);
// Get spline point to duplicate
var hitPoint = mouseRay.Position + mouseRay.Direction * closest;
@@ -423,7 +431,7 @@ namespace FlaxEditor.SceneGraph.Actors
// Select new point node
SyncSplineKeyframeWithNodes();
- Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]);
+ root.SceneContext.Select(ChildNodes[newPointIndex]);
spline.UpdateSpline();
}
@@ -436,6 +444,7 @@ namespace FlaxEditor.SceneGraph.Actors
allSplinesInView.Remove(spline);
if (allSplinesInView.Count == 0)
return;
+ var cameraTransform = Root.SceneContext.Viewport.ViewTransform.Translation;
var snappedOnSplinePoint = false;
for (int i = 0; i < allSplinesInView.Count; i++)
@@ -443,7 +452,7 @@ namespace FlaxEditor.SceneGraph.Actors
for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++)
{
var keyframePosition = allSplinesInView[i].GetSplinePoint(x);
- var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize);
+ var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize, cameraTransform);
var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize);
DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false);
@@ -459,7 +468,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (!snappedOnSplinePoint)
{
var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedPoint.Transform.Translation, allSplinesInView);
- var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize);
+ var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize, cameraTransform);
var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize);
if (snapBounds.Intersects(selectedPointBounds))
{
@@ -551,11 +560,16 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
- private static List GetSplinesInView()
+ private List GetSplinesInView()
{
- var splines = Level.GetActors(true);
+ Spline[] splines;
+ var sceneContext = Root.SceneContext;
+ if (sceneContext is Windows.Assets.PrefabWindow prefabWindow)
+ splines = new Spline[0]; // TODO: add GetActors or similar utility to SceneContext and use Level.GetActors(..) with specific root actor
+ else
+ splines = Level.GetActors(true);
var result = new List();
- var viewBounds = Editor.Instance.Windows.EditWin.Viewport.ViewFrustum;
+ var viewBounds = sceneContext.Viewport.ViewFrustum;
foreach (var s in splines)
{
var contains = viewBounds.Contains(s.EditorBox);
@@ -569,7 +583,6 @@ namespace FlaxEditor.SceneGraph.Actors
{
var nearPoint = splines[0].GetSplinePointClosestToPoint(position);
var nearDistance = Vector3.Distance(nearPoint, position);
-
for (int i = 1; i < splines.Count; i++)
{
var point = splines[i].GetSplinePointClosestToPoint(position);
@@ -580,14 +593,12 @@ namespace FlaxEditor.SceneGraph.Actors
nearDistance = distance;
}
}
-
return nearPoint;
}
- internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize)
+ internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize, Vector3 viewPosition)
{
- var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform;
- var distance = Vector3.Distance(cameraTransform.Translation, nodePosition) / 100;
+ var distance = Vector3.Distance(viewPosition, nodePosition) / 100;
return distance * nodeSize;
}
diff --git a/Source/Editor/SceneGraph/ISceneEditingContext.cs b/Source/Editor/SceneGraph/ISceneEditingContext.cs
new file mode 100644
index 000000000..63c2cc9c2
--- /dev/null
+++ b/Source/Editor/SceneGraph/ISceneEditingContext.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System.Collections.Generic;
+using FlaxEditor.SceneGraph;
+using FlaxEditor.Viewport;
+
+namespace FlaxEditor
+{
+ ///
+ /// Shared interface for scene editing utilities.
+ ///
+ public interface ISceneEditingContext
+ {
+ ///
+ /// Gets the main or last editor viewport used for scene editing within this context.
+ ///
+ EditorViewport Viewport { get; }
+
+ ///
+ /// Gets the list of selected scene graph nodes in the editor context.
+ ///
+ List Selection { get; }
+
+ ///
+ /// Selects the specified node.
+ ///
+ /// The node.
+ /// if set to true will use additive mode, otherwise will clear previous selection.
+ void Select(SceneGraphNode node, bool additive = false);
+
+ ///
+ /// Deselects node.
+ ///
+ /// The node to deselect.
+ void Deselect(SceneGraphNode node);
+
+ ///
+ /// Opends popup for renaming selected objects.
+ ///
+ void RenameSelection();
+
+ ///
+ /// Focuses selected objects.
+ ///
+ void FocusSelection();
+ }
+}
diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs
index 68044cc1a..e387520da 100644
--- a/Source/Editor/SceneGraph/RootNode.cs
+++ b/Source/Editor/SceneGraph/RootNode.cs
@@ -168,7 +168,14 @@ namespace FlaxEditor.SceneGraph
///
/// Gets the list of selected scene graph nodes in the editor context.
+ /// [Deprecated in v1.10]
///
- public abstract List Selection { get; }
+ [Obsolete("Use SceneContext.Selection instead.")]
+ public List Selection => SceneContext.Selection;
+
+ ///
+ /// Gets the list of selected scene graph nodes in the editor context.
+ ///
+ public abstract ISceneEditingContext SceneContext { get; }
}
}
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index ad84e6737..12c5ebfd0 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -1557,11 +1557,11 @@ namespace FlaxEditor.Utilities
return false;
}
- internal static ISceneContextWindow GetSceneContext(this Control c)
+ internal static ISceneEditingContext GetSceneContext(this Control c)
{
- while (c != null && !(c is ISceneContextWindow))
+ while (c != null && !(c is ISceneEditingContext))
c = c.Parent;
- return c as ISceneContextWindow;
+ return c as ISceneEditingContext;
}
}
}
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 68e0723b0..e30e242e9 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -968,6 +968,14 @@ namespace FlaxEditor.Viewport
debugView.VisibleChanged += WidgetViewModeShowHide;
}
+ // Clear Debug Draw
+ {
+ var button = ViewWidgetButtonMenu.AddButton("Clear Debug Draw");
+ button.CloseMenuOnClick = false;
+ button.Clicked += () => DebugDraw.Clear();
+ ViewWidgetButtonMenu.VisibleChanged += (Control cm) => { button.Visible = DebugDraw.CanClear(); };
+ }
+
ViewWidgetButtonMenu.AddSeparator();
// Brightness
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 256b41290..f5ef95bce 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -10,7 +10,6 @@ using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
-using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 3ecc43567..540145e63 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -41,7 +41,7 @@ namespace FlaxEditor.Windows.Assets
public override Undo Undo => _window.Undo;
///
- public override List Selection => _window.Selection;
+ public override ISceneEditingContext SceneContext => _window;
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
index 85dfc1595..48c0c5685 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
@@ -12,10 +12,7 @@ namespace FlaxEditor.Windows.Assets
{
public sealed partial class PrefabWindow
{
- ///
- /// The current selection (readonly).
- ///
- public readonly List Selection = new List();
+ private readonly List _selection = new List();
///
/// Occurs when selection gets changed.
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 06990db46..191a6911e 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+using System.Collections.Generic;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
@@ -19,7 +20,7 @@ namespace FlaxEditor.Windows.Assets
///
///
///
- public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner, ISceneContextWindow
+ public sealed partial class PrefabWindow : AssetEditorWindowBase, IPresenterOwner, ISceneEditingContext
{
private readonly SplitPanel _split1;
private readonly SplitPanel _split2;
@@ -54,6 +55,9 @@ namespace FlaxEditor.Windows.Assets
///
public PrefabWindowViewport Viewport => _viewport;
+ ///
+ public List Selection => _selection;
+
///
/// Gets the prefab objects properties editor.
///
@@ -559,5 +563,8 @@ namespace FlaxEditor.Windows.Assets
///
public EditorViewport PresenterViewport => _viewport;
+
+ ///
+ EditorViewport ISceneEditingContext.Viewport => Viewport;
}
}
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 182467566..292ec79c5 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -76,6 +76,15 @@ namespace FlaxEditor.Windows
});
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
+
+ if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
+ {
+ cm.AddButton("Show in Content Panel", () =>
+ {
+ Editor.Instance.Windows.ContentWin.ClearItemsSearch();
+ Editor.Instance.Windows.ContentWin.Select(item);
+ });
+ }
if (item.HasDefaultThumbnail == false)
{
diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs
index 6fb3c1a08..3ff213652 100644
--- a/Source/Editor/Windows/EditGameWindow.cs
+++ b/Source/Editor/Windows/EditGameWindow.cs
@@ -130,7 +130,7 @@ namespace FlaxEditor.Windows
///
/// The viewport control.
///
- public readonly MainEditorGizmoViewport Viewport;
+ public new readonly MainEditorGizmoViewport Viewport;
///
/// Initializes a new instance of the class.
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index e6b9fac2e..7f576e7be 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -2,16 +2,20 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
using FlaxEditor.GUI.Tabs;
+using FlaxEditor.GUI.Tree;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
+using Debug = FlaxEngine.Debug;
+using PlatformType = FlaxEngine.PlatformType;
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Local
@@ -192,6 +196,12 @@ namespace FlaxEditor.Windows
var label = layout.Label(text, TextAlignment.Center);
label.Label.AutoHeight = true;
}
+
+ ///
+ /// Used to add platform specific tools if available.
+ ///
+ /// The layout to start the tools at.
+ public virtual void OnCustomToolsLayout(LayoutElementsContainer layout) { }
public virtual void Build()
{
@@ -233,6 +243,272 @@ namespace FlaxEditor.Windows
class Android : Platform
{
protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64;
+
+ ///
+ public override void OnCustomToolsLayout(LayoutElementsContainer layout)
+ {
+ base.OnCustomToolsLayout(layout);
+
+ // Add emulation options to android tab.
+ layout.Space(5);
+ var emulatorGroup = layout.Group("Tools");
+ var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+ if (string.IsNullOrEmpty(sdkPath))
+ sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+ emulatorGroup.Label($"SDK path: {sdkPath}");
+
+ // AVD and starting emulator
+ var avdGroup = emulatorGroup.Group("AVD Emulator");
+ avdGroup.Label("Note: Create AVDs using Android Studio.");
+ avdGroup.Panel.IsClosed = false;
+ var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+ var avdListGroup = avdGroup.Group("AVD List");
+ avdListGroup.Panel.IsClosed = false;
+ var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+ var avdListTree = new Tree(false)
+ {
+ Parent = avdListGroup.Panel,
+ };
+ refreshAVDListButton.Clicked += () =>
+ {
+ if (avdListTree.Children.Count > 0)
+ avdListTree.DisposeChildren();
+
+ var processStartInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+ Arguments = "-list-avds",
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ };
+
+ var process = new System.Diagnostics.Process
+ {
+ StartInfo = processStartInfo
+ };
+ process.Start();
+ var output = new string(process.StandardOutput.ReadToEnd());
+ /*
+ CreateProcessSettings processSettings = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+ Arguments = "-list-avds",
+ HiddenWindow = false,
+ SaveOutput = true,
+ WaitForEnd = true,
+ };
+ //processSettings.ShellExecute = true;
+ FlaxEngine.Platform.CreateProcess(ref processSettings);
+
+ var output = new string(processSettings.Output);*/
+ if (output.Length == 0)
+ {
+ noAvdLabel.Visible = true;
+ FlaxEditor.Editor.LogWarning("No AVDs detected.");
+ return;
+ }
+ noAvdLabel.Visible = false;
+ var splitOutput = output.Split('\n');
+ foreach (var line in splitOutput)
+ {
+ if (string.IsNullOrEmpty(line.Trim()))
+ continue;
+ var item = new TreeNode
+ {
+ Text = line.Trim(),
+ Parent = avdListTree,
+ };
+ }
+ avdListGroup.Panel.IsClosed = false;
+ };
+
+ avdGroup.Label("Emulator AVD Commands:");
+ var commandsTextBox = avdGroup.TextBox().TextBox;
+ commandsTextBox.IsMultiline = false;
+ commandsTextBox.Text = "-no-snapshot-load -no-boot-anim"; // TODO: save user changes
+
+ var startEmulatorButton = avdGroup.Button("Start AVD Emulator").Button;
+ startEmulatorButton.TooltipText = "Starts selected AVD from list.";
+ startEmulatorButton.Clicked += () =>
+ {
+ if (avdListTree.Selection.Count == 0)
+ return;
+
+ CreateProcessSettings processSettings = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+ Arguments = $"-avd {avdListTree.Selection[0].Text} {commandsTextBox.Text}",
+ HiddenWindow = true,
+ SaveOutput = false,
+ WaitForEnd = false,
+ };
+ processSettings.ShellExecute = true;
+ FlaxEngine.Platform.CreateProcess(ref processSettings);
+ };
+
+ emulatorGroup.Space(2);
+
+ // Device
+ var installGroup = emulatorGroup.Group("Install");
+ installGroup.Panel.IsClosed = false;
+ installGroup.Label("Note: Used to install to AVD or physical devices.");
+ var refreshDeviceListButton = installGroup.Button("Refresh device list").Button;
+ var deviceListGroup = installGroup.Group("List of devices");
+ deviceListGroup.Panel.IsClosed = false;
+ var noDevicesLabel = deviceListGroup.Label("No devices found. Click Refresh.", TextAlignment.Center).Label;
+ var deviceListTree = new Tree(false)
+ {
+ Parent = deviceListGroup.Panel,
+ };
+ refreshDeviceListButton.Clicked += () =>
+ {
+ if (deviceListTree.Children.Count > 0)
+ deviceListTree.DisposeChildren();
+
+ var processStartInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = "devices -l",
+ RedirectStandardOutput = true,
+ CreateNoWindow = true,
+ };
+
+ var process = new System.Diagnostics.Process
+ {
+ StartInfo = processStartInfo
+ };
+ process.Start();
+ var output = new string(process.StandardOutput.ReadToEnd());
+ /*
+ CreateProcessSettings processSettings = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = "devices -l",
+ HiddenWindow = false,
+ //SaveOutput = true,
+ WaitForEnd = true,
+ };
+ processSettings.SaveOutput = true;
+ processSettings.ShellExecute = false;
+ FlaxEngine.Platform.CreateProcess(ref processSettings);
+
+ var output = new string(processSettings.Output);
+ */
+
+ if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal))
+ {
+ noDevicesLabel.Visible = false;
+ var splitLines = output.Split('\n');
+ foreach (var line in splitLines)
+ {
+ if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim()))
+ continue;
+
+ var tab = line.Split("device ");
+ if (tab.Length < 2)
+ continue;
+ var item = new TreeNode
+ {
+ Text = $"{tab[0].Trim()} - {tab[1].Trim()}",
+ Tag = tab[0].Trim(),
+ Parent = deviceListTree,
+ };
+ }
+ }
+ else
+ {
+ noDevicesLabel.Visible = true;
+ }
+
+ deviceListGroup.Panel.IsClosed = false;
+ };
+
+ var autoStart = installGroup.Checkbox("Try to auto start activity on device.");
+ var installButton = installGroup.Button("Install APK to Device").Button;
+ installButton.TooltipText = "Installs APK from the output folder to the selected device.";
+ installButton.Clicked += () =>
+ {
+ if (deviceListTree.Selection.Count == 0)
+ return;
+
+ // Get built APK at output path
+ string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output));
+ if (!Directory.Exists(output))
+ {
+ FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist.");
+ return;
+ }
+
+ var apkFiles = Directory.GetFiles(output, "*.apk");
+ if (apkFiles.Length == 0)
+ {
+ FlaxEditor.Editor.LogWarning("Can not copy APK because no .apk files were found in output folder.");
+ return;
+ }
+
+ string apkFilesString = string.Empty;
+ for (int i = 0; i < apkFiles.Length; i++)
+ {
+ var file = apkFiles[i];
+ if (i == 0)
+ {
+ apkFilesString = $"\"{file}\"";
+ continue;
+ }
+ apkFilesString += $" \"{file}\"";
+ }
+
+ CreateProcessSettings processSettings = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = $"-s {deviceListTree.Selection[0].Tag} {(apkFiles.Length > 1 ? "install-multiple" : "install")} {apkFilesString}",
+ LogOutput = true,
+ };
+ FlaxEngine.Platform.CreateProcess(ref processSettings);
+
+ if (autoStart.CheckBox.Checked)
+ {
+ var gameSettings = GameSettings.Load();
+ var productName = gameSettings.ProductName.Replace(" ", "").ToLower();
+ var companyName = gameSettings.CompanyName.Replace(" ", "").ToLower();
+ CreateProcessSettings processSettings1 = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = $"shell am start -n com.{companyName}.{productName}/com.flaxengine.GameActivity",
+ LogOutput = true,
+ };
+ FlaxEngine.Platform.CreateProcess(ref processSettings1);
+ }
+ };
+
+ var adbLogButton = emulatorGroup.Button("Start adb log collecting").Button;
+ adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb.";
+ adbLogButton.Clicked += () =>
+ {
+ var processStartInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = "logcat Flax:I *:S",
+ CreateNoWindow = false,
+ WindowStyle = ProcessWindowStyle.Normal,
+ };
+
+ var process = new System.Diagnostics.Process
+ {
+ StartInfo = processStartInfo
+ };
+ process.Start();
+ /*
+ CreateProcessSettings processSettings = new CreateProcessSettings
+ {
+ FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+ Arguments = $"logcat Flax:I *:S",
+ WaitForEnd = false,
+ };
+ FlaxEngine.Platform.CreateProcess(ref processSettings);
+ */
+ };
+ }
}
class Switch : Platform
@@ -343,6 +619,7 @@ namespace FlaxEditor.Windows
_buildButton = layout.Button("Build").Button;
_buildButton.Clicked += OnBuildClicked;
+ platformObj.OnCustomToolsLayout(layout);
}
else
{
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 5835acaee..e8f5626ca 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -117,7 +117,6 @@ namespace FlaxEditor.Windows
{
if (!AudioMuted)
Audio.MasterVolume = value;
-
_audioVolume = value;
}
}
@@ -679,6 +678,14 @@ namespace FlaxEditor.Windows
checkbox.StateChanged += x => ShowDebugDraw = x.Checked;
}
+ // Clear Debug Draw
+ if (DebugDraw.CanClear())
+ {
+ var button = menu.AddButton("Clear Debug Draw");
+ button.CloseMenuOnClick = false;
+ button.Clicked += () => DebugDraw.Clear();
+ }
+
menu.AddSeparator();
// Mute Audio
diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs
index 62922ebed..4a20ab7af 100644
--- a/Source/Editor/Windows/SceneEditorWindow.cs
+++ b/Source/Editor/Windows/SceneEditorWindow.cs
@@ -1,32 +1,18 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using System.Collections.Generic;
using FlaxEditor.SceneGraph;
+using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows
{
- ///
- /// Shared interface for scene editing utilities.
- ///
- public interface ISceneContextWindow
- {
- ///
- /// Opends popup for renaming selected objects.
- ///
- void RenameSelection();
-
- ///
- /// Focuses selected objects.
- ///
- void FocusSelection();
- }
-
///
/// Base class for editor windows dedicated to scene editing.
///
///
- public abstract class SceneEditorWindow : EditorWindow, ISceneContextWindow
+ public abstract class SceneEditorWindow : EditorWindow, ISceneEditingContext
{
///
/// Initializes a new instance of the class.
@@ -46,6 +32,24 @@ namespace FlaxEditor.Windows
Editor.Windows.EditWin.Viewport.FocusSelection();
}
+ ///
+ public EditorViewport Viewport => Editor.Windows.EditWin.Viewport;
+
+ ///
+ public List Selection => Editor.SceneEditing.Selection;
+
+ ///
+ public void Select(SceneGraphNode node, bool additive = false)
+ {
+ Editor.SceneEditing.Select(node, additive);
+ }
+
+ ///
+ public void Deselect(SceneGraphNode node)
+ {
+ Editor.SceneEditing.Deselect(node);
+ }
+
///
public void RenameSelection()
{
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index ad1126ec6..ef267b1cc 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -1051,6 +1051,7 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
LOG(Error, "Cannot create virtual asset object.");
return nullptr;
}
+ asset->RegisterObject();
// Call initializer function
asset->InitAsVirtual();
@@ -1291,6 +1292,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LOAD_FAILED();
}
#endif
+ result->RegisterObject();
// Register asset
AssetsLocker.Lock();
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index d7eb3d3dd..a260e2fa0 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -348,6 +348,11 @@ struct DebugDrawContext
DebugDrawData DebugDrawDepthTest;
Float3 LastViewPos = Float3::Zero;
Matrix LastViewProj = Matrix::Identity;
+
+ inline int32 Count() const
+ {
+ return DebugDrawDefault.Count() + DebugDrawDepthTest.Count();
+ }
};
namespace
@@ -748,6 +753,13 @@ void DebugDraw::SetContext(void* context)
Context = context ? (DebugDrawContext*)context : &GlobalContext;
}
+bool DebugDraw::CanClear(void* context)
+{
+ if (!context)
+ context = &GlobalContext;
+ return ((DebugDrawContext*)context)->Count() != 0;
+}
+
#endif
Vector3 DebugDraw::GetViewPos()
diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h
index 7b6cc7f20..a032f2057 100644
--- a/Source/Engine/Debug/DebugDraw.h
+++ b/Source/Engine/Debug/DebugDraw.h
@@ -65,6 +65,13 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
///
/// The context or null.
API_FUNCTION() static void SetContext(void* context);
+
+ ///
+ /// Checks if can clear all debug shapes displayed on screen. Can be used to disable this functionality when not needed for the user.
+ ///
+ /// The context.
+ /// True fi context can be cleared (has any shapes), otherwise false.
+ API_FUNCTION() static bool CanClear(void* context = nullptr);
#endif
// Gets the last view position when rendering the current context. Can be sued for custom culling or LODing when drawing more complex shapes.
diff --git a/Source/Engine/Debug/DebugLog.h b/Source/Engine/Debug/DebugLog.h
index a70fc3d3b..fe3708b46 100644
--- a/Source/Engine/Debug/DebugLog.h
+++ b/Source/Engine/Debug/DebugLog.h
@@ -19,7 +19,7 @@ public:
static void Log(LogType type, const StringView& message);
///
- /// A variant of Debug.Log that logs a warning message to the console.
+ /// A variant of Debug.Log that logs an info message to the console.
///
/// The text message to display.
FORCE_INLINE static void Log(const StringView& message)
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 1a505831a..c6790e8e5 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -613,6 +613,209 @@ float Input::GetAxisRaw(const StringView& name)
return e ? e->ValueRaw : false;
}
+void Input::SetInputMappingFromSettings(const JsonAssetReference& settings)
+{
+ auto actionMappings = settings.GetInstance()->ActionMappings;
+ ActionMappings.Resize(actionMappings.Count(), false);
+ for (int i = 0; i < actionMappings.Count(); i++)
+ {
+ ActionMappings[i] = actionMappings.At(i);
+ }
+
+ auto axisMappings = settings.GetInstance()->AxisMappings;
+ AxisMappings.Resize(axisMappings.Count(), false);
+ for (int i = 0; i < axisMappings.Count(); i++)
+ {
+ AxisMappings[i] = axisMappings.At(i);
+ }
+ Axes.Clear();
+ Actions.Clear();
+}
+
+void Input::SetInputMappingToDefaultSettings()
+{
+ InputSettings* settings = InputSettings::Get();
+ if (settings)
+ {
+ ActionMappings = settings->ActionMappings;
+ AxisMappings = settings->AxisMappings;
+ Axes.Clear();
+ Actions.Clear();
+ }
+}
+
+ActionConfig Input::GetActionConfigByName(const StringView& name)
+{
+ ActionConfig config = {};
+ for (const auto& a : ActionMappings)
+ {
+ if (a.Name == name)
+ {
+ config = a;
+ return config;
+ }
+ }
+ return config;
+}
+
+Array Input::GetAllActionConfigsByName(const StringView& name)
+{
+ Array actionConfigs;
+ for (const auto& a : ActionMappings)
+ {
+ if (a.Name == name)
+ actionConfigs.Add(a);
+ }
+ return actionConfigs;
+}
+
+AxisConfig Input::GetAxisConfigByName(const StringView& name)
+{
+ AxisConfig config = {};
+ for (const auto& a : AxisMappings)
+ {
+ if (a.Name == name)
+ {
+ config = a;
+ return config;
+ }
+ }
+ return config;
+}
+
+Array Input::GetAllAxisConfigsByName(const StringView& name)
+{
+ Array actionConfigs;
+ for (const auto& a : AxisMappings)
+ {
+ if (a.Name == name)
+ actionConfigs.Add(a);
+ }
+ return actionConfigs;
+}
+
+void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all)
+{
+ for (int i = 0; i < AxisMappings.Count(); ++i)
+ {
+ auto& mapping = AxisMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0)
+ {
+ if (config.Name.IsEmpty())
+ config.Name = name;
+ mapping = config;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all)
+{
+ for (int i = 0; i < AxisMappings.Count(); ++i)
+ {
+ auto& mapping = AxisMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType)
+ {
+ mapping.PositiveButton = positiveButton;
+ mapping.NegativeButton = negativeButton;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all)
+{
+ for (int i = 0; i < AxisMappings.Count(); ++i)
+ {
+ auto& mapping = AxisMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex && mapping.Axis == inputType)
+ {
+ mapping.GamepadPositiveButton = positiveButton;
+ mapping.GamepadNegativeButton = negativeButton;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all)
+{
+ for (int i = 0; i < AxisMappings.Count(); ++i)
+ {
+ auto& mapping = AxisMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType)
+ {
+ mapping.Gravity = gravity;
+ mapping.DeadZone = deadZone;
+ mapping.Sensitivity = sensitivity;
+ mapping.Scale = scale;
+ mapping.Snap = snap;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all)
+{
+ for (int i = 0; i < ActionMappings.Count(); ++i)
+ {
+ auto& mapping = ActionMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0)
+ {
+ mapping.Key = key;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all)
+{
+ for (int i = 0; i < ActionMappings.Count(); ++i)
+ {
+ auto& mapping = ActionMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0)
+ {
+ mapping.MouseButton = mouseButton;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all)
+{
+ for (int i = 0; i < ActionMappings.Count(); ++i)
+ {
+ auto& mapping = ActionMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex)
+ {
+ mapping.GamepadButton = gamepadButton;
+ if (!all)
+ break;
+ }
+ }
+}
+
+void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, bool all)
+{
+ for (int i = 0; i < ActionMappings.Count(); ++i)
+ {
+ auto& mapping = ActionMappings.At(i);
+ if (mapping.Name.Compare(name.ToString()) == 0)
+ {
+ if (config.Name.IsEmpty())
+ config.Name = name;
+ mapping = config;
+ if (!all)
+ break;
+ }
+ }
+}
+
void InputService::Update()
{
PROFILE_CPU();
diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h
index 2f8c09b38..dec381ca9 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -9,6 +9,7 @@
#include "KeyboardKeys.h"
#include "Enums.h"
#include "VirtualInput.h"
+#include "Engine/Content/JsonAssetReference.h"
class Mouse;
class Keyboard;
@@ -332,4 +333,118 @@ public:
/// The current axis value (e.g for gamepads it's in the range -1..1). No smoothing applied.
///
API_FUNCTION() static float GetAxisRaw(const StringView& name);
+
+ ///
+ /// Sets and overwrites the Action and Axis mappings with the values from a new InputSettings.
+ ///
+ /// The input settings.
+ API_FUNCTION() static void SetInputMappingFromSettings(const JsonAssetReference& settings);
+
+ ///
+ /// Sets and overwrites the Action and Axis mappings with the values from the InputSettings in GameSettings.
+ ///
+ API_FUNCTION() static void SetInputMappingToDefaultSettings();
+
+ ///
+ /// Gets the first action configuration by name.
+ ///
+ /// The name of the action config.
+ /// The first Action configuration with the name. Empty configuration if not found.
+ API_FUNCTION() static ActionConfig GetActionConfigByName(const StringView& name);
+
+ ///
+ /// Gets all the action configurations by name.
+ ///
+ /// The name of the action config.
+ /// The Action configurations with the name.
+ API_FUNCTION() static Array GetAllActionConfigsByName(const StringView& name);
+
+ ///
+ /// Sets the action configuration keyboard key by name.
+ ///
+ /// The name of the action config.
+ /// The key to set.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all = false);
+
+ ///
+ /// Sets the action configuration mouse button by name.
+ ///
+ /// The name of the action config.
+ /// The mouse button to set.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all = false);
+
+ ///
+ /// Sets the action configuration gamepad button by name and index.
+ ///
+ /// The name of the action config.
+ /// The gamepad button to set.
+ /// The gamepad index used to find the correct config.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all = false);
+
+ ///
+ /// Sets the action configuration by name.
+ ///
+ /// The name of the action config.
+ /// The action configuration to set. Leave the config name empty to use set name.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetActionConfigByName(const StringView& name, ActionConfig& config, bool all = false);
+
+ ///
+ /// Gets the first axis configurations by name.
+ ///
+ /// The name of the axis config.
+ /// The first Axis configuration with the name. Empty configuration if not found.
+ API_FUNCTION() static AxisConfig GetAxisConfigByName(const StringView& name);
+
+ ///
+ /// Gets all the axis configurations by name.
+ ///
+ /// The name of the axis config.
+ /// The axis configurations with the name.
+ API_FUNCTION() static Array GetAllAxisConfigsByName(const StringView& name);
+
+ ///
+ /// Sets the axis configuration keyboard key by name and type.
+ ///
+ /// The name of the action config.
+ /// The configuration to set. Leave the config name empty to use set name.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all = false);
+
+ ///
+ /// Sets the axis configuration keyboard key buttons by name and type.
+ ///
+ /// The name of the action config.
+ /// The type to sort by.
+ /// The positive key button.
+ /// The negative key button.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all = false);
+
+ ///
+ /// Sets the axis configuration gamepad buttons by name, type, and index.
+ ///
+ /// The name of the action config.
+ /// The type to sort by.
+ /// The positive gamepad button.
+ /// The negative gamepad button.
+ /// The gamepad index to sort by.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all = false);
+
+ ///
+ /// Sets the axis configuration accessories by name, and type.
+ ///
+ /// The name of the action config.
+ /// The type to sort by.
+ /// The gravity to set.
+ /// The dead zone to set.
+ /// The sensitivity to set.
+ /// The scale to set.
+ /// The snap to set.
+ /// Whether to set only the first config found or all of them.
+ API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all = false);
};
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 8d1f52982..98d399efe 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -960,10 +960,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
objects[i] = obj;
if (obj)
{
- obj->RegisterObject();
+ if (!obj->IsRegistered())
+ obj->RegisterObject();
#if USE_EDITOR
// Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them)
- obj->CreateManaged();
+ if (!obj->GetManagedInstance())
+ obj->CreateManaged();
#endif
}
else
diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp
index fb8402cf6..8dce977db 100644
--- a/Source/Engine/Physics/Actors/Cloth.cpp
+++ b/Source/Engine/Physics/Actors/Cloth.cpp
@@ -644,6 +644,14 @@ void Cloth::CalculateInvMasses(Array& invMasses)
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
+ if (_paint.Count() != verticesCount)
+ {
+ // Fix incorrect paint data
+ int32 countBefore = _paint.Count();
+ _paint.Resize(verticesCount);
+ for (int32 i = countBefore; i < verticesCount; i++)
+ _paint.Get()[i] = 0.0f;
+ }
const int32 verticesStride = verticesData.Length() / verticesCount;
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
@@ -697,12 +705,12 @@ void Cloth::CalculateInvMasses(Array& invMasses)
float massSum = 0;
for (int32 i = 0; i < verticesCount; i++)
{
- float& mass = invMasses[i];
+ float& mass = invMasses.Get()[i];
#if USE_CLOTH_SANITY_CHECKS
// Sanity check
ASSERT(!isnan(mass) && !isinf(mass) && mass >= 0.0f);
#endif
- const float maxDistance = _paint[i];
+ const float maxDistance = _paint.Get()[i];
if (maxDistance < 0.01f)
{
// Fixed
@@ -722,7 +730,7 @@ void Cloth::CalculateInvMasses(Array& invMasses)
const float massScale = (float)(verticesCount - fixedCount) / massSum;
for (int32 i = 0; i < verticesCount; i++)
{
- float& mass = invMasses[i];
+ float& mass = invMasses.Get()[i];
if (mass > 0.0f)
{
mass *= massScale;
@@ -786,6 +794,8 @@ bool Cloth::OnPreUpdate()
auto blendWeightsStream = accessor.BlendWeights();
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false;
+ if (verticesCount != _paint.Count())
+ return false;
PROFILE_CPU_NAMED("Skinned Pose");
PhysicsBackend::LockClothParticles(_cloth);
const Span particles = PhysicsBackend::GetClothParticles(_cloth);
diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp
index fffffa5d0..cfbe7fb4c 100644
--- a/Source/Engine/Platform/Base/PlatformBase.cpp
+++ b/Source/Engine/Platform/Base/PlatformBase.cpp
@@ -272,7 +272,7 @@ bool PlatformBase::Is64BitApp()
int32 PlatformBase::GetCacheLineSize()
{
- return Platform::GetCPUInfo().CacheLineSize;
+ return (int32)Platform::GetCPUInfo().CacheLineSize;
}
void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error)
diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h
index 56da92e50..1ad6e9c43 100644
--- a/Source/Engine/Platform/Base/PlatformBase.h
+++ b/Source/Engine/Platform/Base/PlatformBase.h
@@ -394,7 +394,7 @@ public:
/// [Deprecated in v1.10]
///
/// The cache line size.
- API_PROPERTY() DEPRECATED("Use CPUInfo.CacheLineSize instead") static int32 GetCacheLineSize();
+ API_PROPERTY() DEPRECATED("Use CacheLineSize field from CPUInfo.") static int32 GetCacheLineSize();
///
/// Gets the current memory stats.
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index 166887d71..fb6eb3d70 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -796,6 +796,17 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp
// Mark as managed type
object->Flags |= ObjectFlags::IsManagedType;
+ // Initialize managed instance
+ if (params.Managed)
+ {
+ object->SetManagedInstance((MObject*)params.Managed);
+ }
+ else
+ {
+ // Invoke managed ctor (to match C++ logic)
+ object->CreateManaged();
+ }
+
return object;
}
diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp
index 7f9d92369..14ce087cf 100644
--- a/Source/Engine/Scripting/ScriptingObject.cpp
+++ b/Source/Engine/Scripting/ScriptingObject.cpp
@@ -412,7 +412,8 @@ void ScriptingObject::DestroyManaged()
void ScriptingObject::RegisterObject()
{
- ASSERT(!IsRegistered());
+ if (IsRegistered())
+ return;
Flags |= ObjectFlags::IsRegistered;
Scripting::RegisterObject(this);
}
@@ -532,10 +533,6 @@ bool ManagedScriptingObject::CreateManaged()
}
#endif
- // Ensure to be registered
- if (!IsRegistered())
- RegisterObject();
-
return false;
}
@@ -598,8 +595,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create1(MTypeObject* type)
}
// Create managed object
- obj->CreateManaged();
- MObject* managedInstance = obj->GetManagedInstance();
+ MObject* managedInstance = obj->GetOrCreateManagedInstance();
if (managedInstance == nullptr)
{
LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeClass->GetFullName()));
@@ -636,8 +632,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create2(MString* typeNameObj)
}
// Create managed object
- obj->CreateManaged();
- MObject* managedInstance = obj->GetManagedInstance();
+ MObject* managedInstance = obj->GetOrCreateManagedInstance();
if (managedInstance == nullptr)
{
LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeName));
@@ -667,7 +662,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
const ScriptingType& scriptingType = module->Types[typeIndex];
// Create unmanaged object
- const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
+ ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
+ params.Managed = managedInstance; // Link created managed instance to the unmanaged object
ScriptingObject* obj = scriptingType.Script.Spawn(params);
if (obj == nullptr)
{
@@ -675,9 +671,6 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
return;
}
- // Link created managed instance to the unmanaged object
- obj->SetManagedInstance(managedInstance);
-
// Set default name for actors
if (auto* actor = dynamic_cast(obj))
{
@@ -689,8 +682,7 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
MCore::ScriptingObject::SetInternalValues(klass, managedInstance, obj, &id);
// Register object
- if (!obj->IsRegistered())
- obj->RegisterObject();
+ obj->RegisterObject();
}
DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceDeleted(ScriptingObject* obj)
diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h
index f53778401..72cbaaac9 100644
--- a/Source/Engine/Scripting/ScriptingType.h
+++ b/Source/Engine/Scripting/ScriptingType.h
@@ -355,9 +355,15 @@ struct ScriptingObjectSpawnParams
///
const ScriptingTypeHandle Type;
+ ///
+ /// Optional C# object instance to use for unmanaged object.
+ ///
+ void* Managed;
+
FORCE_INLINE ScriptingObjectSpawnParams(const Guid& id, const ScriptingTypeHandle& typeHandle)
: ID(id)
, Type(typeHandle)
+ , Managed(nullptr)
{
}
};
diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs
index 04a20fdb4..6120ba704 100644
--- a/Source/Engine/Serialization/JsonConverters.cs
+++ b/Source/Engine/Serialization/JsonConverters.cs
@@ -442,7 +442,7 @@ namespace FlaxEngine.Json
///
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
- var result = Activator.CreateInstance(objectType);
+ var result = existingValue ?? Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.String)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
@@ -483,6 +483,44 @@ namespace FlaxEngine.Json
}
}
+ internal class ControlReferenceConverter : JsonConverter
+ {
+ ///
+ public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var id = (value as IControlReference)?.UIControl?.ID ?? Guid.Empty;
+ writer.WriteValue(JsonSerializer.GetStringID(&id));
+ }
+
+ ///
+ public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ if (value is IControlReference valueRef &&
+ other is IControlReference otherRef &&
+ JsonSerializer.SceneObjectEquals(valueRef.UIControl, otherRef.UIControl))
+ return;
+ base.WriteJsonDiff(writer, value, other, serializer);
+ }
+
+ ///
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var result = existingValue ?? Activator.CreateInstance(objectType);
+ if (reader.TokenType == JsonToken.String && result is IControlReference controlReference)
+ {
+ JsonSerializer.ParseID((string)reader.Value, out var id);
+ controlReference.Load(Object.Find(ref id));
+ }
+ return result;
+ }
+
+ ///
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType.Name.StartsWith("ControlReference", StringComparison.Ordinal);
+ }
+ }
+
/*
///
/// Serialize Guid values using `N` format
diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs
index f13be1e8f..a87eacb9f 100644
--- a/Source/Engine/Serialization/JsonSerializer.cs
+++ b/Source/Engine/Serialization/JsonSerializer.cs
@@ -194,6 +194,7 @@ namespace FlaxEngine.Json
settings.Converters.Add(new SoftObjectReferenceConverter());
settings.Converters.Add(new SoftTypeReferenceConverter());
settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter());
+ settings.Converters.Add(new ControlReferenceConverter());
settings.Converters.Add(new MarginConverter());
settings.Converters.Add(new VersionConverter());
settings.Converters.Add(new LocalizedStringConverter());
@@ -227,6 +228,23 @@ namespace FlaxEngine.Json
CacheManagedOnly.Dispose();
}
+ ///
+ /// The default implementation of the values comparision function used by the serialization system.
+ ///
+ /// The object a.
+ /// The object b.
+ /// True if both objects are equal, otherwise false.
+ public static bool SceneObjectEquals(SceneObject objA, SceneObject objB)
+ {
+ if (objA == objB)
+ return true;
+ if (objA == null || objB == null)
+ return false;
+ if (objA.HasPrefabLink && objB.HasPrefabLink)
+ return objA.PrefabObjectID == objB.PrefabObjectID;
+ return false;
+ }
+
///
/// The default implementation of the values comparision function used by the serialization system.
///
diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp
index 70697adfd..7d572e929 100644
--- a/Source/Engine/Terrain/TerrainPatch.cpp
+++ b/Source/Engine/Terrain/TerrainPatch.cpp
@@ -74,13 +74,13 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
_physicsHeightField = nullptr;
_x = x;
_z = z;
- const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
_offset = Float3(_x * size, 0.0f, _z * size);
_yOffset = 0.0f;
_yHeight = 1.0f;
for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
- Chunks[i].Init(this, i % Terrain::Terrain::ChunksCountEdge, i / Terrain::Terrain::ChunksCountEdge);
+ Chunks[i].Init(this, i % Terrain::ChunksCountEdge, i / Terrain::ChunksCountEdge);
}
Heightmap = nullptr;
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
@@ -99,7 +99,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
}
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
- SAFE_DELETE(_debugLines);
+ SAFE_DELETE_GPU_RESOURCE(_debugLines);
_debugLinesDirty = true;
#endif
#if USE_EDITOR
@@ -117,6 +117,9 @@ TerrainPatch::~TerrainPatch()
SAFE_DELETE(_dataSplatmap[i]);
}
#endif
+#if TERRAIN_USE_PHYSICS_DEBUG
+ SAFE_DELETE_GPU_RESOURCE(_debugLines);
+#endif
}
RawDataAsset* TerrainPatch::GetHeightfield() const
@@ -2324,9 +2327,9 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{
if (!_debugLines || _debugLinesDirty)
CacheDebugLines();
- const Transform terrainTransform = _terrain->_transform;
- const Transform localTransform(Vector3(0, _yOffset, 0), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
- const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
+ const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
+ const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
DebugDraw::DrawLines(_debugLines, world);
}
}
@@ -2352,10 +2355,9 @@ const Array& TerrainPatch::GetCollisionTriangles()
#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)
- const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
- const Transform terrainTransform = _terrain->_transform;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
- const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
+ const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
for (int32 row = 0; row < rows - 1; row++)
{
@@ -2401,7 +2403,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
Transform transform;
transform.Translation = _offset + Vector3(0, _yOffset, 0);
transform.Orientation = Quaternion::Identity;
@@ -2507,10 +2509,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
- const Transform terrainTransform = _terrain->_transform;
+ const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Float3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
- const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
+ const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
const int32 vertexCount = rows * cols;
_collisionVertices.Resize(vertexCount);
@@ -2569,7 +2570,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj)
stream.JKEY("Chunks");
stream.StartArray();
- for (int32 i = 0; i < Terrain::Terrain::ChunksCount; i++)
+ for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
stream.StartObject();
Chunks[i].Serialize(stream, other ? &other->Chunks[i] : nullptr);
diff --git a/Source/Engine/UI/ControlReference.cs b/Source/Engine/UI/ControlReference.cs
new file mode 100644
index 000000000..eee093909
--- /dev/null
+++ b/Source/Engine/UI/ControlReference.cs
@@ -0,0 +1,199 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using FlaxEngine.GUI;
+
+namespace FlaxEngine
+{
+ ///
+ /// Interface for control references access.
+ ///
+ public interface IControlReference
+ {
+ ///
+ /// Gets or sets the reference to actor.
+ ///
+ public UIControl UIControl { get; set; }
+
+ ///
+ /// Gets the type of the control the interface uses.
+ ///
+ public Type ControlType { get; }
+
+ ///
+ /// Sets control ref by force - used during loading when is not loaded yet.
+ ///
+ /// The reference.
+ internal void Load(UIControl control);
+ }
+
+ ///
+ /// UI Control reference utility. References UI Control actor with a typed control type.
+ ///
+ /// Type of the UI control object.
+#if FLAX_EDITOR
+ [TypeConverter(typeof(TypeConverters.ControlReferenceConverter))]
+#endif
+ public struct ControlReference : IControlReference, IComparable, IComparable>, IEquatable> where T : Control
+ {
+ private UIControl _uiControl;
+
+ ///
+ /// Gets the typed UI control object owned by the referenced actor.
+ ///
+ [HideInEditor]
+ public T Control
+ {
+ get
+ {
+ var control = _uiControl?.Control;
+ if (control == null)
+ return null;
+ if (control is T t)
+ return t;
+ Debug.Write(LogType.Warning, $"Trying to get Control from ControlReference but UIControl.Control is not correct type. It should be {typeof(T)} but is {control.GetType()}.");
+ return null;
+ }
+ }
+
+ ///
+ public UIControl UIControl
+ {
+ get => _uiControl;
+ set
+ {
+ var control = value?.Control;
+ if (value == null)
+ {
+ _uiControl = null;
+ }
+ else if (control is T)
+ {
+ _uiControl = value;
+ }
+ else
+ {
+ Debug.Write(LogType.Warning, $"Trying to set UIControl but UIControl.Control is not the correct type. It should be {typeof(T)} but is {control.GetType()}.");
+ }
+ }
+ }
+
+ ///
+ public Type ControlType => typeof(T);
+
+ ///
+ public void Load(UIControl value)
+ {
+ _uiControl = value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _uiControl?.ToString() ?? "null";
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _uiControl?.GetHashCode() ?? 0;
+ }
+
+ ///
+ public int CompareTo(object obj)
+ {
+ if (obj is IControlReference other)
+ return Json.JsonSerializer.SceneObjectEquals(_uiControl, other.UIControl) ? 0 : 1;
+ return 1;
+ }
+
+ ///
+ public int CompareTo(ControlReference other)
+ {
+ return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl) ? 0 : 1;
+ }
+
+ ///
+ public bool Equals(ControlReference other)
+ {
+ return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is ControlReference other && Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl);
+ }
+
+ ///
+ /// The implicit operator for the Control.
+ ///
+ /// The control reference.
+ /// The control object.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator T(ControlReference reference) => reference.Control;
+
+ ///
+ /// The implicit operator for the UIControl.
+ ///
+ /// The control reference.
+ /// The control actor.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator UIControl(ControlReference reference) => reference.UIControl;
+
+ ///
+ /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
+ ///
+ /// The object to check.
+ /// True if object is valid, otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator bool(ControlReference obj) => obj._uiControl;
+
+ ///
+ /// Checks whether the two objects are equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(ControlReference left, ControlReference right) => Json.JsonSerializer.SceneObjectEquals(left._uiControl, right._uiControl);
+
+ ///
+ /// Checks whether the two objects are not equal.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(ControlReference left, ControlReference right) => !Json.JsonSerializer.SceneObjectEquals(left._uiControl, right._uiControl);
+ }
+}
+
+#if FLAX_EDITOR
+namespace FlaxEngine.TypeConverters
+{
+ internal class ControlReferenceConverter : TypeConverter
+ {
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value is string valueStr)
+ {
+ var result = Activator.CreateInstance(destinationType);
+ if (result is IControlReference control)
+ {
+ Json.JsonSerializer.ParseID(valueStr, out var id);
+ control.Load(Object.Find(ref id));
+ }
+ return result;
+ }
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType.Name.StartsWith("ControlReference", StringComparison.Ordinal))
+ return true;
+ return base.CanConvertTo(context, destinationType);
+ }
+ }
+}
+#endif
diff --git a/Source/Engine/UI/GUI/Panels/GridPanel.cs b/Source/Engine/UI/GUI/Panels/GridPanel.cs
index 2b1a0a123..24a7e0090 100644
--- a/Source/Engine/UI/GUI/Panels/GridPanel.cs
+++ b/Source/Engine/UI/GUI/Panels/GridPanel.cs
@@ -35,7 +35,7 @@ namespace FlaxEngine.GUI
///
/// The cells heights in container height percentage (from top to bottom). Use negative values to set fixed widths for the cells.
///
- [EditorOrder(10), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")]
+ [EditorOrder(10), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")]
public float[] RowFill
{
get => _cellsV;
@@ -49,7 +49,7 @@ namespace FlaxEngine.GUI
///
/// The cells heights in container width percentage (from left to right). Use negative values to set fixed heights for the cells.
///
- [EditorOrder(20), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")]
+ [EditorOrder(20), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")]
public float[] ColumnFill
{
get => _cellsH;