diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 1f5eddc75..fde4967e8 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -408,6 +408,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
private Type _cachedType;
private bool _anchorDropDownClosed = true;
+ private Button _pivotRelativeButton;
///
public override void Initialize(LayoutElementsContainer layout)
@@ -484,13 +485,54 @@ namespace FlaxEditor.CustomEditors.Dedicated
horDown.CustomControl.Height = TextBoxBase.DefaultHeight;
GetAnchorEquality(out _cachedXEq, out _cachedYEq, valueTypes);
-
BuildLocationSizeOffsets(horUp, horDown, _cachedXEq, _cachedYEq, valueTypes);
+ BuildExtraButtons(group);
main.Space(10);
BuildAnchorsDropper(main, valueTypes);
}
+ private void BuildExtraButtons(VerticalPanelElement group)
+ {
+ var control = (Control)Values[0];
+ var pivotRelative = Editor.Instance.Windows.PropertiesWin.UIPivotRelative;
+ control.PivotRelative = pivotRelative;
+
+ var panel = group.CustomContainer();
+ panel.CustomControl.Height = TextBoxBase.DefaultHeight;
+ panel.CustomControl.ClipChildren = false;
+ panel.CustomControl.Parent = group.ContainerControl;
+
+ _pivotRelativeButton = new Button
+ {
+ TooltipText = "Toggles UI control resizing based on where the pivot is rather than just the top-left.",
+ Size = new Float2(18),
+ Parent = panel.ContainerControl,
+ BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Scale32),
+ AnchorPreset = AnchorPresets.TopRight,
+ X = 77,
+ };
+
+ SetStyle(pivotRelative);
+ _pivotRelativeButton.Clicked += PivotRelativeClicked;
+ }
+
+ private void PivotRelativeClicked()
+ {
+ var control = (Control)Values[0];
+ var pivotRelative = control.PivotRelative;
+ control.PivotRelative = !pivotRelative;
+ Editor.Instance.Windows.PropertiesWin.UIPivotRelative = !pivotRelative;
+ SetStyle(control.PivotRelative);
+ }
+
+ private void SetStyle(bool current)
+ {
+ var style = FlaxEngine.GUI.Style.Current;
+ var backgroundColor = current ? style.Foreground : style.ForegroundDisabled;
+ _pivotRelativeButton.SetColors(backgroundColor);
+ }
+
private void BuildAnchorsDropper(LayoutElementsContainer main, ScriptType[] valueTypes)
{
ScriptMemberInfo minInfo = valueTypes[0].GetProperty("AnchorMin");
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index 076b94b0a..cb4e7b9c1 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -97,6 +97,7 @@ namespace FlaxEditor.CustomEditors.Editors
AnchorPreset = AnchorPresets.TopLeft,
};
_linkButton.Clicked += ToggleLink;
+ ToggleEnabled();
SetLinkStyle();
var x = LinkedLabel.Text.Value.Length * 7 + 5;
_linkButton.LocalX += x;
@@ -128,9 +129,38 @@ namespace FlaxEditor.CustomEditors.Editors
{
LinkValues = !LinkValues;
Editor.Instance.Windows.PropertiesWin.ScaleLinked = LinkValues;
+ ToggleEnabled();
SetLinkStyle();
}
+ ///
+ /// Toggles enables on value boxes.
+ ///
+ public void ToggleEnabled()
+ {
+ if (LinkValues)
+ {
+ if (Mathf.NearEqual(((Float3)Values[0]).X, 0))
+ {
+ XElement.ValueBox.Enabled = false;
+ }
+ if (Mathf.NearEqual(((Float3)Values[0]).Y, 0))
+ {
+ YElement.ValueBox.Enabled = false;
+ }
+ if (Mathf.NearEqual(((Float3)Values[0]).Z, 0))
+ {
+ ZElement.ValueBox.Enabled = false;
+ }
+ }
+ else
+ {
+ XElement.ValueBox.Enabled = true;
+ YElement.ValueBox.Enabled = true;
+ ZElement.ValueBox.Enabled = true;
+ }
+ }
+
private void SetLinkStyle()
{
var style = FlaxEngine.GUI.Style.Current;
diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
index bd154056f..c3edd3913 100644
--- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
+++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs
@@ -129,25 +129,39 @@ namespace FlaxEditor.CustomEditors.Editors
if (LinkValues)
{
- var valueChange = 0.0f;
+ var valueRatio = 0.0f;
switch (_valueChanged)
{
case ValueChanged.X:
- valueChange = xValue - ((Float3)Values[0]).X;
- yValue += valueChange;
- zValue += valueChange;
+ valueRatio = GetRatio(xValue, ((Float3)Values[0]).X);
+ if (Mathf.NearEqual(valueRatio, 0))
+ {
+ XElement.ValueBox.Enabled = false;
+ valueRatio = 1;
+ }
+ yValue = NewLinkedValue(yValue, valueRatio);
+ zValue = NewLinkedValue(zValue, valueRatio);
break;
case ValueChanged.Y:
- valueChange = yValue - ((Float3)Values[0]).Y;
- xValue += valueChange;
- zValue += valueChange;
+ valueRatio = GetRatio(yValue, ((Float3)Values[0]).Y);
+ if (Mathf.NearEqual(valueRatio, 0))
+ {
+ YElement.ValueBox.Enabled = false;
+ valueRatio = 1;
+ }
+ xValue = NewLinkedValue(xValue, valueRatio);
+ zValue = NewLinkedValue(zValue, valueRatio);
break;
case ValueChanged.Z:
- valueChange = zValue - ((Float3)Values[0]).Z;
- xValue += valueChange;
- yValue += valueChange;
+ valueRatio = GetRatio(zValue, ((Float3)Values[0]).Z);
+ if (Mathf.NearEqual(valueRatio, 0))
+ {
+ ZElement.ValueBox.Enabled = false;
+ valueRatio = 1;
+ }
+ xValue = NewLinkedValue(xValue, valueRatio);
+ yValue = NewLinkedValue(yValue, valueRatio);
break;
- default: break;
}
}
@@ -164,6 +178,16 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(v, token);
}
+ private float GetRatio(float value, float initialValue)
+ {
+ return Mathf.NearEqual(initialValue, 0) ? 0 : value / initialValue;
+ }
+
+ private float NewLinkedValue(float value, float valueRatio)
+ {
+ return Mathf.NearEqual(value, 0) ? value : value * valueRatio;
+ }
+
///
public override void Refresh()
{
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index 2a746bcbe..d17913dfd 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -81,6 +81,7 @@ namespace FlaxEditor.Gizmo
: base(owner)
{
InitDrawing();
+ ModeChanged += ResetTranslationScale;
}
///
@@ -326,6 +327,11 @@ namespace FlaxEditor.Gizmo
}
}
+ private void ResetTranslationScale()
+ {
+ _translationScaleSnapDelta.Normalize();
+ }
+
private void UpdateRotate(float dt)
{
float mouseDelta = _activeAxis == Axis.Y ? -Owner.MouseDelta.X : Owner.MouseDelta.X;
diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs
index 8b8f3cccf..eebee0cd9 100644
--- a/Source/Editor/SceneGraph/Actors/SplineNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs
@@ -146,7 +146,18 @@ namespace FlaxEditor.SceneGraph.Actors
Time = newTime,
Value = actor.GetSplineLocalTransform(Index),
};
+
+ var oldkeyframe = actor.GetSplineKeyframe(Index);
+ var newKeyframe = new BezierCurve.Keyframe();
+
+ // copy old curve point data to new curve point
+ newKeyframe.Value = oldkeyframe.Value;
+ newKeyframe.TangentIn = oldkeyframe.TangentIn;
+ newKeyframe.TangentOut = oldkeyframe.TangentOut;
+
actor.InsertSplineLocalPoint(newIndex, newTime, action.Value);
+ actor.SetSplineKeyframe(newIndex, newKeyframe);
+
undoAction = action;
var splineNode = (SplineNode)SceneGraphFactory.FindNode(action.SplineId);
splineNode.OnUpdate();
@@ -317,6 +328,20 @@ namespace FlaxEditor.SceneGraph.Actors
var spline = (Spline)Actor;
spline.AddSplineLocalPoint(Vector3.Zero, false);
spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f));
+
+ spline.SetSplineKeyframe(0, new BezierCurve.Keyframe()
+ {
+ Value = new Transform(Vector3.Zero, Quaternion.Identity, Vector3.One),
+ TangentIn = new Transform(Vector3.Backward * 100, Quaternion.Identity, Vector3.One),
+ TangentOut = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One),
+ });
+
+ spline.SetSplineKeyframe(1, new BezierCurve.Keyframe()
+ {
+ Value = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One),
+ TangentIn = new Transform(Vector3.Backward * 100, Quaternion.Identity, Vector3.One),
+ TangentOut = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One),
+ });
}
///
diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs
index 601717f38..baa10f8b9 100644
--- a/Source/Editor/Windows/PropertiesWindow.cs
+++ b/Source/Editor/Windows/PropertiesWindow.cs
@@ -30,6 +30,11 @@ namespace FlaxEditor.Windows
///
public bool ScaleLinked = false;
+ ///
+ /// Indication of if UI elements should size relative to the pivot point.
+ ///
+ public bool UIPivotRelative = true;
+
///
/// Initializes a new instance of the class.
///
@@ -66,13 +71,16 @@ namespace FlaxEditor.Windows
public override void OnLayoutSerialize(XmlWriter writer)
{
writer.WriteAttributeString("ScaleLinked", ScaleLinked.ToString());
+ writer.WriteAttributeString("UIPivotRelative", UIPivotRelative.ToString());
}
-
+
///
public override void OnLayoutDeserialize(XmlElement node)
{
if (bool.TryParse(node.GetAttribute("ScaleLinked"), out bool value1))
ScaleLinked = value1;
+ if (bool.TryParse(node.GetAttribute("UIPivotRelative"), out value1))
+ UIPivotRelative = value1;
}
}
}
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 1fa7454ea..22969c752 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -85,18 +85,18 @@ Keyboard* Input::Keyboard = nullptr;
Array> Input::Gamepads;
Action Input::GamepadsChanged;
Array> Input::CustomDevices;
-Input::CharDelegate Input::CharInput;
-Input::KeyboardDelegate Input::KeyDown;
-Input::KeyboardDelegate Input::KeyUp;
-Input::MouseButtonDelegate Input::MouseDown;
-Input::MouseButtonDelegate Input::MouseUp;
-Input::MouseButtonDelegate Input::MouseDoubleClick;
-Input::MouseWheelDelegate Input::MouseWheel;
-Input::MouseDelegate Input::MouseMove;
+Delegate Input::CharInput;
+Delegate Input::KeyDown;
+Delegate Input::KeyUp;
+Delegate Input::MouseDown;
+Delegate Input::MouseUp;
+Delegate Input::MouseDoubleClick;
+Delegate Input::MouseWheel;
+Delegate Input::MouseMove;
Action Input::MouseLeave;
-Input::TouchDelegate Input::TouchDown;
-Input::TouchDelegate Input::TouchMove;
-Input::TouchDelegate Input::TouchUp;
+Delegate Input::TouchDown;
+Delegate Input::TouchMove;
+Delegate Input::TouchUp;
Delegate Input::ActionTriggered;
Array Input::ActionMappings;
Array Input::AxisMappings;
diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h
index 1fcb352ea..84d312fa3 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -66,72 +66,66 @@ API_CLASS(Static) class FLAXENGINE_API Input
static Array> CustomDevices;
public:
- typedef Delegate CharDelegate;
- typedef Delegate KeyboardDelegate;
- typedef Delegate MouseDelegate;
- typedef Delegate MouseButtonDelegate;
- typedef Delegate MouseWheelDelegate;
- typedef Delegate TouchDelegate;
///
/// Event fired on character input.
///
- static CharDelegate CharInput;
+ API_EVENT() static Delegate CharInput;
///
/// Event fired on key pressed.
///
- static KeyboardDelegate KeyDown;
+ API_EVENT() static Delegate KeyDown;
///
/// Event fired on key released.
///
- static KeyboardDelegate KeyUp;
+ API_EVENT() static Delegate KeyUp;
///
/// Event fired when mouse button goes down.
///
- static MouseButtonDelegate MouseDown;
+ API_EVENT() static Delegate MouseDown;
///
/// Event fired when mouse button goes up.
///
- static MouseButtonDelegate MouseUp;
+ API_EVENT() static Delegate MouseUp;
///
/// Event fired when mouse button double clicks.
///
- static MouseButtonDelegate MouseDoubleClick;
+ API_EVENT() static Delegate MouseDoubleClick;
///
/// Event fired when mouse wheel is scrolling (wheel delta is normalized).
///
- static MouseWheelDelegate MouseWheel;
+ API_EVENT() static Delegate MouseWheel;
///
/// Event fired when mouse moves.
///
- static MouseDelegate MouseMove;
+ API_EVENT() static Delegate MouseMove;
///
/// Event fired when mouse leaves window.
///
- static Action MouseLeave;
+ API_EVENT() static Action MouseLeave;
///
/// Event fired when touch action begins.
///
- static TouchDelegate TouchDown;
+ API_EVENT() static Delegate TouchDown;
///
/// Event fired when touch action moves.
///
- static TouchDelegate TouchMove;
+ API_EVENT() static Delegate TouchMove;
///
/// Event fired when touch action ends.
///
- static TouchDelegate TouchUp;
+ API_EVENT() static Delegate TouchUp;
public:
///
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 090f3c071..67d84b81d 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -1323,6 +1323,20 @@ Actor* Actor::FindActor(const MClass* type) const
return nullptr;
}
+Actor* Actor::FindActor(const MClass* type, const StringView& name) const
+{
+ CHECK_RETURN(type, nullptr);
+ if (GetClass()->IsSubClassOf(type) && StringUtils::Compare(*_name, *name) == 0)
+ return const_cast(this);
+ for (auto child : Children)
+ {
+ const auto actor = child->FindActor(type, name);
+ if (actor)
+ return actor;
+ }
+ return nullptr;
+}
+
Script* Actor::FindScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs
index bf3606ffc..957d09c7a 100644
--- a/Source/Engine/Level/Actor.cs
+++ b/Source/Engine/Level/Actor.cs
@@ -259,6 +259,17 @@ namespace FlaxEngine
return FindActor(typeof(T)) as T;
}
+ ///
+ /// Tries to find the actor of the given type and name in this actor hierarchy (checks this actor and all children hierarchy).
+ ///
+ /// Name of the object.
+ /// Type of the object.
+ /// Actor instance if found, null otherwise.
+ public T FindActor(string name) where T : Actor
+ {
+ return FindActor(typeof(T), name) as T;
+ }
+
///
/// Searches for all actors of a specific type in this actor children list.
///
diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h
index 53427ab52..0dc7d34e3 100644
--- a/Source/Engine/Level/Actor.h
+++ b/Source/Engine/Level/Actor.h
@@ -734,6 +734,14 @@ public:
/// Actor instance if found, null otherwise.
API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const;
+ ///
+ /// Tries to find the actor of the given type and name in this actor hierarchy (checks this actor and all children hierarchy).
+ ///
+ /// Type of the actor to search for. Includes any actors derived from the type.
+ /// The name of the actor.
+ /// Actor instance if found, null otherwise.
+ API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name) const;
+
///
/// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy).
///
@@ -744,6 +752,17 @@ public:
return (T*)FindActor(T::GetStaticClass());
}
+ ///
+ /// Tries to find the actor of the given type and name in this actor hierarchy (checks this actor and all children hierarchy).
+ ///
+ /// The name of the actor.
+ /// Actor instance if found, null otherwise.
+ template
+ FORCE_INLINE T* FindActor(const StringView& name) const
+ {
+ return (T*)FindActor(T::GetStaticClass(), name);
+ }
+
///
/// Tries to find the script of the given type in this actor hierarchy (checks this actor and all children hierarchy).
///
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 124a415cd..ae617b980 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -748,6 +748,20 @@ void FindActorsRecursive(Actor* node, const Tag& tag, Array& result)
FindActorsRecursive(child, tag, result);
}
+void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, Array& result)
+{
+ for (Tag tag : tags)
+ {
+ if (node->HasTag(tag))
+ {
+ result.Add(node);
+ break;
+ }
+ }
+ for (Actor* child : node->Children)
+ FindActorsRecursiveByParentTags(child, tags, result);
+}
+
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
@@ -781,12 +795,43 @@ Array Level::FindActors(const Tag& tag, Actor* root)
}
else
{
+ ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, result);
}
return result;
}
+Array Level::FindActorsByParentTag(const Tag& parentTag, Actor* root)
+{
+ PROFILE_CPU();
+ Array result;
+ const Array subTags = Tags::GetSubTags(parentTag);
+
+ if (subTags.Count() == 0)
+ {
+ return result;
+ }
+ if (subTags.Count() == 1)
+ {
+ result = FindActors(subTags[0], root);
+ return result;
+ }
+
+ if (root)
+ {
+ FindActorsRecursiveByParentTags(root, subTags, result);
+ }
+ else
+ {
+ ScopeLock lock(ScenesLock);
+ for (Scene* scene : Scenes)
+ FindActorsRecursiveByParentTags(scene, subTags, result);
+ }
+
+ return result;
+}
+
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
{
PROFILE_CPU();
@@ -1266,7 +1311,7 @@ bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData
}
// Info
- LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt(static_cast((DateTime::NowUTC()- startTime).GetTotalMilliseconds())));
+ LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt(static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds())));
// Fire event
CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID());
@@ -1446,6 +1491,16 @@ Actor* Level::FindActor(const MClass* type)
return result;
}
+Actor* Level::FindActor(const MClass* type, const StringView& name)
+{
+ CHECK_RETURN(type, nullptr);
+ Actor* result = nullptr;
+ ScopeLock lock(ScenesLock);
+ for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
+ result = Scenes[i]->FindActor(type, name);
+ return result;
+}
+
Script* Level::FindScript(const MClass* type)
{
CHECK_RETURN(type, nullptr);
diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs
index de589f6df..c38b287ca 100644
--- a/Source/Engine/Level/Level.cs
+++ b/Source/Engine/Level/Level.cs
@@ -66,6 +66,17 @@ namespace FlaxEngine
{
return FindActor(typeof(T)) as T;
}
+
+ ///
+ /// Tries to find actor of the given type and name in all loaded scenes.
+ ///
+ /// Name of the object.
+ /// Type of the object.
+ /// Found actor or null.
+ public static T FindActor(string name) where T : Actor
+ {
+ return FindActor(typeof(T), name) as T;
+ }
///
/// Tries to find actor with the given ID in all loaded scenes. It's very fast O(1) lookup.
diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h
index ed2efe485..e27b81bb9 100644
--- a/Source/Engine/Level/Level.h
+++ b/Source/Engine/Level/Level.h
@@ -363,6 +363,14 @@ public:
/// Found actor or null.
API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type);
+ ///
+ /// Tries to find the actor of the given type and name in all the loaded scenes.
+ ///
+ /// Type of the actor to search for. Includes any actors derived from the type.
+ /// The name of the actor.
+ /// Actor instance if found, null otherwise.
+ API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name);
+
///
/// Tries to find the actor of the given type in all the loaded scenes.
///
@@ -373,6 +381,17 @@ public:
return (T*)FindActor(T::GetStaticClass());
}
+ ///
+ /// Tries to find the actor of the given type and name in all the loaded scenes.
+ ///
+ /// The name of the actor.
+ /// Actor instance if found, null otherwise.
+ template
+ FORCE_INLINE static T* FindActor(const StringView& name)
+ {
+ return (T*)FindActor(T::GetStaticClass(), name);
+ }
+
///
/// Tries to find the script of the given type in all the loaded scenes.
///
@@ -479,6 +498,14 @@ public:
/// Found actors or empty if none.
API_FUNCTION() static Array FindActors(const Tag& tag, Actor* root = nullptr);
+ ///
+ /// Search actors using a parent parentTag.
+ ///
+ /// The tag to search actors with subtags belonging to this tag
+ /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.
+ /// Returns all actors that have subtags belonging to the given parent parentTag
+ API_FUNCTION() static Array FindActorsByParentTag(const Tag& parentTag, Actor* root = nullptr);
+
private:
// Actor API
enum class ActorEventType
diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h
index b8637b45a..919deaafe 100644
--- a/Source/Engine/Physics/Colliders/CharacterController.h
+++ b/Source/Engine/Physics/Colliders/CharacterController.h
@@ -127,7 +127,7 @@ public:
///
/// Gets the character up vector.
///
- API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(typeof(Vector3), \"0,1,0\"), EditorDisplay(\"Character Controller\")")
+ API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(typeof(Vector3), \"0,1,0\"), EditorDisplay(\"Character Controller\"), Limit(-1, 1)")
Vector3 GetUpDirection() const;
///
diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp
index 007bfb6fd..1f9b242ad 100644
--- a/Source/Engine/Physics/Physics.cpp
+++ b/Source/Engine/Physics/Physics.cpp
@@ -218,6 +218,21 @@ void Physics::FlushRequests()
PhysicsBackend::FlushRequests();
}
+bool Physics::LineCast(const Vector3& start, const Vector3& end, uint32 layerMask, bool hitTriggers)
+{
+ return DefaultScene->LineCast(start, end, layerMask, hitTriggers);
+}
+
+bool Physics::LineCast(const Vector3& start, const Vector3& end, RayCastHit& hitInfo, uint32 layerMask, bool hitTriggers)
+{
+ return DefaultScene->LineCast(start, end, hitInfo, layerMask, hitTriggers);
+}
+
+bool Physics::LineCastAll(const Vector3& start, const Vector3& end, Array& results, uint32 layerMask, bool hitTriggers)
+{
+ return DefaultScene->LineCastAll(start, end, results, layerMask, hitTriggers);
+}
+
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
return DefaultScene->RayCast(origin, direction, maxDistance, layerMask, hitTriggers);
@@ -461,6 +476,33 @@ void PhysicsScene::CollectResults()
_isDuringSimulation = false;
}
+bool PhysicsScene::LineCast(const Vector3& start, const Vector3& end, uint32 layerMask, bool hitTriggers)
+{
+ Vector3 directionToEnd = end - start;
+ const float distanceToEnd = directionToEnd.Length();
+ if (distanceToEnd >= ZeroTolerance)
+ directionToEnd /= distanceToEnd;
+ return PhysicsBackend::RayCast(_scene, start, directionToEnd, distanceToEnd, layerMask, hitTriggers);
+}
+
+bool PhysicsScene::LineCast(const Vector3& start, const Vector3& end, RayCastHit& hitInfo, uint32 layerMask, bool hitTriggers)
+{
+ Vector3 directionToEnd = end - start;
+ const float distanceToEnd = directionToEnd.Length();
+ if (distanceToEnd >= ZeroTolerance)
+ directionToEnd /= distanceToEnd;
+ return PhysicsBackend::RayCast(_scene, start, directionToEnd, hitInfo, distanceToEnd, layerMask, hitTriggers);
+}
+
+bool PhysicsScene::LineCastAll(const Vector3& start, const Vector3& end, Array& results, uint32 layerMask, bool hitTriggers)
+{
+ Vector3 directionToEnd = end - start;
+ const float distanceToEnd = directionToEnd.Length();
+ if (distanceToEnd >= ZeroTolerance)
+ directionToEnd /= distanceToEnd;
+ return PhysicsBackend::RayCastAll(_scene, start, directionToEnd, results, distanceToEnd, layerMask, hitTriggers);
+}
+
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
return PhysicsBackend::RayCast(_scene, origin, direction, maxDistance, layerMask, hitTriggers);
diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h
index 62344e082..3e7bff8d9 100644
--- a/Source/Engine/Physics/Physics.h
+++ b/Source/Engine/Physics/Physics.h
@@ -95,6 +95,38 @@ public:
API_FUNCTION() static void FlushRequests();
public:
+ ///
+ /// Performs a line between two points in the scene.
+ ///
+ /// The start position of the line.
+ /// The end position of the line.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
+ ///
+ /// Performs a line between two points in the scene.
+ ///
+ /// The start position of the line.
+ /// The end position of the line.
+ /// The result hit information. Valid only when method returns true.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
+ //
+ /// Performs a line between two points in the scene, returns all hitpoints infos.
+ ///
+ /// The origin of the ray.
+ /// The normalized direction of the ray.
+ /// The result hits. Valid only when method returns true.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() static bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
///
/// Performs a raycast against objects in the scene.
///
diff --git a/Source/Engine/Physics/PhysicsScene.h b/Source/Engine/Physics/PhysicsScene.h
index bc6da6618..e238afc98 100644
--- a/Source/Engine/Physics/PhysicsScene.h
+++ b/Source/Engine/Physics/PhysicsScene.h
@@ -133,6 +133,38 @@ public:
API_FUNCTION() void CollectResults();
public:
+ ///
+ /// Performs a line between two points in the scene.
+ ///
+ /// The start position of the line.
+ /// The end position of the line.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
+ ///
+ /// Performs a line between two points in the scene.
+ ///
+ /// The start position of the line.
+ /// The end position of the line.
+ /// The result hit information. Valid only when method returns true.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
+ //
+ /// Performs a line between two points in the scene, returns all hitpoints infos.
+ ///
+ /// The origin of the ray.
+ /// The normalized direction of the ray.
+ /// The result hits. Valid only when method returns true.
+ /// The layer mask used to filter the results.
+ /// If set to true triggers will be hit, otherwise will skip them.
+ /// True if ray hits an matching object, otherwise false.
+ API_FUNCTION() bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
+
///
/// Performs a raycast against objects in the scene.
///
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index e7849f52f..c766f9525 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -1380,7 +1380,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
}
// Automatic LOD generation
- if (options.GenerateLODs && data.LODs.HasItems() && options.TriangleReduction < 1.0f - ZeroTolerance)
+ if (options.GenerateLODs && options.LODCount > 1 && data.LODs.HasItems() && options.TriangleReduction < 1.0f - ZeroTolerance)
{
auto lodStartTime = DateTime::NowUTC();
meshopt_setAllocator(MeshOptAllocate, MeshOptDeallocate);
diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs
index 428e485d8..c6260b648 100644
--- a/Source/Engine/UI/GUI/Control.Bounds.cs
+++ b/Source/Engine/UI/GUI/Control.Bounds.cs
@@ -169,6 +169,16 @@ namespace FlaxEngine.GUI
set => Bounds = new Rectangle(value + (_parent != null ? _parent.Bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Float2.Zero) - _bounds.Size * _pivot, _bounds.Size);
}
+ ///
+ /// Whether to resize the UI Control based on where the pivot is rather than just the top-left.
+ ///
+ [NoSerialize, HideInEditor]
+ public bool PivotRelative
+ {
+ get => _pivotRelativeSizing;
+ set => _pivotRelativeSizing = value;
+ }
+
///
/// Gets or sets width of the control.
///
@@ -181,6 +191,11 @@ namespace FlaxEngine.GUI
if (Mathf.NearEqual(_bounds.Size.X, value))
return;
var bounds = new Rectangle(_bounds.Location, value, _bounds.Size.Y);
+ if (_pivotRelativeSizing)
+ {
+ var delta = _bounds.Size.X - value;
+ bounds.Location.X += delta * Pivot.X;
+ }
SetBounds(ref bounds);
}
}
@@ -197,6 +212,11 @@ namespace FlaxEngine.GUI
if (Mathf.NearEqual(_bounds.Size.Y, value))
return;
var bounds = new Rectangle(_bounds.Location, _bounds.Size.X, value);
+ if (_pivotRelativeSizing)
+ {
+ var delta = _bounds.Size.Y - value;
+ bounds.Location.Y += delta * Pivot.Y;
+ }
SetBounds(ref bounds);
}
}
diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs
index 49b7f65d3..3bc2610a4 100644
--- a/Source/Engine/UI/GUI/Control.cs
+++ b/Source/Engine/UI/GUI/Control.cs
@@ -61,6 +61,7 @@ namespace FlaxEngine.GUI
private bool _isVisible = true;
private bool _isEnabled = true;
private bool _autoFocus = true;
+ private bool _pivotRelativeSizing = false;
private List _touchOvers;
private RootControl.UpdateDelegate _tooltipUpdate;