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:
///