diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index 0f650da2c..fa47c5ee5 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -36,6 +36,7 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { + const auto gameSettings = GameSettings::Get(); const auto platformSettings = LinuxPlatformSettings::Get(); const auto outputPath = data.OutputPath; @@ -64,7 +65,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) // Apply game executable file name #if !BUILD_DEBUG const String outputExePath = outputPath / TEXT("FlaxGame"); - const String gameExePath = outputPath / GameSettings::ProductName; + const String gameExePath = outputPath / gameSettings->ProductName; if (FileSystem::FileExists(outputExePath) && gameExePath.Compare(outputExePath, StringSearchCase::IgnoreCase) == 0) { if (FileSystem::MoveFile(gameExePath, outputExePath, true)) diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs new file mode 100644 index 000000000..9eedd0ed3 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Actions; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(Spline)), DefaultEditor] + public class SplineEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (Values.HasDifferentTypes == false) + { + layout.Space(10); + var grid = layout.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear; + grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth; + } + } + + private void OnSetTangentsLinear() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsLinear(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + + private void OnSetTangentsSmooth() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsSmooth(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 9df2de8be..59058056d 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -553,6 +554,8 @@ namespace FlaxEditor.CustomEditors var group = Group(name, true); group.Panel.Close(false); group.Panel.TooltipText = tooltip; + group.Panel.Tag = editor; + group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; return group.Object(values, editor); } @@ -560,6 +563,23 @@ namespace FlaxEditor.CustomEditors return property.Object(values, editor); } + private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location) + { + var linkedEditor = (CustomEditor)groupPanel.Tag; + var menu = new ContextMenu(); + + var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue); + revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue; + var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue); + resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue; + menu.AddSeparator(); + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + menu.Show(groupPanel, location); + } + /// /// Adds object property editor. Selects proper based on overrides. /// diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs new file mode 100644 index 000000000..ea826c29e --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Scene tree node for actor type. + /// + [HideInEditor] + public sealed class SplineNode : ActorNode + { + /// + public SplineNode(Actor actor) + : base(actor) + { + } + } +} diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 0076742b3..5f3dd8b0f 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -65,6 +65,7 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(NavModifierVolume), typeof(NavModifierVolumeNode)); CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); + CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); } /// diff --git a/Source/Editor/Undo/Actions/EditSplineAction.cs b/Source/Editor/Undo/Actions/EditSplineAction.cs new file mode 100644 index 000000000..3bb8dac5c --- /dev/null +++ b/Source/Editor/Undo/Actions/EditSplineAction.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Modules; +using FlaxEngine; + +namespace FlaxEditor.Actions +{ + /// + /// Change keyframes undo action. + /// + /// + /// + [Serializable] + public class EditSplineAction : IUndoAction, ISceneEditAction + { + [Serialize] + private Guid _splineId; + + [Serialize] + private BezierCurve.Keyframe[] _before; + + [Serialize] + private BezierCurve.Keyframe[] _after; + + /// + /// Initializes a new instance of the class. + /// + /// The spline. + /// The spline keyframes state before editing it. + public EditSplineAction(Spline spline, BezierCurve.Keyframe[] before) + { + _splineId = spline.ID; + _before = before; + _after = (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone(); + } + + /// + public string ActionString => "Edit spline keyframes"; + + /// + public void Do() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _after; + } + + /// + public void Undo() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _before; + } + + /// + public void Dispose() + { + _before = _after = null; + } + + /// + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline != null) + sceneModule.MarkSceneEdited(spline.Scene); + } + } +} diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs index 6fafe444f..8970b6a48 100644 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs @@ -99,6 +99,7 @@ namespace FlaxEditor.Windows new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)), new KeyValuePair("Nav Link", typeof(NavLink)), new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)), + new KeyValuePair("Spline", typeof(Spline)), } }, new ActorsGroup diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 8c30621b0..63e1f1f05 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -167,6 +167,7 @@ namespace FlaxEditor.Windows groupOther.AddChild(CreateActorItem("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume))); groupOther.AddChild(CreateActorItem("Nav Link", typeof(NavLink))); groupOther.AddChild(CreateActorItem("Nav Modifier Volume", typeof(NavModifierVolume))); + groupOther.AddChild(CreateActorItem("Spline", typeof(Spline))); var groupGui = CreateGroupWithList(actorGroups, "GUI"); groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl))); diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index 248efea0c..c90afcea3 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" namespace AnimationUtils { @@ -40,6 +41,12 @@ namespace AnimationUtils return Quaternion::Identity; } + template<> + FORCE_INLINE Transform GetZero() + { + return Transform::Identity; + } + template<> FORCE_INLINE Color GetZero() { @@ -66,22 +73,32 @@ namespace AnimationUtils Quaternion::Slerp(a, b, oneThird, result); } + template<> + FORCE_INLINE void GetTangent(const Transform& a, const Transform& b, float length, Transform& result) + { + const float oneThird = 1.0f / 3.0f; + const float oneThirdLength = length * oneThird; + result.Translation = a.Translation + b.Translation * oneThirdLength; + Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation); + result.Scale = a.Scale + b.Scale * oneThirdLength; + } + template - FORCE_INLINE static void Interpolate(const T& a, const T& b, float alpha, T& result) + FORCE_INLINE static void Interpolate(const T& a, const T& b, float t, T& result) { - result = (T)(a + alpha * (b - a)); + result = (T)(a + t * (b - a)); } template<> - FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float alpha, Vector3& result) + FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float t, Vector3& result) { - Vector3::Lerp(a, b, alpha, result); + Vector3::Lerp(a, b, t, result); } template<> - FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float alpha, Quaternion& result) + FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float t, Quaternion& result) { - Quaternion::Slerp(a, b, alpha, result); + Quaternion::Slerp(a, b, t, result); } static void WrapTime(float& time, float start, float end, bool loop) @@ -117,90 +134,111 @@ namespace AnimationUtils /// Evaluates a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t = 1). /// The evaluated value. template - static void CubicHermite(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermite(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - const float t3 = t2 * t; - - float a = 2 * t3 - 3 * t2 + 1; - float b = t3 - 2 * t2 + t; - float c = -2 * t3 + 3 * t2; - float d = t3 - t2; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + const float ttt = tt * t; + result = (2 * ttt - 3 * tt + 1) * p0 + (ttt - 2 * tt + t) * t0 + (-2 * ttt + 3 * tt) * p1 + (ttt - tt) * t1; } /// /// Evaluates the first derivative of a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t=1). /// The evaluated value. template - static void CubicHermiteD1(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermiteFirstDerivative(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - - float a = 6 * t2 - 6 * t; - float b = 3 * t2 - 4 * t + 1; - float c = -6 * t2 + 6 * t; - float d = 3 * t2 - 2 * t; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + result = (6 * tt - 6 * t) * p0 + (3 * tt - 4 * t + 1) * t0 + (-6 * tt + 6 * t) * p1 + (3 * tt - 2 * t) * t1; } template - static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float alpha, T& result) + static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) { T p01, p12, p23, p012, p123; - Interpolate(p0, p1, alpha, p01); - Interpolate(p1, p2, alpha, p12); - Interpolate(p2, p3, alpha, p23); - Interpolate(p01, p12, alpha, p012); - Interpolate(p12, p23, alpha, p123); - Interpolate(p012, p123, alpha, result); + Interpolate(p0, p1, t, p01); + Interpolate(p1, p2, t, p12); + Interpolate(p2, p3, t, p23); + Interpolate(p01, p12, t, p012); + Interpolate(p12, p23, t, p123); + Interpolate(p012, p123, t, result); } template<> - void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float alpha, Vector2& result) + void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float t, Vector2& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float alpha, Vector3& result) + void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t, Vector3& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float alpha, Quaternion& result) + void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) { Quaternion p01, p12, p23, p012, p123; - Quaternion::Slerp(p0, p1, alpha, p01); - Quaternion::Slerp(p1, p2, alpha, p12); - Quaternion::Slerp(p2, p3, alpha, p23); - Quaternion::Slerp(p01, p12, alpha, p012); - Quaternion::Slerp(p12, p23, alpha, p123); - Quaternion::Slerp(p012, p123, alpha, result); + Quaternion::Slerp(p0, p1, t, p01); + Quaternion::Slerp(p1, p2, t, p12); + Quaternion::Slerp(p2, p3, t, p23); + Quaternion::Slerp(p01, p12, t, p012); + Quaternion::Slerp(p12, p23, t, p123); + Quaternion::Slerp(p012, p123, t, result); + } + + template<> + void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); + } + + template + static void BezierFirstDerivative(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) + { + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + result = 3.0f * uu * (p1 - p0) + 6.0f * u * t * (p2 - p1) + 3.0f * tt * (p3 - p2); + } + + template<> + static void BezierFirstDerivative(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) + { + Vector3 euler; + BezierFirstDerivative(p0.GetEuler(), p1.GetEuler(), p2.GetEuler(), p3.GetEuler(), t, euler); + result = Quaternion::Euler(euler); + } + + template<> + static void BezierFirstDerivative(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + BezierFirstDerivative(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + BezierFirstDerivative(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + BezierFirstDerivative(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); } } diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs index d9762d05f..3a8ddc100 100644 --- a/Source/Engine/Animations/Curve.cs +++ b/Source/Engine/Animations/Curve.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace FlaxEngine { @@ -338,6 +339,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into linear curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// @@ -586,6 +588,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into Bezier curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 8261dea5b..24dcd87a8 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -48,10 +48,20 @@ public: result = a.Value; } + FORCE_INLINE static void InterpolateFirstDerivative(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) + { + result = AnimationUtils::GetZero(); + } + FORCE_INLINE static void InterpolateKey(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, StepCurveKeyframe& result) { result = a; } + + bool operator==(const StepCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -92,11 +102,21 @@ public: AnimationUtils::Interpolate(a.Value, b.Value, alpha, result); } + FORCE_INLINE static void InterpolateFirstDerivative(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) + { + result = b.Value - a.Value; + } + FORCE_INLINE static void InterpolateKey(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, LinearCurveKeyframe& result) { result.Time = a.Time + (b.Time - a.Time) * alpha; AnimationUtils::Interpolate(a.Value, b.Value, alpha, result.Value); } + + bool operator==(const LinearCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -147,23 +167,31 @@ public: { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermite( a.Value, b.Value, leftTangent, rightTangent, alpha, result); + } - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result); + static void InterpolateFirstDerivative(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent = a.Value + a.TangentOut * length; + T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermiteFirstDerivative( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateKey(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, HermiteCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; - - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result.Value); - AnimationUtils::CubicHermiteD1(alpha, a.Value, b.Value, leftTangent, rightTangent, result.TangentIn); - + AnimationUtils::CubicHermite(a.Value, b.Value, leftTangent, rightTangent, alpha, result.Value); + AnimationUtils::CubicHermiteFirstDerivative(a.Value, b.Value, leftTangent, rightTangent, alpha, result.TangentIn); result.TangentIn /= length; result.TangentOut = result.TangentIn; } + + bool operator==(const HermiteCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); /// @@ -223,23 +251,32 @@ public: T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } + static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); + AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result); + } + static void InterpolateKey(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, BezierCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value); - result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } + + bool operator==(const BezierCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); // @formatter:on @@ -346,6 +383,48 @@ public: KeyFrame::Interpolate(leftKey, rightKey, t, length, result); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The keyframes data container. + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(const KeyFrameData& data, T& result, float time, bool loop = true) const + { + const int32 count = data.Length(); + if (count == 0) + { + result = _default; + return; + } + + const float start = 0; + const float end = data[count - 1].Time; + AnimationUtils::WrapTime(time, start, end, loop); + + int32 leftKeyIdx; + int32 rightKeyIdx; + FindKeys(data, time, leftKeyIdx, rightKeyIdx); + + const KeyFrame& leftKey = data[leftKeyIdx]; + const KeyFrame& rightKey = data[rightKeyIdx]; + + if (leftKeyIdx == rightKeyIdx) + { + result = leftKey.Value; + return; + } + + const float length = rightKey.Time - leftKey.Time; + + // Scale from arbitrary range to [0, 1] + float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; + + // Evaluate the derivative at the curve + KeyFrame::InterpolateFirstDerivative(leftKey, rightKey, t, length, result); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -566,6 +645,18 @@ public: Base::Evaluate(data, result, time, loop); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(T& result, float time, bool loop = true) const + { + typename Base::KeyFrameData data(_keyframes); + Base::EvaluateFirstDerivative(data, result, time, loop); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -708,6 +799,30 @@ public: return false; } + +public: + + FORCE_INLINE KeyFrame& operator[](int32 index) + { + return _keyframes[index]; + } + + FORCE_INLINE const KeyFrame& operator[](int32 index) const + { + return _keyframes[index]; + } + + bool operator==(const Curve& other) const + { + if (_keyframes.Count() != other._keyframes.Count()) + return false; + for (int32 i = 0; i < _keyframes.Count(); i++) + { + if (!(_keyframes[i] == other._keyframes[i])) + return false; + } + return true; + } }; /// diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h new file mode 100644 index 000000000..06f7788ea --- /dev/null +++ b/Source/Engine/Animations/CurveSerialization.h @@ -0,0 +1,171 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Curve.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Serialization/Serialization.h" + +// @formatter:off + +namespace Serialization +{ + // StepCurveKeyframe + + template + inline bool ShouldSerialize(const StepCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const StepCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const StepCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, StepCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // LinearCurveKeyframe + + template + inline bool ShouldSerialize(const LinearCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const LinearCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const LinearCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, LinearCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // HermiteCurveKeyframe + + template + inline bool ShouldSerialize(const HermiteCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const HermiteCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const HermiteCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, HermiteCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // BezierCurveKeyframe + + template + inline bool ShouldSerialize(const BezierCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const BezierCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const BezierCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, BezierCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // Curve + + template + inline bool ShouldSerialize(const Curve& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const Curve*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const Curve& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Keyframes"); + stream.StartArray(); + for (auto& k : v.GetKeyframes()) + Serialize(stream, k, nullptr); + stream.EndArray(); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, Curve& v, ISerializeModifier* modifier) + { + if (!stream.IsObject()) + return; + const auto mKeyframes = SERIALIZE_FIND_MEMBER(stream, "Keyframes"); + if (mKeyframes != stream.MemberEnd()) + { + const auto& keyframesArray = mKeyframes->value.GetArray(); + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(keyframesArray.Size()); + for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++) + Deserialize(keyframesArray[i], keyframes[i], modifier); + } + } +} + +// @formatter:on diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index 3eee6d9f8..5a7986517 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -506,6 +506,14 @@ inline Color operator*(float a, const Color& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color& a, const Color& b) + { + return Color::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h index 89f64cc35..f848f6f73 100644 --- a/Source/Engine/Core/Math/Color32.h +++ b/Source/Engine/Core/Math/Color32.h @@ -231,6 +231,14 @@ inline Color32 operator*(float a, const Color32& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b) + { + return a == b; + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 5ac71779b..728fc6a90 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -94,6 +94,13 @@ Vector3 Transform::LocalToWorld(const Vector3& point) const return result + Translation; } +Vector3 Transform::LocalToWorldVector(const Vector3& vector) const +{ + Vector3 result = vector * Scale; + Vector3::Transform(result, Orientation, result); + return result; +} + void Transform::LocalToWorld(const Vector3& point, Vector3& result) const { Vector3 tmp = point * Scale; @@ -171,6 +178,24 @@ Vector3 Transform::WorldToLocal(const Vector3& point) const return result * invScale; } +Vector3 Transform::WorldToLocalVector(const Vector3& vector) const +{ + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + const Quaternion invRotation = Orientation.Conjugated(); + + Vector3 result; + Vector3::Transform(vector, invRotation, result); + + return result * invScale; +} + void Transform::WorldToLocal(const Vector3* points, int32 pointsCount, Vector3* result) const { Vector3 invScale = Scale; diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs index 21ed705ff..7065309e3 100644 --- a/Source/Engine/Core/Math/Transform.cs +++ b/Source/Engine/Core/Math/Transform.cs @@ -197,6 +197,18 @@ namespace FlaxEngine return point + Translation; } + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + public Vector3 LocalToWorldVector(Vector3 vector) + { + vector *= Scale; + Vector3.Transform(ref vector, ref Orientation, out vector); + return vector; + } + /// /// Perform transformation of the given points in local space /// @@ -259,6 +271,29 @@ namespace FlaxEngine return result * invScale; } + /// + /// Perform transformation of the given vector in world space + /// + /// World space vector + /// Local space vector + public Vector3 WorldToLocalVector(Vector3 vector) + { + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + Quaternion invRotation = Orientation; + invRotation.Invert(); + + Vector3.Transform(ref vector, ref invRotation, out var result); + + return result * invScale; + } + /// /// Perform transformation of the given points in world space /// diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index ed19efeae..5a39c2d86 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -184,6 +184,13 @@ public: /// The world space point. Vector3 LocalToWorld(const Vector3& point) const; + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + Vector3 LocalToWorldVector(const Vector3& vector) const; + /// /// Performs transformation of the given point in local space to the world space of this transform. /// @@ -220,6 +227,13 @@ public: /// The local space point. Vector3 WorldToLocal(const Vector3& point) const; + /// + /// Performs transformation of the given vector in world space to the local space of this transform. + /// + /// The world space vector. + /// The local space vector. + Vector3 WorldToLocalVector(const Vector3& vector) const; + /// /// Performs transformation of the given points in world space to the local space of this transform. /// @@ -293,6 +307,14 @@ public: static void Lerp(const Transform& t1, const Transform& t2, float amount, Transform& result); }; +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Transform& a, const Transform& b) + { + return Transform::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index adca955fc..c7771aa67 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -379,7 +379,7 @@ public: static bool NearEqual(const Vector4& a, const Vector4& b) { - return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) & Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); + return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) && Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); } static bool NearEqual(const Vector4& a, const Vector4& b, float epsilon) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 457c90411..596956dea 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -650,16 +650,6 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c } } -static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) -{ - const float oneMinusT = 1.0f - t; - return - oneMinusT * oneMinusT * oneMinusT * p0 + - 3.0f * oneMinusT * oneMinusT * t * p1 + - 3.0f * oneMinusT * t * t * p2 + - t * t * t * p3; -} - void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration, bool depthTest) { // Create draw call entry @@ -676,13 +666,13 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& const Vector3 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const float segmentCountInv = 1.0f / (float)segmentCount; list->EnsureCapacity(list->Count() + segmentCount + 2); // Draw segmented curve for (int32 i = 0; i <= segmentCount; i++) { - const float t = i * segmentCountInv; + const float t = (float)i * segmentCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); list->Add(l); l.Start = l.End; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 63c6c4807..c431429fd 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -42,7 +42,7 @@ public: /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// /// The material slot entry index. - /// The material to set.. + /// The material to set. API_FUNCTION() void SetMaterial(int32 entryIndex, MaterialBase* material); /// diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp new file mode 100644 index 000000000..9f47aa06e --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -0,0 +1,421 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Animations/CurveSerialization.h" +#include + +Spline::Spline(const SpawnParams& params) + : Actor(params) +{ +} + +void Spline::SetIsLoop(bool value) +{ + if (_loop != value) + { + _loop = value; + UpdateSpline(); + } +} + +Vector3 Spline::GetSplinePoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Translation; +} + +Quaternion Spline::GetSplineOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + Quaternion::Multiply(_transform.Orientation, t.Orientation, t.Orientation); + t.Orientation.Normalize(); + return t.Orientation; +} + +Quaternion Spline::GetSplineLocalOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Orientation; +} + +Vector3 Spline::GetSplineScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.Scale * t.Scale; +} + +Vector3 Spline::GetSplineLocalScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Scale; +} + +Transform Spline::GetSplineTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t); +} + +Transform Spline::GetSplineLocalTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t; +} + +Vector3 Spline::GetSplineDirection(float time) const +{ + return _transform.LocalToWorldVector(GetSplineLocalDirection(time)); +} + +Vector3 Spline::GetSplineLocalDirection(float time) const +{ + if (Curve.GetKeyframes().Count() == 0) + return Vector3::Forward; + Transform t; + Curve.EvaluateFirstDerivative(t, time, _loop); + t.Translation.Normalize(); + return t.Translation; +} + +Vector3 Spline::GetSplinePoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value.Translation; +} + +Transform Spline::GetSplineTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value); +} + +Transform Spline::GetSplineLocalTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value; +} + +int32 Spline::GetSplinePointsCount() const +{ + return Curve.GetKeyframes().Count(); +} + +float Spline::GetSplineDuration() const +{ + return Curve.GetLength(); +} + +float Spline::GetSplineLength() const +{ + float sum = 0.0f; + const int32 slices = 20; + const float step = 1.0f / (float)slices; + Vector3 prevPoint = Vector3::Zero; + for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) + { + const auto& a = Curve[i = 1]; + const auto& b = Curve[i]; + + const float length = Math::Abs(b.Time - a.Time); + Vector3 leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); + AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); + + // TODO: implement sth more analytical than brute-force solution + for (int32 slice = 0; slice < slices; slice++) + { + const float t = (float)slice * step; + Vector3 pos; + AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); + pos *= _transform.Scale; + sum += Vector3::DistanceSquared(pos, prevPoint); + prevPoint = pos; + } + } + return Math::Sqrt(sum); +} + +namespace +{ + void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime) + { + // TODO: implement sth more analytical than brute-force solution + const int32 slices = 100; + const float step = 1.0f / (float)slices; + const float length = Math::Abs(end.Time - start.Time); + for (int32 i = 0; i <= slices; i++) + { + const float t = (float)i * step; + Transform result; + Spline::Keyframe::Interpolate(start, end, t, length, result); + const float distanceSquared = Vector3::DistanceSquared(point, result.Translation); + if (distanceSquared < bestDistanceSquared) + { + bestDistanceSquared = distanceSquared; + bestTime = start.Time + t * length; + } + } + } +} + +float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const +{ + const int32 pointsCount = Curve.GetKeyframes().Count(); + if (pointsCount == 0) + return 0.0f; + if (pointsCount == 1) + return Curve[0].Time; + const Vector3 localPoint = _transform.WorldToLocal(point); + float bestDistanceSquared = MAX_float; + float bestTime = 0.0f; + for (int32 i = 1; i < pointsCount; i++) + FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); + return bestTime; +} + +Vector3 Spline::GetSplinePointClosestToPoint(const Vector3& point) const +{ + return GetSplinePoint(GetSplineTimeClosestToPoint(point)); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value.Translation)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value.Translation); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value); +} + +void Spline::ClearSpline() +{ + if (Curve.IsEmpty()) + return; + Curve.Clear(); + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, point); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetTangentsLinear() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i < count; i++) + { + auto& k = Curve[i]; + k.TangentIn = k.TangentOut = Transform::Identity; + } + + UpdateSpline(); +} + +void Spline::SetTangentsSmooth() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + auto& keys = Curve.GetKeyframes(); + const int32 last = count - 2; + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i <= last; i++) + { + auto& key = keys[i]; + const auto& prevKey = keys[i == 0 ? (_loop ? last : 0) : i - 1]; + const auto& nextKey = keys[i == last ? (_loop ? 0 : last) : i + 1]; + const float prevTime = _loop && i == 0 ? key.Time : prevKey.Time; + const float nextTime = _loop && i == last ? key.Time : nextKey.Time; + const Vector3 slope = key.Value.Translation - prevKey.Value.Translation + nextKey.Value.Translation - key.Value.Translation; + const Vector3 tangent = slope / Math::Max(nextTime - prevTime, ZeroTolerance); + key.TangentIn.Translation = -tangent; + key.TangentOut.Translation = tangent; + } + + UpdateSpline(); +} + +void Spline::UpdateSpline() +{ + // Always keep last point in the loop + const int32 count = Curve.GetKeyframes().Count(); + if (_loop && count > 1) + { + auto& first = Curve[0]; + auto& last = Curve[count - 1]; + last.Value = first.Value; + last.TangentIn = first.TangentIn; + last.TangentOut = first.TangentOut; + } +} + +void Spline::GetKeyframes(MonoArray* data) +{ + Platform::MemoryCopy(mono_array_addr_with_size(data, sizeof(Keyframe), 0), Curve.GetKeyframes().Get(), sizeof(Keyframe) * Curve.GetKeyframes().Count()); +} + +void Spline::SetKeyframes(MonoArray* data) +{ + const auto count = (int32)mono_array_length(data); + Curve.GetKeyframes().Resize(count, false); + Platform::MemoryCopy(Curve.GetKeyframes().Get(), mono_array_addr_with_size(data, sizeof(Keyframe), 0), sizeof(Keyframe) * count); + UpdateSpline(); +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +namespace +{ + void DrawSegment(Spline* spline, int32 start, int32 end, const Color& color, const Transform& transform, bool depthTest) + { + const auto& startKey = spline->Curve[start]; + const auto& endKey = spline->Curve[end]; + const Vector3 startPos = transform.LocalToWorld(startKey.Value.Translation); + const Vector3& startTangent = startKey.TangentOut.Translation; + const Vector3 endPos = transform.LocalToWorld(endKey.Value.Translation); + const Vector3& endTangent = endKey.TangentIn.Translation; + const float d = (endKey.Time - startKey.Time) / 3.0f; + DEBUG_DRAW_BEZIER(startPos, startPos + startTangent * d, endPos + endTangent * d, endPos, color, 0.0f, depthTest); + } + + void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest) + { + const int32 count = spline->Curve.GetKeyframes().Count(); + for (int32 i = 0; i < count; i++) + { + Vector3 p = transform.LocalToWorld(spline->Curve[i].Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(p, 5.0f), color, 0.0f, true); + if (i != 0) + DrawSegment(spline, i - 1, i, color, transform, depthTest); + } + } +} + +void Spline::OnDebugDraw() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true); + + // Base + Actor::OnDebugDraw(); +} + +void Spline::OnDebugDrawSelected() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false); + DrawSpline(this, color, _transform, true); + + // Base + Actor::OnDebugDrawSelected(); +} + +#endif + +void Spline::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(Spline); + + SERIALIZE_MEMBER(IsLoop, _loop); + SERIALIZE(Curve); +} + +void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(IsLoop, _loop); + DESERIALIZE(Curve); + + // Initialize spline when loading data during gameplay + if (IsDuringPlay()) + { + UpdateSpline(); + } +} + +void Spline::OnEnable() +{ + // Base + Actor::OnEnable(); + + // Initialize spline + UpdateSpline(); +} diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h new file mode 100644 index 000000000..cc4226414 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.h @@ -0,0 +1,271 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../Actor.h" +#include "Engine/Animations/Curve.h" + +/// +/// Spline shape actor that defines spatial curve with utility functions for general purpose usage. +/// +API_CLASS() class FLAXENGINE_API Spline : public Actor +{ +DECLARE_SCENE_OBJECT(Spline); + typedef BezierCurveKeyframe Keyframe; +private: + + bool _loop = false; + +public: + + /// + /// The spline bezier curve points represented as series of transformations in 3D space (with tangents). Points are stored in local-space of the actor. + /// + /// Ensure to call UpdateSpline() after editing curve to reflect the changes. + BezierCurve Curve; + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") + FORCE_INLINE bool GetIsLoop() const + { + return _loop; + } + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY() void SetIsLoop(bool value); + +public: + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (world-space). + API_FUNCTION() Quaternion GetSplineOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (local-space). + API_FUNCTION() Quaternion GetSplineLocalOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (world-space). + API_FUNCTION() Vector3 GetSplineScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (local-space). + API_FUNCTION() Vector3 GetSplineLocalScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (world-space). + API_FUNCTION() Vector3 GetSplineDirection(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (local-space). + API_FUNCTION() Vector3 GetSplineLocalDirection(float time) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(int32 index) const; + + /// + /// Gets the amount of points in the spline. + /// + API_PROPERTY() int32 GetSplinePointsCount() const; + + /// + /// Gets the total duration of the spline curve (time of the last point). + /// + API_PROPERTY() float GetSplineDuration() const; + + /// + /// Gets the total length of the spline curve (distance between all the points). + /// + API_PROPERTY() float GetSplineLength() const; + + /// + /// Calculates the closest point to the given location and returns the spline time at that point. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline time. + API_FUNCTION() float GetSplineTimeClosestToPoint(const Vector3& point) const; + + /// + /// Calculates the closest point to the given location. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline position. + API_FUNCTION() Vector3 GetSplinePointClosestToPoint(const Vector3& point) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + +public: + + /// + /// Clears the spline to be empty. + /// + API_FUNCTION() void ClearSpline(); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Transform& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + + /// + /// Updates the curve tangent points to make curve linear. + /// + API_FUNCTION() void SetTangentsLinear(); + + /// + /// Updates the curve tangent points to make curve smooth. + /// + API_FUNCTION() void SetTangentsSmooth(); + +public: + + /// + /// Updates the spline after it was modified. Recreates the collision and/or any cached state that depends on the spline type. + /// + API_FUNCTION() virtual void UpdateSpline(); + +protected: + +#if USE_EDITOR + virtual Color GetSplineColor() + { + return Color::White; + } +#endif + +private: + + // Internal bindings + API_FUNCTION(NoProxy) void GetKeyframes(MonoArray* data); + API_FUNCTION(NoProxy) void SetKeyframes(MonoArray* data); + +public: + + // [Actor] +#if USE_EDITOR + void OnDebugDraw() override; + void OnDebugDrawSelected() override; +#endif + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnEnable() override; +}; diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs new file mode 100644 index 000000000..46a05cd5d --- /dev/null +++ b/Source/Engine/Level/Spline.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace FlaxEngine +{ + partial class Spline + { + private BezierCurve.Keyframe[] _keyframes; + + /// + /// Gets or sets the spline keyframes collection. + /// + [Unmanaged] + [Tooltip("Spline keyframes collection.")] + [EditorOrder(10), EditorDisplay("Spline"), Collection(CanReorderItems = false)] + public BezierCurve.Keyframe[] SplineKeyframes + { + get + { + var count = SplinePointsCount; + if (_keyframes == null || _keyframes.Length != count) + _keyframes = new BezierCurve.Keyframe[count]; +#if !BUILD_RELEASE + if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) + throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes."); +#endif + Internal_GetKeyframes(__unmanagedPtr, _keyframes); + return _keyframes; + } + set + { + if (value == null) + value = Utils.GetEmptyArray.Keyframe>(); + _keyframes = null; + Internal_SetKeyframes(__unmanagedPtr, value); + } + } + } +} diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index fa5ff7f59..04493c7ef 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -31,9 +31,14 @@ namespace FlaxEngine.GUI protected bool _mouseOverHeader; /// - /// The 'mouse down' flag (over header). + /// The 'mouse down' flag (over header) for the left mouse button. /// - protected bool _mouseDown; + protected bool _mouseButtonLeftDown; + + /// + /// The 'mouse down' flag (over header) for the right mouse button. + /// + protected bool _mouseButtonRightDown; /// /// The animation progress (normalized). @@ -126,6 +131,11 @@ namespace FlaxEngine.GUI [EditorDisplay("Style"), EditorOrder(2000)] public bool EnableDropDownIcon { get; set; } + /// + /// Occurs when mouse right-clicks over the header. + /// + public event Action MouseButtonRightClicked; + /// /// Occurs when drop panel is opened or closed. /// @@ -430,10 +440,14 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - if (button == MouseButton.Left && _mouseOverHeader) { - _mouseDown = true; + _mouseButtonLeftDown = true; + return true; + } + if (button == MouseButton.Right && _mouseOverHeader) + { + _mouseButtonRightDown = true; return true; } @@ -455,16 +469,17 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - - if (button == MouseButton.Left && _mouseDown) + if (button == MouseButton.Left && _mouseButtonLeftDown) { - _mouseDown = false; - + _mouseButtonLeftDown = false; if (_mouseOverHeader) - { Toggle(); - } - + return true; + } + if (button == MouseButton.Right && _mouseButtonRightDown) + { + _mouseButtonRightDown = false; + MouseButtonRightClicked?.Invoke(this, location); return true; } @@ -474,7 +489,8 @@ namespace FlaxEngine.GUI /// public override void OnMouseLeave() { - _mouseDown = false; + _mouseButtonLeftDown = false; + _mouseButtonRightDown = false; _mouseOverHeader = false; base.OnMouseLeave();