зеркало из https://github.com/SixLabors/Shapes.git
add shape builder
This commit is contained in:
Родитель
083c5f4013
Коммит
b7c7e684cc
|
@ -5,6 +5,7 @@
|
|||
|
||||
namespace Shaper2D
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
@ -26,6 +27,7 @@ namespace Shaper2D
|
|||
/// The line points.
|
||||
/// </summary>
|
||||
private readonly ImmutableArray<Point> linePoints;
|
||||
private readonly Point[] controlPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
|
||||
|
@ -36,7 +38,16 @@ namespace Shaper2D
|
|||
Guard.NotNull(points, nameof(points));
|
||||
Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points));
|
||||
|
||||
int correctPointCount = (points.Length - 1) % 3;
|
||||
if (correctPointCount != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(points), "points must be a multiple of 3 plus 1 long.");
|
||||
}
|
||||
|
||||
this.controlPoints = points.ToArray();
|
||||
this.linePoints = this.GetDrawingPoints(points);
|
||||
|
||||
this.EndPoint = points[points.Length - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -52,17 +63,48 @@ namespace Shaper2D
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end point.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The end point.
|
||||
/// </value>
|
||||
public Point EndPoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current <see cref="ILineSegment" /> a simple linear path.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
|
||||
/// </returns>
|
||||
public ImmutableArray<Point> AsSimpleLinearPath()
|
||||
public ImmutableArray<Point> Flatten()
|
||||
{
|
||||
return this.linePoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the current LineSegment using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>A line segment with the matrix applied to it.</returns>
|
||||
public ILineSegment Transform(Matrix3x2 matrix)
|
||||
{
|
||||
if (matrix.IsIdentity)
|
||||
{
|
||||
// no transform to apply skip it
|
||||
return this;
|
||||
}
|
||||
|
||||
var points = new Point[this.controlPoints.Length];
|
||||
var i = 0;
|
||||
foreach (var p in this.controlPoints)
|
||||
{
|
||||
points[i++] = p.Transform(matrix);
|
||||
}
|
||||
|
||||
return new BezierLineSegment(points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the drawing points along the line.
|
||||
/// </summary>
|
||||
|
|
|
@ -15,8 +15,7 @@ namespace Shaper2D
|
|||
using PolygonClipper;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a complex polygon made up of one or more outline
|
||||
/// polygons and one or more holes to punch out of them.
|
||||
/// Represents a complex polygon made up of one or more shapes overlayed on each other, where overlaps causes holes.
|
||||
/// </summary>
|
||||
/// <seealso cref="Shaper2D.IShape" />
|
||||
public sealed class ComplexPolygon : IShape
|
||||
|
@ -28,29 +27,51 @@ namespace Shaper2D
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComplexPolygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="outline">The outline.</param>
|
||||
/// <param name="holes">The holes.</param>
|
||||
public ComplexPolygon(IShape outline, params IShape[] holes)
|
||||
: this(new[] { outline }, holes)
|
||||
/// <param name="shapes">The shapes.</param>
|
||||
public ComplexPolygon(params IShape[] shapes)
|
||||
{
|
||||
Guard.NotNull(shapes, nameof(shapes));
|
||||
Guard.MustBeGreaterThanOrEqualTo(shapes.Length, 1, nameof(shapes));
|
||||
|
||||
this.shapes = shapes;
|
||||
var pathCount = shapes.Sum(x => x.Paths.Length);
|
||||
var paths = new IPath[pathCount];
|
||||
int index = 0;
|
||||
|
||||
float minX = float.MaxValue;
|
||||
float maxX = float.MinValue;
|
||||
float minY = float.MaxValue;
|
||||
float maxY = float.MinValue;
|
||||
|
||||
foreach (var s in shapes)
|
||||
{
|
||||
if (s.Bounds.Left < minX)
|
||||
{
|
||||
minX = s.Bounds.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComplexPolygon"/> class.
|
||||
/// </summary>
|
||||
/// <param name="outlines">The outlines.</param>
|
||||
/// <param name="holes">The holes.</param>
|
||||
public ComplexPolygon(IShape[] outlines, IShape[] holes)
|
||||
if (s.Bounds.Right > maxX)
|
||||
{
|
||||
Guard.NotNull(outlines, nameof(outlines));
|
||||
Guard.MustBeGreaterThanOrEqualTo(outlines.Length, 1, nameof(outlines));
|
||||
maxX = s.Bounds.Right;
|
||||
}
|
||||
|
||||
this.MaxIntersections = this.FixAndSetShapes(outlines, holes);
|
||||
if (s.Bounds.Top < minY)
|
||||
{
|
||||
minY = s.Bounds.Top;
|
||||
}
|
||||
|
||||
float minX = this.shapes.Min(x => x.Bounds.Left);
|
||||
float maxX = this.shapes.Max(x => x.Bounds.Right);
|
||||
float minY = this.shapes.Min(x => x.Bounds.Top);
|
||||
float maxY = this.shapes.Max(x => x.Bounds.Bottom);
|
||||
if (s.Bounds.Bottom > maxY)
|
||||
{
|
||||
maxY = s.Bounds.Bottom;
|
||||
}
|
||||
|
||||
foreach (var p in s.Paths)
|
||||
{
|
||||
paths[index++] = p;
|
||||
}
|
||||
}
|
||||
|
||||
this.paths = ImmutableArray.Create(paths);
|
||||
|
||||
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
@ -189,24 +210,29 @@ namespace Shaper2D
|
|||
}
|
||||
}
|
||||
|
||||
private int FixAndSetShapes(IEnumerable<IShape> outlines, IEnumerable<IShape> holes)
|
||||
/// <summary>
|
||||
/// Transforms the shape using the specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new shape with the matrix applied to it.
|
||||
/// </returns>
|
||||
public IShape Transform(Matrix3x2 matrix)
|
||||
{
|
||||
Clipper clipper = new Clipper();
|
||||
|
||||
// add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses
|
||||
clipper.AddPaths(outlines, PolyType.Subject);
|
||||
clipper.AddPaths(holes, PolyType.Clip);
|
||||
|
||||
this.shapes = clipper.Execute();
|
||||
this.paths = ImmutableArray.Create(this.shapes.SelectMany(x => x.Paths).ToArray());
|
||||
|
||||
int intersections = 0;
|
||||
foreach (IShape s in this.shapes)
|
||||
if (matrix.IsIdentity)
|
||||
{
|
||||
intersections += s.MaxIntersections;
|
||||
// no transform to apply skip it
|
||||
return this;
|
||||
}
|
||||
|
||||
return intersections;
|
||||
var shapes = new IShape[this.shapes.Length];
|
||||
var i = 0;
|
||||
foreach (var s in this.shapes)
|
||||
{
|
||||
shapes[i++] = s.Transform(matrix);
|
||||
}
|
||||
|
||||
return new ComplexPolygon(shapes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,10 +13,25 @@ namespace Shaper2D
|
|||
/// </summary>
|
||||
public interface ILineSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the end point.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The end point.
|
||||
/// </value>
|
||||
Point EndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
|
||||
/// </summary>
|
||||
/// <returns>Returns the current <see cref="ILineSegment" /> as simple linear path.</returns>
|
||||
ImmutableArray<Point> AsSimpleLinearPath(); // TODO move this over to ReadonlySpan<Vector2> once available
|
||||
ImmutableArray<Point> Flatten();
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the current LineSegment using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>A line segment with the matrix applied to it.</returns>
|
||||
ILineSegment Transform(Matrix3x2 matrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
namespace Shaper2D
|
||||
{
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a logic path that can be drawn
|
||||
|
@ -41,6 +42,13 @@ namespace Shaper2D
|
|||
/// Converts the <see cref="IPath" /> into a simple linear path..
|
||||
/// </summary>
|
||||
/// <returns>Returns the current <see cref="IPath" /> as simple linear path.</returns>
|
||||
ImmutableArray<Point> AsSimpleLinearPath();
|
||||
ImmutableArray<Point> Flatten();
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the path using the specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>A new path with the matrix applied to it.</returns>
|
||||
IPath Transform(Matrix3x2 matrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,5 +78,12 @@ namespace Shaper2D
|
|||
/// <param name="end">The end.</param>
|
||||
/// <returns>The locations along the line segment that intersect with the edges of the shape.</returns>
|
||||
IEnumerable<Point> FindIntersections(Point start, Point end);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the shape using the specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>A new shape with the matrix applied to it.</returns>
|
||||
IShape Transform(Matrix3x2 matrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace Shaper2D
|
|||
/// <param name="segment">The segment.</param>
|
||||
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
|
||||
internal InternalPath(ILineSegment segment, bool isClosedPath)
|
||||
: this(segment.AsSimpleLinearPath(), isClosedPath)
|
||||
: this(segment.Flatten(), isClosedPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -395,7 +395,7 @@ namespace Shaper2D
|
|||
List<Point> simplified = new List<Point>();
|
||||
foreach (ILineSegment seg in segments)
|
||||
{
|
||||
simplified.AddRange(seg.AsSimpleLinearPath());
|
||||
simplified.AddRange(seg.Flatten());
|
||||
}
|
||||
|
||||
return simplified.ToImmutableArray();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
namespace Shaper2D
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
@ -51,17 +52,52 @@ namespace Shaper2D
|
|||
Guard.MustBeGreaterThanOrEqualTo(points.Count(), 2, nameof(points));
|
||||
|
||||
this.points = ImmutableArray.Create(points);
|
||||
|
||||
this.EndPoint = points[points.Length - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end point.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The end point.
|
||||
/// </value>
|
||||
public Point EndPoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
|
||||
/// </returns>
|
||||
public ImmutableArray<Point> AsSimpleLinearPath()
|
||||
public ImmutableArray<Point> Flatten()
|
||||
{
|
||||
return this.points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the current LineSegment using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A line segment with the matrix applied to it.
|
||||
/// </returns>
|
||||
public ILineSegment Transform(Matrix3x2 matrix)
|
||||
{
|
||||
if (matrix.IsIdentity)
|
||||
{
|
||||
// no transform to apply skip it
|
||||
return this;
|
||||
}
|
||||
|
||||
var points = new Point[this.points.Length];
|
||||
var i = 0;
|
||||
foreach (var p in this.points)
|
||||
{
|
||||
points[i++] = p.Transform(matrix);
|
||||
}
|
||||
|
||||
return new LinearLineSegment(points);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,8 +26,17 @@ namespace Shaper2D
|
|||
public Path(params ILineSegment[] segment)
|
||||
{
|
||||
this.innerPath = new InternalPath(segment, false);
|
||||
this.LineSegments = ImmutableArray.Create(segment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line segments.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The line segments.
|
||||
/// </value>
|
||||
public ImmutableArray<ILineSegment> LineSegments { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Rectangle Bounds => this.innerPath.Bounds;
|
||||
|
||||
|
@ -35,7 +44,7 @@ namespace Shaper2D
|
|||
public float Length => this.innerPath.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImmutableArray<Point> AsSimpleLinearPath()
|
||||
public ImmutableArray<Point> Flatten()
|
||||
{
|
||||
return this.innerPath.Points;
|
||||
}
|
||||
|
@ -45,5 +54,24 @@ namespace Shaper2D
|
|||
{
|
||||
return this.innerPath.DistanceFromPath(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the rectangle using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new path with the matrix applied to it.
|
||||
/// </returns>
|
||||
public IPath Transform(Matrix3x2 matrix)
|
||||
{
|
||||
var segments = new ILineSegment[this.LineSegments.Length];
|
||||
var i = 0;
|
||||
foreach (var s in this.LineSegments)
|
||||
{
|
||||
segments[i++] = s.Transform(matrix);
|
||||
}
|
||||
|
||||
return new Path(segments);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,15 @@ namespace Shaper2D
|
|||
public struct Point : IEquatable<Point>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="Point"/> that has X and Y values set to zero.
|
||||
/// Represents an unset <see cref="Point"/>.
|
||||
/// </summary>
|
||||
public static readonly Point Empty = default(Point);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="Point"/> that has X and Y values set to zero.
|
||||
/// </summary>
|
||||
public static readonly Point Zero = new Point(0, 0);
|
||||
|
||||
private readonly Vector2 backingVector;
|
||||
|
||||
private bool isSet;
|
||||
|
@ -186,6 +191,16 @@ namespace Shaper2D
|
|||
return new Point(this.backingVector + p.ToVector2());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the specified matrix to this point
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>A new point with the transofrm applied upon it.</returns>
|
||||
public Point Transform(Matrix3x2 matrix)
|
||||
{
|
||||
return new Point(Vector2.Transform(this.backingVector, matrix));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace Shaper2D
|
|||
/// <param name="segments">The segments.</param>
|
||||
public Polygon(params ILineSegment[] segments)
|
||||
{
|
||||
this.LineSegments = ImmutableArray.Create(segments);
|
||||
this.innerPath = new InternalPath(segments, true);
|
||||
this.pathCollection = ImmutableArray.Create<IPath>(this);
|
||||
}
|
||||
|
@ -35,10 +36,19 @@ namespace Shaper2D
|
|||
/// <param name="segment">The segment.</param>
|
||||
public Polygon(ILineSegment segment)
|
||||
{
|
||||
this.LineSegments = ImmutableArray.Create(segment);
|
||||
this.innerPath = new InternalPath(segment, true);
|
||||
this.pathCollection = ImmutableArray.Create<IPath>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line segments.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The line segments.
|
||||
/// </value>
|
||||
public ImmutableArray<ILineSegment> LineSegments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding box of this shape.
|
||||
/// </summary>
|
||||
|
@ -119,7 +129,7 @@ namespace Shaper2D
|
|||
/// <returns>
|
||||
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
|
||||
/// </returns>
|
||||
public ImmutableArray<Point> AsSimpleLinearPath()
|
||||
public ImmutableArray<Point> Flatten()
|
||||
{
|
||||
return this.innerPath.Points;
|
||||
}
|
||||
|
@ -154,5 +164,48 @@ namespace Shaper2D
|
|||
{
|
||||
return this.innerPath.FindIntersections(start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the rectangle using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new shape with the matrix applied to it.
|
||||
/// </returns>
|
||||
public IShape Transform(Matrix3x2 matrix)
|
||||
{
|
||||
var segments = new ILineSegment[this.LineSegments.Length];
|
||||
var i = 0;
|
||||
foreach (var s in this.LineSegments)
|
||||
{
|
||||
segments[i++] = s.Transform(matrix);
|
||||
}
|
||||
|
||||
return new Polygon(segments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the path using the specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new path with the matrix applied to it.
|
||||
/// </returns>
|
||||
IPath IPath.Transform(Matrix3x2 matrix)
|
||||
{
|
||||
if (matrix.IsIdentity)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var segments = new ILineSegment[this.LineSegments.Length];
|
||||
var i = 0;
|
||||
foreach (var s in this.LineSegments)
|
||||
{
|
||||
segments[i++] = s.Transform(matrix);
|
||||
}
|
||||
|
||||
return new Polygon(segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// <copyright file="ClipableShape.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
||||
namespace Shaper2D.PolygonClipper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a shape and its type for when clipping is applies.
|
||||
/// </summary>
|
||||
public struct ClipableShape
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClipableShape"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="shape">The shape.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
public ClipableShape(IShape shape, ClippingType type)
|
||||
{
|
||||
this.Shape = shape;
|
||||
this.Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shape.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The shape.
|
||||
/// </value>
|
||||
public IShape Shape { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public ClippingType Type { get; private set; }
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ namespace Shaper2D.PolygonClipper
|
|||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -14,7 +15,7 @@ namespace Shaper2D.PolygonClipper
|
|||
/// <summary>
|
||||
/// Library to clip polygons.
|
||||
/// </summary>
|
||||
internal class Clipper
|
||||
public class Clipper
|
||||
{
|
||||
private const double HorizontalDeltaLimit = -3.4E+38;
|
||||
private const int Skip = -2;
|
||||
|
@ -27,44 +28,117 @@ namespace Shaper2D.PolygonClipper
|
|||
private readonly List<Join> ghostJoins = new List<Join>();
|
||||
private readonly List<List<Edge>> edges = new List<List<Edge>>();
|
||||
private readonly List<OutRec> polyOuts = new List<OutRec>();
|
||||
private readonly object syncRoot = new object();
|
||||
|
||||
private Maxima maxima = null;
|
||||
private Edge sortedEdges = null;
|
||||
|
||||
private LocalMinima minimaList;
|
||||
private LocalMinima currentLocalMinima;
|
||||
private Scanbeam scanbeam = null;
|
||||
private Edge activeEdges = null;
|
||||
private bool resultsDirty = true;
|
||||
private ImmutableArray<IShape> results;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Clipper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="shapes">The shapes.</param>
|
||||
public Clipper(IEnumerable<ClipableShape> shapes)
|
||||
{
|
||||
Guard.NotNull(shapes, nameof(shapes));
|
||||
this.AddShapes(shapes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Clipper" /> class.
|
||||
/// </summary>
|
||||
/// <param name="shapes">The shapes.</param>
|
||||
public Clipper(params ClipableShape[] shapes)
|
||||
{
|
||||
this.AddShapes(shapes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified clip type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the <see cref="IShape" /> array containing the converted polygons.
|
||||
/// </returns>
|
||||
public ImmutableArray<IShape> GenerateClippedShapes()
|
||||
{
|
||||
if (!this.resultsDirty)
|
||||
{
|
||||
return this.results;
|
||||
}
|
||||
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
if (!this.resultsDirty)
|
||||
{
|
||||
return this.results;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool succeeded = this.ExecuteInternal();
|
||||
|
||||
// build the return polygons ...
|
||||
if (succeeded)
|
||||
{
|
||||
this.results = this.BuildResult();
|
||||
this.resultsDirty = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.DisposeAllPolyPoints();
|
||||
}
|
||||
|
||||
return this.results;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the paths.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="polyType">Type of the poly.</param>
|
||||
public void AddPaths(IEnumerable<IShape> path, PolyType polyType)
|
||||
/// <param name="clipableShaps">The clipable shaps.</param>
|
||||
public void AddShapes(IEnumerable<ClipableShape> clipableShaps)
|
||||
{
|
||||
foreach (var p in path)
|
||||
foreach (var p in clipableShaps)
|
||||
{
|
||||
this.AddPath(p, polyType);
|
||||
this.AddShape(p.Shape, p.Type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the shapes.
|
||||
/// </summary>
|
||||
/// <param name="shapes">The shapes.</param>
|
||||
/// <param name="clippingType">The clipping type.</param>
|
||||
public void AddShapes(IEnumerable<IShape> shapes, ClippingType clippingType)
|
||||
{
|
||||
foreach (var p in shapes)
|
||||
{
|
||||
this.AddShape(p, clippingType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="polyType">Type of the poly.</param>
|
||||
public void AddPath(IShape path, PolyType polyType)
|
||||
/// <param name="shape">The shape.</param>
|
||||
/// <param name="clippingType">The clipping type.</param>
|
||||
internal void AddShape(IShape shape, ClippingType clippingType)
|
||||
{
|
||||
if (path is IPath)
|
||||
if (shape is IPath)
|
||||
{
|
||||
this.AddPath((IPath)path, polyType);
|
||||
this.AddPath((IPath)shape, clippingType);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var p in path.Paths)
|
||||
foreach (var p in shape.Paths)
|
||||
{
|
||||
this.AddPath(p, polyType);
|
||||
this.AddPath(p, clippingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,12 +147,17 @@ namespace Shaper2D.PolygonClipper
|
|||
/// Adds the path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="polyType">Type of the poly.</param>
|
||||
/// <param name="clippingType">Type of the poly.</param>
|
||||
/// <returns>True if the path was added.</returns>
|
||||
/// <exception cref="ClipperException">AddPath: Open paths have been disabled.</exception>
|
||||
public bool AddPath(IPath path, PolyType polyType)
|
||||
internal bool AddPath(IPath path, ClippingType clippingType)
|
||||
{
|
||||
var points = path.AsSimpleLinearPath();
|
||||
// every path we add lock the clipper to prevent state curruption
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.resultsDirty = true;
|
||||
|
||||
var points = path.Flatten();
|
||||
|
||||
int hi = points.Length - 1;
|
||||
while (hi > 0 && (points[hi] == points[0]))
|
||||
|
@ -173,7 +252,7 @@ namespace Shaper2D.PolygonClipper
|
|||
edge = startEdge;
|
||||
do
|
||||
{
|
||||
this.InitEdge2(edge, polyType);
|
||||
this.InitEdge2(edge, clippingType);
|
||||
edge = edge.NextEdge;
|
||||
if (isFlat && edge.Current.Y != startEdge.Current.Y)
|
||||
{
|
||||
|
@ -278,25 +357,6 @@ namespace Shaper2D.PolygonClipper
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified clip type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the <see cref="IShape" /> array containing the converted polygons.
|
||||
/// </returns>
|
||||
public IShape[] Execute()
|
||||
{
|
||||
PolyTree polytree = new PolyTree();
|
||||
bool succeeded = this.ExecuteInternal();
|
||||
|
||||
// build the return polygons ...
|
||||
if (succeeded)
|
||||
{
|
||||
this.BuildResult2(polytree);
|
||||
}
|
||||
|
||||
return ExtractOutlines(polytree).ToArray();
|
||||
}
|
||||
|
||||
private static float Round(double value)
|
||||
|
@ -314,37 +374,6 @@ namespace Shaper2D.PolygonClipper
|
|||
return edge.Bottom.X + Round(edge.Dx * (currentY - edge.Bottom.Y));
|
||||
}
|
||||
|
||||
private static List<IShape> ExtractOutlines(PolyNode tree)
|
||||
{
|
||||
var result = new List<IShape>();
|
||||
ExtractOutlines(tree, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ExtractOutlines(PolyNode tree, List<IShape> shapes)
|
||||
{
|
||||
if (tree.Contour.Any())
|
||||
{
|
||||
// if the source path is set then we clipper retained the full path intact thus we can freely
|
||||
// use it and get any shape optimizations that are available.
|
||||
if (tree.SourcePath != null)
|
||||
{
|
||||
shapes.Add((IShape)tree.SourcePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Polygon polygon = new Polygon(new LinearLineSegment(tree.Contour.Select(x => new Point(x)).ToArray()));
|
||||
|
||||
shapes.Add(polygon);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PolyNode c in tree.Children)
|
||||
{
|
||||
ExtractOutlines(c, shapes);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FixHoleLinkage(OutRec outRec)
|
||||
{
|
||||
// skip if an outermost polygon or
|
||||
|
@ -621,6 +650,16 @@ namespace Shaper2D.PolygonClipper
|
|||
// Swap(ref e.Top.X, ref e.Bot.X);
|
||||
}
|
||||
|
||||
private void DisposeAllPolyPoints()
|
||||
{
|
||||
foreach (var polyout in this.polyOuts)
|
||||
{
|
||||
polyout.Points = null;
|
||||
}
|
||||
|
||||
this.polyOuts.Clear();
|
||||
}
|
||||
|
||||
private bool ExecuteInternal()
|
||||
{
|
||||
try
|
||||
|
@ -886,7 +925,7 @@ namespace Shaper2D.PolygonClipper
|
|||
return false;
|
||||
}
|
||||
|
||||
if (edge.PolyType == PolyType.Subject)
|
||||
if (edge.PolyType == ClippingType.Subject)
|
||||
{
|
||||
return edge.WindingCountInOppositePolyType == 0;
|
||||
}
|
||||
|
@ -1659,8 +1698,8 @@ namespace Shaper2D.PolygonClipper
|
|||
}
|
||||
else if (e1Wc == 1 && e2Wc == 1)
|
||||
{
|
||||
if (((e1.PolyType == PolyType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
|
||||
((e1.PolyType == PolyType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
|
||||
if (((e1.PolyType == ClippingType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
|
||||
((e1.PolyType == ClippingType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
|
||||
{
|
||||
this.AddLocalMinPoly(e1, e2, pt);
|
||||
}
|
||||
|
@ -2380,12 +2419,11 @@ namespace Shaper2D.PolygonClipper
|
|||
return result;
|
||||
}
|
||||
|
||||
private void BuildResult2(PolyTree polytree)
|
||||
private ImmutableArray<IShape> BuildResult()
|
||||
{
|
||||
polytree.Clear();
|
||||
List<IShape> shapes = new List<IShape>(this.polyOuts.Count);
|
||||
|
||||
// add each output polygon/contour to polytree ...
|
||||
polytree.AllPolygonNodes.Capacity = this.polyOuts.Count;
|
||||
for (int i = 0; i < this.polyOuts.Count; i++)
|
||||
{
|
||||
OutRec outRec = this.polyOuts[i];
|
||||
|
@ -2397,42 +2435,26 @@ namespace Shaper2D.PolygonClipper
|
|||
}
|
||||
|
||||
FixHoleLinkage(outRec);
|
||||
PolyNode pn = new PolyNode();
|
||||
pn.SourcePath = outRec.SourcePath;
|
||||
polytree.AllPolygonNodes.Add(pn);
|
||||
outRec.PolyNode = pn;
|
||||
pn.Contour.Capacity = cnt;
|
||||
OutPoint op = outRec.Points.Previous;
|
||||
for (int j = 0; j < cnt; j++)
|
||||
var shape = outRec.SourcePath as IShape;
|
||||
if (shape != null)
|
||||
{
|
||||
pn.Contour.Add(op.Point);
|
||||
op = op.Previous;
|
||||
}
|
||||
}
|
||||
|
||||
// fixup PolyNode links etc ...
|
||||
polytree.Children.Capacity = this.polyOuts.Count;
|
||||
for (int i = 0; i < this.polyOuts.Count; i++)
|
||||
{
|
||||
OutRec outRec = this.polyOuts[i];
|
||||
if (outRec.PolyNode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (outRec.IsOpen)
|
||||
{
|
||||
polytree.AddChild(outRec.PolyNode);
|
||||
}
|
||||
else if (outRec.FirstLeft != null &&
|
||||
outRec.FirstLeft.PolyNode != null)
|
||||
{
|
||||
outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode);
|
||||
shapes.Add(shape);
|
||||
}
|
||||
else
|
||||
{
|
||||
polytree.AddChild(outRec.PolyNode);
|
||||
var points = new Point[cnt];
|
||||
OutPoint op = outRec.Points.Previous;
|
||||
for (int j = 0; j < cnt; j++)
|
||||
{
|
||||
points[i] = op.Point;
|
||||
op = op.Previous;
|
||||
}
|
||||
|
||||
shapes.Add(new Polygon(new LinearLineSegment(points)));
|
||||
}
|
||||
}
|
||||
|
||||
return shapes.ToImmutableArray();
|
||||
}
|
||||
|
||||
private void FixupOutPolyline(OutRec outrec)
|
||||
|
@ -3305,16 +3327,8 @@ namespace Shaper2D.PolygonClipper
|
|||
|
||||
private OutRec CreateOutRec()
|
||||
{
|
||||
OutRec result = new OutRec();
|
||||
result.Index = Unassigned;
|
||||
result.IsHole = false;
|
||||
result.IsOpen = false;
|
||||
result.FirstLeft = null;
|
||||
result.Points = null;
|
||||
result.BottomPoint = null;
|
||||
result.PolyNode = null;
|
||||
var result = new OutRec(this.polyOuts.Count);
|
||||
this.polyOuts.Add(result);
|
||||
result.Index = this.polyOuts.Count - 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3469,7 +3483,7 @@ namespace Shaper2D.PolygonClipper
|
|||
e.PreviousInAEL = null;
|
||||
}
|
||||
|
||||
private void InitEdge2(Edge e, PolyType polyType)
|
||||
private void InitEdge2(Edge e, ClippingType polyType)
|
||||
{
|
||||
if (e.Current.Y >= e.NextEdge.Current.Y)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// <copyright file="PolyType.cs" company="Scott Williams">
|
||||
// <copyright file="ClippingType.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
@ -14,15 +14,15 @@ namespace Shaper2D.PolygonClipper
|
|||
/// <summary>
|
||||
/// Poly Type
|
||||
/// </summary>
|
||||
internal enum PolyType
|
||||
public enum ClippingType
|
||||
{
|
||||
/// <summary>
|
||||
/// The subject
|
||||
/// Represent a main shape to act as a main subject whoes path will be clipped or merged.
|
||||
/// </summary>
|
||||
Subject,
|
||||
|
||||
/// <summary>
|
||||
/// The clip
|
||||
/// Represents a shape to act and a clipped path.
|
||||
/// </summary>
|
||||
Clip
|
||||
}
|
|
@ -73,7 +73,7 @@ namespace Shaper2D.PolygonClipper
|
|||
/// <value>
|
||||
/// The poly type.
|
||||
/// </value>
|
||||
public PolyType PolyType { get; set; }
|
||||
public ClippingType PolyType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the side.
|
||||
|
|
|
@ -17,6 +17,20 @@ namespace Shaper2D.PolygonClipper
|
|||
/// </summary>
|
||||
internal class OutRec
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutRec"/> class.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
public OutRec(int index)
|
||||
{
|
||||
this.Index = index;
|
||||
this.IsHole = false;
|
||||
this.IsOpen = false;
|
||||
this.FirstLeft = null;
|
||||
this.Points = null;
|
||||
this.BottomPoint = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source path
|
||||
/// </summary>
|
||||
|
@ -60,10 +74,5 @@ namespace Shaper2D.PolygonClipper
|
|||
/// The bottom point.
|
||||
/// </value>
|
||||
public OutPoint BottomPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the poly node
|
||||
/// </summary>
|
||||
public PolyNode PolyNode { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// <copyright file="PolyNode.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
||||
namespace Shaper2D.PolygonClipper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// Poly Node
|
||||
/// </summary>
|
||||
internal class PolyNode
|
||||
{
|
||||
private List<PolyNode> children = new List<PolyNode>();
|
||||
|
||||
private List<Vector2> polygon = new List<Vector2>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contour.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The contour.
|
||||
/// </value>
|
||||
public List<Vector2> Contour => this.polygon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The children.
|
||||
/// </value>
|
||||
public List<PolyNode> Children => this.children;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source path.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The source path.
|
||||
/// </value>
|
||||
public IPath SourcePath { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the child.
|
||||
/// </summary>
|
||||
/// <param name="child">The child.</param>
|
||||
internal void AddChild(PolyNode child)
|
||||
{
|
||||
int cnt = this.children.Count;
|
||||
this.children.Add(child);
|
||||
child.Index = cnt;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
// <copyright file="PolyTree.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
||||
namespace Shaper2D.PolygonClipper
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// Poly Tree
|
||||
/// </summary>
|
||||
/// <seealso cref="Shaper2D.PolygonClipper.PolyNode" />
|
||||
internal class PolyTree : PolyNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets all polygon nodes.
|
||||
/// </summary>
|
||||
public List<PolyNode> AllPolygonNodes { get; set; } = new List<PolyNode>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The total.
|
||||
/// </value>
|
||||
public int Total
|
||||
{
|
||||
get
|
||||
{
|
||||
int result = this.AllPolygonNodes.Count;
|
||||
|
||||
// with negative offsets, ignore the hidden outer polygon ...
|
||||
if (result > 0 && this.Children[0] != this.AllPolygonNodes[0])
|
||||
{
|
||||
result--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this instance.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < this.AllPolygonNodes.Count; i++)
|
||||
{
|
||||
this.AllPolygonNodes[i] = null;
|
||||
}
|
||||
|
||||
this.AllPolygonNodes.Clear();
|
||||
this.Children.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first.
|
||||
/// </summary>
|
||||
/// <returns>the first node</returns>
|
||||
public PolyNode GetFirst()
|
||||
{
|
||||
if (this.Children.Count > 0)
|
||||
{
|
||||
return this.Children[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,14 +41,14 @@ namespace Shaper2D
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle" /> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
public Rectangle(Point location, Size size)
|
||||
/// <param name="topLeft">The top left.</param>
|
||||
/// <param name="bottomRight">The bottom right.</param>
|
||||
public Rectangle(Point topLeft, Point bottomRight)
|
||||
{
|
||||
this.Location = location;
|
||||
this.topLeft = location;
|
||||
this.bottomRight = location.Offset(size);
|
||||
this.Size = size;
|
||||
this.Location = topLeft;
|
||||
this.topLeft = topLeft;
|
||||
this.bottomRight = bottomRight;
|
||||
this.Size = new Size(bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
|
||||
|
||||
this.points = ImmutableArray.Create(new Point[4]
|
||||
{
|
||||
|
@ -58,11 +58,21 @@ namespace Shaper2D
|
|||
new Vector2(this.topLeft.X, this.bottomRight.Y)
|
||||
});
|
||||
|
||||
this.halfLength = size.Width + size.Height;
|
||||
this.halfLength = this.Size.Width + this.Size.Height;
|
||||
this.length = this.halfLength * 2;
|
||||
this.pathCollection = ImmutableArray.Create<IPath>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
public Rectangle(Point location, Size size)
|
||||
: this(location, location.Offset(size))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location.
|
||||
/// </summary>
|
||||
|
@ -79,6 +89,14 @@ namespace Shaper2D
|
|||
/// </value>
|
||||
public float Left => this.topLeft.X;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The X.
|
||||
/// </value>
|
||||
public float X => this.topLeft.X;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right.
|
||||
/// </summary>
|
||||
|
@ -95,6 +113,14 @@ namespace Shaper2D
|
|||
/// </value>
|
||||
public float Top => this.topLeft.Y;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The Y.
|
||||
/// </value>
|
||||
public float Y => this.topLeft.Y;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bottom.
|
||||
/// </summary>
|
||||
|
@ -262,13 +288,42 @@ namespace Shaper2D
|
|||
return discovered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the rectangle using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new shape with the matrix applied to it.
|
||||
/// </returns>
|
||||
public IShape Transform(Matrix3x2 matrix)
|
||||
{
|
||||
if (matrix.IsIdentity)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return new Rectangle(Vector2.Transform(this.topLeft, matrix), Vector2.Transform(this.bottomRight, matrix));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the rectangle using specified matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix.</param>
|
||||
/// <returns>
|
||||
/// A new path with the matrix applied to it.
|
||||
/// </returns>
|
||||
IPath IPath.Transform(Matrix3x2 matrix)
|
||||
{
|
||||
return (IPath)this.Transform(matrix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
|
||||
/// </returns>
|
||||
ImmutableArray<Point> IPath.AsSimpleLinearPath()
|
||||
ImmutableArray<Point> IPath.Flatten()
|
||||
{
|
||||
return this.points;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// <copyright file="ShapeBuilder.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
||||
namespace Shaper2D
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Allow you to delacatrivly build shapes and paths.
|
||||
/// </summary>
|
||||
public class ShapeBuilder
|
||||
{
|
||||
private readonly Matrix3x2 defaultTransform;
|
||||
private Point currentPoint = Point.Empty;
|
||||
private Matrix3x2 currentTransform;
|
||||
|
||||
private List<ILineSegment[]> figures = new List<ILineSegment[]>();
|
||||
private List<ILineSegment> segments = new List<ILineSegment>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShapeBuilder" /> class.
|
||||
/// </summary>
|
||||
public ShapeBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShapeBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="defaultTransform">The default transform.</param>
|
||||
public ShapeBuilder(Matrix3x2 defaultTransform)
|
||||
{
|
||||
this.defaultTransform = defaultTransform;
|
||||
this.ResetTransform();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the translation to be applied to all items to follow being applied to the <see cref="ShapeBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="translation">The translation.</param>
|
||||
public void SetTransform(Matrix3x2 translation)
|
||||
{
|
||||
this.currentTransform = translation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the translation to the default.
|
||||
/// </summary>
|
||||
public void ResetTransform()
|
||||
{
|
||||
this.currentTransform = this.defaultTransform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the line connecting the current point to the new point.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
public void AddLine(Point point)
|
||||
{
|
||||
var endPoint = point.Transform(this.currentTransform);
|
||||
this.segments.Add(new LinearLineSegment(this.currentPoint, endPoint));
|
||||
this.currentPoint = endPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a series of line segments connecting the current point to the new points.
|
||||
/// </summary>
|
||||
/// <param name="points">The points.</param>
|
||||
public void AddLines(IEnumerable<Point> points)
|
||||
{
|
||||
foreach (var p in points)
|
||||
{
|
||||
this.AddLine(p);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the segment.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment.</param>
|
||||
public void AddSegment(ILineSegment segment)
|
||||
{
|
||||
var segments = segment.Transform(this.currentTransform);
|
||||
this.segments.Add(segments);
|
||||
this.currentPoint = segments.EndPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a bezier curve to the current figure joining the last point to the endPoint.
|
||||
/// </summary>
|
||||
/// <param name="controlPoint1">The control point1.</param>
|
||||
/// <param name="controlPoint2">The control point2.</param>
|
||||
/// <param name="endPoint">The end point.</param>
|
||||
public void AddBezier(Point controlPoint1, Point controlPoint2, Point endPoint)
|
||||
{
|
||||
endPoint = endPoint.Transform(this.currentTransform);
|
||||
this.segments.Add(new BezierLineSegment(
|
||||
this.currentPoint,
|
||||
controlPoint1.Transform(this.currentTransform),
|
||||
controlPoint2.Transform(this.currentTransform),
|
||||
endPoint));
|
||||
this.currentPoint = endPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the current point.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
public void MoveTo(Point point)
|
||||
{
|
||||
if (this.segments.Any())
|
||||
{
|
||||
this.figures.Add(this.segments.ToArray());
|
||||
this.segments.Clear();
|
||||
}
|
||||
|
||||
this.currentPoint = point.Transform(this.currentTransform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the current point
|
||||
/// </summary>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
public void MoveTo(float x, float y)
|
||||
{
|
||||
this.MoveTo(new Point(x, y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a complex polygon fromn the current working set of working operations.
|
||||
/// </summary>
|
||||
/// <returns>The current set of operations as a complex polygon</returns>
|
||||
public ComplexPolygon Build()
|
||||
{
|
||||
var isWorking = this.segments.Any();
|
||||
|
||||
var shapes = new IShape[this.figures.Count + (isWorking ? 1 : 0)];
|
||||
var index = 0;
|
||||
foreach (var segments in this.figures)
|
||||
{
|
||||
shapes[index++] = new Polygon(segments);
|
||||
}
|
||||
|
||||
if (isWorking)
|
||||
{
|
||||
shapes[index++] = new Polygon(this.segments.ToArray());
|
||||
}
|
||||
|
||||
return new ComplexPolygon(shapes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Shaper2D.Tests
|
|||
public void SingleSegmentConstructor()
|
||||
{
|
||||
var segment = new BezierLineSegment(new Point(0, 0), new Point(10, 0), new Point(10, 0), new Point(20, 0));
|
||||
var points = segment.AsSimpleLinearPath();
|
||||
var points = segment.Flatten();
|
||||
Assert.Equal(51, points.Length);
|
||||
Assert.Contains(new Point(0, 0), points);
|
||||
Assert.Contains(new Point(10, 0), points);
|
||||
|
|
|
@ -12,9 +12,10 @@ namespace Shaper2D.Tests
|
|||
public void SingleSegmentConstructor()
|
||||
{
|
||||
var segment = new LinearLineSegment(new Point(0, 0), new Point(10, 10));
|
||||
Assert.Equal(2, segment.AsSimpleLinearPath().Length);
|
||||
Assert.Equal(new Point(0, 0), segment.AsSimpleLinearPath()[0]);
|
||||
Assert.Equal(new Point(10, 10), segment.AsSimpleLinearPath()[1]);
|
||||
var flatPath = segment.Flatten();
|
||||
Assert.Equal(2, flatPath.Length);
|
||||
Assert.Equal(new Point(0, 0), flatPath[0]);
|
||||
Assert.Equal(new Point(10, 10), flatPath[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace Shaper2D.Tests
|
|||
public void SimplePath()
|
||||
{
|
||||
var path = new Path(new LinearLineSegment(new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)));
|
||||
var points = path.AsSimpleLinearPath();
|
||||
var points = path.Flatten();
|
||||
|
||||
Assert.Equal(4, points.Length);
|
||||
Assert.Equal(new Point(0, 0), points[0]);
|
||||
|
|
|
@ -19,19 +19,19 @@ namespace Shaper2D.Tests.PolygonClipper
|
|||
private Rectangle TopLeft = new Rectangle(0,0, 20,20);
|
||||
private Rectangle TopRight = new Rectangle(30,0, 20,20);
|
||||
|
||||
private IShape[] Clip(IPath shape, params IPath[] hole)
|
||||
private ImmutableArray<IShape> Clip(IPath shape, params IPath[] hole)
|
||||
{
|
||||
var clipper = new Clipper();
|
||||
|
||||
clipper.AddPath(shape, PolyType.Subject);
|
||||
clipper.AddPath(shape, ClippingType.Subject);
|
||||
if (hole != null)
|
||||
{
|
||||
foreach (var s in hole)
|
||||
{
|
||||
clipper.AddPath(s, PolyType.Clip);
|
||||
clipper.AddPath(s, ClippingType.Clip);
|
||||
}
|
||||
}
|
||||
return clipper.Execute();
|
||||
return clipper.GenerateClippedShapes();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -66,10 +66,10 @@ namespace Shaper2D.Tests.PolygonClipper
|
|||
{
|
||||
var clipper = new Clipper();
|
||||
var mockPath = new Mock<IPath>();
|
||||
mockPath.Setup(x => x.AsSimpleLinearPath())
|
||||
mockPath.Setup(x => x.Flatten())
|
||||
.Returns(ImmutableArray.Create(new Point(0, 0), new Point(1, 1), new Point(0, 0)));
|
||||
|
||||
Assert.Throws<ClipperException>(() => { clipper.AddPath(mockPath.Object, PolyType.Subject); });
|
||||
Assert.Throws<ClipperException>(() => { clipper.AddPath(mockPath.Object, ClippingType.Subject); });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -77,10 +77,10 @@ namespace Shaper2D.Tests.PolygonClipper
|
|||
{
|
||||
var clipper = new Clipper();
|
||||
var mockPath = new Mock<IPath>();
|
||||
mockPath.Setup(x => x.AsSimpleLinearPath())
|
||||
mockPath.Setup(x => x.Flatten())
|
||||
.Returns(ImmutableArray.Create(new Point(0, 0), new Point(1, 1), new Point(1, 1)));
|
||||
|
||||
Assert.Throws<ClipperException>(() => { clipper.AddPath(mockPath.Object, PolyType.Subject); });
|
||||
Assert.Throws<ClipperException>(() => { clipper.AddPath(mockPath.Object, ClippingType.Subject); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ namespace Shaper2D.Tests
|
|||
public void AsSimpleLinearPath()
|
||||
{
|
||||
var poly = new Polygon(new LinearLineSegment(new Point(0, 0), new Point(0, 10), new Point(5, 5)));
|
||||
var paths = poly.AsSimpleLinearPath();
|
||||
var paths = poly.Flatten();
|
||||
Assert.Equal(3, paths.Length);
|
||||
Assert.Equal(new Point(0, 0), paths[0]);
|
||||
Assert.Equal(new Point(0, 10), paths[1]);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
|
@ -175,7 +176,7 @@ namespace Shaper2D.Tests
|
|||
public void LienearSegements()
|
||||
{
|
||||
IPath shape = new Rectangle(10, 11, 12, 13);
|
||||
var segemnts = shape.AsSimpleLinearPath();
|
||||
var segemnts = shape.Flatten();
|
||||
Assert.Equal(new Point(10, 11), segemnts[0]);
|
||||
Assert.Equal(new Point(22, 11), segemnts[1]);
|
||||
Assert.Equal(new Point(22, 24), segemnts[2]);
|
||||
|
@ -243,5 +244,26 @@ namespace Shaper2D.Tests
|
|||
|
||||
Assert.Equal((IPath)shape, shape.Paths.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransformIdnetityReturnsSahpeObject()
|
||||
{
|
||||
|
||||
Rectangle shape = new Rectangle(0, 0, 200, 60);
|
||||
var transformdShape = shape.Transform(Matrix3x2.Identity);
|
||||
|
||||
Assert.Same(shape, transformdShape);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform()
|
||||
{
|
||||
Rectangle shape = new Rectangle(0, 0, 200, 60);
|
||||
|
||||
var newShape = (Rectangle)shape.Transform(new Matrix3x2(0, 1, 1, 0, 20, 2));
|
||||
|
||||
Assert.Equal(new Point(20, 2), newShape.Location);
|
||||
Assert.Equal(new Size(60, 200), newShape.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче