Migrate shaper code from ImageSharp to Shaper2D

This commit is contained in:
Scott Williams 2017-01-14 11:40:36 +00:00
Родитель 12d3d5a9be
Коммит 10cf98fab8
47 изменённых файлов: 7544 добавлений и 6 удалений

3
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,3 @@
[*.cs]
indent_style = space
indent_size = 4

13
APACHE-2.0-LICENSE.txt Normal file
Просмотреть файл

@ -0,0 +1,13 @@
Copyright 2012 James South
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

124
README.md Normal file
Просмотреть файл

@ -0,0 +1,124 @@
# <img src="build/icons/imagesharp-logo-64.png" width="52" height="52"/> ImageSharp
**ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`.
> **ImageSharp is still in early stages (alpha) but progress has been pretty quick. As such, please do not use on production environments until the library reaches release candidate status. Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).**
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues)
[![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network)
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south)
| |Build Status|Code Coverage|
|-------------|:----------:|:-----------:|
|**Linux/Mac**|[![Build Status](https://travis-ci.org/JimBobSquarePants/ImageSharp.svg)](https://travis-ci.org/JimBobSquarePants/ImageSharp)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)|
|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/hu6d1gdpxdw0q360/branch/master?svg=true)](https://ci.appveyor.com/project/JamesSouth/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)|
### Installation
At present the code is pre-release but when ready it will be available on [Nuget](http://www.nuget.org).
**Pre-release downloads**
We already have a [MyGet package repository](https://www.myget.org/gallery/imagesharp) - for bleeding-edge / development NuGet releases.
### Packages
The **ImageSharp** library is made up of multiple packages, to make **ImageSharp** do anything useful you will want to make sure you include at least one format as a dependency otherwise you will not be able to save/load any images.
Packages include:
- **ImageSharp**
Contains the Image classes, Colors, Primitives, Bootstrapper, IImageFormat interface, and other core functionality.
- **ImageSharp.Formats.Jpeg**
The jpeg decoder/encoder (Auto registered)
- **ImageSharp.Formats.Png**
The png decoder/encoder (Auto registered)
- **ImageSharp.Formats.Gif**
The gif decoder/encoder (Auto registered)
- **ImageSharp.Formats.Bmp**
The bmp decoder/encoder (Auto registered)
- **ImageSharp.Processing**
Contains methods like Resize, Crop, Skew, Rotate - Anything that alters the dimensions of the image.
Contains methods like Gaussian Blur, Pixelate, Edge Detection - Anything that maintains the original image dimensions.
- **ImageSharp.Drawing**
Brushes and various drawing algorithms.
### Manual build
If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need:
- [Visual Studio 2015 with Update 3 (or above)](https://www.visualstudio.com/news/releasenotes/vs2015-update3-vs)
- The [.NET Core 1.0 SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link.
To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash
git clone https://github.com/JimBobSquarePants/ImageSharp
```
### Features
There's plenty there and more coming. Check out the [current features](features.md)!
### API
Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks. Images and processors are thread safe usable in parallel processing utilizing all the availables cores.
Many `Image` methods are also fluent.
Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix.
```csharp
using (FileStream stream = File.OpenRead("foo.jpg"))
using (FileStream output = File.OpenWrite("bar.jpg"))
{
Image image = new Image(stream);
image.Resize(image.Width / 2, image.Height / 2)
.Grayscale()
.Save(output);
}
```
Individual processors can be initialised and apply processing against images. This allows nesting which brings the potential for powerful combinations of processing methods:
```csharp
new BrightnessProcessor(50).Apply(sourceImage, sourceImage.Bounds);
```
Setting individual pixel values is perfomed as follows:
```csharp
Image image = new Image(400, 400);
using (var pixels = image.Lock())
{
pixels[200, 200] = Color.White;
}
```
For advanced usage the `Image<TColor>` and `PixelAccessor<TColor>` classes are available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.
### How can you help?
Please... Spread the word, contribute algorithms, submit performance improvements, unit tests.
Performance is a biggie, if you know anything about the new vector types and can apply some fancy new stuff with that it would be awesome.
There's a lot of developers out there who could write this stuff a lot better and faster than I and I would love to see what we collectively can come up with so please, if you can help in any way it would be most welcome and benificial for all.
### The ImageSharp Team
Grand High Eternal Dictator
- [Scott Williams](https://github.com/jimbobsquarepants)
Core Team
- [Dirk Lemstra](https://github.com/dlemstra)
- [Jeavon Leopold](https://github.com/jeavon)
- [Anton Firsov](https://github.com/antonfirsov)
- [Olivia Ifrim](https://github.com/olivif)
- [Scott Williams](https://github.com/tocsoft)

6
Shaper2D.ruleset Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="ImageSharp" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1413" Action="None" />
</Rules>
</RuleSet>

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

@ -3,9 +3,26 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shaper2D", "Shaper2D\Shaper2D.csproj", "{8EC582C9-750F-48BC-B48E-B3F89A5BA7B7}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
global.json = global.json
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Items", "Items", "{20777789-C567-4304-BB8D-11FAF5328812}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E574A07-F879-4811-9C41-5CBDC6BAFDB7}"
ProjectSection(SolutionItems) = preProject
src\Shared\AssemblyInfo.Common.cs = src\Shared\AssemblyInfo.Common.cs
src\Shared\stylecop.json = src\Shared\stylecop.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shaper2D", "src\Shaper2D\Shaper2D.xproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shaper2D.Tests", "tests\Shaper2D.Tests\Shaper2D.Tests.xproj", "{F836E8E6-B4D9-4208-8346-140C74678B91}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -13,12 +30,21 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8EC582C9-750F-48BC-B48E-B3F89A5BA7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EC582C9-750F-48BC-B48E-B3F89A5BA7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EC582C9-750F-48BC-B48E-B3F89A5BA7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EC582C9-750F-48BC-B48E-B3F89A5BA7B7}.Release|Any CPU.Build.0 = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
EndGlobalSection
EndGlobal

6
global.json Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

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

@ -0,0 +1,120 @@
// <copyright file="BezierLineSegment.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections.Immutable;
using System.Numerics;
/// <summary>
/// Represents a line segment that conistst of control points that will be rendered as a cubic bezier curve
/// </summary>
/// <seealso cref="Shaper2D.ILineSegment" />
public class BezierLineSegment : ILineSegment
{
/// <summary>
/// The segments per curve.
/// code for this taken from <see href="http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/"/>
/// </summary>
private const int SegmentsPerCurve = 50;
/// <summary>
/// The line points.
/// </summary>
private readonly Point[] linePoints;
/// <summary>
/// Initializes a new instance of the <see cref="BezierLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
public BezierLineSegment(params Point[] points)
{
Guard.NotNull(points, nameof(points));
Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points));
this.linePoints = this.GetDrawingPoints(points);
}
/// <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()
{
return ImmutableArray.Create(this.linePoints);
}
/// <summary>
/// Returns the drawing points along the line.
/// </summary>
/// <param name="controlPoints">The control points.</param>
/// <returns>
/// The <see cref="T:Vector2[]"/>.
/// </returns>
private Point[] GetDrawingPoints(Point[] controlPoints)
{
// TODO we need to calculate an optimal SegmentsPerCurve value
// depending on the calcualted length of this curve
int curveCount = (controlPoints.Length - 1) / 3;
int finalPointCount = (SegmentsPerCurve * curveCount) + 1; // we have SegmentsPerCurve for each curve plus the origon point;
Point[] drawingPoints = new Point[finalPointCount];
int position = 0;
int targetPoint = controlPoints.Length - 3;
for (int i = 0; i < targetPoint; i += 3)
{
Vector2 p0 = controlPoints[i];
Vector2 p1 = controlPoints[i + 1];
Vector2 p2 = controlPoints[i + 2];
Vector2 p3 = controlPoints[i + 3];
// only do this for the first end point. When i != 0, this coincides with the end point of the previous segment,
if (i == 0)
{
drawingPoints[position++] = this.CalculateBezierPoint(0, p0, p1, p2, p3);
}
for (int j = 1; j <= SegmentsPerCurve; j++)
{
float t = j / (float)SegmentsPerCurve;
drawingPoints[position++] = this.CalculateBezierPoint(t, p0, p1, p2, p3);
}
}
return drawingPoints;
}
/// <summary>
/// Calculates the bezier point along the line.
/// </summary>
/// <param name="t">The position within the line.</param>
/// <param name="p0">The p 0.</param>
/// <param name="p1">The p 1.</param>
/// <param name="p2">The p 2.</param>
/// <param name="p3">The p 3.</param>
/// <returns>
/// The <see cref="Vector2"/>.
/// </returns>
private Vector2 CalculateBezierPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector2 p = uuu * p0; // first term
p += 3 * uu * t * p1; // second term
p += 3 * u * tt * p2; // third term
p += ttt * p3; // fourth term
return p;
}
}
}

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

