19 using System.Collections.Generic;
87 public abstract IEnumerable<Segment>
Linearise(
Point? previousPoint,
double resolution);
102 public abstract IEnumerable<Segment>
Transform(Func<Point, Point> transformationFunction);
105 internal class MoveSegment :
Segment
109 public MoveSegment(
Point p)
111 this.Points =
new Point[] { p };
114 public MoveSegment(
double x,
double y)
119 public override Segment Clone()
121 return new MoveSegment(this.Point);
124 public override double Measure(Point previousPoint)
129 public override Point GetPointAt(Point previousPoint,
double position)
131 throw new InvalidOperationException();
134 public override Point GetTangentAt(Point previousPoint,
double position)
136 throw new InvalidOperationException();
139 public override IEnumerable<Segment> Linearise(Point? previousPoint,
double resolution)
141 yield
return new MoveSegment(this.Point);
144 public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint,
double resolution)
146 throw new InvalidOperationException();
149 public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
151 yield
return new MoveSegment(transformationFunction(this.Point));
155 internal class LineSegment : Segment
159 public LineSegment(Point p)
164 public LineSegment(
double x,
double y)
169 public override Segment Clone()
171 return new LineSegment(this.Point);
174 private double cachedLength =
double.NaN;
176 public override double Measure(Point previousPoint)
178 if (
double.IsNaN(cachedLength))
180 cachedLength = Math.Sqrt((this.
Point.
X - previousPoint.X) * (
this.Point.X - previousPoint.X) + (
this.Point.Y - previousPoint.Y) * (
this.Point.Y - previousPoint.Y));
186 public override Point GetPointAt(Point previousPoint,
double position)
188 return new Point(previousPoint.X * (1 - position) +
this.Point.X * position, previousPoint.Y * (1 - position) +
this.Point.Y * position);
191 public override Point GetTangentAt(Point previousPoint,
double position)
196 public override IEnumerable<Segment> Linearise(Point? previousPoint,
double resolution)
198 yield
return new LineSegment(this.Point);
201 public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint,
double resolution)
203 yield
return this.GetTangentAt(previousPoint.Value, 1);
206 public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
208 yield
return new LineSegment(transformationFunction(this.Point));
212 internal class CloseSegment : Segment
216 public CloseSegment() { }
218 public override Segment Clone()
220 return new CloseSegment();
223 public override double Measure(Point previousPoint)
228 public override Point GetPointAt(Point previousPoint,
double position)
230 throw new InvalidOperationException();
233 public override Point GetTangentAt(Point previousPoint,
double position)
235 throw new InvalidOperationException();
238 public override IEnumerable<Segment> Linearise(Point? previousPoint,
double resolution)
240 yield
return new CloseSegment();
243 public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint,
double resolution)
245 throw new InvalidOperationException();
248 public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
250 yield
return new CloseSegment();
254 internal class CubicBezierSegment : Segment
257 public CubicBezierSegment(
double x1,
double y1,
double x2,
double y2,
double x3,
double y3)
262 public CubicBezierSegment(Point p1, Point p2, Point p3)
267 public override Segment Clone()
269 return new CubicBezierSegment(Points[0], Points[1], Points[2]);
272 private double cachedLength =
double.NaN;
273 private int cachedSegments = -1;
275 public override double Measure(Point previousPoint)
277 if (
double.IsNaN(cachedLength))
280 double prevLength = 0;
281 double currLength = Measure(previousPoint, segments);
283 while (currLength > 0.00001 && Math.Abs(currLength - prevLength) / currLength > 0.0001)
286 prevLength = currLength;
287 currLength = Measure(previousPoint, segments);
290 cachedSegments = segments;
292 cachedLength = currLength;
298 public Point GetBezierPointAt(Point previousPoint,
double position)
300 if (position <= 1 && position >= 0)
303 this.Points[2].X * position * position * position + 3 * this.Points[1].X * position * position * (1 - position) + 3 * this.Points[0].X * position * (1 - position) * (1 - position) + previousPoint.X * (1 - position) * (1 - position) * (1 - position),
304 this.Points[2].Y * position * position * position + 3 *
this.Points[1].Y * position * position * (1 - position) + 3 *
this.Points[0].Y * position * (1 - position) * (1 - position) + previousPoint.Y * (1 - position) * (1 - position) * (1 - position)
307 else if (position > 1)
309 Point tangent = GetBezierTangentAt(previousPoint, 1);
311 double excessLength = (position - 1) * this.Measure(previousPoint);
313 return new Point(this.
Point.
X + tangent.X * excessLength,
this.Point.Y + tangent.Y * excessLength);
317 Point tangent = GetBezierTangentAt(previousPoint, 0);
319 return new Point(previousPoint.X + tangent.X * position *
this.Measure(previousPoint), previousPoint.Y + tangent.Y * position *
this.Measure(previousPoint));
323 public override Point GetPointAt(Point previousPoint,
double position)
325 double t = GetTFromPosition(previousPoint, position);
326 return this.GetBezierPointAt(previousPoint, t);
329 public override Point GetTangentAt(Point previousPoint,
double position)
331 double t = GetTFromPosition(previousPoint, position);
332 return this.GetBezierTangentAt(previousPoint, t);
335 private double Measure(Point startPoint,
int segments)
337 double delta = 1.0 / segments;
341 for (
int i = 1; i < segments; i++)
343 Point p1 = GetBezierPointAt(startPoint, delta * (i - 1));
344 Point p2 = GetBezierPointAt(startPoint, delta * i);
346 tbr += Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
352 private double Measure(Point startPoint,
int segments,
double maxT)
354 double delta = maxT / segments;
358 for (
int i = 1; i < segments; i++)
360 Point p1 = GetBezierPointAt(startPoint, delta * (i - 1));
361 Point p2 = GetBezierPointAt(startPoint, delta * i);
363 tbr += Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
369 private Point GetBezierTangentAt(Point previousPoint,
double position)
371 if (position <= 1 && position >= 0)
374 3 * this.Points[2].X * position * position +
375 3 * this.Points[1].X * position * (2 - 3 * position) +
376 3 * this.Points[0].X * (3 * position * position - 4 * position + 1) +
377 -3 * previousPoint.X * (1 - position) * (1 - position),
379 3 *
this.Points[2].Y * position * position +
380 3 *
this.Points[1].Y * position * (2 - 3 * position) +
381 3 *
this.Points[0].Y * (3 * position * position - 4 * position + 1) +
382 -3 * previousPoint.Y * (1 - position) * (1 - position)).
Normalize();
384 else if (position > 1)
386 return GetBezierTangentAt(previousPoint, 1);
390 return GetBezierTangentAt(previousPoint, 0);
394 private double GetTFromPosition(Point previousPoint,
double position)
396 if (position <= 0 || position >= 1)
402 double length = this.Measure(previousPoint);
404 double lowerBound = 0;
405 double upperBound = 0.5;
408 double upperPos = Measure(previousPoint, (
int)Math.Ceiling(
this.cachedSegments * upperBound), upperBound) / length;
410 if (upperPos < position)
412 lowerBound = upperBound;
419 while (Math.Min(upperPos - position, position - lowerPos) > 0.001)
421 double mid = (lowerBound + upperBound) * 0.5;
422 double midPos = Measure(previousPoint, (
int)Math.Ceiling(
this.cachedSegments * mid), mid) / length;
424 if (midPos > position)
436 return lowerBound + (position - lowerPos) / (upperPos - lowerPos) * (upperBound - lowerBound);
440 public override IEnumerable<Segment> Linearise(Point? previousPoint,
double resolution)
442 double length = this.Measure(previousPoint.Value);
443 int segmentCount = (int)Math.Ceiling(length / resolution);
445 for (
int i = 0; i < segmentCount; i++)
447 yield
return new LineSegment(this.GetPointAt(previousPoint.Value, (
double)(i + 1) / segmentCount));
451 public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint,
double resolution)
453 double length = this.Measure(previousPoint.Value);
454 int segmentCount = (int)Math.Ceiling(length / resolution);
456 for (
int i = 0; i < segmentCount; i++)
458 yield
return this.GetTangentAt(previousPoint.Value, (
double)(i + 1) / segmentCount);
462 public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
464 yield
return new CubicBezierSegment(transformationFunction(this.Points[0]), transformationFunction(this.Points[1]), transformationFunction(this.Points[2]));
468 internal class ArcSegment : Segment
472 public Segment[] ToBezierSegments()
474 List<Segment> tbr =
new List<Segment>();
476 if (EndAngle > StartAngle)
478 if (EndAngle - StartAngle <= Math.PI / 2)
480 tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, StartAngle, EndAngle,
true));
484 int count = (int)Math.Ceiling(2 * (EndAngle - StartAngle) / Math.PI);
485 double angle = StartAngle;
487 for (
int i = 0; i < count; i++)
489 tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, angle, angle + (EndAngle - StartAngle) / count, i == 0));
490 angle += (EndAngle - StartAngle) / count;
494 else if (EndAngle < StartAngle)
496 Point startPoint =
new Point(Points[0].X + Radius * Math.Cos(EndAngle), Points[0].Y + Radius * Math.Sin(EndAngle));
497 if (StartAngle - EndAngle <= Math.PI / 2)
499 tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, EndAngle, StartAngle,
true));
503 int count = (int)Math.Ceiling(2 * (StartAngle - EndAngle) / Math.PI);
504 double angle = EndAngle;
506 for (
int i = 0; i < count; i++)
508 tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, angle, angle + (StartAngle - EndAngle) / count, i == 0));
509 angle += (StartAngle - EndAngle) / count;
513 return ReverseSegments(tbr, startPoint).ToArray();
516 return tbr.ToArray();
519 private static Segment[] ReverseSegments(IReadOnlyList<Segment> originalSegments, Point startPoint)
521 List<Segment> tbr =
new List<Segment>(originalSegments.Count);
523 for (
int i = originalSegments.Count - 1; i >= 0; i--)
525 switch (originalSegments[i].Type)
530 tbr.Add(
new LineSegment(originalSegments[i - 1].Point));
534 tbr.Add(
new LineSegment(startPoint));
538 CubicBezierSegment originalSegment = (CubicBezierSegment)originalSegments[i];
541 tbr.Add(
new CubicBezierSegment(originalSegment.Points[1], originalSegment.Points[0], originalSegments[i - 1].Point));
545 tbr.Add(
new CubicBezierSegment(originalSegment.Points[1], originalSegment.Points[0], startPoint));
551 return tbr.ToArray();
554 const double k = 0.55191496;
556 private static Segment[] GetBezierSegment(
double cX,
double cY,
double radius,
double startAngle,
double endAngle,
bool firstArc)
558 double phi = Math.PI / 4;
560 double x1 = radius * Math.Cos(phi);
561 double y1 = radius * Math.Sin(phi);
566 double x3 = x1 + k * radius * Math.Sin(phi);
567 double y3 = y1 - k * radius * Math.Cos(phi);
569 double x2 = x4 + k * radius * Math.Sin(phi);
570 double y2 = y4 + k * radius * Math.Cos(phi);
572 double u = 2 * (endAngle - startAngle) / Math.PI;
574 double fx2 = (1 - u) * x4 + u * x2;
575 double fy2 = (1 - u) * y4 + u * y2;
577 double fx3 = (1 - u) * fx2 + u * ((1 - u) * x2 + u * x3);
578 double fy3 = (1 - u) * fy2 + u * ((1 - u) * y2 + u * y3);
580 double rX1 = cX + radius * Math.Cos(startAngle);
581 double rY1 = cY + radius * Math.Sin(startAngle);
583 double rX4 = cX + radius * Math.Cos(endAngle);
584 double rY4 = cY + radius * Math.Sin(endAngle);
586 Point rot2 = Utils.RotatePoint(
new Point(fx2, fy2), phi + startAngle);
587 Point rot3 = Utils.RotatePoint(
new Point(fx3, fy3), phi + startAngle);
589 List<Segment> tbr =
new List<Segment>();
593 tbr.Add(
new LineSegment(rX1, rY1));
596 tbr.Add(
new CubicBezierSegment(cX + rot2.X, cY + rot2.Y, cX + rot3.X, cY + rot3.Y, rX4, rY4));
598 return tbr.ToArray();
600 public double Radius {
get; }
601 public double StartAngle {
get; }
602 public double EndAngle {
get; }
604 public ArcSegment(Point center,
double radius,
double startAngle,
double endAngle)
606 this.
Points =
new Point[] { center };
607 this.Radius = radius;
608 this.StartAngle = startAngle;
609 this.EndAngle = endAngle;
612 public ArcSegment(
double centerX,
double centerY,
double radius,
double startAngle,
double endAngle)
614 this.
Points =
new Point[] {
new Point(centerX, centerY) };
615 this.Radius = radius;
616 this.StartAngle = startAngle;
617 this.EndAngle = endAngle;
620 public override Segment Clone()
622 return new ArcSegment(Point.X, Point.Y, Radius, StartAngle, EndAngle);
625 public override Point Point
629 return new Point(this.Points[0].X + Math.Cos(EndAngle) * Radius,
this.Points[0].Y + Math.Sin(EndAngle) * Radius);
634 private double cachedLength =
double.NaN;
636 public override double Measure(Point previousPoint)
638 if (
double.IsNaN(cachedLength))
640 Point arcStartPoint =
new Point(this.Points[0].X + Math.Cos(StartAngle) * Radius,
this.Points[0].Y + Math.Sin(StartAngle) * Radius);
642 cachedLength = Radius * Math.Abs(EndAngle - StartAngle) + Math.Sqrt((arcStartPoint.X - previousPoint.X) * (arcStartPoint.X - previousPoint.X) + (arcStartPoint.Y - previousPoint.Y) * (arcStartPoint.Y - previousPoint.Y));
648 public override Point GetPointAt(Point previousPoint,
double position)
650 double totalLength = this.Measure(previousPoint);
651 double arcLength = Radius * Math.Abs(EndAngle - StartAngle);
653 double preArc = (totalLength - arcLength) / totalLength;
655 if (position < preArc)
659 double relPos = position / preArc;
660 Point arcStartPoint =
new Point(this.Points[0].X + Math.Cos(StartAngle) * Radius,
this.Points[0].Y + Math.Sin(StartAngle) * Radius);
662 return new Point(previousPoint.X * (1 - relPos) + arcStartPoint.X * relPos, previousPoint.Y * (1 - relPos) + arcStartPoint.Y * relPos);
666 Point arcStartPoint =
new Point(this.Points[0].X + Math.Cos(StartAngle) * Radius,
this.Points[0].Y + Math.Sin(StartAngle) * Radius);
667 Point tangent = GetTangentAt(previousPoint, 0);
668 double excessLength = position * this.Measure(previousPoint);
669 return new Point(arcStartPoint.X + tangent.X * excessLength, arcStartPoint.Y + tangent.Y * excessLength);
674 double relPos = position - preArc / (1 - preArc);
678 double angle = StartAngle * (1 - relPos) + EndAngle * relPos;
679 return new Point(this.Points[0].X + Radius * Math.Cos(angle),
this.Points[0].Y + Radius * Math.Sin(angle));
683 Point arcEndPoint = this.Point;
684 Point tangent = GetTangentAt(previousPoint, 1);
685 double excessLength = (position - 1) * this.Measure(previousPoint);
686 return new Point(arcEndPoint.X + tangent.X * excessLength, arcEndPoint.Y + tangent.Y * excessLength);
691 public override Point GetTangentAt(Point previousPoint,
double position)
693 double totalLength = this.Measure(previousPoint);
694 double arcLength = Radius * Math.Abs(EndAngle - StartAngle);
696 double preArc = (totalLength - arcLength) / totalLength;
698 if (position < preArc)
700 Point arcStartPoint =
new Point(this.Points[0].X + Math.Cos(StartAngle) * Radius,
this.Points[0].Y + Math.Sin(StartAngle) * Radius);
701 Point tang =
new Point((arcStartPoint.X - previousPoint.X) * Math.Sign(EndAngle - StartAngle), (arcStartPoint.Y - previousPoint.Y) * Math.Sign(EndAngle - StartAngle)).Normalize();
703 if (tang.Modulus() > 0.001)
705 return tang.Normalize();
709 return this.GetTangentAt(previousPoint, 0);
714 double relPos = position - preArc / (1 - preArc);
718 double angle = StartAngle * (1 - relPos) + EndAngle * relPos;
719 return new Point(-Math.Sin(angle) * Math.Sign(EndAngle - StartAngle), Math.Cos(angle) * Math.Sign(EndAngle - StartAngle));
723 return new Point(-Math.Sin(EndAngle) * Math.Sign(EndAngle - StartAngle), Math.Cos(EndAngle) * Math.Sign(EndAngle - StartAngle));
729 public override IEnumerable<Segment> Linearise(Point? previousPoint,
double resolution)
731 double length = this.Measure(previousPoint.Value);
732 int segmentCount = (int)Math.Ceiling(length / resolution);
734 for (
int i = 0; i < segmentCount; i++)
736 yield
return new LineSegment(this.GetPointAt(previousPoint.Value, (
double)(i + 1) / segmentCount));
739 public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint,
double resolution)
741 double length = this.Measure(previousPoint.Value);
742 int segmentCount = (int)Math.Ceiling(length / resolution);
744 for (
int i = 0; i < segmentCount; i++)
746 yield
return this.GetTangentAt(previousPoint.Value, (
double)(i + 1) / segmentCount);
750 public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
752 foreach (Segment seg
in this.ToBezierSegments())
754 foreach (Segment seg2
in seg.Transform(transformationFunction))