Add utility to make spline curve linear or smooth
This commit is contained in:
64
Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
Normal file
64
Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Source/Editor/Undo/Actions/EditSplineAction.cs
Normal file
73
Source/Editor/Undo/Actions/EditSplineAction.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user