@ -0,0 +1,78 @@
// <copyright file="BezierPolygon.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
/// <summary>
/// Represents a polygon made up exclusivly of a single close cubic Bezier curve.
/// </summary>
public sealed class BezierPolygon : IShape
{
private Polygon innerPolygon;
/// <summary>
/// Initializes a new instance of the <see cref="BezierPolygon"/> class.
/// </summary>
/// <param name="points">The points.</param>
public BezierPolygon(params Point[] points)
{
this.innerPolygon = new Polygon(new BezierLineSegment(points));
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds => this.innerPolygon.Bounds;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => this.innerPolygon.MaxIntersections;
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
public IEnumerable<IPath> Paths => this.innerPolygon.Paths;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The distance from the shape.
/// </returns>
public float Distance(Point point) => this.innerPolygon.Distance(point);
/// <summary>
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
/// populate a buffer for all points on the polygon that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset)
{
return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
}
}
}

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

@ -0,0 +1,230 @@
// <copyright file="ComplexPolygon.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;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
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.
/// </summary>
/// <seealso cref="Shaper2D.IShape" />
public sealed class ComplexPolygon : IShape
{
private const float ClipperScaleFactor = 100f;
private IShape[] shapes;
private IEnumerable<IPath> paths;
/// <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)
{
}
/// <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.MaxIntersections = this.FixAndSetShapes(outlines, holes);
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);
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
public IEnumerable<IPath> Paths => this.paths;
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds { get; }
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from thr shape to the point
/// </returns>
/// <remarks>
/// Due to the clipping we did during construction we know that out shapes do not overlap at there edges
/// therefore for apoint to be in more that one we must be in a hole of another, theoretically this could
/// then flip again to be in a outlin inside a hole inside an outline :)
/// </remarks>
float IShape.Distance(Point point)
{
float dist = float.MaxValue;
bool inside = false;
foreach (IShape shape in this.shapes)
{
float d = shape.Distance(point);
if (d <= 0)
{
// we are inside a poly
d = -d; // flip the sign
inside ^= true; // flip the inside flag
}
if (d < dist)
{
dist = d;
}
}
if (inside)
{
return -dist;
}
return dist;
}
/// <summary>
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
/// populate a buffer for all points on all the polygons, that make up this complex shape,
/// that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset)
{
int totalAdded = 0;
for (int i = 0; i < this.shapes.Length; i++)
{
int added = this.shapes[i].FindIntersections(start, end, buffer, count, offset);
count -= added;
offset += added;
totalAdded += added;
}
return totalAdded;
}
private void AddPoints(Clipper clipper, IShape shape, PolyType polyType)
{
// if the path is already the shape use it directly and skip the path loop.
if (shape is IPath)
{
clipper.AddPath(
(IPath)shape,
polyType);
}
else
{
foreach (IPath path in shape.Paths)
{
clipper.AddPath(
path,
polyType);
}
}
}
private void AddPoints(Clipper clipper, IEnumerable<IShape> shapes, PolyType polyType)
{
foreach (IShape shape in shapes)
{
this.AddPoints(clipper, shape, polyType);
}
}
private void ExtractOutlines(PolyNode tree, List<IShape> shapes, List<IPath> paths)
{
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 optimisations that are availible.
if (tree.SourcePath != null)
{
shapes.Add((IShape)tree.SourcePath);
paths.Add(tree.SourcePath);
}
else
{
Polygon polygon = new Polygon(new LinearLineSegment(tree.Contour.Select(x => new Point(x)).ToArray()));
shapes.Add(polygon);
paths.Add(polygon);
}
}
foreach (PolyNode c in tree.Children)
{
this.ExtractOutlines(c, shapes, paths);
}
}
private int FixAndSetShapes(IEnumerable<IShape> outlines, IEnumerable<IShape> holes)
{
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
this.AddPoints(clipper, outlines, PolyType.Subject);
this.AddPoints(clipper, holes, PolyType.Clip);
PolyTree tree = clipper.Execute();
List<IShape> shapes = new List<IShape>();
List<IPath> paths = new List<IPath>();
// convert the 'tree' back to paths
this.ExtractOutlines(tree, shapes, paths);
this.shapes = shapes.ToArray();
this.paths = paths.ToArray();
int intersections = 0;
foreach (IShape s in this.shapes)
{
intersections += s.MaxIntersections;
}
return intersections;
}
}
}

234
src/Shaper2D/Guard.cs Normal file
Просмотреть файл

@ -0,0 +1,234 @@
// <copyright file="Guard.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.Diagnostics;
using System.Linq;
/// <summary>
/// Provides methods to protect against invalid parameters.
/// </summary>
[DebuggerStepThrough]
internal static class Guard
{
/// <summary>
/// Verifies, that the method parameter with specified object value is not null
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
public static void NotNull(object target, string parameterName, string message = "")
{
if (target == null)
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentNullException(parameterName, message);
}
throw new ArgumentNullException(parameterName);
}
}
/// <summary>
/// Verifies, that the string method parameter with specified object value and message
/// is not null, not empty and does not contain only blanks and throws an exception
/// if the object is null.
/// </summary>
/// <param name="target">The target string, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="target"/> is empty or contains only blanks.</exception>
public static void NotNullOrEmpty(string target, string parameterName, string message = "")
{
NotNull(target, parameterName, message);
if (string.IsNullOrWhiteSpace(target))
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentException(message, parameterName);
}
throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName);
}
}
/// <summary>
/// Verifies, that the enumeration is not null and not empty.
/// </summary>
/// <typeparam name="T">The type of objects in the <paramref name="target"/></typeparam>
/// <param name="target">The target enumeration, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="target"/> is empty.</exception>
public static void NotNullOrEmpty<T>(IEnumerable<T> target, string parameterName, string message = "")
{
NotNull(target, parameterName, message);
if (!target.Any())
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentException(message, parameterName);
}
throw new ArgumentException("Value cannot be empty.", parameterName);
}
}
/// <summary>
/// Verifies that the specified value is less than a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) >= 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}.");
}
}
/// <summary>
/// Verifies that the specified value is less than or equal to a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) <= 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
$"Value must be greater than {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value and less than
/// or equal to a maximum value and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
/// </exception>
public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue min, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min} and less than or equal to {max}.");
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is true
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">
/// The target value, which cannot be false.
/// </param>
/// <param name="parameterName">
/// The name of the parameter that is to be checked.
/// </param>
/// <param name="message">
/// The error message, if any to add to the exception.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is false
/// </exception>
public static void IsTrue(bool target, string parameterName, string message)
{
if (!target)
{
throw new ArgumentException(message, parameterName);
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is false
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target value, which cannot be true.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
public static void IsFalse(bool target, string parameterName, string message)
{
if (target)
{
throw new ArgumentException(message, parameterName);
}
}
}
}

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

