Add utility to make spline curve linear or smooth

This commit is contained in:
Wojtek Figat
2021-01-26 09:32:41 +01:00
parent fe78fa7575
commit 023cdced0a
5 changed files with 206 additions and 20 deletions

View File

@@ -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
{
/// <summary>
/// Custom editor for <see cref="Spline"/>.
/// </summary>
/// <seealso cref="ActorEditor" />
[CustomEditor(typeof(Spline)), DefaultEditor]
public class SplineEditor : ActorEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (Values.HasDifferentTypes == false)
{
layout.Space(10);
var grid = layout.CustomContainer<UniformGridPanel>();
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<Transform>.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<Transform>.Keyframe[])spline.SplineKeyframes.Clone() : null;
spline.SetTangentsSmooth();
if (enableUndo)
Presenter.Undo.AddAction(new EditSplineAction(spline, before));
Editor.Instance.Scene.MarkSceneEdited(spline.Scene);
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Modules;
using FlaxEngine;
namespace FlaxEditor.Actions
{
/// <summary>
/// Change <see cref="Spline"/> keyframes undo action.
/// </summary>
/// <seealso cref="FlaxEditor.IUndoAction" />
/// <seealso cref="FlaxEditor.ISceneEditAction" />
[Serializable]
public class EditSplineAction : IUndoAction, ISceneEditAction
{
[Serialize]
private Guid _splineId;
[Serialize]
private BezierCurve<Transform>.Keyframe[] _before;
[Serialize]
private BezierCurve<Transform>.Keyframe[] _after;
/// <summary>
/// Initializes a new instance of the <see cref="EditSplineAction"/> class.
/// </summary>
/// <param name="spline">The spline.</param>
/// <param name="before">The spline keyframes state before editing it.</param>
public EditSplineAction(Spline spline, BezierCurve<Transform>.Keyframe[] before)
{
_splineId = spline.ID;
_before = before;
_after = (BezierCurve<Transform>.Keyframe[])spline.SplineKeyframes.Clone();
}
/// <inheritdoc />
public string ActionString => "Edit spline keyframes";
/// <inheritdoc />
public void Do()
{
var spline = FlaxEngine.Object.Find<Spline>(ref _splineId);
if (spline == null)
return;
spline.SplineKeyframes = _after;
}
/// <inheritdoc />
public void Undo()
{
var spline = FlaxEngine.Object.Find<Spline>(ref _splineId);
if (spline == null)
return;
spline.SplineKeyframes = _before;
}
/// <inheritdoc />
public void Dispose()
{
_before = _after = null;
}
/// <inheritdoc />
public void MarkSceneEdited(SceneModule sceneModule)
{
var spline = FlaxEngine.Object.Find<Spline>(ref _splineId);
if (spline != null)
sceneModule.MarkSceneEdited(spline.Scene);
}
}
}

View File

@@ -650,16 +650,6 @@ void DebugDraw::DrawLines(const Span<Vector3>& 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;

View File

@@ -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);
}
}

View File

@@ -25,7 +25,7 @@ public:
BezierCurve<Transform> Curve;
/// <summary>
/// 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.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")")
FORCE_INLINE bool GetIsLoop() const
@@ -34,7 +34,7 @@ public:
}
/// <summary>
/// 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.
/// </summary>
API_PROPERTY() void SetIsLoop(bool value);
@@ -212,6 +212,16 @@ public:
/// <param name="updateSpline">True if update spline after adding the point, otherwise false.</param>
API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true);
/// <summary>
/// Updates the curve tangent points to make curve linear.
/// </summary>
API_FUNCTION() void SetTangentsLinear();
/// <summary>
/// Updates the curve tangent points to make curve smooth.
/// </summary>
API_FUNCTION() void SetTangentsSmooth();
public:
/// <summary>