/// <summary> /// Calculates the completion values of all the red anchors along the path. /// </summary> /// <param name="sliderPath"></param> /// <returns></returns> public static IEnumerable <double> GetRedAnchorCompletions(SliderPath sliderPath) { int start = 0; int end = 0; double totalLength = 0; var anchors = sliderPath.ControlPoints; for (int i = 0; i < anchors.Count; i++) { end++; if (i == anchors.Count - 1 || anchors[i] != anchors[i + 1]) { continue; } var cpSpan = anchors.GetRange(start, end - start); var subdivision = new BezierSubdivision(cpSpan); totalLength += subdivision.SubdividedApproximationLength(); yield return(totalLength / sliderPath.Distance); start = end; } }
public BezierSubdivision Prev() // Previous index at current level { var next = new BezierSubdivision(new List <Vector2>(Points), Level, Index - 1); next.ScaleRight(-1); next.Reverse(); return(next); }
public BezierSubdivision Next() // Next index at current level { var next = new BezierSubdivision(new List <Vector2>(Points), Level, Index + 1); next.ScaleLeft(2); next.Reverse(); return(next); }
public static IEnumerable <BezierSubdivision> ChopAnchorsLinear(List <Vector2> anchors) { for (int i = 1; i < anchors.Count; i++) { var subdivision = new BezierSubdivision(new List <Vector2> { anchors[i - 1], anchors[i] }); yield return(subdivision); } }
public double LengthToT(double length, double precision = 0.1, double tolerance = 0.25) // approximate bezier progress t for a desired path length, t can be > 1 { if (Length() == 0) { return(double.NaN); } if (length <= 0) { return(0); } BezierSubdivision baseSubdivision = null; LinkedListNode <BezierSubdivision> current = null; double l = 0; double lnext = 0; while (length > lnext) { current = current?.Next; if (current == null) { baseSubdivision = baseSubdivision == null ? this : baseSubdivision.Next(); var pathApproximation = new LinkedList <BezierSubdivision>(); pathApproximation.AddLast(baseSubdivision); Subdivide(ref pathApproximation, tolerance); current = pathApproximation.First; } l = lnext; lnext += current.Value.ApproximationLength(); } var curr = current.Value; while (curr.ApproximationLength() > precision) { curr.Children(out var left, out var right); lnext = l + left.ApproximationLength(); if (length > lnext) { curr = right; l = lnext; } else { curr = left; } } return((curr.Index + (length - l) / curr.ApproximationLength()) / (1 << curr.Level)); }
public BezierSubdivision Parent() // Parent subdivision (inverse of BezierSubdivide) { var parent = new BezierSubdivision(new List <Vector2>(Points), Level - 1, Index >> 1); if ((Index & 1) == 0) { parent.ScaleRight(2); } else { parent.ScaleLeft(-1); } return(parent); }
public void Children(out BezierSubdivision leftChild, out BezierSubdivision rightChild) // Child subdivisions (BezierSubdivide) { var left = new List <Vector2>(Points); var right = new List <Vector2>(Points); for (int j = 0; j < Order; j++) { for (int i = Order; i > j; i--) { left[i] = (left[i] + left[i - 1]) / 2; right[Order - i] = (right[Order - i] + right[Order - i + 1]) / 2; } } leftChild = new BezierSubdivision(left, Level + 1, Index << 1); rightChild = new BezierSubdivision(right, Level + 1, Index << 1 | 1); }
public static IEnumerable <BezierSubdivision> ChopAnchors(List <Vector2> anchors) { int start = 0; int end = 0; for (int i = 0; i < anchors.Count; i++) { end++; if (i != anchors.Count - 1 && anchors[i] != anchors[i + 1] || i == anchors.Count - 2) { continue; } var cpSpan = anchors.GetRange(start, end - start); var subdivision = new BezierSubdivision(cpSpan); yield return(subdivision); start = end; } }
public static double CalculatePathLength(List <Vector2> anchors) { double length = 0; int start = 0; int end = 0; for (int i = 0; i < anchors.Length(); ++i) { end++; if (i == anchors.Length() - 1 || anchors[i] == anchors[i + 1]) { List <Vector2> cpSpan = anchors.GetRange(start, end - start); length += new BezierSubdivision(cpSpan).SubdividedApproximationLength(); start = end; } } return(length); }
public static List <Vector2> MoveAnchorsToLength(List <Vector2> anchors, PathType pathType, double newLength, out PathType newPathType) { var newAnchors = new List <Vector2>(); var sliderPath = new SliderPath(pathType, anchors.ToArray(), newLength); var maxSliderPath = new SliderPath(pathType, anchors.ToArray()); if (newLength > maxSliderPath.Distance) { // Extend linearly switch (pathType) { case PathType.Bezier: newPathType = PathType.Bezier; newAnchors.AddRange(anchors); if (newAnchors.Count > 1 && newAnchors[newAnchors.Count - 2] == newAnchors[newAnchors.Count - 1]) { newAnchors[newAnchors.Count - 2] = newAnchors[newAnchors.Count - 2] + Vector2.UnitX; } newAnchors.Add(anchors.Last()); newAnchors.Add(sliderPath.PositionAt(1)); break; case PathType.Catmull: case PathType.PerfectCurve: // Convert to bezier and then extend newPathType = PathType.Bezier; newAnchors = BezierConverter.ConvertToBezier(sliderPath).ControlPoints; newAnchors.Add(anchors.Last()); newAnchors.Add(sliderPath.PositionAt(1)); break; default: newPathType = pathType; newAnchors.AddRange(anchors); newAnchors[newAnchors.Count - 1] = sliderPath.PositionAt(1); break; } } else { switch (sliderPath.Type) { case PathType.Catmull: case PathType.Bezier: newPathType = PathType.Bezier; // Convert in case the path type is catmull var convert = BezierConverter.ConvertToBezier(sliderPath).ControlPoints; // Find the last bezier segment and the pixel length at that part BezierSubdivision subdivision = null; double totalLength = 0; foreach (var bezierSubdivision in ChopAnchors(convert)) { subdivision = bezierSubdivision; var length = bezierSubdivision.SubdividedApproximationLength(); if (totalLength + length > newLength) { break; } totalLength += length; newAnchors.AddRange(bezierSubdivision.Points); } if (subdivision == null) { break; } // Find T for the remaining pixel length var t = subdivision.LengthToT(newLength - totalLength); // ScaleRight the BezierSubdivision so the anchors end at T subdivision.ScaleRight(t); // Add the scaled anchors newAnchors.AddRange(subdivision.Points); break; case PathType.PerfectCurve: newPathType = PathType.PerfectCurve; newAnchors.AddRange(anchors); newAnchors[1] = sliderPath.PositionAt(0.5); newAnchors[2] = sliderPath.PositionAt(1); break; default: newPathType = pathType; if (anchors.Count > 2) { // Find the section of the linear slider which contains the slider end totalLength = 0; foreach (var bezierSubdivision in ChopAnchorsLinear(anchors)) { newAnchors.Add(bezierSubdivision.Points[0]); var length = bezierSubdivision.Length(); if (totalLength + length > newLength) { break; } totalLength += length; } newAnchors.Add(sliderPath.PositionAt(1)); } else { newAnchors.AddRange(anchors); newAnchors[newAnchors.Count - 1] = sliderPath.PositionAt(1); } break; } } return(newAnchors); }