@ -0,0 +1,22 @@
// <copyright file="ILineSegment.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections.Immutable;
using System.Numerics;
/// <summary>
/// Represents a simple path segment
/// </summary>
public interface ILineSegment
{
/// <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
}
}

48
src/Shaper2D/IPath.cs Normal file
Просмотреть файл

@ -0,0 +1,48 @@
// <copyright file="IPath.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Numerics;
/// <summary>
/// Represents a logic path that can be drawn
/// </summary>
public interface IPath : ILineSegment
{
/// <summary>
/// Gets the bounds enclosing the path
/// </summary>
/// <value>
/// The bounds.
/// </value>
Rectangle Bounds { get; }
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
bool IsClosed { get; }
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
float Length { get; }
/// <summary>
/// Calculates the distance along and away from the path for a specified point.
/// </summary>
/// <param name="point">The point along the path.</param>
/// <returns>
/// Returns details about the point and its distance away from the path.
/// </returns>
PointInfo Distance(Point point);
}
}

63
src/Shaper2D/IShape.cs Normal file
Просмотреть файл

@ -0,0 +1,63 @@
// <copyright file="IShape.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections.Generic;
using System.Numerics;
/// <summary>
/// Represents a closed set of paths making up a single shape.
/// </summary>
public interface IShape
{
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
Rectangle Bounds { get; }
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
IEnumerable<IPath> Paths { get; }
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
int MaxIntersections { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
float Distance(Point point);
/// <summary>
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
/// populate a buffer for all points on the polygon that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset);
}
}

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

