This commit is contained in:
Scott Williams 2017-01-21 16:15:29 +00:00
Родитель 083c5f4013
Коммит b7c7e684cc
25 изменённых файлов: 935 добавлений и 540 удалений

Просмотреть файл

@ -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
@ -26,31 +25,53 @@ namespace Shaper2D
private ImmutableArray<IPath> paths;
/// <summary>
/// Initializes a new instance of the <see cref="ComplexPolygon"/> class.
/// 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));
/// <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)
{
Guard.NotNull(outlines, nameof(outlines));
Guard.MustBeGreaterThanOrEqualTo(outlines.Length, 1, nameof(outlines));
this.shapes = shapes;
var pathCount = shapes.Sum(x => x.Paths.Length);
var paths = new IPath[pathCount];
int index = 0;
this.MaxIntersections = this.FixAndSetShapes(outlines, holes);
float minX = float.MaxValue;
float maxX = float.MinValue;
float minY = float.MaxValue;
float maxY = float.MinValue;
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);
foreach (var s in shapes)
{
if (s.Bounds.Left < minX)
{
minX = s.Bounds.Left;
}
if (s.Bounds.Right > maxX)
{
maxX = s.Bounds.Right;
}
if (s.Bounds.Top < minY)
{
minY = s.Bounds.Top;
}
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,256 +28,34 @@ 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>
/// Adds the paths.
/// Initializes a new instance of the <see cref="Clipper"/> class.
/// </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="shapes">The shapes.</param>
public Clipper(IEnumerable<ClipableShape> shapes)
{
foreach (var p in path)
{
this.AddPath(p, polyType);
}
Guard.NotNull(shapes, nameof(shapes));
this.AddShapes(shapes);
}
/// <summary>
/// Adds the path.
/// Initializes a new instance of the <see cref="Clipper" /> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="polyType">Type of the poly.</param>
public void AddPath(IShape path, PolyType polyType)
/// <param name="shapes">The shapes.</param>
public Clipper(params ClipableShape[] shapes)
{
if (path is IPath)
{
this.AddPath((IPath)path, polyType);
}
else
{
foreach (var p in path.Paths)
{
this.AddPath(p, polyType);
}
}
}
/// <summary>
/// Adds the path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="polyType">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)
{
var points = path.AsSimpleLinearPath();
int hi = points.Length - 1;
while (hi > 0 && (points[hi] == points[0]))
{
--hi;
}
while (hi > 0 && (points[hi] == points[hi - 1]))
{
--hi;
}
if (hi < 2)
{
throw new ClipperException("must have more than 2 distinct points");
}
// create a new edge array ...
List<Edge> edges = new List<Edge>(hi + 1);
for (int i = 0; i <= hi; i++)
{
edges.Add(new Edge() { SourcePath = path });
}
bool isFlat = true;
// 1. Basic (first) edge initialization ...
edges[1].Current = points[1];
InitEdge(edges[0], edges[1], edges[hi], points[0]);
InitEdge(edges[hi], edges[0], edges[hi - 1], points[hi]);
for (int i = hi - 1; i >= 1; --i)
{
InitEdge(edges[i], edges[i + 1], edges[i - 1], points[i]);
}
Edge startEdge = edges[0];
// 2. Remove duplicate vertices, and (when closed) collinear edges ...
Edge edge = startEdge;
Edge loopStop = startEdge;
while (true)
{
if (edge.Current == edge.NextEdge.Current)
{
//remove unneeded edges
if (edge == edge.NextEdge)
{
break;
}
if (edge == startEdge)
{
startEdge = edge.NextEdge;
}
edge = RemoveEdge(edge);
loopStop = edge;
continue;
}
if (SlopesEqual(edge.PreviousEdge.Current, edge.Current, edge.NextEdge.Current))
{
// Collinear edges are allowed for open paths but in closed paths
// the default is to merge adjacent collinear edges into a single edge.
// However, if the PreserveCollinear property is enabled, only overlapping
// collinear edges (ie spikes) will be removed from closed paths.
if (edge == startEdge)
{
startEdge = edge.NextEdge;
}
edge = RemoveEdge(edge);
edge = edge.PreviousEdge;
loopStop = edge;
continue;
}
edge = edge.NextEdge;
if (edge == loopStop)
{
break;
}
}
if (edge.PreviousEdge == edge.NextEdge)
{
return false;
}
// 3. Do second stage of edge initialization ...
edge = startEdge;
do
{
this.InitEdge2(edge, polyType);
edge = edge.NextEdge;
if (isFlat && edge.Current.Y != startEdge.Current.Y)
{
isFlat = false;
}
}
while (edge != startEdge);
// 4. Finally, add edge bounds to LocalMinima list ...
// Totally flat paths must be handled differently when adding them
// to LocalMinima list to avoid endless loops etc ...
if (isFlat)
{
return false;
}
this.edges.Add(edges);
Edge loopBreakerEdge = null;
// workaround to avoid an endless loop in the while loop below when
// open paths have matching start and end points ...
if (edge.PreviousEdge.Bottom == edge.PreviousEdge.Top)
{
edge = edge.NextEdge;
}
while (true)
{
edge = FindNextLocMin(edge);
if (edge == loopBreakerEdge)
{
break;
}
else if (loopBreakerEdge == null)
{
loopBreakerEdge = edge;
}
// E and E.Prev now share a local minima (left aligned if horizontal).
// Compare their slopes to find which starts which bound ...
LocalMinima locMin = new LocalMinima
{
Next = null,
Y = edge.Bottom.Y
};
bool leftBoundIsForward;
if (edge.Dx < edge.PreviousEdge.Dx)
{
locMin.LeftBound = edge.PreviousEdge;
locMin.RightBound = edge;
leftBoundIsForward = false; // Q.nextInLML = Q.prev
}
else
{
locMin.LeftBound = edge;
locMin.RightBound = edge.PreviousEdge;
leftBoundIsForward = true; // Q.nextInLML = Q.next
}
locMin.LeftBound.Side = EdgeSide.Left;
locMin.RightBound.Side = EdgeSide.Right;
if (locMin.LeftBound.NextEdge == locMin.RightBound)
{
locMin.LeftBound.WindingDelta = -1;
}
else
{
locMin.LeftBound.WindingDelta = 1;
}
locMin.RightBound.WindingDelta = -locMin.LeftBound.WindingDelta;
edge = this.ProcessBound(locMin.LeftBound, leftBoundIsForward);
if (edge.OutIndex == Skip)
{
edge = this.ProcessBound(edge, leftBoundIsForward);
}
Edge edge2 = this.ProcessBound(locMin.RightBound, !leftBoundIsForward);
if (edge2.OutIndex == Skip)
{
edge2 = this.ProcessBound(edge2, !leftBoundIsForward);
}
if (locMin.LeftBound.OutIndex == Skip)
{
locMin.LeftBound = null;
}
else if (locMin.RightBound.OutIndex == Skip)
{
locMin.RightBound = null;
}
this.InsertLocalMinima(locMin);
if (!leftBoundIsForward)
{
edge = edge2;
}
}
return true;
this.AddShapes(shapes);
}
/// <summary>
@ -285,18 +64,299 @@ namespace Shaper2D.PolygonClipper
/// <returns>
/// Returns the <see cref="IShape" /> array containing the converted polygons.
/// </returns>
public IShape[] Execute()
public ImmutableArray<IShape> GenerateClippedShapes()
{
PolyTree polytree = new PolyTree();
bool succeeded = this.ExecuteInternal();
// build the return polygons ...
if (succeeded)
if (!this.resultsDirty)
{
this.BuildResult2(polytree);
return this.results;
}
return ExtractOutlines(polytree).ToArray();
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="clipableShaps">The clipable shaps.</param>
public void AddShapes(IEnumerable<ClipableShape> clipableShaps)
{
foreach (var p in clipableShaps)
{
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="shape">The shape.</param>
/// <param name="clippingType">The clipping type.</param>
internal void AddShape(IShape shape, ClippingType clippingType)
{
if (shape is IPath)
{
this.AddPath((IPath)shape, clippingType);
}
else
{
foreach (var p in shape.Paths)
{
this.AddPath(p, clippingType);
}
}
}
/// <summary>
/// Adds the path.
/// </summary>
/// <param name="path">The path.</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>
internal bool AddPath(IPath path, ClippingType clippingType)
{
// 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]))
{
--hi;
}
while (hi > 0 && (points[hi] == points[hi - 1]))
{
--hi;
}
if (hi < 2)
{
throw new ClipperException("must have more than 2 distinct points");
}
// create a new edge array ...
List<Edge> edges = new List<Edge>(hi + 1);
for (int i = 0; i <= hi; i++)
{
edges.Add(new Edge() { SourcePath = path });
}
bool isFlat = true;
// 1. Basic (first) edge initialization ...
edges[1].Current = points[1];
InitEdge(edges[0], edges[1], edges[hi], points[0]);
InitEdge(edges[hi], edges[0], edges[hi - 1], points[hi]);
for (int i = hi - 1; i >= 1; --i)
{
InitEdge(edges[i], edges[i + 1], edges[i - 1], points[i]);
}
Edge startEdge = edges[0];
// 2. Remove duplicate vertices, and (when closed) collinear edges ...
Edge edge = startEdge;
Edge loopStop = startEdge;
while (true)
{
if (edge.Current == edge.NextEdge.Current)
{
// remove unneeded edges
if (edge == edge.NextEdge)
{
break;
}
if (edge == startEdge)
{
startEdge = edge.NextEdge;
}
edge = RemoveEdge(edge);
loopStop = edge;
continue;
}
if (SlopesEqual(edge.PreviousEdge.Current, edge.Current, edge.NextEdge.Current))
{
// Collinear edges are allowed for open paths but in closed paths
// the default is to merge adjacent collinear edges into a single edge.
// However, if the PreserveCollinear property is enabled, only overlapping
// collinear edges (ie spikes) will be removed from closed paths.
if (edge == startEdge)
{
startEdge = edge.NextEdge;
}
edge = RemoveEdge(edge);
edge = edge.PreviousEdge;
loopStop = edge;
continue;
}
edge = edge.NextEdge;
if (edge == loopStop)
{
break;
}
}
if (edge.PreviousEdge == edge.NextEdge)
{
return false;
}
// 3. Do second stage of edge initialization ...
edge = startEdge;
do
{
this.InitEdge2(edge, clippingType);
edge = edge.NextEdge;
if (isFlat && edge.Current.Y != startEdge.Current.Y)
{
isFlat = false;
}
}
while (edge != startEdge);
// 4. Finally, add edge bounds to LocalMinima list ...
// Totally flat paths must be handled differently when adding them
// to LocalMinima list to avoid endless loops etc ...
if (isFlat)
{
return false;
}
this.edges.Add(edges);
Edge loopBreakerEdge = null;
// workaround to avoid an endless loop in the while loop below when
// open paths have matching start and end points ...
if (edge.PreviousEdge.Bottom == edge.PreviousEdge.Top)
{
edge = edge.NextEdge;
}
while (true)
{
edge = FindNextLocMin(edge);
if (edge == loopBreakerEdge)
{
break;
}
else if (loopBreakerEdge == null)
{
loopBreakerEdge = edge;
}
// E and E.Prev now share a local minima (left aligned if horizontal).
// Compare their slopes to find which starts which bound ...
LocalMinima locMin = new LocalMinima
{
Next = null,
Y = edge.Bottom.Y
};
bool leftBoundIsForward;
if (edge.Dx < edge.PreviousEdge.Dx)
{
locMin.LeftBound = edge.PreviousEdge;
locMin.RightBound = edge;
leftBoundIsForward = false; // Q.nextInLML = Q.prev
}
else
{
locMin.LeftBound = edge;
locMin.RightBound = edge.PreviousEdge;
leftBoundIsForward = true; // Q.nextInLML = Q.next
}
locMin.LeftBound.Side = EdgeSide.Left;
locMin.RightBound.Side = EdgeSide.Right;
if (locMin.LeftBound.NextEdge == locMin.RightBound)
{
locMin.LeftBound.WindingDelta = -1;
}
else
{
locMin.LeftBound.WindingDelta = 1;
}
locMin.RightBound.WindingDelta = -locMin.LeftBound.WindingDelta;
edge = this.ProcessBound(locMin.LeftBound, leftBoundIsForward);
if (edge.OutIndex == Skip)
{
edge = this.ProcessBound(edge, leftBoundIsForward);
}
Edge edge2 = this.ProcessBound(locMin.RightBound, !leftBoundIsForward);
if (edge2.OutIndex == Skip)
{
edge2 = this.ProcessBound(edge2, !leftBoundIsForward);
}
if (locMin.LeftBound.OutIndex == Skip)
{
locMin.LeftBound = null;
}
else if (locMin.RightBound.OutIndex == Skip)
{
locMin.RightBound = null;
}
this.InsertLocalMinima(locMin);
if (!leftBoundIsForward)
{
edge = edge2;
}
}
return true;
}
}
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;
}
}
}
}

Просмотреть файл

@ -39,16 +39,16 @@ namespace Shaper2D
}
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class.
/// 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);
}
}
}