/**/ static bool AnyOffCurveSelected(Data.Segment segment) { return(Enumerable.Any(segment.OffCurves, point => point.IsSelected)); } static void BreakPathsSelection(Data.Layer layer) { var outPaths = new List <Data.Path>(); foreach (var path in Selection.FilterSelection(layer.Paths, invert: true)) { var segmentsList = new List <Data.Segment>(path.Segments); IEnumerable <Data.Segment> iter; if (!(path.IsOpen || AnyOffCurveSelected(segmentsList.First()) )) { int index; for (index = segmentsList.Count - 1; index >= 0; --index) { if (AnyOffCurveSelected(segmentsList[index])) { break; } } if (index <= 0) { // None selected, bring on the original path outPaths.Add(path); continue; } else { iter = Sequence.IterAt(segmentsList, index); } } else { iter = segmentsList; } Data.Path outPath = null; foreach (var segment in iter) { if (AnyOffCurveSelected(segment) || segment.OnCurve.Type == PointType.Move) { if (outPath != null) { outPaths.Add(outPath); } outPath = new Data.Path(); var point = segment.OnCurve.Clone(); point.IsSmooth = false; point.Type = PointType.Move; outPath.Points.Add(point); } else { outPath.Points.AddRange(segment.Points .Select(p => p.Clone()) .ToList()); } } outPaths.Add(outPath); } layer.Paths.Clear(); layer.Paths.AddRange(outPaths); } static void DeletePathsSelection(Data.Layer layer) { if (!TryMergeCurves(layer)) { var outPaths = new List <Data.Path>(); foreach (var path in layer.Paths) { var segments = path.Segments.ToList(); var forwardMove = false; for (int ix = segments.Count - 1; ix >= 0; --ix) { var segment = segments[ix]; var onCurve = segment.OnCurve; if (onCurve.IsSelected) { forwardMove = ix == 0 && onCurve.Type == PointType.Move; segment.Remove(nodeBias: true); } else if (AnyOffCurveSelected(segment)) { segment.ConvertTo(PointType.Line); } } if (path.Points.Count > 0) { if (forwardMove) { segments[0].ConvertTo(PointType.Move); } outPaths.Add(path); } } layer.Paths.Clear(); layer.Paths.AddRange(outPaths); } } static Vector2 SamplePoints(List <Vector2> samples, IList <Vector2> points, bool atStart) { var n = 20; // TODO: adaptive sample count var start = atStart ? 0 : 1; if (points.Count == 4) { for (int i = start; i < n; ++i) { samples.Add(BezierMath.Q(points, (float)i / (n - 1))); } } else { Debug.Assert(points.Count == 2); if (atStart) { samples.Add(points[0]); } samples.Add(points[1]); } return(Vector2.Normalize(atStart ? points[1] - points[0] : points[points.Count - 2] - points[points.Count - 1])); } static bool TryMergeCurves(Data.Layer layer) { var selection = layer.Selection; if (selection.Count == 1 && layer.Selection.First() is Data.Point point) { var path = point.Parent; var segments = path.Segments.ToList(); Data.Segment firstSegment = default; Data.Segment secondSegment = default; var segment = segments.Last(); foreach (var next in segments) { var onCurve = segment.OnCurve; if (onCurve.IsSelected) { if (!(path.IsOpen && path.Points.Last() == onCurve) && onCurve.Type == PointType.Curve || next.OnCurve.Type == PointType.Curve) { firstSegment = segment; secondSegment = next; } } segment = next; } if (!Equals(firstSegment, secondSegment)) { var samples = new List <Vector2>(); var leftTangent = SamplePoints(samples, firstSegment.PointsInclusive .Select(p => p.ToVector2()) .ToArray(), true); var rightTangent = SamplePoints(samples, secondSegment.PointsInclusive .Select(p => p.ToVector2()) .ToArray(), false); var fitPoints = BezierMath.FitCubic(samples, leftTangent, rightTangent, .01f); layer.ClearSelection(); var curveSegment = firstSegment; var otherSegment = secondSegment; if (curveSegment.OnCurve.Type != PointType.Curve) { (curveSegment, otherSegment) = (otherSegment, curveSegment); } var offCurves = curveSegment.OffCurves; offCurves[0].X = RoundToGrid(fitPoints[1].X); offCurves[0].Y = RoundToGrid(fitPoints[1].Y); offCurves[1].X = RoundToGrid(fitPoints[2].X); offCurves[1].Y = RoundToGrid(fitPoints[2].Y); var onCurve = firstSegment.OnCurve; onCurve.X = secondSegment.OnCurve.X; onCurve.Y = secondSegment.OnCurve.Y; curveSegment.OnCurve.IsSmooth = otherSegment.OnCurve.IsSmooth; otherSegment.Remove(); return(true); } } return(false); } /**/ static void ConstrainSmoothPoint(Data.Point p1, Data.Point p2, Data.Point p3, bool handleMovement) { if (p2.IsSelected) { if (p1.Type == PointType.None) { (p1, p3) = (p3, p1); } if (p1.Type != PointType.None) { VectorRotation(p3, p1.ToVector2(), p2.ToVector2()); } } else if (p1.IsSelected != p3.IsSelected) { if (p1.IsSelected) { (p1, p3) = (p3, p1); } if (p1.Type != PointType.None) { VectorProjection(p3, p1.ToVector2(), p2.ToVector2()); } else if (handleMovement) { VectorRotation(p1, p3.ToVector2(), p2.ToVector2()); } } }
static bool IsFlatAngle(Data.Point p0, Data.Point p1, Data.Point p2, float tol = 0.05f) { var p01 = p1.ToVector2() - p0.ToVector2(); var p12 = p2.ToVector2() - p1.ToVector2(); return(Math.Abs(Ops.AngleBetween(p01, p12)) <= tol); }
static void InterpolateCurve(Data.Point on1, Data.Point off1, Data.Point off2, Data.Point on2, float dx, float dy) { if (on2.IsSelected != on1.IsSelected) { var sign = on1.IsSelected ? -1 : 1; var sdelta = new Vector2(sign * dx, sign * dy); var ondelta = on2.ToVector2() - on1.ToVector2(); var factor = ondelta - sdelta; if (factor.X != 0 && factor.Y != 0) { factor = ondelta / factor; } if (!off1.IsSelected) { off1.X = RoundToGrid(on1.X + factor.X * (off1.X - on1.X)); off1.Y = RoundToGrid(on1.Y + factor.Y * (off1.Y - on1.Y)); } if (!off2.IsSelected) { off2.X = RoundToGrid(on1.X + factor.X * (off2.X - on1.X - sdelta.X)); off2.Y = RoundToGrid(on1.Y + factor.Y * (off2.Y - on1.Y - sdelta.Y)); } } }
static void ConstrainStaticHandles(Data.Point p1, Data.Point p2, Data.Point p3, float dx, float dy) { if (p2.IsSelected && p2.Type != PointType.Move) { if (p1.IsSelected || p3.IsSelected) { if (p1.IsSelected) { (p1, p3) = (p3, p1); } if (!p1.IsSelected && p3.Type == PointType.None) { VectorRotation(p3, p1.ToVector2(), p2.ToVector2()); } } else { if (p2.IsSmooth) { VectorProjection(p2, p1.ToVector2(), p3.ToVector2()); } } } else if (p1.IsSelected != p3.IsSelected) { if (p1.IsSelected) { (p1, p3) = (p3, p1); } if (p3.Type == PointType.None) { if (p2.IsSmooth && p2.Type != PointType.Move) { VectorProjection(p3, p1.ToVector2(), p2.ToVector2()); } else { // XXX: now that we're rounding, this doesn't work so well anymore var rvec = new Vector2(p3.X - dx, p3.Y - dy); VectorProjection(p3, p2.ToVector2(), rvec); } } } }
static void VectorRotation(Data.Point point, Vector2 a, Vector2 b) { var ab = b - a; var ab_len = ab.Length(); if (ab_len != 0) { var p = point.ToVector2(); var pb_len = (b - p).Length(); var t = (ab_len + pb_len) / ab_len; point.X = RoundToGrid(a.X + t * ab.X); point.Y = RoundToGrid(a.Y + t * ab.Y); } }
static void VectorProjection(Data.Point point, Vector2 a, Vector2 b) { var ab = b - a; var l2 = ab.LengthSquared(); if (l2 != 0) { var ap = point.ToVector2() - a; var t = Vector2.Dot(ap, ab) / l2; point.X = RoundToGrid(a.X + t * ab.X); point.Y = RoundToGrid(a.Y + t * ab.Y); } else { point.X = RoundToGrid(a.X); point.Y = RoundToGrid(a.Y); } }