@ -0,0 +1,517 @@
// <copyright file="InternalPath.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.Collections.Immutable;
using System.Linq;
using System.Numerics;
/// <summary>
/// Internal logic for integrating linear paths.
/// </summary>
internal class InternalPath
{
/// <summary>
/// The maximum vector
/// </summary>
private static readonly Vector2 MaxVector = new Vector2(float.MaxValue);
/// <summary>
/// The locker.
/// </summary>
private static readonly object Locker = new object();
/// <summary>
/// The points.
/// </summary>
private readonly ImmutableArray<Point> points;
/// <summary>
/// The closed path.
/// </summary>
private readonly bool closedPath;
/// <summary>
/// The total distance.
/// </summary>
private readonly Lazy<float> totalDistance;
/// <summary>
/// The constant.
/// </summary>
private float[] constant;
/// <summary>
/// The multiples.
/// </summary>
private float[] multiple;
/// <summary>
/// The distances.
/// </summary>
private float[] distance;
/// <summary>
/// The calculated.
/// </summary>
private bool calculated = false;
/// <summary>
/// Initializes a new instance of the <see cref="InternalPath"/> class.
/// </summary>
/// <param name="segments">The segments.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
internal InternalPath(ILineSegment[] segments, bool isClosedPath)
: this(Simplify(segments), isClosedPath)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="InternalPath" /> class.
/// </summary>
/// <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)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="InternalPath" /> class.
/// </summary>
/// <param name="points">The points.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
internal InternalPath(ImmutableArray<Point> points, bool isClosedPath)
{
this.points = points;
this.closedPath = isClosedPath;
float minX = this.points.Min(x => x.X);
float maxX = this.points.Max(x => x.X);
float minY = this.points.Min(x => x.Y);
float maxY = this.points.Max(x => x.Y);
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
this.totalDistance = new Lazy<float>(this.CalculateLength);
}
/// <summary>
/// Gets the bounds.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds
{
get;
}
/// <summary>
/// Gets the length.
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length => this.totalDistance.Value;
/// <summary>
/// Gets the points.
/// </summary>
/// <value>
/// The points.
/// </value>
internal ImmutableArray<Point> Points => this.points;
/// <summary>
/// Calculates the distance from the path.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>Returns the distance from the path</returns>
public PointInfo DistanceFromPath(Vector2 point)
{
this.CalculateConstants();
PointInfoInternal internalInfo = default(PointInfoInternal);
internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down
int polyCorners = this.points.Length;
if (!this.closedPath)
{
polyCorners -= 1;
}
int closestPoint = 0;
for (int i = 0; i < polyCorners; i++)
{
int next = i + 1;
if (this.closedPath && next == polyCorners)
{
next = 0;
}
if (this.CalculateShorterDistance(this.points[i], this.points[next], point, ref internalInfo))
{
closestPoint = i;
}
}
return new PointInfo
{
DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], point),
DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared),
SearchPoint = point,
ClosestPointOnPath = internalInfo.PointOnLine
};
}
/// <summary>
/// Based on a line described by <paramref name="start" /> and <paramref name="end" />
/// populate a buffer for all points on the path that the line intersects.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>number iof intersections hit</returns>
public int FindIntersections(Vector2 start, Vector2 end, Point[] buffer, int count, int offset)
{
int polyCorners = this.points.Length;
if (!this.closedPath)
{
polyCorners -= 1;
}
int position = 0;
for (int i = 0; i < polyCorners && count > 0; i++)
{
int next = i + 1;
if (this.closedPath && next == polyCorners)
{
next = 0;
}
Vector2 point = FindIntersection(this.points[i], this.points[next], start, end);
if (point != MaxVector)
{
buffer[position + offset] = point;
position++;
count--;
}
}
return position;
}
/// <summary>
/// Determines if the specified point is inside or outside the path.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>Returns true if the point is inside the closed path.</returns>
public bool PointInPolygon(Point point)
{
// You can only be inside a path if its "closed"
if (!this.closedPath)
{
return false;
}
if (!this.Bounds.Contains(point))
{
return false;
}
this.CalculateConstants();
ImmutableArray<Point> poly = this.points;
int polyCorners = poly.Length;
int j = polyCorners - 1;
bool oddNodes = false;
for (int i = 0; i < polyCorners; i++)
{
if ((poly[i].Y < point.Y && poly[j].Y >= point.Y)
|| (poly[j].Y < point.Y && poly[i].Y >= point.Y))
{
oddNodes ^= (point.Y * this.multiple[i]) + this.constant[i] < point.X;
}
j = i;
}
return oddNodes;
}
/// <summary>
/// Determins if the bounding box for 2 lines
/// described by <paramref name="line1Start" /> and <paramref name="line1End" />
/// and <paramref name="line2Start" /> and <paramref name="line2End" /> overlap.
/// </summary>
/// <param name="line1Start">The line1 start.</param>
/// <param name="line1End">The line1 end.</param>
/// <param name="line2Start">The line2 start.</param>
/// <param name="line2End">The line2 end.</param>
/// <returns>Returns true it the bounding box of the 2 lines intersect</returns>
private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
{
Vector2 topLeft1 = Vector2.Min(line1Start, line1End);
Vector2 bottomRight1 = Vector2.Max(line1Start, line1End);
Vector2 topLeft2 = Vector2.Min(line2Start, line2End);
Vector2 bottomRight2 = Vector2.Max(line2Start, line2End);
float left1 = topLeft1.X;
float right1 = bottomRight1.X;
float top1 = topLeft1.Y;
float bottom1 = bottomRight1.Y;
float left2 = topLeft2.X;
float right2 = bottomRight2.X;
float top2 = topLeft2.Y;
float bottom2 = bottomRight2.Y;
return left1 <= right2 && right1 >= left2
&&
top1 <= bottom2 && bottom1 >= top2;
}
/// <summary>
/// Finds the point on line described by <paramref name="line1Start" /> and <paramref name="line1End" />
/// that intersects with line described by <paramref name="line2Start" /> and <paramref name="line2End" />
/// </summary>
/// <param name="line1Start">The line1 start.</param>
/// <param name="line1End">The line1 end.</param>
/// <param name="line2Start">The line2 start.</param>
/// <param name="line2End">The line2 end.</param>
/// <returns>
/// A <see cref="Vector2"/> describing the point that the 2 lines cross or <see cref="MaxVector"/> if they do not.
/// </returns>
private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
{
// do bounding boxes overlap, if not then the lines can't and return fast.
if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End))
{
return MaxVector;
}
Vector2 line1Diff = line1End - line1Start;
Vector2 line2Diff = line2End - line2Start;
Vector2 point;
if (line1Diff.X == 0)
{
float slope = line2Diff.Y / line2Diff.X;
float yinter = line2Start.Y - (slope * line2Start.X);
float y = (line1Start.X * slope) + yinter;
point = new Vector2(line1Start.X, y);
// horizontal and vertical lines
}
else if (line2Diff.X == 0)
{
float slope = line1Diff.Y / line1Diff.X;
float yinter = line1Start.Y - (slope * line1Start.X);
float y = (line2Start.X * slope) + yinter;
point = new Vector2(line2Start.X, y);
// horizontal and vertical lines
}
else
{
float slope1 = line1Diff.Y / line1Diff.X;
float slope2 = line2Diff.Y / line2Diff.X;
float yinter1 = line1Start.Y - (slope1 * line1Start.X);
float yinter2 = line2Start.Y - (slope2 * line2Start.X);
if (slope1 == slope2 && yinter1 != yinter2)
{
return MaxVector;
}
float x = (yinter2 - yinter1) / (slope1 - slope2);
float y = (slope1 * x) + yinter1;
point = new Vector2(x, y);
}
if (BoundingBoxesIntersect(line1Start, line1End, point, point))
{
return point;
}
else if (BoundingBoxesIntersect(line2Start, line2End, point, point))
{
return point;
}
return MaxVector;
}
/// <summary>
/// Simplifies the collection of segments.
/// </summary>
/// <param name="segments">The segments.</param>
/// <returns>
/// The <see cref="T:Vector2[]"/>.
/// </returns>
private static ImmutableArray<Point> Simplify(ILineSegment[] segments)
{
List<Point> simplified = new List<Point>();
foreach (ILineSegment seg in segments)
{
simplified.AddRange(seg.AsSimpleLinearPath());
}
return simplified.ToImmutableArray();
}
/// <summary>
/// Returns the length of the path.
/// </summary>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private float CalculateLength()
{
float length = 0;
int polyCorners = this.points.Length;
if (!this.closedPath)
{
polyCorners -= 1;
}
for (int i = 0; i < polyCorners; i++)
{
int next = i + 1;
if (this.closedPath && next == polyCorners)
{
next = 0;
}
length += Vector2.Distance(this.points[i], this.points[next]);
}
return length;
}
/// <summary>
/// Calculate the constants.
/// </summary>
private void CalculateConstants()
{
// http://alienryderflex.com/polygon/ source for point in polygon logic
if (this.calculated)
{
return;
}
lock (Locker)
{
if (this.calculated)
{
return;
}
ImmutableArray<Point> poly = this.points;
int polyCorners = poly.Length;
this.constant = new float[polyCorners];
this.multiple = new float[polyCorners];
this.distance = new float[polyCorners];
int i, j = polyCorners - 1;
this.distance[0] = 0;
for (i = 0; i < polyCorners; i++)
{
this.distance[j] = this.distance[i] + Vector2.Distance(poly[i], poly[j]);
if (poly[j].Y == poly[i].Y)
{
this.constant[i] = poly[i].X;
this.multiple[i] = 0;
}
else
{
Vector2 subtracted = poly[j] - poly[i];
this.constant[i] = (poly[i].X - ((poly[i].Y * poly[j].X) / subtracted.Y)) + ((poly[i].Y * poly[i].X) / subtracted.Y);
this.multiple[i] = subtracted.X / subtracted.Y;
}
j = i;
}
this.calculated = true;
}
}
/// <summary>
/// Calculate any shorter distances along the path.
/// </summary>
/// <param name="start">The start position.</param>
/// <param name="end">The end position.</param>
/// <param name="point">The current point.</param>
/// <param name="info">The info.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
private bool CalculateShorterDistance(Vector2 start, Vector2 end, Vector2 point, ref PointInfoInternal info)
{
Vector2 diffEnds = end - start;
float lengthSquared = diffEnds.LengthSquared();
Vector2 diff = point - start;
Vector2 multiplied = diff * diffEnds;
float u = (multiplied.X + multiplied.Y) / lengthSquared;
if (u > 1)
{
u = 1;
}
else if (u < 0)
{
u = 0;
}
Vector2 multipliedByU = diffEnds * u;
Vector2 pointOnLine = start + multipliedByU;
Vector2 d = pointOnLine - point;
float dist = d.LengthSquared();
if (info.DistanceSquared > dist)
{
info.DistanceSquared = dist;
info.PointOnLine = pointOnLine;
return true;
}
return false;
}
/// <summary>
/// Contains information about the current point.
/// </summary>
private struct PointInfoInternal
{
/// <summary>
/// The distance squared.
/// </summary>
public float DistanceSquared;
/// <summary>
/// The point on the current line.
/// </summary>
public Point PointOnLine;
}
}
}

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

@ -0,0 +1,56 @@
// <copyright file="LinearLineSegment.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections.Immutable;
using System.Linq;
using System.Numerics;
/// <summary>
/// Represents a series of control points that will be joined by straight lines
/// </summary>
/// <seealso cref="ILineSegment" />
public class LinearLineSegment : ILineSegment
{
/// <summary>
/// The collection of points.
/// </summary>
private readonly ImmutableArray<Point> points;
/// <summary>
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
public LinearLineSegment(Point start, Point end)
: this(new[] { start, end })
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
public LinearLineSegment(params Point[] points)
{
Guard.NotNull(points, nameof(points));
Guard.MustBeGreaterThanOrEqualTo(points.Count(), 2, nameof(points));
this.points = ImmutableArray.Create(points);
}
/// <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()
{
return this.points;
}
}
}

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

@ -0,0 +1,84 @@
// <copyright file="LinearPolygon.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
/// <summary>
/// Represents a polygon made up exclusivly of a single Linear path.
/// </summary>
public sealed class LinearPolygon : IShape
{
private Polygon innerPolygon;
/// <summary>
/// Initializes a new instance of the <see cref="LinearPolygon"/> class.
/// </summary>
/// <param name="points">The points.</param>
public LinearPolygon(params Point[] points)
{
this.innerPolygon = new Polygon(new LinearLineSegment(points));
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds => this.innerPolygon.Bounds;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections
{
get
{
return this.innerPolygon.MaxIntersections;
}
}
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
public IEnumerable<IPath> Paths => this.innerPolygon.Paths;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
public float Distance(Point point) => this.innerPolygon.Distance(point);
/// <summary>
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
/// populate a buffer for all points on the polygon that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset)
{
return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
}
}
}

