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;