VectSharp  2.2.1
A light library for C# vector graphics
Segment.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 
22 namespace VectSharp
23 {
24 
25  /// <summary>
26  /// Represents a segment as part of a <see cref="GraphicsPath"/>.
27  /// </summary>
28  public abstract class Segment
29  {
30 
31  /// <summary>
32  /// The type of the <see cref="Segment"/>.
33  /// </summary>
34  public abstract SegmentType Type { get; }
35 
36  /// <summary>
37  /// The points used to define the <see cref="Segment"/>.
38  /// </summary>
39  public Point[] Points { get; protected set; }
40 
41  /// <summary>
42  /// The end point of the <see cref="Segment"/>.
43  /// </summary>
44  public virtual Point Point
45  {
46  get
47  {
48  return Points[Points.Length - 1];
49  }
50  }
51 
52  /// <summary>
53  /// Creates a copy of the <see cref="Segment"/>.
54  /// </summary>
55  /// <returns>A copy of the <see cref="Segment"/>.</returns>
56  public abstract Segment Clone();
57 
58  /// <summary>
59  /// Computes the length of the <see cref="Segment"/>.
60  /// </summary>
61  /// <param name="previousPoint">The point from which the <see cref="Segment"/> starts (i.e. the endpoint of the previous <see cref="Segment"/>).</param>
62  /// <returns>The length of the segment.</returns>
63  public abstract double Measure(Point previousPoint);
64 
65  /// <summary>
66  /// Gets the point on the <see cref="Segment"/> at the specified (relative) <paramref name="position"/>).
67  /// </summary>
68  /// <param name="previousPoint">The point from which the <see cref="Segment"/> starts (i.e. the endpoint of the previous <see cref="Segment"/>).</param>
69  /// <param name="position">The relative position on the <see cref="Segment"/> (0 is the start of the <see cref="Segment"/>, 1 is the end of the <see cref="Segment"/>).</param>
70  /// <returns>The point at the specified position.</returns>
71  public abstract Point GetPointAt(Point previousPoint, double position);
72 
73  /// <summary>
74  /// Gets the tangent to the <see cref="Segment"/> at the specified (relative) <paramref name="position"/>).
75  /// </summary>
76  /// <param name="previousPoint">The point from which the <see cref="Segment"/> starts (i.e. the endpoint of the previous <see cref="Segment"/>).</param>
77  /// <param name="position">The relative position on the <see cref="Segment"/> (0 is the start of the <see cref="Segment"/>, 1 is the end of the <see cref="Segment"/>).</param>
78  /// <returns>The tangent to the point at the specified position.</returns>
79  public abstract Point GetTangentAt(Point previousPoint, double position);
80 
81  /// <summary>
82  /// Transform the segment into a series of linear segments. Segments that are already linear are not changed.
83  /// </summary>
84  /// <param name="previousPoint">The point from which the <see cref="Segment"/> starts (i.e. the endpoint of the previous <see cref="Segment"/>).</param>
85  /// <param name="resolution">The absolute length between successive samples in curve segments.</param>
86  /// <returns>A collection of linear segments that approximate the current segment.</returns>
87  public abstract IEnumerable<Segment> Linearise(Point? previousPoint, double resolution);
88 
89  /// <summary>
90  /// Gets the tanget at the points at which the segment would be linearised.
91  /// </summary>
92  /// <param name="previousPoint">The point from which the <see cref="Segment"/> starts (i.e. the endpoint of the previous <see cref="Segment"/>).</param>
93  /// <param name="resolution">The absolute length between successive samples in curve segments.</param>
94  /// <returns>A collection of tangents at the points in which the segment would be linearised.</returns>
95  public abstract IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution);
96 
97  /// <summary>
98  /// Applies an arbitrary transformation to all of the points of the <see cref="Segment"/>.
99  /// </summary>
100  /// <param name="transformationFunction">An arbitrary transformation function.</param>
101  /// <returns>A collection of <see cref="Segment"/>s that have been transformed according to the <paramref name="transformationFunction"/>.</returns>
102  public abstract IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction);
103  }
104 
105  internal class MoveSegment : Segment
106  {
107  public override SegmentType Type => SegmentType.Move;
108 
109  public MoveSegment(Point p)
110  {
111  this.Points = new Point[] { p };
112  }
113 
114  public MoveSegment(double x, double y)
115  {
116  this.Points = new Point[] { new Point(x, y) };
117  }
118 
119  public override Segment Clone()
120  {
121  return new MoveSegment(this.Point);
122  }
123 
124  public override double Measure(Point previousPoint)
125  {
126  return 0;
127  }
128 
129  public override Point GetPointAt(Point previousPoint, double position)
130  {
131  throw new InvalidOperationException();
132  }
133 
134  public override Point GetTangentAt(Point previousPoint, double position)
135  {
136  throw new InvalidOperationException();
137  }
138 
139  public override IEnumerable<Segment> Linearise(Point? previousPoint, double resolution)
140  {
141  yield return new MoveSegment(this.Point);
142  }
143 
144  public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution)
145  {
146  throw new InvalidOperationException();
147  }
148 
149  public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
150  {
151  yield return new MoveSegment(transformationFunction(this.Point));
152  }
153  }
154 
155  internal class LineSegment : Segment
156  {
157  public override SegmentType Type => SegmentType.Line;
158 
159  public LineSegment(Point p)
160  {
161  this.Points = new Point[] { p };
162  }
163 
164  public LineSegment(double x, double y)
165  {
166  this.Points = new Point[] { new Point(x, y) };
167  }
168 
169  public override Segment Clone()
170  {
171  return new LineSegment(this.Point);
172  }
173 
174  private double cachedLength = double.NaN;
175 
176  public override double Measure(Point previousPoint)
177  {
178  if (double.IsNaN(cachedLength))
179  {
180  cachedLength = Math.Sqrt((this.Point.X - previousPoint.X) * (this.Point.X - previousPoint.X) + (this.Point.Y - previousPoint.Y) * (this.Point.Y - previousPoint.Y));
181  }
182 
183  return cachedLength;
184  }
185 
186  public override Point GetPointAt(Point previousPoint, double position)
187  {
188  return new Point(previousPoint.X * (1 - position) + this.Point.X * position, previousPoint.Y * (1 - position) + this.Point.Y * position);
189  }
190 
191  public override Point GetTangentAt(Point previousPoint, double position)
192  {
193  return new Point(this.Point.X - previousPoint.X, this.Point.Y - previousPoint.Y).Normalize();
194  }
195 
196  public override IEnumerable<Segment> Linearise(Point? previousPoint, double resolution)
197  {
198  yield return new LineSegment(this.Point);
199  }
200 
201  public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution)
202  {
203  yield return this.GetTangentAt(previousPoint.Value, 1);
204  }
205 
206  public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
207  {
208  yield return new LineSegment(transformationFunction(this.Point));
209  }
210  }
211 
212  internal class CloseSegment : Segment
213  {
214  public override SegmentType Type => SegmentType.Close;
215 
216  public CloseSegment() { }
217 
218  public override Segment Clone()
219  {
220  return new CloseSegment();
221  }
222 
223  public override double Measure(Point previousPoint)
224  {
225  return 0;
226  }
227 
228  public override Point GetPointAt(Point previousPoint, double position)
229  {
230  throw new InvalidOperationException();
231  }
232 
233  public override Point GetTangentAt(Point previousPoint, double position)
234  {
235  throw new InvalidOperationException();
236  }
237 
238  public override IEnumerable<Segment> Linearise(Point? previousPoint, double resolution)
239  {
240  yield return new CloseSegment();
241  }
242 
243  public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution)
244  {
245  throw new InvalidOperationException();
246  }
247 
248  public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
249  {
250  yield return new CloseSegment();
251  }
252  }
253 
254  internal class CubicBezierSegment : Segment
255  {
256  public override SegmentType Type => SegmentType.CubicBezier;
257  public CubicBezierSegment(double x1, double y1, double x2, double y2, double x3, double y3)
258  {
259  Points = new Point[] { new Point(x1, y1), new Point(x2, y2), new Point(x3, y3) };
260  }
261 
262  public CubicBezierSegment(Point p1, Point p2, Point p3)
263  {
264  Points = new Point[] { p1, p2, p3 };
265  }
266 
267  public override Segment Clone()
268  {
269  return new CubicBezierSegment(Points[0], Points[1], Points[2]);
270  }
271 
272  private double cachedLength = double.NaN;
273  private int cachedSegments = -1;
274 
275  public override double Measure(Point previousPoint)
276  {
277  if (double.IsNaN(cachedLength))
278  {
279  int segments = 16;
280  double prevLength = 0;
281  double currLength = Measure(previousPoint, segments);
282 
283  while (currLength > 0.00001 && Math.Abs(currLength - prevLength) / currLength > 0.0001)
284  {
285  segments *= 2;
286  prevLength = currLength;
287  currLength = Measure(previousPoint, segments);
288  }
289 
290  cachedSegments = segments;
291 
292  cachedLength = currLength;
293  }
294 
295  return cachedLength;
296  }
297 
298  public Point GetBezierPointAt(Point previousPoint, double position)
299  {
300  if (position <= 1 && position >= 0)
301  {
302  return new Point(
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)
305  );
306  }
307  else if (position > 1)
308  {
309  Point tangent = GetBezierTangentAt(previousPoint, 1);
310 
311  double excessLength = (position - 1) * this.Measure(previousPoint);
312 
313  return new Point(this.Point.X + tangent.X * excessLength, this.Point.Y + tangent.Y * excessLength);
314  }
315  else
316  {
317  Point tangent = GetBezierTangentAt(previousPoint, 0);
318 
319  return new Point(previousPoint.X + tangent.X * position * this.Measure(previousPoint), previousPoint.Y + tangent.Y * position * this.Measure(previousPoint));
320  }
321  }
322 
323  public override Point GetPointAt(Point previousPoint, double position)
324  {
325  double t = GetTFromPosition(previousPoint, position);
326  return this.GetBezierPointAt(previousPoint, t);
327  }
328 
329  public override Point GetTangentAt(Point previousPoint, double position)
330  {
331  double t = GetTFromPosition(previousPoint, position);
332  return this.GetBezierTangentAt(previousPoint, t);
333  }
334 
335  private double Measure(Point startPoint, int segments)
336  {
337  double delta = 1.0 / segments;
338 
339  double tbr = 0;
340 
341  for (int i = 1; i < segments; i++)
342  {
343  Point p1 = GetBezierPointAt(startPoint, delta * (i - 1));
344  Point p2 = GetBezierPointAt(startPoint, delta * i);
345 
346  tbr += Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
347  }
348 
349  return tbr;
350  }
351 
352  private double Measure(Point startPoint, int segments, double maxT)
353  {
354  double delta = maxT / segments;
355 
356  double tbr = 0;
357 
358  for (int i = 1; i < segments; i++)
359  {
360  Point p1 = GetBezierPointAt(startPoint, delta * (i - 1));
361  Point p2 = GetBezierPointAt(startPoint, delta * i);
362 
363  tbr += Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
364  }
365 
366  return tbr;
367  }
368 
369  private Point GetBezierTangentAt(Point previousPoint, double position)
370  {
371  if (position <= 1 && position >= 0)
372  {
373  return new Point(
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),
378 
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();
383  }
384  else if (position > 1)
385  {
386  return GetBezierTangentAt(previousPoint, 1);
387  }
388  else
389  {
390  return GetBezierTangentAt(previousPoint, 0);
391  }
392  }
393 
394  private double GetTFromPosition(Point previousPoint, double position)
395  {
396  if (position <= 0 || position >= 1)
397  {
398  return position;
399  }
400  else
401  {
402  double length = this.Measure(previousPoint);
403 
404  double lowerBound = 0;
405  double upperBound = 0.5;
406 
407  double lowerPos = 0;
408  double upperPos = Measure(previousPoint, (int)Math.Ceiling(this.cachedSegments * upperBound), upperBound) / length;
409 
410  if (upperPos < position)
411  {
412  lowerBound = upperBound;
413  lowerPos = upperPos;
414 
415  upperBound = 1;
416  upperPos = 1;
417  }
418 
419  while (Math.Min(upperPos - position, position - lowerPos) > 0.001)
420  {
421  double mid = (lowerBound + upperBound) * 0.5;
422  double midPos = Measure(previousPoint, (int)Math.Ceiling(this.cachedSegments * mid), mid) / length;
423 
424  if (midPos > position)
425  {
426  upperBound = mid;
427  upperPos = midPos;
428  }
429  else
430  {
431  lowerBound = mid;
432  lowerPos = midPos;
433  }
434  }
435 
436  return lowerBound + (position - lowerPos) / (upperPos - lowerPos) * (upperBound - lowerBound);
437  }
438  }
439 
440  public override IEnumerable<Segment> Linearise(Point? previousPoint, double resolution)
441  {
442  double length = this.Measure(previousPoint.Value);
443  int segmentCount = (int)Math.Ceiling(length / resolution);
444 
445  for (int i = 0; i < segmentCount; i++)
446  {
447  yield return new LineSegment(this.GetPointAt(previousPoint.Value, (double)(i + 1) / segmentCount));
448  }
449  }
450 
451  public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution)
452  {
453  double length = this.Measure(previousPoint.Value);
454  int segmentCount = (int)Math.Ceiling(length / resolution);
455 
456  for (int i = 0; i < segmentCount; i++)
457  {
458  yield return this.GetTangentAt(previousPoint.Value, (double)(i + 1) / segmentCount);
459  }
460  }
461 
462  public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
463  {
464  yield return new CubicBezierSegment(transformationFunction(this.Points[0]), transformationFunction(this.Points[1]), transformationFunction(this.Points[2]));
465  }
466  }
467 
468  internal class ArcSegment : Segment
469  {
470  public override SegmentType Type => SegmentType.Arc;
471 
472  public Segment[] ToBezierSegments()
473  {
474  List<Segment> tbr = new List<Segment>();
475 
476  if (EndAngle > StartAngle)
477  {
478  if (EndAngle - StartAngle <= Math.PI / 2)
479  {
480  tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, StartAngle, EndAngle, true));
481  }
482  else
483  {
484  int count = (int)Math.Ceiling(2 * (EndAngle - StartAngle) / Math.PI);
485  double angle = StartAngle;
486 
487  for (int i = 0; i < count; i++)
488  {
489  tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, angle, angle + (EndAngle - StartAngle) / count, i == 0));
490  angle += (EndAngle - StartAngle) / count;
491  }
492  }
493  }
494  else if (EndAngle < StartAngle)
495  {
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)
498  {
499  tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, EndAngle, StartAngle, true));
500  }
501  else
502  {
503  int count = (int)Math.Ceiling(2 * (StartAngle - EndAngle) / Math.PI);
504  double angle = EndAngle;
505 
506  for (int i = 0; i < count; i++)
507  {
508  tbr.AddRange(GetBezierSegment(Points[0].X, Points[0].Y, Radius, angle, angle + (StartAngle - EndAngle) / count, i == 0));
509  angle += (StartAngle - EndAngle) / count;
510  }
511  }
512 
513  return ReverseSegments(tbr, startPoint).ToArray();
514  }
515 
516  return tbr.ToArray();
517  }
518 
519  private static Segment[] ReverseSegments(IReadOnlyList<Segment> originalSegments, Point startPoint)
520  {
521  List<Segment> tbr = new List<Segment>(originalSegments.Count);
522 
523  for (int i = originalSegments.Count - 1; i >= 0; i--)
524  {
525  switch (originalSegments[i].Type)
526  {
527  case SegmentType.Line:
528  if (i > 0)
529  {
530  tbr.Add(new LineSegment(originalSegments[i - 1].Point));
531  }
532  else
533  {
534  tbr.Add(new LineSegment(startPoint));
535  }
536  break;
537  case SegmentType.CubicBezier:
538  CubicBezierSegment originalSegment = (CubicBezierSegment)originalSegments[i];
539  if (i > 0)
540  {
541  tbr.Add(new CubicBezierSegment(originalSegment.Points[1], originalSegment.Points[0], originalSegments[i - 1].Point));
542  }
543  else
544  {
545  tbr.Add(new CubicBezierSegment(originalSegment.Points[1], originalSegment.Points[0], startPoint));
546  }
547  break;
548  }
549  }
550 
551  return tbr.ToArray();
552  }
553 
554  const double k = 0.55191496;
555 
556  private static Segment[] GetBezierSegment(double cX, double cY, double radius, double startAngle, double endAngle, bool firstArc)
557  {
558  double phi = Math.PI / 4;
559 
560  double x1 = radius * Math.Cos(phi);
561  double y1 = radius * Math.Sin(phi);
562 
563  double x4 = x1;
564  double y4 = -y1;
565 
566  double x3 = x1 + k * radius * Math.Sin(phi);
567  double y3 = y1 - k * radius * Math.Cos(phi);
568 
569  double x2 = x4 + k * radius * Math.Sin(phi);
570  double y2 = y4 + k * radius * Math.Cos(phi);
571 
572  double u = 2 * (endAngle - startAngle) / Math.PI;
573 
574  double fx2 = (1 - u) * x4 + u * x2;
575  double fy2 = (1 - u) * y4 + u * y2;
576 
577  double fx3 = (1 - u) * fx2 + u * ((1 - u) * x2 + u * x3);
578  double fy3 = (1 - u) * fy2 + u * ((1 - u) * y2 + u * y3);
579 
580  double rX1 = cX + radius * Math.Cos(startAngle);
581  double rY1 = cY + radius * Math.Sin(startAngle);
582 
583  double rX4 = cX + radius * Math.Cos(endAngle);
584  double rY4 = cY + radius * Math.Sin(endAngle);
585 
586  Point rot2 = Utils.RotatePoint(new Point(fx2, fy2), phi + startAngle);
587  Point rot3 = Utils.RotatePoint(new Point(fx3, fy3), phi + startAngle);
588 
589  List<Segment> tbr = new List<Segment>();
590 
591  if (firstArc)
592  {
593  tbr.Add(new LineSegment(rX1, rY1));
594  }
595 
596  tbr.Add(new CubicBezierSegment(cX + rot2.X, cY + rot2.Y, cX + rot3.X, cY + rot3.Y, rX4, rY4));
597 
598  return tbr.ToArray();
599  }
600  public double Radius { get; }
601  public double StartAngle { get; }
602  public double EndAngle { get; }
603 
604  public ArcSegment(Point center, double radius, double startAngle, double endAngle)
605  {
606  this.Points = new Point[] { center };
607  this.Radius = radius;
608  this.StartAngle = startAngle;
609  this.EndAngle = endAngle;
610  }
611 
612  public ArcSegment(double centerX, double centerY, double radius, double startAngle, double endAngle)
613  {
614  this.Points = new Point[] { new Point(centerX, centerY) };
615  this.Radius = radius;
616  this.StartAngle = startAngle;
617  this.EndAngle = endAngle;
618  }
619 
620  public override Segment Clone()
621  {
622  return new ArcSegment(Point.X, Point.Y, Radius, StartAngle, EndAngle);
623  }
624 
625  public override Point Point
626  {
627  get
628  {
629  return new Point(this.Points[0].X + Math.Cos(EndAngle) * Radius, this.Points[0].Y + Math.Sin(EndAngle) * Radius);
630  }
631  }
632 
633 
634  private double cachedLength = double.NaN;
635 
636  public override double Measure(Point previousPoint)
637  {
638  if (double.IsNaN(cachedLength))
639  {
640  Point arcStartPoint = new Point(this.Points[0].X + Math.Cos(StartAngle) * Radius, this.Points[0].Y + Math.Sin(StartAngle) * Radius);
641 
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));
643  }
644 
645  return cachedLength;
646  }
647 
648  public override Point GetPointAt(Point previousPoint, double position)
649  {
650  double totalLength = this.Measure(previousPoint);
651  double arcLength = Radius * Math.Abs(EndAngle - StartAngle);
652 
653  double preArc = (totalLength - arcLength) / totalLength;
654 
655  if (position < preArc)
656  {
657  if (position >= 0)
658  {
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);
661 
662  return new Point(previousPoint.X * (1 - relPos) + arcStartPoint.X * relPos, previousPoint.Y * (1 - relPos) + arcStartPoint.Y * relPos);
663  }
664  else
665  {
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);
670  }
671  }
672  else
673  {
674  double relPos = position - preArc / (1 - preArc);
675 
676  if (relPos <= 1)
677  {
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));
680  }
681  else
682  {
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);
687  }
688  }
689  }
690 
691  public override Point GetTangentAt(Point previousPoint, double position)
692  {
693  double totalLength = this.Measure(previousPoint);
694  double arcLength = Radius * Math.Abs(EndAngle - StartAngle);
695 
696  double preArc = (totalLength - arcLength) / totalLength;
697 
698  if (position < preArc)
699  {
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();
702 
703  if (tang.Modulus() > 0.001)
704  {
705  return tang.Normalize();
706  }
707  else
708  {
709  return this.GetTangentAt(previousPoint, 0);
710  }
711  }
712  else
713  {
714  double relPos = position - preArc / (1 - preArc);
715 
716  if (relPos <= 1)
717  {
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));
720  }
721  else
722  {
723  return new Point(-Math.Sin(EndAngle) * Math.Sign(EndAngle - StartAngle), Math.Cos(EndAngle) * Math.Sign(EndAngle - StartAngle));
724  }
725  }
726 
727  }
728 
729  public override IEnumerable<Segment> Linearise(Point? previousPoint, double resolution)
730  {
731  double length = this.Measure(previousPoint.Value);
732  int segmentCount = (int)Math.Ceiling(length / resolution);
733 
734  for (int i = 0; i < segmentCount; i++)
735  {
736  yield return new LineSegment(this.GetPointAt(previousPoint.Value, (double)(i + 1) / segmentCount));
737  }
738  }
739  public override IEnumerable<Point> GetLinearisationTangents(Point? previousPoint, double resolution)
740  {
741  double length = this.Measure(previousPoint.Value);
742  int segmentCount = (int)Math.Ceiling(length / resolution);
743 
744  for (int i = 0; i < segmentCount; i++)
745  {
746  yield return this.GetTangentAt(previousPoint.Value, (double)(i + 1) / segmentCount);
747  }
748  }
749 
750  public override IEnumerable<Segment> Transform(Func<Point, Point> transformationFunction)
751  {
752  foreach (Segment seg in this.ToBezierSegments())
753  {
754  foreach (Segment seg2 in seg.Transform(transformationFunction))
755  {
756  yield return seg2;
757  }
758  }
759  }
760  }
761 
762 
763 }
VectSharp.Segment.Measure
abstract double Measure(Point previousPoint)
Computes the length of the Segment.
VectSharp.Segment.GetPointAt
abstract Point GetPointAt(Point previousPoint, double position)
Gets the point on the Segment at the specified (relative) position ).
VectSharp
Definition: Brush.cs:26
VectSharp.Segment.GetLinearisationTangents
abstract IEnumerable< Point > GetLinearisationTangents(Point? previousPoint, double resolution)
Gets the tanget at the points at which the segment would be linearised.
VectSharp.Segment
Represents a segment as part of a GraphicsPath.
Definition: Segment.cs:29
VectSharp.Segment.GetTangentAt
abstract Point GetTangentAt(Point previousPoint, double position)
Gets the tangent to the Segment at the specified (relative) position ).
VectSharp.SegmentType
SegmentType
Types of Segment.
Definition: Enums.cs:152
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.Segment.Clone
abstract Segment Clone()
Creates a copy of the Segment.
VectSharp.Segment.Points
Point[] Points
The points used to define the Segment.
Definition: Segment.cs:39
VectSharp.Segment.Type
abstract SegmentType Type
The type of the Segment.
Definition: Segment.cs:34
VectSharp.Point.Normalize
Point Normalize()
Normalises a Point.
Definition: Point.cs:63
VectSharp.Segment.Linearise
abstract IEnumerable< Segment > Linearise(Point? previousPoint, double resolution)
Transform the segment into a series of linear segments. Segments that are already linear are not chan...
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Segment.Transform
abstract IEnumerable< Segment > Transform(Func< Point, Point > transformationFunction)
Applies an arbitrary transformation to all of the points of the Segment.
VectSharp.Segment.Point
virtual Point Point
The end point of the Segment.
Definition: Segment.cs:45