52
src/Shaper2D/Path.cs Normal file
Просмотреть файл

@ -0,0 +1,52 @@
// <copyright file="Path.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections.Immutable;
using System.Numerics;
/// <summary>
/// A aggregate of <see cref="ILineSegment"/>s making a single logical path
/// </summary>
/// <seealso cref="IPath" />
public class Path : IPath
{
/// <summary>
/// The inner path.
/// </summary>
private readonly InternalPath innerPath;
/// <summary>
/// Initializes a new instance of the <see cref="Path"/> class.
/// </summary>
/// <param name="segment">The segment.</param>
public Path(params ILineSegment[] segment)
{
this.innerPath = new InternalPath(segment, false);
}
/// <inheritdoc />
public Rectangle Bounds => this.innerPath.Bounds;
/// <inheritdoc />
public bool IsClosed => false;
/// <inheritdoc />
public float Length => this.innerPath.Length;
/// <inheritdoc />
public ImmutableArray<Point> AsSimpleLinearPath()
{
return this.innerPath.Points;
}
/// <inheritdoc />
public PointInfo Distance(Point point)
{
return this.innerPath.DistanceFromPath(point);
}
}
}

227
src/Shaper2D/Point.cs Normal file
Просмотреть файл

@ -0,0 +1,227 @@
// <copyright file="Point.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.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an ordered pair of integer x- and y-coordinates that defines a point in
/// a two-dimensional plane.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct Point : IEquatable<Point>
{
/// <summary>
/// Represents a <see cref="Point"/> that has X and Y values set to zero.
/// </summary>
public static readonly Point Empty = default(Point);
private readonly Vector2 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="x">The horizontal position of the point.</param>
/// <param name="y">The vertical position of the point.</param>
public Point(float x, float y)
: this(new Vector2(x, y))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="vector">
/// The vector representing the width and height.
/// </param>
public Point(Vector2 vector)
{
this.backingVector = vector;
}
/// <summary>
/// Gets the x-coordinate of this <see cref="Point"/>.
/// </summary>
public float X => this.backingVector.X;
/// <summary>
/// Gets the y-coordinate of this <see cref="Point"/>.
/// </summary>
public float Y => this.backingVector.Y;
/// <summary>
/// Gets a value indicating whether this <see cref="Point"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Performs an implicit conversion from <see cref="Point"/> to <see cref="Vector2"/>.
/// </summary>
/// <param name="d">The d.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(Point d)
{
return d.backingVector;
}
/// <summary>
/// Performs an implicit conversion from <see cref="Vector2"/> to <see cref="Point"/>.
/// </summary>
/// <param name="d">The d.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Point(Vector2 d)
{
return new Point(d);
}
/// <summary>
/// Computes the sum of adding two points.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator +(Point left, Point right)
{
return new Point(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one point from another.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator -(Point left, Point right)
{
return new Point(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="Point"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Point left, Point right)
{
return left.backingVector == right.backingVector;
}
/// <summary>
/// Compares two <see cref="Point"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Point left, Point right)
{
return left.backingVector != right.backingVector;
}
/// <summary>
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>.
/// </summary>
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public Vector2 ToVector2()
{
return this.backingVector;
}
/// <summary>
/// Translates this <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
/// <returns>A new point offset by the size</returns>
public Point Offset(float dx, float dy)
{
return new Point(this.backingVector + new Vector2(dx, dy));
}
/// <summary>
/// Translates this <see cref="Point" /> by the specified amount.
/// </summary>
/// <param name="p">The <see cref="Point" /> used offset this <see cref="Point" />.</param>
/// <returns>A new point offset by the size</returns>
public Point Offset(Size p)
{
return new Point(this.backingVector + new Vector2(p.Width, p.Height));
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Point [ Empty ]";
}
return $"Point [ X={this.X}, Y={this.Y} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Point)
{
return this.backingVector == ((Point)obj).backingVector;
}
return false;
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
/// </returns>
public bool Equals(Point other)
{
return this.backingVector == other.backingVector;
}
}
}

35
src/Shaper2D/PointInfo.cs Normal file
Просмотреть файл

@ -0,0 +1,35 @@
// <copyright file="PointInfo.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Numerics;
/// <summary>
/// Returns meta data about the nearest point on a path from a vector
/// </summary>
public struct PointInfo
{
/// <summary>
/// The search point
/// </summary>
public Point SearchPoint;
/// <summary>
/// The distance along path <see cref="ClosestPointOnPath"/> is away from the start of the path
/// </summary>
public float DistanceAlongPath;
/// <summary>
/// The distance <see cref="SearchPoint"/> is away from <see cref="ClosestPointOnPath"/>.
/// </summary>
public float DistanceFromPath;
/// <summary>
/// The closest point to <see cref="SearchPoint"/> that lies on the path.
/// </summary>
public Point ClosestPointOnPath;
}
}

141
src/Shaper2D/Polygon.cs Normal file
Просмотреть файл

@ -0,0 +1,141 @@
// <copyright file="Polygon.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace Shaper2D
{
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Numerics;
/// <summary>
/// A shape made up of a single path made up of one of more <see cref="ILineSegment"/>s
/// </summary>
public sealed class Polygon : IShape, IPath
{
private readonly InternalPath innerPath;
private readonly IEnumerable<IPath> pathCollection;
/// <summary>
/// Initializes a new instance of the <see cref="Polygon"/> class.
/// </summary>
/// <param name="segments">The segments.</param>
public Polygon(params ILineSegment[] segments)
{
this.innerPath = new InternalPath(segments, true);
this.pathCollection = new[] { this };
}
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> class.
/// </summary>
/// <param name="segment">The segment.</param>
public Polygon(ILineSegment segment)
{
this.innerPath = new InternalPath(segment, true);
this.pathCollection = new[] { this };
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds => this.innerPath.Bounds;
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length => this.innerPath.Length;
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
public bool IsClosed => true;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => this.innerPath.Points.Length;
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
public IEnumerable<IPath> Paths => this.pathCollection;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The distance of the point away from the shape
/// </returns>
public float Distance(Point point)
{
bool isInside = this.innerPath.PointInPolygon(point);
float distance = this.innerPath.DistanceFromPath(point).DistanceFromPath;
if (isInside)
{
return -distance;
}
return distance;
}
/// <summary>
/// Calcualtes the distance along and away from the path for a specified point.
/// </summary>
/// <param name="point">The point along the path.</param>
/// <returns>
/// distance metadata about the point.
/// </returns>
PointInfo IPath.Distance(Point point)
{
return this.innerPath.DistanceFromPath(point);
}
/// <summary>
/// Returns the current shape as a simple linear path.
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
public ImmutableArray<Point> AsSimpleLinearPath()
{
return this.innerPath.Points;
}
/// <summary>
/// Based on a line described by <paramref name="start" /> and <paramref name="end" />
/// populate a buffer for all points on the polygon that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset)
{
return this.innerPath.FindIntersections(start, end, buffer, count, offset);
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,29 @@
// <copyright file="ClipperException.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>
/// Clipper Exception
/// </summary>
/// <seealso cref="System.Exception" />
internal class ClipperException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ClipperException"/> class.
/// </summary>
/// <param name="description">The description.</param>
public ClipperException(string description)
: base(description)
{
}
}
}

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

@ -0,0 +1,29 @@
// <copyright file="Direction.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>
/// ???
/// </summary>
internal enum Direction
{
/// <summary>
/// The right to left
/// </summary>
RightToLeft,
/// <summary>
/// The left to right
/// </summary>
LeftToRight
}
}

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

@ -0,0 +1,29 @@
// <copyright file="EdgeSide.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>
/// ??
/// </summary>
internal enum EdgeSide
{
/// <summary>
/// The left
/// </summary>
Left,
/// <summary>
/// The right
/// </summary>
Right
}
}

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

@ -0,0 +1,36 @@
// <copyright file="IntersectNode.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>
/// ??
/// </summary>
internal class IntersectNode
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The edge1
/// </summary>
internal TEdge Edge1;
/// <summary>
/// The edge2
/// </summary>
internal TEdge Edge2;
/// <summary>
/// The pt
/// </summary>
internal System.Numerics.Vector2 Pt;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,46 @@
// <copyright file="IntersectNodeSort.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>
/// Compares <see cref="IntersectNode"/>s
/// </summary>
internal class IntersectNodeSort : IComparer<IntersectNode>
{
/// <summary>
/// Compares the specified node1.
/// </summary>
/// <param name="node1">The node1.</param>
/// <param name="node2">The node2.</param>
/// <returns>
/// 1 if node2 %gt; node1
/// -1 if node2 $lt; node1
/// 0 if same
/// </returns>
public int Compare(IntersectNode node1, IntersectNode node2)
{
float i = node2.Pt.Y - node1.Pt.Y;
if (i > 0)
{
return 1;
}
else if (i < 0)
{
return -1;
}
else
{
return 0;
}
}
}
}

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

