diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs
index acd23130d..a3ceb66f9 100644
--- a/Source/Editor/SceneGraph/Actors/SplineNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs
@@ -7,11 +7,14 @@ using Real = System.Single;
#endif
using System;
-using FlaxEditor.GUI.ContextMenu;
+using System.Collections.Generic;
+using FlaxEditor.Actions;
using FlaxEditor.Modules;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.Actors
@@ -288,6 +291,10 @@ namespace FlaxEditor.SceneGraph.Actors
private const Real PointNodeSize = 1.5f;
private const Real TangentNodeSize = 1.0f;
+ private const Real SnapIndicatorSize = 1.7f;
+ private const Real SnapPointIndicatorSize = 2f;
+
+ private static Spline _currentEditSpline;
///
public SplineNode(Actor actor)
@@ -297,9 +304,24 @@ namespace FlaxEditor.SceneGraph.Actors
FlaxEngine.Scripting.Update += OnUpdate;
}
- private unsafe void OnUpdate()
+ private void OnUpdate()
+ {
+ if (Input.Keyboard.GetKey(KeyboardKeys.Shift))
+ {
+ EditSplineWithSnap();
+ }
+
+ var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero;
+ var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right);
+
+ if (requestAddSplinePoint && canAddSplinePoint)
+ AddSplinePoint();
+
+ SyncSplineKeyframeWithNodes();
+ }
+
+ private unsafe void SyncSplineKeyframeWithNodes()
{
- // Sync spline points with gizmo handles
var actor = (Spline)Actor;
var dstCount = actor.SplinePointsCount;
if (dstCount > 1 && actor.IsLoop)
@@ -329,6 +351,135 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
+ private unsafe void AddSplinePoint()
+ {
+ var selectedPoint = GetSelectedSplineNode();
+ if (selectedPoint == null)
+ return;
+
+ // checking mouse hit on scene
+ var spline = (Spline)Actor;
+ var viewport = Editor.Instance.Windows.EditWin.Viewport;
+ var mouseRay = viewport.MouseRay;
+ var viewRay = new Ray(viewport.ViewPosition, viewport.ViewDirection);
+ var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives;
+ var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags);
+
+ if (hit == null)
+ return;
+
+ // Undo data
+ var oldSpline = spline.SplineKeyframes;
+ var editAction = new EditSplineAction(spline, oldSpline);
+ Root.Undo.AddAction(editAction);
+
+ // Getting spline point to duplicate
+ var hitPoint = mouseRay.Position + mouseRay.Direction * closest;
+ var lastPointIndex = selectedPoint.Index;
+ var newPointIndex = lastPointIndex > 0 ? lastPointIndex + 1 : 0;
+ var lastKeyframe = spline.GetSplineKeyframe(lastPointIndex);
+ var isLastPoint = lastPointIndex == spline.SplinePointsCount - 1;
+ var isFirstPoint = lastPointIndex == 0;
+
+ // Getting data to create new point
+
+ var lastPointTime = spline.GetSplineTime(lastPointIndex);
+ var nextPointTime = isLastPoint ? lastPointTime : spline.GetSplineTime(newPointIndex);
+ var newTime = isLastPoint ? lastPointTime + 1.0f : (lastPointTime + nextPointTime) * 0.5f;
+ var distanceFromLastPoint = Vector3.Distance(hitPoint, spline.GetSplinePoint(lastPointIndex));
+ var newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint;
+
+ // set correctly keyframe direction on spawn point
+ if (isFirstPoint)
+ newPointDirection = hitPoint - spline.GetSplineTangent(lastPointIndex, true).Translation;
+ else if (isLastPoint)
+ newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint;
+
+ var newPointLocalPosition = spline.Transform.WorldToLocal(hitPoint);
+ var newPointLocalOrientation = Quaternion.LookRotation(newPointDirection);
+
+ // Adding new point
+ spline.InsertSplinePoint(newPointIndex, newTime, Transform.Identity, false);
+ var newKeyframe = lastKeyframe.DeepClone();
+ var newKeyframeTransform = newKeyframe.Value;
+ newKeyframeTransform.Translation = newPointLocalPosition;
+ newKeyframeTransform.Orientation = newPointLocalOrientation;
+ newKeyframe.Value = newKeyframeTransform;
+
+ // Setting new point keyframe
+ var newkeyframeTangentIn = Transform.Identity;
+ var newkeyframeTangentOut = Transform.Identity;
+ newkeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint;
+ newkeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint;
+ newKeyframe.TangentIn = newkeyframeTangentIn;
+ newKeyframe.TangentOut = newkeyframeTangentOut;
+ spline.SetSplineKeyframe(newPointIndex, newKeyframe);
+
+ for (int i = 1; i < spline.SplinePointsCount; i++)
+ {
+ // check all elements to don't left keyframe has invalid time
+ // because points can be added on start or on middle of spline
+ // conflicting with time of another keyframes
+ spline.SetSplinePointTime(i, i, false);
+ }
+
+ // Select new point node
+ SyncSplineKeyframeWithNodes();
+ Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]);
+
+ spline.UpdateSpline();
+ }
+
+ private void EditSplineWithSnap()
+ {
+ if (_currentEditSpline == null || _currentEditSpline != Actor)
+ return;
+
+ var selectedNode = GetSelectedSplineNode();
+ if (selectedNode == null)
+ return;
+
+ var selectedNodeBounds = new BoundingSphere(selectedNode.Transform.Translation, 1f);
+ var allSplinesInView = GetSplinesOnView();
+ allSplinesInView.Remove(_currentEditSpline);
+
+ if (allSplinesInView.Count == 0 || selectedNode == null)
+ return;
+
+ var snappedOnSplinePoint = false;
+ for (int i = 0; i < allSplinesInView.Count; i++)
+ {
+ for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++)
+ {
+ var keyframePosition = allSplinesInView[i].GetSplinePoint(x);
+ var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize);
+ var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize);
+ DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false);
+
+ if (keyframeBounds.Intersects(selectedNodeBounds))
+ {
+ _currentEditSpline.SetSplinePoint(selectedNode.Index, keyframeBounds.Center);
+ snappedOnSplinePoint = true;
+ break;
+ }
+ }
+ }
+
+ if (!snappedOnSplinePoint)
+ {
+ var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedNode.Transform.Translation, allSplinesInView);
+ var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize);
+ var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize);
+
+ if (snapBounds.Intersects(selectedNodeBounds))
+ {
+ _currentEditSpline.SetSplinePoint(selectedNode.Index, snapBounds.Center);
+ }
+
+ DebugDraw.DrawSphere(snapBounds, Color.Yellow, 0, true);
+ }
+ }
+
///
public override void PostSpawn()
{
@@ -402,6 +553,7 @@ namespace FlaxEditor.SceneGraph.Actors
internal static void OnSplineEdited(Spline spline)
{
+ _currentEditSpline = spline;
var collider = spline.GetChild();
if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode)
{
@@ -413,6 +565,60 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
+ private static SplinePointNode GetSelectedSplineNode()
+ {
+ var selection = Editor.Instance.SceneEditing.Selection;
+ if (selection.Count != 1)
+ return null;
+ if (selection[0] is not SplineNode.SplinePointNode)
+ return null;
+
+ return (SplinePointNode)selection[0];
+ }
+
+ private static List GetSplinesOnView()
+ {
+ var splines = Level.GetActors();
+ var splinesOnView = new List();
+
+ var viewTransform = Editor.Instance.Windows.EditWin.Viewport.ViewTransform;
+ var viewFov = Editor.Instance.Windows.EditWin.Viewport.FieldOfView;
+ var viewNear = Editor.Instance.Windows.EditWin.Viewport.NearPlane;
+ var viewFar = Editor.Instance.Windows.EditWin.Viewport.FarPlane;
+ var viewAspect = Editor.Instance.Windows.EditWin.Width / Editor.Instance.Windows.EditWin.Height;
+ var viewBounds = BoundingFrustum.FromCamera(viewTransform.Translation, viewTransform.Forward, viewTransform.Up, viewFov, viewNear, viewFar, viewAspect);
+
+ foreach (var s in splines)
+ {
+ var contains = viewBounds.Contains(s.EditorBox);
+ if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects)
+ {
+ splinesOnView.Add(s);
+ }
+ }
+
+ return splinesOnView;
+ }
+
+ private static Vector3 GetNearSplineSnapPosition(Vector3 position, List splines)
+ {
+ var nearPoint = splines[0].GetSplinePointClosestToPoint(position);
+ var nearDistance = Vector3.Distance(nearPoint, position);
+
+ for (int i = 1; i < splines.Count; i++)
+ {
+ var point = splines[i].GetSplinePointClosestToPoint(position);
+ var distance = Vector3.Distance(point, position);
+ if (distance < nearDistance)
+ {
+ nearPoint = point;
+ nearDistance = distance;
+ }
+ }
+
+ return nearPoint;
+ }
+
internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize)
{
var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform;