Browse Source

Inserting points work

flabbet 8 months ago
parent
commit
98f241bf50

+ 20 - 15
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -1,4 +1,5 @@
-using Avalonia.Media;
+using Avalonia.Input;
+using Avalonia.Media;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
@@ -150,23 +151,27 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
             return;
         }
 
-        VecD mouseSnap =
-            document.SnappingHandler.SnappingController.GetSnapPoint(args.PositionOnCanvas, out _,
-                out _);
-
-        if (startingPath.Points.Count > 0 && startingPath.Points[0] == (VecF)mouseSnap)
+        if (args.KeyModifiers == KeyModifiers.None)
         {
-            startingPath.Close();
-        }
-        else
-        {
-            startingPath.LineTo((VecF)mouseSnap);
-        }
 
-        PathVectorData vectorData = ConstructShapeData();
+            VecD mouseSnap =
+                document.SnappingHandler.SnappingController.GetSnapPoint(args.PositionOnCanvas, out _,
+                    out _);
+
+            if (startingPath.Points.Count > 0 && startingPath.Points[0] == (VecF)mouseSnap)
+            {
+                startingPath.Close();
+            }
+            else
+            {
+                startingPath.LineTo((VecF)mouseSnap);
+            }
+
+            PathVectorData vectorData = ConstructShapeData();
 
-        internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, vectorData));
-        mouseDown = true;
+            internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, vectorData));
+            mouseDown = true;
+        }
     }
 
     public override void OnLeftMouseButtonUp(VecD pos)

+ 35 - 27
src/PixiEditor/Views/Overlays/PathOverlay/SubShape.cs

@@ -79,12 +79,43 @@ public class SubShape
         }
 
         var oldTo = onVerb.To;
-        onVerb.To = point;
-        AdjustControlPoints(point, onVerb);
+        VecF[] data = new VecF[4];
+        VecF insertPoint = point;
+
+        if (onVerb.VerbType == PathVerb.Line)
+        {
+            onVerb.To = point;
+            data = [onVerb.To, oldTo, VecF.Zero, VecF.Zero];
+        }
+        else
+        {
+            float t = VectorMath.GetNormalizedSegmentPosition(onVerb, point);
+            VecD oldControlPoint1 = (VecD)onVerb.ControlPoint1.Value;
+            VecD oldControlPoint2 = (VecD)onVerb.ControlPoint2.Value;
+            
+            // de Casteljau's algorithm
+            
+            var q0 = ((VecD)onVerb.From).Lerp(oldControlPoint1, t);
+            var q1 = oldControlPoint1.Lerp(oldControlPoint2, t);
+            var q2 = oldControlPoint2.Lerp((VecD)oldTo, t);
+            
+            var r0 = q0.Lerp(q1, t);
+            var r1 = q1.Lerp(q2, t);
+            
+            var s0 = r0.Lerp(r1, t);
+            
+            onVerb.ControlPoint1 = (VecF)q0;
+            onVerb.ControlPoint2 = (VecF)r0;
+            
+            onVerb.To = (VecF)s0;
+            
+            data = [(VecF)s0, (VecF)r1, (VecF)q2, oldTo];
+            
+            insertPoint = (VecF)s0;
+        }
 
-        VecF[] data = GetDataForNewPoint(onVerb, oldTo);
         this.points.Insert(indexOfVerb + 1,
-            new ShapePoint(point, indexOfVerb + 1, new Verb((onVerb.VerbType.Value, data, 0))));
+            new ShapePoint(insertPoint, indexOfVerb + 1, new Verb((onVerb.VerbType.Value, data, 0))));
 
         for (int i = indexOfVerb + 2; i < this.points.Count; i++)
         {
@@ -120,27 +151,4 @@ public class SubShape
 
         return null;
     }
-
-    private static VecF[] GetDataForNewPoint(Verb onVerb, VecF to)
-    {
-        if (onVerb.VerbType.Value == PathVerb.Line)
-        {
-            return [onVerb.To, to, VecF.Zero, VecF.Zero];
-        }
-
-        if (onVerb.VerbType.Value == PathVerb.Cubic)
-        {
-            return [onVerb.To, onVerb.To, to, to];
-        }
-        
-        return [onVerb.To, to, VecF.Zero, VecF.Zero];
-    }
-
-    private static void AdjustControlPoints(VecF point, Verb onVerb)
-    {
-        if (onVerb.ControlPoint2 != null)
-        {
-            onVerb.ControlPoint2 = point;
-        }
-    }
 }