@ -0,0 +1,36 @@
// <copyright file="Join.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>
/// ??
/// </summary>
internal class Join
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The out PT1
/// </summary>
internal OutPt OutPt1;
/// <summary>
/// The out PT2
/// </summary>
internal OutPt OutPt2;
/// <summary>
/// The off pt
/// </summary>
internal System.Numerics.Vector2 OffPt;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,42 @@
// <copyright file="LocalMinima.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>
/// ??
/// </summary>
internal class LocalMinima
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The y
/// </summary>
internal float Y;
/// <summary>
/// The left bound
/// </summary>
internal TEdge LeftBound;
/// <summary>
/// The right bound
/// </summary>
internal TEdge RightBound;
/// <summary>
/// The next
/// </summary>
internal LocalMinima Next;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,36 @@
// <copyright file="Maxima.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>
/// ??
/// </summary>
internal class Maxima
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The x
/// </summary>
internal float X;
/// <summary>
/// The next
/// </summary>
internal Maxima Next;
/// <summary>
/// The previous
/// </summary>
internal Maxima Prev;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,41 @@
// <copyright file="OutPt.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>
/// ??
/// </summary>
internal class OutPt
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The index
/// </summary>
internal int Idx;
/// <summary>
/// The pt
/// </summary>
internal System.Numerics.Vector2 Pt;
/// <summary>
/// The next
/// </summary>
internal OutPt Next;
/// <summary>
/// The previous
/// </summary>
internal OutPt Prev;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,62 @@
// <copyright file="OutRec.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>
/// OutRec: contains a path in the clipping solution. Edges in the AEL will
/// carry a pointer to an OutRec when they are part of the clipping solution.
/// </summary>
internal class OutRec
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The source path
/// </summary>
internal IPath SourcePath;
/// <summary>
/// The index
/// </summary>
internal int Idx;
/// <summary>
/// The is hole
/// </summary>
internal bool IsHole;
/// <summary>
/// The is open
/// </summary>
internal bool IsOpen;
/// <summary>
/// The first left
/// </summary>
internal OutRec FirstLeft;
/// <summary>
/// The PTS
/// </summary>
internal OutPt Pts;
/// <summary>
/// The bottom pt
/// </summary>
internal OutPt BottomPt;
/// <summary>
/// The poly node
/// </summary>
internal PolyNode PolyNode;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,177 @@
// <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
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The polygon
/// </summary>
internal List<Vector2> Polygon = new List<Vector2>();
/// <summary>
/// The index
/// </summary>
internal int Index;
/// <summary>
/// The childs
/// </summary>
protected List<PolyNode> children = new List<PolyNode>();
private PolyNode parent;
#pragma warning restore SA1401 // Field must be private
/// <summary>
/// Gets the child count.
/// </summary>
/// <value>
/// The child count.
/// </value>
public int ChildCount
{
get { return this.children.Count; }
}
/// <summary>
/// Gets the contour.
/// </summary>
/// <value>
/// The contour.
/// </value>
public List<Vector2> Contour
{
get { return this.Polygon; }
}
/// <summary>
/// Gets the childs.
/// </summary>
/// <value>
/// The childs.
/// </value>
public List<PolyNode> Children
{
get { return this.children; }
}
/// <summary>
/// Gets or sets the parent.
/// </summary>
/// <value>
/// The parent.
/// </value>
public PolyNode Parent
{
get { return this.parent; }
internal set { this.parent = value; }
}
/// <summary>
/// Gets a value indicating whether this instance is hole.
/// </summary>
/// <value>
/// <c>true</c> if this instance is hole; otherwise, <c>false</c>.
/// </value>
public bool IsHole
{
get { return this.IsHoleNode(); }
}
/// <summary>
/// Gets or sets a value indicating whether this instance is open.
/// </summary>
/// <value>
/// <c>true</c> if this instance is open; otherwise, <c>false</c>.
/// </value>
public bool IsOpen { get; set; }
/// <summary>
/// Gets or sets the source path.
/// </summary>
/// <value>
/// The source path.
/// </value>
public IPath SourcePath { get; internal set; }
/// <summary>
/// Gets the next.
/// </summary>
/// <returns>The next node</returns>
public PolyNode GetNext()
{
if (this.children.Count > 0)
{
return this.children[0];
}
else
{
return this.GetNextSiblingUp();
}
}
/// <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.parent = this;
child.Index = cnt;
}
/// <summary>
/// Gets the next sibling up.
/// </summary>
/// <returns>The next sibling up</returns>
internal PolyNode GetNextSiblingUp()
{
if (this.parent == null)
{
return null;
}
else if (this.Index == this.parent.children.Count - 1)
{
return this.parent.GetNextSiblingUp();
}
else
{
return this.parent.Children[this.Index + 1];
}
}
/// <summary>
/// Determines whether [is hole node].
/// </summary>
/// <returns>
/// <c>true</c> if [is hole node]; otherwise, <c>false</c>.
/// </returns>
private bool IsHoleNode()
{
bool result = true;
PolyNode node = this.parent;
while (node != null)
{
result = !result;
node = node.parent;
}
return result;
}
}
}

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

@ -0,0 +1,79 @@
// <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
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// All polys
/// </summary>
internal List<PolyNode> AllPolys = new List<PolyNode>();
#pragma warning restore SA1401 // Field must be private
/// <summary>
/// Gets the total.
/// </summary>
/// <value>
/// The total.
/// </value>
public int Total
{
get
{
int result = this.AllPolys.Count;
// with negative offsets, ignore the hidden outer polygon ...
if (result > 0 && this.Children[0] != this.AllPolys[0])
{
result--;
}
return result;
}
}
/// <summary>
/// Clears this instance.
/// </summary>
public void Clear()
{
for (int i = 0; i < this.AllPolys.Count; i++)
{
this.AllPolys[i] = null;
}
this.AllPolys.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;
}
}
}
}

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

@ -0,0 +1,29 @@
// <copyright file="PolyType.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 Type
/// </summary>
internal enum PolyType
{
/// <summary>
/// The subject
/// </summary>
Subject,
/// <summary>
/// The clip
/// </summary>
Clip
}
}

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

