diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 877342154..8f6dccd39 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -4,6 +4,8 @@ using FlaxEditor.Actions; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEditor.CustomEditors.Elements; +using System; namespace FlaxEditor.CustomEditors.Dedicated { @@ -14,6 +16,214 @@ namespace FlaxEditor.CustomEditors.Dedicated [CustomEditor(typeof(Spline)), DefaultEditor] public class SplineEditor : ActorEditor { + /// + /// Basis for creating tangent manipulation types for bezier curves. + /// + public abstract class TangentModeBase + { + /// + /// Called when user set selected tangent mode. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSetMode(Spline spline, int index); + + /// + /// Called when user select a keyframe (spline point) of current selected spline on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSelectKeyframe(Spline spline, int index); + + /// + /// Called when user select a tangent of current keyframe selected from spline. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSelectTangent(Spline spline, int index); + + /// + /// Called when the tangent in from current keyframe selected from spline is moved on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnMoveTangentIn(Spline spline, int index); + + /// + /// Called when the tangent out from current keyframe selected from spline is moved on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Current spline selected on editor viewport. + public abstract void OnMoveTangentOut(Spline spline, int index); + } + + /// + /// Edit curve options manipulate the curve as free mode + /// + public sealed class FreeTangentMode : TangentModeBase + { + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + + /// + public override void OnSetMode(Spline spline, int index) { } + } + + /// + /// Edit curve options to set tangents to linear + /// + public sealed class LinearTangentMode : TangentModeBase + { + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + + /// + public override void OnSetMode(Spline spline, int index) + { + SetKeyframeLinear(spline, index); + } + + private void SetKeyframeLinear(Spline spline, int index) + { + var tangentIn = spline.GetSplineTangent(index, true); + var tangentOut = spline.GetSplineTangent(index, false); + tangentIn.Translation = spline.GetSplinePoint(index); + tangentOut.Translation = spline.GetSplinePoint(index); + spline.SetSplineTangent(index, tangentIn, true, false); + spline.SetSplineTangent(index, tangentOut, false, false); + spline.UpdateSpline(); + } + } + + /// + /// Edit curve options to align tangents of selected spline + /// + public sealed class AlignedTangentMode : TangentModeBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + SmoothIfNotAligned(spline, index); + } + + /// + public override void OnSelectKeyframe(Spline spline, int index) + { + SmoothIfNotAligned(spline, index); + } + + /// + public override void OnSelectTangent(Spline selectedSpline, int index) { } + + /// + public override void OnMoveTangentIn(Spline spline, int index) + { + SetPointAligned(spline, index, true); + } + + /// + public override void OnMoveTangentOut(Spline spline, int index) + { + SetPointAligned(spline, index, false); + } + + private void SmoothIfNotAligned(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + var isAligned = Vector3.Dot(keyframe.TangentIn.Translation.Normalized, keyframe.TangentOut.Translation.Normalized) == 1f; + + if (!isAligned) + { + SetPointSmooth(spline, index); + } + } + + private void SetPointSmooth(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + var tangentIn = keyframe.TangentIn; + var tangentOut = keyframe.TangentOut; + var tangentInSize = tangentIn.Translation.Length; + var tangentOutSize = tangentOut.Translation.Length; + + var isLastKeyframe = index >= spline.SplinePointsCount - 1; + var isFirstKeyframe = index <= 0; + + if (!isLastKeyframe && !isFirstKeyframe) + { + var nextKeyframe = spline.GetSplineKeyframe(++index); + var previousKeyframe = spline.GetSplineKeyframe(--index); + + // calc form from Spline.cpp -> SetTangentsSmooth + var slop = (keyframe.Value.Translation - previousKeyframe.Value.Translation + nextKeyframe.Value.Translation - keyframe.Value.Translation).Normalized; + + keyframe.TangentIn.Translation = -slop * tangentInSize; + keyframe.TangentOut.Translation = slop * tangentOutSize; + spline.SetSplineKeyframe(index, keyframe); + } + } + + private void SetPointAligned(Spline spline, int index, bool isIn) + { + var keyframe = spline.GetSplineKeyframe(index); + var referenceTangent = isIn ? keyframe.TangentIn : keyframe.TangentOut; + var otherTangent = !isIn ? keyframe.TangentIn : keyframe.TangentOut; + + // inverse of reference tangent + otherTangent.Translation = -referenceTangent.Translation.Normalized * otherTangent.Translation.Length; + + if (isIn) keyframe.TangentOut = otherTangent; + if (!isIn) keyframe.TangentIn = otherTangent; + + spline.SetSplineKeyframe(index, keyframe); + } + } + + private TangentModeBase _currentTangentMode; + + private ButtonElement _freeTangentButton; + private ButtonElement _linearTangentButton; + private ButtonElement _alignedTangentButton; + + private bool _tanInChanged; + private bool _tanOutChanged; + private Vector3 _lastTanInPos; + private Vector3 _lastTanOutPos; + private SplineNode.SplinePointNode _lastPointSelected; + private SplineNode.SplinePointTangentNode _selectedTangentIn; + private SplineNode.SplinePointTangentNode _selectedTangentOut; + + /// + /// Current selected spline on editor, if has + /// + public Spline SelectedSpline => !Values.HasDifferentValues && Values[0] is Spline ? (Spline)Values[0] : null; + + /// + /// Create a Spline editor + /// + public SplineEditor() + { + _currentTangentMode = new FreeTangentMode(); + } + /// public override void Initialize(LayoutElementsContainer layout) { @@ -22,6 +232,21 @@ namespace FlaxEditor.CustomEditors.Dedicated if (Values.HasDifferentTypes == false) { layout.Space(10); + + layout.Header("Selected spline point"); + var selectedPointsGrid = layout.CustomContainer(); + selectedPointsGrid.CustomControl.SlotsHorizontally = 3; + selectedPointsGrid.CustomControl.SlotsVertically = 1; + + _linearTangentButton = selectedPointsGrid.Button("Linear"); + _freeTangentButton = selectedPointsGrid.Button("Free"); + _alignedTangentButton = selectedPointsGrid.Button("Aligned"); + + _linearTangentButton.Button.Clicked += SetModeLinear; + _freeTangentButton.Button.Clicked += SetModeFree; + _alignedTangentButton.Button.Clicked += SetModeAligned; + + layout.Header("All spline points"); var grid = layout.CustomContainer(); grid.CustomControl.SlotsHorizontally = 2; grid.CustomControl.SlotsVertically = 1; @@ -30,6 +255,107 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + /// + public override void Refresh() + { + base.Refresh(); + + UpdateSelectedPoint(); + UpdateSelectedTangent(); + + var index = _lastPointSelected.Index; + var currentTangentInPosition = SelectedSpline.GetSplineLocalTangent(index, true).Translation; + var currentTangentOutPosition = SelectedSpline.GetSplineLocalTangent(index, false).Translation; + + if (_selectedTangentIn != null) + { + _tanInChanged = _lastTanInPos != currentTangentInPosition; + _lastTanInPos = currentTangentInPosition; + } + + if (_selectedTangentOut != null) + { + _tanOutChanged = _lastTanOutPos != currentTangentOutPosition; + _lastTanOutPos = currentTangentOutPosition; + } + + if (_tanInChanged) _currentTangentMode.OnMoveTangentIn(SelectedSpline, index); + if (_tanOutChanged) _currentTangentMode.OnMoveTangentOut(SelectedSpline, index); + + currentTangentInPosition = SelectedSpline.GetSplineLocalTangent(index, true).Translation; + currentTangentOutPosition = SelectedSpline.GetSplineLocalTangent(index, false).Translation; + + // update last tangents position after changes + if (SelectedSpline) _lastTanInPos = currentTangentInPosition; + if (SelectedSpline) _lastTanOutPos = currentTangentOutPosition; + _tanInChanged = false; + _tanOutChanged = false; + } + + private void SetModeLinear() + { + _currentTangentMode = new LinearTangentMode(); + _currentTangentMode.OnSetMode(SelectedSpline, _lastPointSelected.Index); + } + + private void SetModeFree() + { + _currentTangentMode = new FreeTangentMode(); + _currentTangentMode.OnSetMode(SelectedSpline, _lastPointSelected.Index); + } + + private void SetModeAligned() + { + _currentTangentMode = new AlignedTangentMode(); + _currentTangentMode.OnSetMode(SelectedSpline, _lastPointSelected.Index); + } + + private void UpdateSelectedPoint() + { + // works only if select one spline + if (Editor.Instance.SceneEditing.SelectionCount != 1) return; + + var currentSelected = Editor.Instance.SceneEditing.Selection[0]; + + if (currentSelected == _lastPointSelected) return; + if (currentSelected is not SplineNode.SplinePointNode) return; + + _lastPointSelected = currentSelected as SplineNode.SplinePointNode; + var index = _lastPointSelected.Index; + _currentTangentMode.OnSelectKeyframe(SelectedSpline, index); + } + + private void UpdateSelectedTangent() + { + // works only if select one spline + if (_lastPointSelected == null || Editor.Instance.SceneEditing.SelectionCount != 1) return; + + var currentSelected = Editor.Instance.SceneEditing.Selection[0]; + + if (currentSelected is not SplineNode.SplinePointTangentNode) return; + if (currentSelected == _selectedTangentIn) return; + if (currentSelected == _selectedTangentOut) return; + + var index = _lastPointSelected.Index; + + if (currentSelected.Transform == SelectedSpline.GetSplineTangent(index, true)) + { + _selectedTangentIn = currentSelected as SplineNode.SplinePointTangentNode; + _currentTangentMode.OnSelectTangent(SelectedSpline, index); + return; + } + + if (currentSelected.Transform == SelectedSpline.GetSplineTangent(index, false)) + { + _selectedTangentOut = currentSelected as SplineNode.SplinePointTangentNode; + _currentTangentMode.OnSelectTangent(SelectedSpline, index); + return; + } + + _selectedTangentIn = null; + _selectedTangentOut = null; + } + private void OnSetTangentsLinear() { var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index eebee0cd9..3ba1d14c3 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.SceneGraph.Actors [HideInEditor] public sealed class SplineNode : ActorNode { - private sealed class SplinePointNode : ActorChildNode + public sealed class SplinePointNode : ActorChildNode { public unsafe SplinePointNode(SplineNode node, Guid id, int index) : base(node, id, index) @@ -219,7 +219,7 @@ namespace FlaxEditor.SceneGraph.Actors } } - private sealed class SplinePointTangentNode : ActorChildNode + public sealed class SplinePointTangentNode : ActorChildNode { private SplineNode _node; private int _index;