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/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/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/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 509c2f389..5e9e7fe94 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -154,8 +154,6 @@ float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const float bestTime = 0.0f; for (int32 i = 1; i < pointsCount; i++) FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); - if (_loop) - FindTimeClosestToPoint(localPoint, Curve[pointsCount - 1], Curve[0], bestDistanceSquared, bestTime); return bestTime; } @@ -228,8 +226,61 @@ void Spline::AddSplineLocalPoint(const Transform& point, bool 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) @@ -256,9 +307,9 @@ namespace const auto& startKey = spline->Curve[start]; const auto& endKey = spline->Curve[end]; const Vector3 startPos = transform.LocalToWorld(startKey.Value.Translation); - const Vector3 startTangent = transform.LocalToWorld(startKey.TangentOut.Translation); + const Vector3& startTangent = startKey.TangentOut.Translation; const Vector3 endPos = transform.LocalToWorld(endKey.Value.Translation); - const Vector3 endTangent = transform.LocalToWorld(endKey.TangentIn.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); } @@ -273,8 +324,6 @@ namespace if (i != 0) DrawSegment(spline, i - 1, i, color, transform, depthTest); } - if (spline->GetIsLoop() && count > 1) - DrawSegment(spline, count - 1, 0, color, transform, depthTest); } } diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index a94804ea7..fd489b823 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -25,7 +25,7 @@ public: BezierCurve Curve; /// - /// Whether to use spline as closed loop. + /// 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 @@ -34,7 +34,7 @@ public: } /// - /// Whether to use spline as closed 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); @@ -212,6 +212,16 @@ public: /// 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: ///