@ -0,0 +1,40 @@
# Clipper
License details for code in this folder, this is code original written by **Angus Johnson**
The license header onthe original file which has now be split across multiple files in this folder.
```
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.4.0 *
* Date : 2 July 2015 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2015 *
* *
* License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt *
* *
* Attributions: *
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
* "A generic solution to polygon clipping" *
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
* http://portal.acm.org/citation.cfm?id=129906 *
* *
* Computer graphics and geometric modeling: implementation and algorithms *
* By Max K. Agoston *
* Springer; 1 edition (January 4, 2005) *
* http://books.google.com/books?q=vatti+clipping+agoston *
* *
* See also: *
* "Polygon Offsetting by Computing Winding Numbers" *
* Paper no. DETC2005-85513 pp. 565-575 *
* ASME 2005 International Design Engineering Technical Conferences *
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
* September 24-28, 2005 , Long Beach, California, USA *
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
* *
*******************************************************************************/
```

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

@ -0,0 +1,31 @@
// <copyright file="Scanbeam.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>
/// Scanbeam
/// </summary>
internal class Scanbeam // would this work as a struct?
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The y
/// </summary>
internal float Y;
/// <summary>
/// The next
/// </summary>
internal Scanbeam Next;
#pragma warning restore SA1401 // Field must be private
}
}

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

@ -0,0 +1,116 @@
// <copyright file="TEdge.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>
/// TEdge
/// </summary>
internal class TEdge
{
#pragma warning disable SA1401 // Field must be private
/// <summary>
/// The source path, see if we can link this back later
/// </summary>
internal IPath SourcePath;
/// <summary>
/// The bot
/// </summary>
internal System.Numerics.Vector2 Bot;
/// <summary>
/// The current (updated for every new scanbeam)
/// </summary>
internal System.Numerics.Vector2 Curr;
/// <summary>
/// The top
/// </summary>
internal System.Numerics.Vector2 Top;
/// <summary>
/// The delta
/// </summary>
internal System.Numerics.Vector2 Delta;
/// <summary>
/// The dx
/// </summary>
internal double Dx;
/// <summary>
/// The poly type
/// </summary>
internal PolyType PolyTyp;
/// <summary>
/// Side only refers to current side of solution poly
/// </summary>
internal EdgeSide Side;
/// <summary>
/// 1 or -1 depending on winding direction
/// </summary>
internal int WindDelta;
/// <summary>
/// The winding count
/// </summary>
internal int WindCnt;
/// <summary>
/// The winding count of the opposite polytype
/// </summary>
internal int WindCnt2;
/// <summary>
/// The out index
/// </summary>
internal int OutIdx;
/// <summary>
/// The next
/// </summary>
internal TEdge Next;
/// <summary>
/// The previous
/// </summary>
internal TEdge Prev;
/// <summary>
/// The next in LML
/// </summary>
internal TEdge NextInLML;
/// <summary>
/// The next in ael
/// </summary>
internal TEdge NextInAEL;
/// <summary>
/// The previous in ael
/// </summary>
internal TEdge PrevInAEL;
/// <summary>
/// The next in sel
/// </summary>
internal TEdge NextInSEL;
/// <summary>
/// The previous in sel
/// </summary>
internal TEdge PrevInSEL;
#pragma warning restore SA1401 // Field must be
}
}

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

@ -0,0 +1,6 @@
// <copyright file="AssemblyInfo.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// Common values read from `AssemblyInfo.Common.cs`

345
src/Shaper2D/Rectangle.cs Normal file
Просмотреть файл

@ -0,0 +1,345 @@
// <copyright file="Rectangle.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// A way of optermising drawing rectangles.
/// </summary>
/// <seealso cref="Shaper2D.IShape" />
public class Rectangle : IShape, IPath
{
private readonly Vector2 topLeft;
private readonly Vector2 bottomRight;
private readonly ImmutableArray<Point> points;
private readonly IEnumerable<IPath> pathCollection;
private readonly float halfLength;
private readonly float length;
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle" /> class.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Rectangle(float x, float y, float width, float height)
: this(new Point(x, y), new Size(width, height))
{
}
/// <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;
this.topLeft = location;
this.bottomRight = location.Offset(size);
this.Size = size;
this.points = ImmutableArray.Create(new Point[4]
{
this.topLeft,
new Vector2(this.bottomRight.X, this.topLeft.Y),
this.bottomRight,
new Vector2(this.topLeft.X, this.bottomRight.Y)
});
this.halfLength = size.Width + size.Height;
this.length = this.halfLength * 2;
this.pathCollection = new[] { this };
}
/// <summary>
/// Gets the location.
/// </summary>
/// <value>
/// The location.
/// </value>
public Point Location { get; }
/// <summary>
/// Gets the left.
/// </summary>
/// <value>
/// The left.
/// </value>
public float Left => this.topLeft.X;
/// <summary>
/// Gets the right.
/// </summary>
/// <value>
/// The right.
/// </value>
public float Right => this.bottomRight.X;
/// <summary>
/// Gets the top.
/// </summary>
/// <value>
/// The top.
/// </value>
public float Top => this.topLeft.Y;
/// <summary>
/// Gets the bottom.
/// </summary>
/// <value>
/// The bottom.
/// </value>
public float Bottom => this.bottomRight.Y;
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
Rectangle IShape.Bounds => this;
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
Rectangle IPath.Bounds => this;
/// <summary>
/// Gets the paths that make up this shape
/// </summary>
/// <value>
/// The paths.
/// </value>
IEnumerable<IPath> IShape.Paths => this.pathCollection;
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
bool IPath.IsClosed => true;
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
float IPath.Length => this.length;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
int IShape.MaxIntersections => 4;
/// <summary>
/// Gets the size.
/// </summary>
/// <value>
/// The size.
/// </value>
public Size Size { get; private set; }
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle" />.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The <see cref="bool" />
/// </returns>
public bool Contains(Point point)
{
var v = point.ToVector2();
return Vector2.Clamp(v, this.topLeft, this.bottomRight) == v;
}
/// <summary>
/// Calculates the distance along and away from the path for a specified point.
/// </summary>
/// <param name="point">The point along the path.</param>
/// <returns>
/// Returns details about the point and its distance away from the path.
/// </returns>
PointInfo IPath.Distance(Point point)
{
bool inside; // dont care about inside/outside for paths just distance
return this.Distance(point, false, out inside);
}
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
public float Distance(Point point)
{
bool insidePoly;
PointInfo result = this.Distance(point, true, out insidePoly);
// invert the distance from path when inside
return insidePoly ? -result.DistanceFromPath : result.DistanceFromPath;
}
/// <summary>
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
/// populate a buffer for all points on the edges of the <see cref="Rectangle"/>
/// that the line intersects.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Point start, Point end, Point[] buffer, int count, int offset)
{
int discovered = 0;
Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight);
Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight);
if (startPoint == Vector2.Clamp(startPoint, start, end))
{
// if start closest is within line then its a valid point
discovered++;
buffer[offset++] = startPoint;
}
if (endPoint == Vector2.Clamp(endPoint, start, end))
{
// if start closest is within line then its a valid point
discovered++;
buffer[offset++] = endPoint;
}
return discovered;
}
/// <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> ILineSegment.AsSimpleLinearPath()
{
return this.points;
}
private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside)
{
// point in rectangle
// if after its clamped by the extreams its still the same then it must be inside :)
Vector2 clamped = Vector2.Clamp(point, this.topLeft, this.bottomRight);
isInside = clamped == point;
float distanceFromEdge = float.MaxValue;
float distanceAlongEdge = 0f;
if (isInside)
{
// get the absolute distances from the extreams
Vector2 topLeftDist = Vector2.Abs(point - this.topLeft);
Vector2 bottomRightDist = Vector2.Abs(point - this.bottomRight);
// get the min components
Vector2 minDists = Vector2.Min(topLeftDist, bottomRightDist);
// and then the single smallest (dont have to worry about direction)
distanceFromEdge = Math.Min(minDists.X, minDists.Y);
if (!getDistanceAwayOnly)
{
// we need to make clamped the closest point
if (this.topLeft.X + distanceFromEdge == point.X)
{
// closer to lhf
clamped.X = this.topLeft.X; // y is already the same
// distance along edge is length minus the amout down we are from the top of the rect
distanceAlongEdge = this.length - (clamped.Y - this.topLeft.Y);
}
else if (this.topLeft.Y + distanceFromEdge == point.Y)
{
// closer to top
clamped.Y = this.topLeft.Y; // x is already the same
distanceAlongEdge = clamped.X - this.topLeft.X;
}
else if (this.bottomRight.Y - distanceFromEdge == point.Y)
{
// closer to bottom
clamped.Y = this.bottomRight.Y; // x is already the same
distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
}
else if (this.bottomRight.X - distanceFromEdge == point.X)
{
// closer to rhs
clamped.X = this.bottomRight.X; // x is already the same
distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.Size.Width;
}
}
}
else
{
// clamped is the point on the path thats closest no matter what
distanceFromEdge = (clamped - point).Length();
if (!getDistanceAwayOnly)
{
// we need to figure out whats the cloests edge now and thus what distance/poitn is closest
if (this.topLeft.X == clamped.X)
{
// distance along edge is length minus the amout down we are from the top of the rect
distanceAlongEdge = this.length - (clamped.Y - this.topLeft.Y);
}
else if (this.topLeft.Y == clamped.Y)
{
distanceAlongEdge = clamped.X - this.topLeft.X;
}
else if (this.bottomRight.Y == clamped.Y)
{
distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
}
else if (this.bottomRight.X == clamped.X)
{
distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.Size.Width;
}
}
}
return new PointInfo
{
SearchPoint = point,
DistanceFromPath = distanceFromEdge,
ClosestPointOnPath = clamped,
DistanceAlongPath = distanceAlongEdge
};
}
}
}

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

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>2e33181e-6e28-4662-a801-e2e7dc206029</ProjectGuid>
<RootNamespace>Shaper2D</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