+ 51 - 8
src/PixiEditor/Views/Overlays/PathOverlay/VectorMath.cs

@@ -45,17 +45,21 @@ internal static class VectorMath
         switch (shapePointVerb.VerbType)
         {
             case PathVerb.Move:
-                return Math.Abs(point.X - shapePointVerb.From.X) < 0.0001 && Math.Abs(point.Y - shapePointVerb.From.Y) < 0.0001;
+                return Math.Abs(point.X - shapePointVerb.From.X) < 0.0001 &&
+                       Math.Abs(point.Y - shapePointVerb.From.Y) < 0.0001;
             case PathVerb.Line:
                 return IsPointOnLine(point, (VecD)shapePointVerb.From, (VecD)shapePointVerb.To);
             case PathVerb.Quad:
-                return IsPointOnQuad(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
+                return IsPointOnQuad(point, (VecD)shapePointVerb.From,
+                    (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
                     (VecD)shapePointVerb.To);
             case PathVerb.Conic:
-                return IsPointOnConic(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
+                return IsPointOnConic(point, (VecD)shapePointVerb.From,
+                    (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
                     (VecD)shapePointVerb.To, shapePointVerb.ConicWeight);
             case PathVerb.Cubic:
-                return IsPointOnCubic(point, (VecD)shapePointVerb.From, (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
+                return IsPointOnCubic(point, (VecD)shapePointVerb.From,
+                    (VecD)(shapePointVerb.ControlPoint1 ?? shapePointVerb.From),
                     (VecD)(shapePointVerb.ControlPoint2 ?? shapePointVerb.To), (VecD)shapePointVerb.To);
             case PathVerb.Close:
                 break;
@@ -113,22 +117,42 @@ internal static class VectorMath
     {
         return FindClosestPointBruteForce(point, (t) => ConicBezier(start, controlPoint, end, weight, t));
     }
-    
+
     public static bool IsPointOnQuad(VecD point, VecD start, VecD controlPoint, VecD end)
     {
         return IsPointOnPath(point, (t) => QuadraticBezier(start, controlPoint, end, t));
     }
-    
+
     public static bool IsPointOnCubic(VecD point, VecD start, VecD controlPoint1, VecD controlPoint2, VecD end)
     {
         return IsPointOnPath(point, (t) => CubicBezier(start, controlPoint1, controlPoint2, end, t));
     }
-    
+
     public static bool IsPointOnConic(VecD point, VecD start, VecD controlPoint, VecD end, float weight)
     {
         return IsPointOnPath(point, (t) => ConicBezier(start, controlPoint, end, weight, t));
     }
 
+    /// <summary>
+    ///     Finds value from 0 to 1 that represents the position of point on the segment.
+    /// </summary>
+    /// <param name="onVerb">Verb that represents the segment.</param>
+    /// <param name="point">Point that is on the segment.</param>
+    /// <returns>Value from 0 to 1 that represents the position of point on the segment.</returns>
+    public static float GetNormalizedSegmentPosition(Verb onVerb, VecF point)
+    {
+        if (onVerb.IsEmptyVerb()) return 0;
+
+        if (onVerb.VerbType == PathVerb.Cubic)
+        {
+            return (float)FindNormalizedSegmentPositionBruteForce(point, (t) =>
+                CubicBezier((VecD)onVerb.From, (VecD)(onVerb.ControlPoint1 ?? onVerb.From),
+                    (VecD)(onVerb.ControlPoint2 ?? onVerb.To), (VecD)onVerb.To, t));
+        }
+        
+        throw new NotImplementedException();
+    }
+
     private static VecD FindClosestPointBruteForce(VecD point, Func<double, VecD> func, double step = 0.001)
     {
         double minDistance = double.MaxValue;
@@ -146,7 +170,26 @@ internal static class VectorMath
 
         return closestPoint;
     }
-    
+
+    private static double FindNormalizedSegmentPositionBruteForce(VecF point, Func<double, VecD> func,
+        double step = 0.001)
+    {
+        double minDistance = float.MaxValue;
+        double closestT = 0;
+        for (double t = 0; t <= 1; t += step)
+        {
+            VecD currentPoint = func(t);
+            float distance = (point - currentPoint).Length;
+            if (distance < minDistance)
+            {
+                minDistance = distance;
+                closestT = t;
+            }
+        }
+
+        return closestT;
+    }
+
     private static bool IsPointOnPath(VecD point, Func<double, VecD> func, double step = 0.001)
     {
         for (double t = 0; t <= 1; t += step)

+ 36 - 5
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -157,7 +157,8 @@ public class VectorPathOverlay : Overlay
         if (point.Verb.ControlPoint1 != null)
         {
             var controlPoint1 = controlPointHandles[controlPointIndex];
-            controlPoint1.HitTestVisible = CapturedHandle == controlPoint1 || controlPoint1.Position != controlPoint1.ConnectedTo.Position;
+            controlPoint1.HitTestVisible = CapturedHandle == controlPoint1 ||
+                                           controlPoint1.Position != controlPoint1.ConnectedTo.Position;
             controlPoint1.Position = (VecD)point.Verb.ControlPoint1;
             if (controlPoint1.HitTestVisible)
             {
@@ -171,7 +172,8 @@ public class VectorPathOverlay : Overlay
         {
             var controlPoint2 = controlPointHandles[controlPointIndex];
             controlPoint2.Position = (VecD)point.Verb.ControlPoint2;
-            controlPoint2.HitTestVisible = CapturedHandle == controlPoint2 || controlPoint2.Position != controlPoint2.ConnectedTo.Position;
+            controlPoint2.HitTestVisible = CapturedHandle == controlPoint2 ||
+                                           controlPoint2.Position != controlPoint2.ConnectedTo.Position;
 
             if (controlPoint2.HitTestVisible)
             {
@@ -212,9 +214,36 @@ public class VectorPathOverlay : Overlay
             ConnectControlPointsToAnchors();
         }
 
+        UpdatePointsPositions();
         Refresh();
     }
 
+    private void UpdatePointsPositions()
+    {
+        int controlPointIndex = 0;
+        foreach (var subShape in editableVectorPath.SubShapes)
+        {
+            int anchorIndex = 0;
+            foreach (var point in subShape.Points)
+            {
+                if (point.Verb.VerbType == PathVerb.Cubic)
+                {
+                    var controlPoint1 = controlPointHandles[controlPointIndex];
+                    var controlPoint2 = controlPointHandles[controlPointIndex + 1];
+
+                    controlPoint1.Position = (VecD)point.Verb.ControlPoint1;
+                    controlPoint2.Position = (VecD)point.Verb.ControlPoint2;
+
+                    controlPointIndex += 2;
+                }
+
+                var anchor = anchorHandles[anchorIndex];
+                anchor.Position = (VecD)point.Position;
+                anchorIndex++;
+            }
+        }
+    }
+
     private void ConnectControlPointsToAnchors()
     {
         if (controlPointHandles.Count == 0)
@@ -387,6 +416,7 @@ public class VectorPathOverlay : Overlay
         {
             AddPointAt(closestPoint);
             AddToUndoCommand.Execute(Path);
+            args.Handled = true;
         }
     }
 
@@ -510,7 +540,7 @@ public class VectorPathOverlay : Overlay
 
             double length = VecD.Distance(handle.Position, controlPos);
             if (!direction.IsNaNOrInfinity())
-            { 
+            {
                 symmetricPos = handle.Position - direction * length;
             }
             else
@@ -553,7 +583,8 @@ public class VectorPathOverlay : Overlay
         {
             bool isDraggingFirst = controlPointHandles.IndexOf(controlPointHandle) % 2 == 0;
             bool constrainRatio = args.Modifiers.HasFlag(KeyModifiers.Control);
-            HandleContinousCubicDrag(targetPos, to, subShapeContainingIndex, localIndex, constrainRatio, !isDraggingFirst);
+            HandleContinousCubicDrag(targetPos, to, subShapeContainingIndex, localIndex, constrainRatio,
+                !isDraggingFirst);
         }
         else
         {
@@ -587,7 +618,7 @@ public class VectorPathOverlay : Overlay
         TryHighlightSnap(axisX, axisY);
         return snapped;
     }
-    
+
     private void OnControlPointPress(Handle source, OverlayPointerArgs args)
     {
         CaptureHandle(source);