diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 1b35cdc35..a6d07fc2a 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -7,6 +7,7 @@ using Real = System.Single; #endif using System; +using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Modules; using FlaxEngine; @@ -287,6 +288,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) @@ -298,6 +303,11 @@ namespace FlaxEditor.SceneGraph.Actors private unsafe void OnUpdate() { + if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + { + EditSplineWithSnap(); + } + // Sync spline points with gizmo handles var actor = (Spline)Actor; var dstCount = actor.SplinePointsCount; @@ -328,6 +338,56 @@ namespace FlaxEditor.SceneGraph.Actors } } + private void EditSplineWithSnap() + { + if (_currentEditSpline == null || _currentEditSpline != Actor) + return; + + var selectedNode = SelectedSplineNode(); + 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() { @@ -396,6 +456,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) { @@ -407,6 +468,60 @@ namespace FlaxEditor.SceneGraph.Actors } } + private static SplinePointNode SelectedSplineNode() + { + 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;