163
src/Shaper2D/Size.cs Normal file
Просмотреть файл

@ -0,0 +1,163 @@
// <copyright file="Size.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.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores an ordered pair of integers, which specify a height and width.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct Size : IEquatable<Size>
{
/// <summary>
/// Represents a <see cref="Size"/> that has Width and Height values set to zero.
/// </summary>
public static readonly Size Empty = default(Size);
private readonly Vector2 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="width">The width of the size.</param>
/// <param name="height">The height of the size.</param>
public Size(float width, float height)
: this(new Vector2(width, height))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="vector">The vector.</param>
public Size(Vector2 vector)
{
this.backingVector = vector;
}
/// <summary>
/// Gets the width of this <see cref="Size"/>.
/// </summary>
public float Width => this.backingVector.X;
/// <summary>
/// Gets the height of this <see cref="Size"/>.
/// </summary>
public float Height => this.backingVector.Y;
/// <summary>
/// Gets a value indicating whether this <see cref="Size"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Computes the sum of adding two sizes.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator +(Size left, Size right)
{
return new Size(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one size from another.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator -(Size left, Size right)
{
return new Size(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="Size"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Size"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Size"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Size left, Size right)
{
return left.backingVector == right.backingVector;
}
/// <summary>
/// Compares two <see cref="Size"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Size"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Size"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Size left, Size right)
{
return left.backingVector != right.backingVector;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Size [ Empty ]";
}
return $"Size [ Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Size)
{
return this.Equals((Size)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Size other)
{
return this.backingVector == other.backingVector;
}
}
}

73
src/Shaper2D/project.json Normal file
Просмотреть файл

@ -0,0 +1,73 @@
{
"version": "0.0.1-alpha1-*",
"title": "Shaper2D",
"description": "Polygon manipulation on mergin library",
"authors": [
"Scott Williams and contributors"
],
"packOptions": {
"owners": [
"Scott Williams and contributors"
],
"projectUrl": "https://github.com/tocsoft/Shaper2D",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"iconUrl": "https://raw.githubusercontent.com/tocsoft/Shaper2D/master/build/icons/shaper2d-logo-128.png",
"requireLicenseAcceptance": false,
"repository": {
"type": "git",
"url": "https://github.com/tocsoft/Shaper2D"
},
"tags": [
"polygon",
"rectangle",
"point in polygon",
"complex polygons",
"shape",
"2D"
]
},
"buildOptions": {
"allowUnsafe": true,
"xmlDoc": true,
"additionalArguments": [ "/additionalfile:../Shared/stylecop.json", "/ruleset:../../Shaper2D.ruleset" ],
"compile": [
"../Shared/*.cs"
]
},
"configurations": {
"Release": {
"buildOptions": {
"warningsAsErrors": true,
"optimize": true
}
}
},
"dependencies": {
"StyleCop.Analyzers": {
"version": "1.0.0",
"type": "build"
},
"System.Collections.Immutable": "1.2.0",
"System.Numerics.Vectors": "4.1.1"
},
"frameworks": {
"netstandard1.1": {
"dependencies": {
"System.Collections": "4.0.11",
"System.Diagnostics.Debug": "4.0.11",
"System.Diagnostics.Tools": "4.0.1",
"System.Linq": "4.1.0",
"System.ObjectModel": "4.0.12",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Runtime.Numerics": "4.0.1"
}
},
"net45": {
"dependencies": {
"System.Runtime": "4.0.0"
}
}
}
}

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

@ -0,0 +1,37 @@
// <copyright file="AssemblyInfo.Common.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyDescription("A cross-platform library for processing of image files; written in C#")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Scott Williams")]
[assembly: AssemblyProduct("ImageSharp")]
[assembly: AssemblyCopyright("Copyright (c) Scott Williams and contributors.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]
// Ensure the internals can be tested.
[assembly: InternalsVisibleTo("Shaper2D.Tests")]

9
src/Shared/stylecop.json Normal file
Просмотреть файл

@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "Scott Williams",
"copyrightText": "Copyright (c) Scott Williams and contributors.\nLicensed under the Apache License, Version 2.0."
}
}
}

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

@ -0,0 +1,23 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ImageSharp.Tests")]
[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ImageSharp.Tests")]
[assembly: AssemblyCopyright("Copyright © Scott Williams and contributors.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f836e8e6-b4d9-4208-8346-140c74678b91")]

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

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f836e8e6-b4d9-4208-8346-140c74678b91</ProjectGuid>
<RootNamespace>ImageSharp.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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

@ -0,0 +1,34 @@
{
"version": "0.0.0-*",
"configurations": {
"Release": {
"buildOptions": {
"warningsAsErrors": true
}
}
},
"dependencies": {
"Shaper2D": {
"target": "project",
"version": "0.0.1-*"
},
"xunit": "2.2.0-*",
"dotnet-test-xunit": "2.2.0-*"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0-*"
},
"Microsoft.CodeCoverage": "1.0.2"
}
},
"net451": {
"dependencies": {
}
}
},
"testRunner": "xunit"
}