зеркало из https://github.com/SixLabors/Shapes.git
Merge pull request #14 from SixLabors/internalpath-optermisations
Internalpath optermisations
This commit is contained in:
Коммит
7ebff77262
|
@ -257,3 +257,4 @@ paket-files/
|
|||
|
||||
/tests/CodeCoverage/OpenCover.*
|
||||
SixLabors.Shapes.Coverage.xml
|
||||
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.4
|
||||
VisualStudioVersion = 15.0.26228.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
|
@ -28,7 +28,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SixLabors.Shapes.Tests", "t
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawShapesWithImageSharp", "samples\DrawShapesWithImageSharp\DrawShapesWithImageSharp.csproj", "{999EDFB3-9FE4-4E09-B669-CB02E597EC20}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SixLabors.Shapes.Benchmarks", "tests\SixLabors.Shapes.Benchmarks\SixLabors.Shapes.Benchmarks.csproj", "{87E262FA-57FE-4AA7-853C-9DD91E769D4B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
|
@ -46,6 +51,10 @@ Global
|
|||
{999EDFB3-9FE4-4E09-B669-CB02E597EC20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{999EDFB3-9FE4-4E09-B669-CB02E597EC20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{999EDFB3-9FE4-4E09-B669-CB02E597EC20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87E262FA-57FE-4AA7-853C-9DD91E769D4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87E262FA-57FE-4AA7-853C-9DD91E769D4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87E262FA-57FE-4AA7-853C-9DD91E769D4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87E262FA-57FE-4AA7-853C-9DD91E769D4B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -55,5 +64,6 @@ Global
|
|||
{09E744EC-4852-4FC7-BE78-C1B399F17967} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
|
||||
{F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
|
||||
{999EDFB3-9FE4-4E09-B669-CB02E597EC20} = {9F33164A-9EA9-4CB4-A384-A8A0A6DCA35D}
|
||||
{87E262FA-57FE-4AA7-853C-9DD91E769D4B} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha2-00115" />
|
||||
<PackageReference Include="ImageSharp.Formats.Png" Version="1.0.0-alpha2-00111" />
|
||||
<PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha5-00040" />
|
||||
<PackageReference Include="ImageSharp" Version="1.0.0-alpha5-00046" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -50,22 +50,20 @@ namespace SixLabors.Shapes.DrawShapesWithImageSharp
|
|||
using (var img = new Image(dimensions, dimensions))
|
||||
{
|
||||
img.Fill(Color.Black);
|
||||
img.Fill(Color.FromHex("e1e1e1ff"), new ShapeRegion(new SixLabors.Shapes.Ellipse(center, 600f).Transform(scaler)));
|
||||
img.Fill(Color.White, new ShapeRegion(new SixLabors.Shapes.Ellipse(center, 600f - 60).Transform(scaler)));
|
||||
img.Fill(Color.FromHex("e1e1e1ff"), new SixLabors.Shapes.Ellipse(center, 600f).Transform(scaler));
|
||||
img.Fill(Color.White, new SixLabors.Shapes.Ellipse(center, 600f - 60).Transform(scaler));
|
||||
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
img.Fill(colors[i], new ShapeRegion(segments[i].Transform(scaler)));
|
||||
img.Fill(colors[i], segments[i].Transform(scaler));
|
||||
}
|
||||
|
||||
img.Fill(new Color(0, 0, 0, 170), new ShapeRegion(new ComplexPolygon(new SixLabors.Shapes.Ellipse(center, 161f), new SixLabors.Shapes.Ellipse(center, 61f)).Transform(scaler)));
|
||||
img.Fill(new Color(0, 0, 0, 170), new ComplexPolygon(new SixLabors.Shapes.Ellipse(center, 161f), new SixLabors.Shapes.Ellipse(center, 61f)).Transform(scaler));
|
||||
|
||||
var fullPath = System.IO.Path.GetFullPath(System.IO.Path.Combine("Output", path));
|
||||
|
||||
img.Save(fullPath);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,16 +166,13 @@ namespace SixLabors.Shapes.DrawShapesWithImageSharp
|
|||
img.Fill(Color.DarkBlue);
|
||||
|
||||
// In ImageSharp.Drawing.Paths there is an extension method that takes in an IShape directly.
|
||||
img.Fill(Color.HotPink, new ShapeRegion(shape), new ImageSharp.Drawing.GraphicsOptions(true));
|
||||
img.Fill(Color.HotPink, shape, new ImageSharp.Drawing.GraphicsOptions(true));
|
||||
// img.Draw(Color.LawnGreen, 1, new ShapePath(shape));
|
||||
|
||||
// Ensure directory exists
|
||||
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fullPath));
|
||||
|
||||
using (var fs = File.Create(fullPath))
|
||||
{
|
||||
img.SaveAsPng(fs);
|
||||
}
|
||||
img.Save(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,16 +185,13 @@ namespace SixLabors.Shapes.DrawShapesWithImageSharp
|
|||
img.Fill(Color.DarkBlue);
|
||||
|
||||
// In ImageSharp.Drawing.Paths there is an extension method that takes in an IShape directly.
|
||||
img.Fill(Color.HotPink, new ShapeRegion(shape), new ImageSharp.Drawing.GraphicsOptions(true));
|
||||
img.Fill(Color.HotPink, shape, new ImageSharp.Drawing.GraphicsOptions(true) { AntialiasSubpixelDepth = 0, Antialias = true });
|
||||
// img.Draw(Color.LawnGreen, 1, new ShapePath(shape));
|
||||
|
||||
// Ensure directory exists
|
||||
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fullPath));
|
||||
|
||||
using (var fs = File.Create(fullPath))
|
||||
{
|
||||
img.SaveAsPng(fs);
|
||||
}
|
||||
img.Save(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* NOTE : this file is note required to draw shapes with imagesharp in production
|
||||
* just reference ImageSharp.Drawing.Paths it already has all the mappings required.
|
||||
* */
|
||||
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using ImageSharp.Drawing;
|
||||
|
||||
namespace SixLabors.Shapes.DrawShapesWithImageSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A drawable mapping between a <see cref="SixLabors.Shapes.IShape"/>/<see cref="SixLabors.Shapes.IPath"/> and a drawable/fillable region.
|
||||
/// </summary>
|
||||
internal class ShapePath : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The fillable shape
|
||||
/// </summary>
|
||||
private readonly IPath shape;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShapePath"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
public ShapePath(IPath path)
|
||||
{
|
||||
this.shape = path;
|
||||
this.Bounds = Convert(path.Bounds);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int MaxIntersections => this.shape.MaxIntersections;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ImageSharp.Rectangle Bounds { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int ScanX(int x, float[] buffer, int length, int offset)
|
||||
{
|
||||
Vector2 start = new Vector2(x, this.Bounds.Top - 1);
|
||||
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
|
||||
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
buffer[i + offset] = innerbuffer[i].Y;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(innerbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int ScanY(int y, float[] buffer, int length, int offset)
|
||||
{
|
||||
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
|
||||
Vector2 end = new Vector2(this.Bounds.Right + 1, y);
|
||||
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
buffer[i + offset] = innerbuffer[i].X;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(innerbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ImageSharp.Drawing.PointInfo GetPointInfo(int x, int y)
|
||||
{
|
||||
Vector2 point = new Vector2(x, y);
|
||||
|
||||
var dist = this.shape.Distance(point);
|
||||
|
||||
return new ImageSharp.Drawing.PointInfo
|
||||
{
|
||||
DistanceAlongPath = dist.DistanceAlongPath,
|
||||
DistanceFromPath =
|
||||
dist.DistanceFromPath < 0
|
||||
? -dist.DistanceFromPath
|
||||
: dist.DistanceFromPath
|
||||
};
|
||||
}
|
||||
|
||||
private static ImageSharp.Rectangle Convert(SixLabors.Shapes.Rectangle source)
|
||||
{
|
||||
int left = (int)Math.Floor(source.Left);
|
||||
int right = (int)Math.Ceiling(source.Right);
|
||||
int top = (int)Math.Floor(source.Top);
|
||||
int bottom = (int)Math.Ceiling(source.Bottom);
|
||||
return new ImageSharp.Rectangle(left, top, right - left, bottom - top);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* NOTE : this file is note required to draw shapes with imagesharp in production
|
||||
* just reference ImageSharp.Drawing.Paths it already has all the mappings required.
|
||||
* */
|
||||
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
|
||||
using ImageSharp.Drawing;
|
||||
using System;
|
||||
|
||||
namespace SixLabors.Shapes.DrawShapesWithImageSharp
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A drawable mapping between a <see cref="SixLabors.Shapes.IShape"/>/<see cref="SixLabors.Shapes.IPath"/> and a drawable/fillable region.
|
||||
/// </summary>
|
||||
internal class ShapeRegion : Region
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="shape">The shape.</param>
|
||||
public ShapeRegion(IPath shape)
|
||||
{
|
||||
this.Shape = shape.AsClosedPath();
|
||||
this.Bounds = Convert(shape.Bounds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fillable shape
|
||||
/// </summary>
|
||||
public IPath Shape { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int MaxIntersections => this.Shape.MaxIntersections;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ImageSharp.Rectangle Bounds { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int ScanX(int x, float[] buffer, int length, int offset)
|
||||
{
|
||||
Vector2 start = new Vector2(x, this.Bounds.Top - 1);
|
||||
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
|
||||
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int count = this.Shape.FindIntersections(
|
||||
start,
|
||||
end,
|
||||
innerbuffer,
|
||||
length,
|
||||
0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
buffer[i + offset] = innerbuffer[i].Y;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(innerbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int ScanY(int y, float[] buffer, int length, int offset)
|
||||
{
|
||||
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
|
||||
Vector2 end = new Vector2(this.Bounds.Right + 1, y);
|
||||
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int count = this.Shape.FindIntersections(
|
||||
start,
|
||||
end,
|
||||
innerbuffer,
|
||||
length,
|
||||
0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
buffer[i + offset] = innerbuffer[i].X;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(innerbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageSharp.Rectangle Convert(SixLabors.Shapes.Rectangle source)
|
||||
{
|
||||
int left = (int)Math.Floor(source.Left);
|
||||
int right = (int)Math.Ceiling(source.Right);
|
||||
int top = (int)Math.Floor(source.Top);
|
||||
int bottom = (int)Math.Ceiling(source.Bottom);
|
||||
return new ImageSharp.Rectangle(left, top, right - left, bottom - top);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,3 +35,4 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
// Ensure the internals can be tested.
|
||||
[assembly: InternalsVisibleTo("SixLabors.Shapes.Tests")]
|
||||
[assembly: InternalsVisibleTo("SixLabors.Shapes.Benchmarks")]
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// Gets the points that make up this simple linear path.
|
||||
/// </summary>
|
||||
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points;
|
||||
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Rectangle Bounds => this.innerPath.Bounds;
|
||||
|
@ -92,7 +92,7 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// Gets the maximum number intersections that a shape can have when testing a line.
|
||||
/// </summary>
|
||||
int IPath.MaxIntersections => this.innerPath.Points.Length;
|
||||
int IPath.MaxIntersections => this.innerPath.PointCount;
|
||||
|
||||
/// <inheritdoc />
|
||||
public PointInfo Distance(Vector2 point)
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace SixLabors.Shapes
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// Internal logic for integrating linear paths.
|
||||
|
@ -35,45 +36,20 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// The points.
|
||||
/// </summary>
|
||||
private readonly ImmutableArray<Vector2> points;
|
||||
private readonly PointData[] points;
|
||||
|
||||
/// <summary>
|
||||
/// The closed path.
|
||||
/// </summary>
|
||||
private readonly bool closedPath;
|
||||
|
||||
/// <summary>
|
||||
/// The total distance.
|
||||
/// </summary>
|
||||
private readonly Lazy<float> totalDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The distances
|
||||
/// </summary>
|
||||
private float[] distance;
|
||||
|
||||
/// <summary>
|
||||
/// The calculated.
|
||||
/// </summary>
|
||||
private bool calculated;
|
||||
|
||||
/// <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(ImmutableArray.Create(segments), isClosedPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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(ImmutableArray<ILineSegment> segments, bool isClosedPath)
|
||||
: this(Simplify(segments), isClosedPath)
|
||||
internal InternalPath(IEnumerable<ILineSegment> segments, bool isClosedPath)
|
||||
: this(Simplify(segments, isClosedPath), isClosedPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -83,7 +59,7 @@ namespace SixLabors.Shapes
|
|||
/// <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?.Flatten() ?? ImmutableArray<Vector2>.Empty, isClosedPath)
|
||||
: this(segment?.Flatten() ?? Enumerable.Empty<Vector2>(), isClosedPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -92,39 +68,27 @@ namespace SixLabors.Shapes
|
|||
/// </summary>
|
||||
/// <param name="points">The points.</param>
|
||||
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
|
||||
internal InternalPath(ImmutableArray<Vector2> points, bool isClosedPath)
|
||||
internal InternalPath(IEnumerable<Vector2> points, bool isClosedPath)
|
||||
: this(Simplify(points, isClosedPath), 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>
|
||||
private InternalPath(PointData[] 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);
|
||||
float minX = this.points.Min(x => x.Point.X);
|
||||
float maxX = this.points.Max(x => x.Point.X);
|
||||
float minY = this.points.Min(x => x.Point.Y);
|
||||
float maxY = this.points.Max(x => x.Point.Y);
|
||||
|
||||
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
this.totalDistance = new Lazy<float>(this.CalculateLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sides a point can land on
|
||||
/// </summary>
|
||||
public enum Side
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the point falls on the left logical side of the line.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the point falls on the right logical side of the line.
|
||||
/// </summary>
|
||||
Right,
|
||||
|
||||
/// <summary>
|
||||
/// /// Indicates the point falls exactly on the line.
|
||||
/// </summary>
|
||||
Same
|
||||
this.Length = this.points.Sum(x => x.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -165,7 +129,15 @@ namespace SixLabors.Shapes
|
|||
/// <value>
|
||||
/// The length.
|
||||
/// </value>
|
||||
public float Length => this.totalDistance.Value;
|
||||
public float Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The length.
|
||||
/// </value>
|
||||
public int PointCount => this.points.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the points.
|
||||
|
@ -173,7 +145,7 @@ namespace SixLabors.Shapes
|
|||
/// <value>
|
||||
/// The points.
|
||||
/// </value>
|
||||
internal ImmutableArray<Vector2> Points => this.points;
|
||||
internal ImmutableArray<Vector2> Points() => this.points.Select(X => X.Point).ToImmutableArray();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the distance from the path.
|
||||
|
@ -182,8 +154,6 @@ namespace SixLabors.Shapes
|
|||
/// <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
|
||||
|
||||
|
@ -203,7 +173,7 @@ namespace SixLabors.Shapes
|
|||
next = 0;
|
||||
}
|
||||
|
||||
if (this.CalculateShorterDistance(this.points[i], this.points[next], point, ref internalInfo))
|
||||
if (this.CalculateShorterDistance(this.points[i].Point, this.points[next].Point, point, ref internalInfo))
|
||||
{
|
||||
closestPoint = i;
|
||||
}
|
||||
|
@ -211,7 +181,7 @@ namespace SixLabors.Shapes
|
|||
|
||||
return new PointInfo
|
||||
{
|
||||
DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], internalInfo.PointOnLine),
|
||||
DistanceAlongPath = this.points[closestPoint].TotalLength + Vector2.Distance(this.points[closestPoint].Point, internalInfo.PointOnLine),
|
||||
DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared),
|
||||
SearchPoint = point,
|
||||
ClosestPointOnPath = internalInfo.PointOnLine
|
||||
|
@ -230,161 +200,176 @@ namespace SixLabors.Shapes
|
|||
/// <returns>number of intersections hit</returns>
|
||||
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
|
||||
{
|
||||
ClampPoints(ref start, ref end);
|
||||
|
||||
var target = new Segment(start, end);
|
||||
|
||||
int polyCorners = this.points.Length;
|
||||
|
||||
if (!this.closedPath)
|
||||
{
|
||||
polyCorners -= 1;
|
||||
}
|
||||
|
||||
|
||||
int position = 0;
|
||||
Vector2 lastPoint = MaxVector;
|
||||
if (this.closedPath)
|
||||
|
||||
PassPointData[] precaclulate = ArrayPool<PassPointData>.Shared.Rent(this.points.Length);
|
||||
|
||||
try
|
||||
{
|
||||
int prev = polyCorners;
|
||||
do
|
||||
// pre calculate relative orientations X places ahead and behind
|
||||
Orientation prevOrientation = CalulateOrientation(start, end, this.points[polyCorners - 1].Point);
|
||||
Orientation nextOrientation = CalulateOrientation(start, end, this.points[0].Point);
|
||||
Orientation nextPlus1Orientation = CalulateOrientation(start, end, this.points[1].Point);
|
||||
|
||||
// iterate over all points and precalculate data about each, pre cacluating it relative orientation
|
||||
for (int i = 0; i < polyCorners && count > 0; i++)
|
||||
{
|
||||
prev--;
|
||||
if (prev == 0)
|
||||
Segment edge = this.points[i].Segment;
|
||||
|
||||
// shift all orientations along but one place and fill in the last one
|
||||
var pointOrientation = nextOrientation;
|
||||
nextOrientation = nextPlus1Orientation;
|
||||
nextPlus1Orientation = CalulateOrientation(start, end, this.points[(i + 2) % this.points.Length].Point);
|
||||
|
||||
// should this point cause the last matched point to be excluded
|
||||
bool removeLastIntersection = nextOrientation == Orientation.Colinear &&
|
||||
pointOrientation == Orientation.Colinear &&
|
||||
nextPlus1Orientation != prevOrientation &&
|
||||
(this.closedPath || i > 0) &&
|
||||
(IsOnSegment(target, edge.Start) || IsOnSegment(target, edge.End));
|
||||
|
||||
// is there any chance the segments will intersection (do their bounding boxes touch)
|
||||
bool doIntersect = false;
|
||||
if (pointOrientation == Orientation.Colinear || pointOrientation != nextOrientation)
|
||||
{
|
||||
// all points are common, shouldn't match anything
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
while (this.points[0].Equivelent(this.points[prev], Epsilon2)); // skip points too close together
|
||||
|
||||
lastPoint = FindIntersection(this.points[prev], this.points[0], start, end);
|
||||
|
||||
polyCorners = prev + 1;
|
||||
}
|
||||
|
||||
int inc = 0;
|
||||
int lastCorner = polyCorners-1;
|
||||
for (int i = 0; i < polyCorners && count > 0; i += inc)
|
||||
{
|
||||
int next = FindNextPoint(polyCorners, i);
|
||||
if (next > i)
|
||||
{
|
||||
inc = next - i;
|
||||
}else
|
||||
{
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
if (closedPath && AreColliner(this.points[i], this.points[next], start, end))
|
||||
{
|
||||
// lines are colinear and intersect
|
||||
// if this is the case we need to tell if this is an inflection or not
|
||||
var nextSide = Side.Same;
|
||||
// keep going next untill we are no longer on the line
|
||||
while (nextSide == Side.Same)
|
||||
{
|
||||
var nextPlus1 = FindNextPoint(polyCorners, next);
|
||||
nextSide = SideOfLine(this.points[nextPlus1], this.points[i], this.points[next]);
|
||||
if (nextSide == Side.Same)
|
||||
{
|
||||
//skip a point
|
||||
next = nextPlus1;
|
||||
if (nextPlus1 > next)
|
||||
{
|
||||
inc += nextPlus1 - next;
|
||||
}
|
||||
else
|
||||
{
|
||||
inc++;
|
||||
}
|
||||
}
|
||||
doIntersect = (edge.Min.X - Epsilon) <= target.Max.X &&
|
||||
(edge.Max.X + Epsilon) >= target.Min.X &&
|
||||
(edge.Min.Y - Epsilon) <= target.Max.Y &&
|
||||
(edge.Max.Y + Epsilon) >= target.Min.Y;
|
||||
}
|
||||
|
||||
var prevSide = SideOfLine(this.points[lastCorner], this.points[i], this.points[next]);
|
||||
if (prevSide != nextSide)
|
||||
precaclulate[i] = new PassPointData
|
||||
{
|
||||
RemoveLastIntersectionAndSkip = removeLastIntersection,
|
||||
RelativeOrientation = pointOrientation,
|
||||
DoIntersect = doIntersect
|
||||
};
|
||||
|
||||
prevOrientation = pointOrientation;
|
||||
}
|
||||
|
||||
// seed the last point for deduping at begining of closed line
|
||||
if (this.closedPath)
|
||||
{
|
||||
int prev = polyCorners - 1;
|
||||
|
||||
if (precaclulate[prev].DoIntersect)
|
||||
{
|
||||
lastPoint = FindIntersection(this.points[prev].Segment, target);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < polyCorners && count > 0; i++)
|
||||
{
|
||||
int next = (i + 1) % this.points.Length;
|
||||
|
||||
if (precaclulate[i].RemoveLastIntersectionAndSkip)
|
||||
{
|
||||
position--;
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (precaclulate[i].DoIntersect)
|
||||
{
|
||||
Vector2 point = FindIntersection(this.points[i].Segment, target);
|
||||
if (point != MaxVector)
|
||||
{
|
||||
if (lastPoint.Equivelent(point, Epsilon2))
|
||||
{
|
||||
lastPoint = MaxVector;
|
||||
|
||||
Vector2 point = FindIntersection(this.points[i], this.points[next], start, end);
|
||||
if (point != MaxVector)
|
||||
{
|
||||
if (lastPoint.Equivelent(point, Epsilon2))
|
||||
int last = (i - 1 + polyCorners) % polyCorners;
|
||||
|
||||
// hit the same point a second time do we need to remove the old one if just clipping
|
||||
if (this.points[next].Point.Equivelent(point, Epsilon))
|
||||
{
|
||||
next = i;
|
||||
}
|
||||
|
||||
if (this.points[last].Point.Equivelent(point, Epsilon))
|
||||
{
|
||||
last = i;
|
||||
}
|
||||
|
||||
Orientation side = precaclulate[next].RelativeOrientation;
|
||||
Orientation side2 = precaclulate[last].RelativeOrientation;
|
||||
|
||||
if (side != side2)
|
||||
{
|
||||
// differnet side we skip adding as we are passing through it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we are not double crossing so just add it once
|
||||
buffer[position + offset] = point;
|
||||
position++;
|
||||
count--;
|
||||
}
|
||||
lastPoint = point;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastPoint = MaxVector;
|
||||
|
||||
int last = (i - 1 + polyCorners) % polyCorners;
|
||||
|
||||
// hit the same point a second time do we need to remove the old one if just clipping
|
||||
if (this.points[next].Equivelent(point, Epsilon))
|
||||
{
|
||||
next = i;
|
||||
}
|
||||
|
||||
if (this.points[last].Equivelent(point, Epsilon))
|
||||
{
|
||||
last = i;
|
||||
}
|
||||
|
||||
var side = SideOfLine(this.points[last], start, end);
|
||||
var side2 = SideOfLine(this.points[next], start, end);
|
||||
|
||||
if (side == Side.Same && side2 == Side.Same)
|
||||
{
|
||||
position--;
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (side != side2)
|
||||
{
|
||||
// differnet side we skip adding as we are passing through it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we are not double crossing so just add it once
|
||||
buffer[position + offset] = point;
|
||||
position++;
|
||||
count--;
|
||||
|
||||
|
||||
}
|
||||
|
||||
lastPoint = point;
|
||||
lastCorner = i;
|
||||
return position;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private int FindNextPoint(int polyCorners, int i)
|
||||
{
|
||||
int inc1 = 0;
|
||||
int nxt = i;
|
||||
do
|
||||
finally
|
||||
{
|
||||
inc1++;
|
||||
nxt = i + inc1;
|
||||
if (this.closedPath && nxt == polyCorners)
|
||||
{
|
||||
nxt -= polyCorners;
|
||||
}
|
||||
ArrayPool<PassPointData>.Shared.Return(precaclulate);
|
||||
}
|
||||
while (this.points[i].Equivelent(this.points[nxt], Epsilon2) && inc1 < polyCorners); // skip points too close together
|
||||
return nxt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ares the colliner.
|
||||
/// </summary>
|
||||
/// <param name="vector21">The vector21.</param>
|
||||
/// <param name="vector22">The vector22.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns></returns>
|
||||
private bool AreColliner(Vector2 vector21, Vector2 vector22, Vector2 start, Vector2 end)
|
||||
private void ClampPoints(ref Vector2 start, ref Vector2 end)
|
||||
{
|
||||
return SideOfLine(vector21, start, end) == Side.Same && SideOfLine(vector22, start, end) == Side.Same && DoIntersect(vector21, vector22, start, end);
|
||||
// clean up start and end points
|
||||
if (start.X == float.MaxValue)
|
||||
{
|
||||
start.X = this.Bounds.Right + 1;
|
||||
}
|
||||
if (start.X == float.MinValue)
|
||||
{
|
||||
start.X = this.Bounds.Left - 1;
|
||||
}
|
||||
if (end.X == float.MaxValue)
|
||||
{
|
||||
end.X = this.Bounds.Right + 1;
|
||||
}
|
||||
if (end.X == float.MinValue)
|
||||
{
|
||||
end.X = this.Bounds.Left - 1;
|
||||
}
|
||||
|
||||
if (start.Y == float.MaxValue)
|
||||
{
|
||||
start.Y = this.Bounds.Bottom + 1;
|
||||
}
|
||||
if (start.Y == float.MinValue)
|
||||
{
|
||||
start.Y = this.Bounds.Top - 1;
|
||||
}
|
||||
if (end.Y == float.MaxValue)
|
||||
{
|
||||
end.Y = this.Bounds.Bottom + 1;
|
||||
}
|
||||
if (end.Y == float.MinValue)
|
||||
{
|
||||
end.Y = this.Bounds.Top - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -406,17 +391,17 @@ namespace SixLabors.Shapes
|
|||
}
|
||||
|
||||
// if it hit any points then class it as inside
|
||||
var buffer = ArrayPool<Vector2>.Shared.Rent(this.points.Length);
|
||||
Vector2[] buffer = ArrayPool<Vector2>.Shared.Rent(this.points.Length);
|
||||
try
|
||||
{
|
||||
var intersection = this.FindIntersections(point, new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1), buffer, this.points.Length, 0);
|
||||
int intersection = this.FindIntersections(point, new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1), buffer, this.points.Length, 0);
|
||||
if (intersection % 2 == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the point is on an intersection is it is then inside
|
||||
for (var i = 0; i < intersection; i++)
|
||||
for (int i = 0; i < intersection; i++)
|
||||
{
|
||||
if (buffer[i].Equivelent(point, Epsilon))
|
||||
{
|
||||
|
@ -432,104 +417,36 @@ namespace SixLabors.Shapes
|
|||
return false;
|
||||
}
|
||||
|
||||
private static Side SideOfLine(Vector2 test, Vector2 lineStart, Vector2 lineEnd)
|
||||
{
|
||||
var testDiff = test - lineStart;
|
||||
var lineDiff = lineEnd - lineStart;
|
||||
if (float.IsInfinity(lineDiff.X))
|
||||
{
|
||||
if (lineDiff.X > 0)
|
||||
{
|
||||
lineDiff.X = float.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineDiff.X = float.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (float.IsInfinity(lineDiff.Y))
|
||||
{
|
||||
if (lineDiff.Y > 0)
|
||||
{
|
||||
lineDiff.Y = float.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineDiff.Y = float.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
var crossProduct = (lineDiff.X * testDiff.Y) - (lineDiff.Y * testDiff.X);
|
||||
|
||||
if (crossProduct > -Epsilon && crossProduct < Epsilon)
|
||||
{
|
||||
return Side.Same;
|
||||
}
|
||||
|
||||
if (crossProduct > 0)
|
||||
{
|
||||
return Side.Left;
|
||||
}
|
||||
|
||||
return Side.Right;
|
||||
}
|
||||
|
||||
private static bool DoIntersect(Vector2 p1, Vector2 q1, Vector2 p2, Vector2 q2)
|
||||
{
|
||||
// Find the four orientations needed for general and
|
||||
// special cases
|
||||
var o1 = CalulateOrientation(p1, q1, p2);
|
||||
var o2 = CalulateOrientation(p1, q1, q2);
|
||||
var o3 = CalulateOrientation(p2, q2, p1);
|
||||
var o4 = CalulateOrientation(p2, q2, q1);
|
||||
|
||||
// General case
|
||||
if (o1 != o2 && o3 != o4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special Cases
|
||||
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
|
||||
if (o1 == Orientation.Colinear && IsOnSegment(p1, p2, q1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p1, q1 and p2 are colinear and q2 lies on segment p1q1
|
||||
if (o2 == Orientation.Colinear && IsOnSegment(p1, q2, q1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
|
||||
if (o3 == Orientation.Colinear && IsOnSegment(p2, p1, q2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
|
||||
if (o4 == Orientation.Colinear && IsOnSegment(p2, q1, q2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Doesn't fall in any of the above cases
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsOnSegment(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
return (q.X-Epsilon2) <= Math.Max(p.X, r.X) && (q.X + Epsilon2) >= Math.Min(p.X, r.X) &&
|
||||
(q.Y - Epsilon2) <= Math.Max(p.Y, r.Y) && (q.Y + Epsilon2) >= Math.Min(p.Y, r.Y);
|
||||
Vector2 min = Vector2.Min(p, r);
|
||||
Vector2 max = Vector2.Max(p, r);
|
||||
|
||||
return (q.X - Epsilon2) <= max.X &&
|
||||
(q.X + Epsilon2) >= min.X &&
|
||||
(q.Y - Epsilon2) <= max.Y &&
|
||||
(q.Y + Epsilon2) >= min.Y;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsOnSegment(Segment seg, Vector2 q)
|
||||
{
|
||||
return (q.X - Epsilon2) <= seg.Max.X &&
|
||||
(q.X + Epsilon2) >= seg.Min.X &&
|
||||
(q.Y - Epsilon2) <= seg.Max.Y &&
|
||||
(q.Y + Epsilon2) >= seg.Min.Y;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Orientation CalulateOrientation(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
// See http://www.geeksforgeeks.org/orientation-3-ordered-points/
|
||||
// for details of below formula.
|
||||
float val = ((q.Y - p.Y) * (r.X - q.X)) -
|
||||
((q.X - p.X) * (r.Y - q.Y));
|
||||
Vector2 qp = q - p;
|
||||
Vector2 rq = r - q;
|
||||
|
||||
float val = (qp.Y * rq.X) - (qp.X * rq.Y);
|
||||
|
||||
if (val > -Epsilon && val < Epsilon)
|
||||
{
|
||||
|
@ -540,64 +457,27 @@ namespace SixLabors.Shapes
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines 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.
|
||||
/// Finds the point on line described by <paramref name="source" />
|
||||
/// that intersects with line described by <paramref name="target" />
|
||||
/// </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 - Epsilon;
|
||||
float right1 = bottomRight1.X + Epsilon;
|
||||
float top1 = topLeft1.Y - Epsilon;
|
||||
float bottom1 = bottomRight1.Y + Epsilon;
|
||||
|
||||
float left2 = topLeft2.X - Epsilon;
|
||||
float right2 = bottomRight2.X + Epsilon;
|
||||
float top2 = topLeft2.Y - Epsilon;
|
||||
float bottom2 = bottomRight2.Y + Epsilon;
|
||||
|
||||
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>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="source">The line1 start.</param>
|
||||
/// <param name="target">The target line.</param>
|
||||
/// <returns>
|
||||
/// the number of points on the line that it hit
|
||||
/// The point on the line that it hit
|
||||
/// </returns>
|
||||
private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
|
||||
private static Vector2 FindIntersection(Segment source, Segment target)
|
||||
{
|
||||
// do bounding boxes overlap, if not then the lines can't and return fast.
|
||||
if (!DoIntersect(line1Start, line1End, line2Start, line2End))
|
||||
{
|
||||
return MaxVector;
|
||||
}
|
||||
Vector2 line1Start = source.Start;
|
||||
Vector2 line1End = source.End;
|
||||
Vector2 line2Start = target.Start;
|
||||
Vector2 line2End = target.End;
|
||||
|
||||
float x1, y1, x2, y2, x3, y3, x4, y4;
|
||||
x1 = line1Start.X;
|
||||
y1 = line1Start.Y;
|
||||
x2 = line1End.X;
|
||||
y2 = line1End.Y;
|
||||
|
||||
|
||||
x3 = line2Start.X;
|
||||
y3 = line2Start.Y;
|
||||
x4 = line2End.X;
|
||||
|
@ -613,7 +493,14 @@ namespace SixLabors.Shapes
|
|||
float x = (((x2 - x1) * ((x3 * y4) - (x4 * y3))) - ((x4 - x3) * ((x1 * y2) - (x2 * y1)))) / inter;
|
||||
float y = (((y3 - y4) * ((x1 * y2) - (x2 * y1))) - ((y1 - y2) * ((x3 * y4) - (x4 * y3)))) / inter;
|
||||
|
||||
return new Vector2(x, y);
|
||||
var point = new Vector2(x, y);
|
||||
|
||||
if (IsOnSegment(source, point) && IsOnSegment(target, point))
|
||||
{
|
||||
return point;
|
||||
}
|
||||
|
||||
return MaxVector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -623,7 +510,7 @@ namespace SixLabors.Shapes
|
|||
/// <returns>
|
||||
/// The <see cref="T:Vector2[]"/>.
|
||||
/// </returns>
|
||||
private static ImmutableArray<Vector2> Simplify(ImmutableArray<ILineSegment> segments)
|
||||
private static PointData[] Simplify(IEnumerable<ILineSegment> segments, bool isClosed)
|
||||
{
|
||||
List<Vector2> simplified = new List<Vector2>();
|
||||
foreach (ILineSegment seg in segments)
|
||||
|
@ -631,7 +518,101 @@ namespace SixLabors.Shapes
|
|||
simplified.AddRange(seg.Flatten());
|
||||
}
|
||||
|
||||
return simplified.ToImmutableArray();
|
||||
return Simplify(simplified, isClosed);
|
||||
}
|
||||
|
||||
private static PointData[] Simplify(IEnumerable<Vector2> vectors, bool isClosed)
|
||||
{
|
||||
Vector2[] points = vectors.ToArray();
|
||||
List<PointData> results = new List<PointData>();
|
||||
|
||||
int polyCorners = points.Length;
|
||||
|
||||
Vector2 lastPoint = points[0];
|
||||
|
||||
if (!isClosed)
|
||||
{
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = Orientation.Colinear,
|
||||
Length = 0
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isClosed)
|
||||
{
|
||||
int prev = polyCorners;
|
||||
do
|
||||
{
|
||||
prev--;
|
||||
if (prev == 0)
|
||||
{
|
||||
// all points are common, shouldn't match anything
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = Orientation.Colinear,
|
||||
Segment = new Segment(points[0], points[1]),
|
||||
Length = 0,
|
||||
TotalLength = 0
|
||||
});
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
while (points[0].Equivelent(points[prev], Epsilon2)); // skip points too close together
|
||||
polyCorners = prev + 1;
|
||||
lastPoint = points[prev];
|
||||
}
|
||||
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = CalulateOrientation(lastPoint, points[0], points[1]),
|
||||
Length = Vector2.Distance(lastPoint, points[0]),
|
||||
TotalLength = 0
|
||||
});
|
||||
|
||||
lastPoint = points[0];
|
||||
}
|
||||
float totalDist = 0;
|
||||
for (int i = 1; i < polyCorners; i++)
|
||||
{
|
||||
int next = (i + 1) % polyCorners;
|
||||
Orientation or = CalulateOrientation(lastPoint, points[i], points[next]);
|
||||
if (or == Orientation.Colinear && next != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist = Vector2.Distance(lastPoint, points[i]);
|
||||
totalDist += dist;
|
||||
results.Add(
|
||||
new PointData
|
||||
{
|
||||
Point = points[i],
|
||||
Orientation = or,
|
||||
Length = dist,
|
||||
TotalLength = totalDist
|
||||
});
|
||||
lastPoint = points[i];
|
||||
}
|
||||
|
||||
// walk back removing collinear points
|
||||
while (results.Count > 2 && results.Last().Orientation == Orientation.Colinear)
|
||||
{
|
||||
results.RemoveAt(results.Count - 1);
|
||||
}
|
||||
|
||||
var data = results.ToArray();
|
||||
for (var i = 0; i< data.Length; i++)
|
||||
{
|
||||
var next = (i + 1) % data.Length;
|
||||
data[i].Segment = new Segment(data[i].Point, data[next].Point);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -658,46 +639,12 @@ namespace SixLabors.Shapes
|
|||
next = 0;
|
||||
}
|
||||
|
||||
length += Vector2.Distance(this.points[i], this.points[next]);
|
||||
length += this.points[i].Length;
|
||||
}
|
||||
|
||||
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<Vector2> poly = this.points;
|
||||
int polyCorners = poly.Length;
|
||||
this.distance = new float[polyCorners];
|
||||
|
||||
this.distance[0] = 0;
|
||||
|
||||
for (int i = 1; i < polyCorners; i++)
|
||||
{
|
||||
int previousIndex = i - 1;
|
||||
this.distance[i] = this.distance[previousIndex] + Vector2.Distance(poly[i], poly[previousIndex]);
|
||||
}
|
||||
|
||||
this.calculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate any shorter distances along the path.
|
||||
/// </summary>
|
||||
|
@ -760,5 +707,39 @@ namespace SixLabors.Shapes
|
|||
/// </summary>
|
||||
public Vector2 PointOnLine;
|
||||
}
|
||||
|
||||
private struct PointData
|
||||
{
|
||||
public Vector2 Point;
|
||||
public Orientation Orientation;
|
||||
|
||||
public float Length;
|
||||
public float TotalLength;
|
||||
public Segment Segment;
|
||||
}
|
||||
|
||||
private struct PassPointData
|
||||
{
|
||||
public bool RemoveLastIntersectionAndSkip;
|
||||
public Orientation RelativeOrientation;
|
||||
public bool DoIntersect;
|
||||
}
|
||||
|
||||
private struct Segment
|
||||
{
|
||||
public Vector2 Start;
|
||||
public Vector2 End;
|
||||
public Vector2 Min;
|
||||
public Vector2 Max;
|
||||
|
||||
public Segment(Vector2 start, Vector2 end)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
|
||||
this.Min = Vector2.Min(start, end);
|
||||
this.Max = Vector2.Max(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// Gets the points that make up this simple linear path.
|
||||
/// </summary>
|
||||
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points;
|
||||
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Rectangle Bounds => this.innerPath.Bounds;
|
||||
|
@ -68,7 +68,7 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// Gets the maximum number intersections that a shape can have when testing a line.
|
||||
/// </summary>
|
||||
public int MaxIntersections => this.innerPath.Points.Length;
|
||||
public int MaxIntersections => this.innerPath.PointCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line segments
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Shapes.Benchmarks
|
||||
{
|
||||
public class InteralPath_FindIntersections
|
||||
{
|
||||
private readonly Vector2[] vectors;
|
||||
|
||||
public InteralPath_FindIntersections()
|
||||
{
|
||||
this.vectors = new Ellipse(new System.Numerics.Vector2(0, 0), new Size(1000, 500))
|
||||
.Flatten()
|
||||
.First().Points.ToArray();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public Vector2[] Internal_Old()
|
||||
{
|
||||
Vector2[] buffer = new Vector2[vectors.Length];
|
||||
var path = new InternalPath_Old(vectors, true);
|
||||
|
||||
for (var y = path.Bounds.Top; y < path.Bounds.Bottom; y += (1f / 32f))
|
||||
{
|
||||
path.FindIntersections(new Vector2(path.Bounds.Left - 1, y), new Vector2(path.Bounds.Right + 1, y), buffer, path.PointCount, 0);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[Benchmark()]
|
||||
public Vector2[] Internal_Current()
|
||||
{
|
||||
Vector2[] buffer = new Vector2[vectors.Length];
|
||||
var path = new InternalPath(vectors, true);
|
||||
|
||||
for (var y = path.Bounds.Top; y < path.Bounds.Bottom; y += (1f / 32f))
|
||||
{
|
||||
path.FindIntersections(new Vector2(path.Bounds.Left - 1, y), new Vector2(path.Bounds.Right + 1, y), buffer, path.PointCount, 0);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,764 @@
|
|||
// <copyright file="InternalPath.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
namespace SixLabors.Shapes
|
||||
{
|
||||
using System;
|
||||
using System.Buffers;
|
||||
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_Old
|
||||
{
|
||||
/// <summary>
|
||||
/// The epsilon for float comparison
|
||||
/// </summary>
|
||||
private const float Epsilon = 0.003f;
|
||||
private const float Epsilon2 = 0.2f;
|
||||
|
||||
/// <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<Vector2> points;
|
||||
|
||||
/// <summary>
|
||||
/// The closed path.
|
||||
/// </summary>
|
||||
private readonly bool closedPath;
|
||||
|
||||
/// <summary>
|
||||
/// The total distance.
|
||||
/// </summary>
|
||||
private readonly Lazy<float> totalDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The distances
|
||||
/// </summary>
|
||||
private float[] distance;
|
||||
|
||||
/// <summary>
|
||||
/// The calculated.
|
||||
/// </summary>
|
||||
private bool calculated;
|
||||
|
||||
/// <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_Old(ILineSegment[] segments, bool isClosedPath)
|
||||
: this(ImmutableArray.Create(segments), isClosedPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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_Old(ImmutableArray<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_Old(ILineSegment segment, bool isClosedPath)
|
||||
: this(segment?.Flatten() ?? ImmutableArray<Vector2>.Empty, 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_Old(IEnumerable<Vector2> points, bool isClosedPath)
|
||||
{
|
||||
this.points = points.ToArray().ToImmutableArray();
|
||||
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>
|
||||
/// The sides a point can land on
|
||||
/// </summary>
|
||||
public enum Side
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the point falls on the left logical side of the line.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the point falls on the right logical side of the line.
|
||||
/// </summary>
|
||||
Right,
|
||||
|
||||
/// <summary>
|
||||
/// /// Indicates the point falls exactly on the line.
|
||||
/// </summary>
|
||||
Same
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the orrientateion of an point form a line
|
||||
/// </summary>
|
||||
private enum Orientation
|
||||
{
|
||||
/// <summary>
|
||||
/// POint is colienier
|
||||
/// </summary>
|
||||
Colinear = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Its clockwise
|
||||
/// </summary>
|
||||
Clockwise = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Its counter clockwise
|
||||
/// </summary>
|
||||
Counterclockwise = 2
|
||||
}
|
||||
|
||||
/// <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<Vector2> Points => this.points;
|
||||
|
||||
public int PointCount => this.points.Length;
|
||||
|
||||
/// <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], internalInfo.PointOnLine),
|
||||
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" />
|
||||
/// populates 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 of intersections hit</returns>
|
||||
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
|
||||
{
|
||||
int polyCorners = this.points.Length;
|
||||
|
||||
if (!this.closedPath)
|
||||
{
|
||||
polyCorners -= 1;
|
||||
}
|
||||
|
||||
int position = 0;
|
||||
Vector2 lastPoint = MaxVector;
|
||||
if (this.closedPath)
|
||||
{
|
||||
int prev = polyCorners;
|
||||
do
|
||||
{
|
||||
prev--;
|
||||
if (prev == 0)
|
||||
{
|
||||
// all points are common, shouldn't match anything
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
while (this.points[0].Equivelent(this.points[prev], Epsilon2)); // skip points too close together
|
||||
|
||||
lastPoint = FindIntersection(this.points[prev], this.points[0], start, end);
|
||||
|
||||
polyCorners = prev + 1;
|
||||
}
|
||||
|
||||
int inc = 0;
|
||||
int lastCorner = polyCorners-1;
|
||||
for (int i = 0; i < polyCorners && count > 0; i += inc)
|
||||
{
|
||||
int next = FindNextPoint(polyCorners, i);
|
||||
if (next > i)
|
||||
{
|
||||
inc = next - i;
|
||||
}else
|
||||
{
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
if (closedPath && AreColliner(this.points[i], this.points[next], start, end))
|
||||
{
|
||||
// lines are colinear and intersect
|
||||
// if this is the case we need to tell if this is an inflection or not
|
||||
var nextSide = Side.Same;
|
||||
// keep going next untill we are no longer on the line
|
||||
while (nextSide == Side.Same)
|
||||
{
|
||||
var nextPlus1 = FindNextPoint(polyCorners, next);
|
||||
nextSide = SideOfLine(this.points[nextPlus1], this.points[i], this.points[next]);
|
||||
if (nextSide == Side.Same)
|
||||
{
|
||||
//skip a point
|
||||
next = nextPlus1;
|
||||
if (nextPlus1 > next)
|
||||
{
|
||||
inc += nextPlus1 - next;
|
||||
}
|
||||
else
|
||||
{
|
||||
inc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var prevSide = SideOfLine(this.points[lastCorner], this.points[i], this.points[next]);
|
||||
if (prevSide != nextSide)
|
||||
{
|
||||
position--;
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 point = FindIntersection(this.points[i], this.points[next], start, end);
|
||||
if (point != MaxVector)
|
||||
{
|
||||
if (lastPoint.Equivelent(point, Epsilon2))
|
||||
{
|
||||
lastPoint = MaxVector;
|
||||
|
||||
int last = (i - 1 + polyCorners) % polyCorners;
|
||||
|
||||
// hit the same point a second time do we need to remove the old one if just clipping
|
||||
if (this.points[next].Equivelent(point, Epsilon))
|
||||
{
|
||||
next = i;
|
||||
}
|
||||
|
||||
if (this.points[last].Equivelent(point, Epsilon))
|
||||
{
|
||||
last = i;
|
||||
}
|
||||
|
||||
var side = SideOfLine(this.points[last], start, end);
|
||||
var side2 = SideOfLine(this.points[next], start, end);
|
||||
|
||||
if (side == Side.Same && side2 == Side.Same)
|
||||
{
|
||||
position--;
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (side != side2)
|
||||
{
|
||||
// differnet side we skip adding as we are passing through it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we are not double crossing so just add it once
|
||||
buffer[position + offset] = point;
|
||||
position++;
|
||||
count--;
|
||||
}
|
||||
|
||||
lastPoint = point;
|
||||
lastCorner = i;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private int FindNextPoint(int polyCorners, int i)
|
||||
{
|
||||
int inc1 = 0;
|
||||
int nxt = i;
|
||||
do
|
||||
{
|
||||
inc1++;
|
||||
nxt = i + inc1;
|
||||
if (this.closedPath && nxt == polyCorners)
|
||||
{
|
||||
nxt -= polyCorners;
|
||||
}
|
||||
}
|
||||
while (this.points[i].Equivelent(this.points[nxt], Epsilon2) && inc1 < polyCorners); // skip points too close together
|
||||
return nxt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ares the colliner.
|
||||
/// </summary>
|
||||
/// <param name="vector21">The vector21.</param>
|
||||
/// <param name="vector22">The vector22.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns></returns>
|
||||
private bool AreColliner(Vector2 vector21, Vector2 vector22, Vector2 start, Vector2 end)
|
||||
{
|
||||
return SideOfLine(vector21, start, end) == Side.Same && SideOfLine(vector22, start, end) == Side.Same && DoIntersect(vector21, vector22, start, end);
|
||||
}
|
||||
|
||||
/// <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(Vector2 point)
|
||||
{
|
||||
// You can only be inside a path if its "closed"
|
||||
if (!this.closedPath)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.Bounds.Contains(point))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it hit any points then class it as inside
|
||||
var buffer = ArrayPool<Vector2>.Shared.Rent(this.points.Length);
|
||||
try
|
||||
{
|
||||
var intersection = this.FindIntersections(point, new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1), buffer, this.points.Length, 0);
|
||||
if (intersection % 2 == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the point is on an intersection is it is then inside
|
||||
for (var i = 0; i < intersection; i++)
|
||||
{
|
||||
if (buffer[i].Equivelent(point, Epsilon))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Side SideOfLine(Vector2 test, Vector2 lineStart, Vector2 lineEnd)
|
||||
{
|
||||
var testDiff = test - lineStart;
|
||||
var lineDiff = lineEnd - lineStart;
|
||||
if (float.IsInfinity(lineDiff.X))
|
||||
{
|
||||
if (lineDiff.X > 0)
|
||||
{
|
||||
lineDiff.X = float.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineDiff.X = float.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (float.IsInfinity(lineDiff.Y))
|
||||
{
|
||||
if (lineDiff.Y > 0)
|
||||
{
|
||||
lineDiff.Y = float.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineDiff.Y = float.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
var crossProduct = (lineDiff.X * testDiff.Y) - (lineDiff.Y * testDiff.X);
|
||||
|
||||
if (crossProduct > -Epsilon && crossProduct < Epsilon)
|
||||
{
|
||||
return Side.Same;
|
||||
}
|
||||
|
||||
if (crossProduct > 0)
|
||||
{
|
||||
return Side.Left;
|
||||
}
|
||||
|
||||
return Side.Right;
|
||||
}
|
||||
|
||||
private static bool DoIntersect(Vector2 p1, Vector2 q1, Vector2 p2, Vector2 q2)
|
||||
{
|
||||
// Find the four orientations needed for general and
|
||||
// special cases
|
||||
var o1 = CalulateOrientation(p1, q1, p2);
|
||||
var o2 = CalulateOrientation(p1, q1, q2);
|
||||
var o3 = CalulateOrientation(p2, q2, p1);
|
||||
var o4 = CalulateOrientation(p2, q2, q1);
|
||||
|
||||
// General case
|
||||
if (o1 != o2 && o3 != o4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special Cases
|
||||
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
|
||||
if (o1 == Orientation.Colinear && IsOnSegment(p1, p2, q1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p1, q1 and p2 are colinear and q2 lies on segment p1q1
|
||||
if (o2 == Orientation.Colinear && IsOnSegment(p1, q2, q1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
|
||||
if (o3 == Orientation.Colinear && IsOnSegment(p2, p1, q2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
|
||||
if (o4 == Orientation.Colinear && IsOnSegment(p2, q1, q2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Doesn't fall in any of the above cases
|
||||
}
|
||||
|
||||
private static bool IsOnSegment(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
return (q.X-Epsilon2) <= Math.Max(p.X, r.X) && (q.X + Epsilon2) >= Math.Min(p.X, r.X) &&
|
||||
(q.Y - Epsilon2) <= Math.Max(p.Y, r.Y) && (q.Y + Epsilon2) >= Math.Min(p.Y, r.Y);
|
||||
}
|
||||
|
||||
private static Orientation CalulateOrientation(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
// See http://www.geeksforgeeks.org/orientation-3-ordered-points/
|
||||
// for details of below formula.
|
||||
float val = ((q.Y - p.Y) * (r.X - q.X)) -
|
||||
((q.X - p.X) * (r.Y - q.Y));
|
||||
|
||||
if (val > -Epsilon && val < Epsilon)
|
||||
{
|
||||
return Orientation.Colinear; // colinear
|
||||
}
|
||||
|
||||
return (val > 0) ? Orientation.Clockwise : Orientation.Counterclockwise; // clock or counterclock wise
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines 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 - Epsilon;
|
||||
float right1 = bottomRight1.X + Epsilon;
|
||||
float top1 = topLeft1.Y - Epsilon;
|
||||
float bottom1 = bottomRight1.Y + Epsilon;
|
||||
|
||||
float left2 = topLeft2.X - Epsilon;
|
||||
float right2 = bottomRight2.X + Epsilon;
|
||||
float top2 = topLeft2.Y - Epsilon;
|
||||
float bottom2 = bottomRight2.Y + Epsilon;
|
||||
|
||||
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>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>
|
||||
/// the number of points on the line that it hit
|
||||
/// </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 (!DoIntersect(line1Start, line1End, line2Start, line2End))
|
||||
{
|
||||
return MaxVector;
|
||||
}
|
||||
|
||||
float x1, y1, x2, y2, x3, y3, x4, y4;
|
||||
x1 = line1Start.X;
|
||||
y1 = line1Start.Y;
|
||||
x2 = line1End.X;
|
||||
y2 = line1End.Y;
|
||||
|
||||
x3 = line2Start.X;
|
||||
y3 = line2Start.Y;
|
||||
x4 = line2End.X;
|
||||
y4 = line2End.Y;
|
||||
|
||||
float inter = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4));
|
||||
|
||||
if (inter > -Epsilon && inter < Epsilon)
|
||||
{
|
||||
return MaxVector;
|
||||
}
|
||||
|
||||
float x = (((x2 - x1) * ((x3 * y4) - (x4 * y3))) - ((x4 - x3) * ((x1 * y2) - (x2 * y1)))) / inter;
|
||||
float y = (((y3 - y4) * ((x1 * y2) - (x2 * y1))) - ((y1 - y2) * ((x3 * y4) - (x4 * y3)))) / inter;
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simplifies the collection of segments.
|
||||
/// </summary>
|
||||
/// <param name="segments">The segments.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="T:Vector2[]"/>.
|
||||
/// </returns>
|
||||
private static ImmutableArray<Vector2> Simplify(ImmutableArray<ILineSegment> segments)
|
||||
{
|
||||
List<Vector2> simplified = new List<Vector2>();
|
||||
foreach (ILineSegment seg in segments)
|
||||
{
|
||||
simplified.AddRange(seg.Flatten());
|
||||
}
|
||||
|
||||
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<Vector2> poly = this.points;
|
||||
int polyCorners = poly.Length;
|
||||
this.distance = new float[polyCorners];
|
||||
|
||||
this.distance[0] = 0;
|
||||
|
||||
for (int i = 1; i < polyCorners; i++)
|
||||
{
|
||||
int previousIndex = i - 1;
|
||||
this.distance[i] = this.distance[previousIndex] + Vector2.Distance(poly[i], poly[previousIndex]);
|
||||
}
|
||||
|
||||
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 Vector2 PointOnLine;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,723 @@
|
|||
// <copyright file="InternalPath.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
namespace SixLabors.Shapes
|
||||
{
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// Internal logic for integrating linear paths.
|
||||
/// </summary>
|
||||
internal class InternalPath_Proposal1
|
||||
{
|
||||
/// <summary>
|
||||
/// The epsilon for float comparison
|
||||
/// </summary>
|
||||
private const float Epsilon = 0.003f;
|
||||
private const float Epsilon2 = 0.2f;
|
||||
|
||||
/// <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 PointData[] points;
|
||||
|
||||
/// <summary>
|
||||
/// The closed path.
|
||||
/// </summary>
|
||||
private readonly bool closedPath;
|
||||
|
||||
/// <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_Proposal1(IEnumerable<ILineSegment> segments, bool isClosedPath)
|
||||
: this(Simplify(segments, isClosedPath), 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_Proposal1(ILineSegment segment, bool isClosedPath)
|
||||
: this(segment?.Flatten() ?? Enumerable.Empty<Vector2>(), 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_Proposal1(IEnumerable<Vector2> points, bool isClosedPath)
|
||||
: this(Simplify(points, isClosedPath), 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>
|
||||
private InternalPath_Proposal1(PointData[] points, bool isClosedPath)
|
||||
{
|
||||
this.points = points;
|
||||
this.closedPath = isClosedPath;
|
||||
|
||||
float minX = this.points.Min(x => x.Point.X);
|
||||
float maxX = this.points.Max(x => x.Point.X);
|
||||
float minY = this.points.Min(x => x.Point.Y);
|
||||
float maxY = this.points.Max(x => x.Point.Y);
|
||||
|
||||
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
this.Length = this.points.Sum(x => x.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the orrientateion of an point form a line
|
||||
/// </summary>
|
||||
private enum Orientation
|
||||
{
|
||||
/// <summary>
|
||||
/// POint is colienier
|
||||
/// </summary>
|
||||
Colinear = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Its clockwise
|
||||
/// </summary>
|
||||
Clockwise = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Its counter clockwise
|
||||
/// </summary>
|
||||
Counterclockwise = 2
|
||||
}
|
||||
|
||||
/// <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 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The length.
|
||||
/// </value>
|
||||
public int PointCount => this.points.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the points.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The points.
|
||||
/// </value>
|
||||
internal ImmutableArray<Vector2> Points() => this.points.Select(X => X.Point).ToImmutableArray();
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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].Point, this.points[next].Point, point, ref internalInfo))
|
||||
{
|
||||
closestPoint = i;
|
||||
}
|
||||
}
|
||||
|
||||
return new PointInfo
|
||||
{
|
||||
DistanceAlongPath = this.points[closestPoint].TotalLength + Vector2.Distance(this.points[closestPoint].Point, internalInfo.PointOnLine),
|
||||
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" />
|
||||
/// populates 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 of intersections hit</returns>
|
||||
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
|
||||
{
|
||||
ClampPoints(ref start, ref end);
|
||||
|
||||
int polyCorners = this.points.Length;
|
||||
|
||||
if (!this.closedPath)
|
||||
{
|
||||
polyCorners -= 1;
|
||||
}
|
||||
|
||||
int position = 0;
|
||||
Vector2 lastPoint = MaxVector;
|
||||
|
||||
PassPointData[] precaclulate = ArrayPool<PassPointData>.Shared.Rent(this.points.Length);
|
||||
|
||||
try
|
||||
{
|
||||
float targetMinX = Math.Min(start.X, end.X);
|
||||
float targetMaxX = Math.Max(start.X, end.X);
|
||||
float targetMinY = Math.Min(start.Y, end.Y);
|
||||
float targetMaxY = Math.Max(start.Y, end.Y);
|
||||
|
||||
int lastCorner = polyCorners - 1;
|
||||
Orientation prevOrientation = CalulateOrientation(start, end, this.points[lastCorner].Point);
|
||||
Orientation nextOrientation = CalulateOrientation(start, end, this.points[0].Point);
|
||||
Orientation nextPlus1Orientation = CalulateOrientation(start, end, this.points[1].Point);
|
||||
|
||||
for (int i = 0; i < polyCorners && count > 0; i++)
|
||||
{
|
||||
int next = (i + 1) % this.points.Length;
|
||||
int nextPlus1 = (next + 1) % this.points.Length;
|
||||
Vector2 edgeStart = this.points[i].Point;
|
||||
Vector2 edgeEnd = this.points[next].Point;
|
||||
|
||||
var pointOrientation = nextOrientation;
|
||||
nextOrientation = nextPlus1Orientation;
|
||||
nextPlus1Orientation = CalulateOrientation(start, end, this.points[nextPlus1].Point);
|
||||
|
||||
bool removeLastIntersection = nextOrientation == Orientation.Colinear &&
|
||||
pointOrientation == Orientation.Colinear &&
|
||||
nextPlus1Orientation != prevOrientation &&
|
||||
(this.closedPath || i > 0) &&
|
||||
(IsOnSegment(start, edgeStart, end) || IsOnSegment(start, edgeEnd, end));
|
||||
|
||||
bool doIntersect = false;
|
||||
if (pointOrientation == Orientation.Colinear || pointOrientation != nextOrientation)
|
||||
{
|
||||
//intersect
|
||||
float edgeMinX = Math.Min(edgeStart.X, edgeEnd.X);
|
||||
float edgeMaxX = Math.Max(edgeStart.X, edgeEnd.X);
|
||||
float edgeMinY = Math.Min(edgeStart.Y, edgeEnd.Y);
|
||||
float edgeMaxY = Math.Max(edgeStart.Y, edgeEnd.Y);
|
||||
|
||||
doIntersect = edgeMinX - Epsilon <= targetMaxX &&
|
||||
edgeMaxX + Epsilon >= targetMinX &&
|
||||
edgeMinY - Epsilon <= targetMaxY &&
|
||||
edgeMaxY + Epsilon >= targetMinY;
|
||||
}
|
||||
|
||||
precaclulate[i] = new PassPointData
|
||||
{
|
||||
RemoveLastIntersectionAndSkip = removeLastIntersection,
|
||||
RelativeOrientation = pointOrientation,
|
||||
DoIntersect = doIntersect // DoIntersect(edgeStart, edgeEnd, start, end)
|
||||
};
|
||||
lastCorner = i;
|
||||
prevOrientation = pointOrientation;
|
||||
}
|
||||
|
||||
if (this.closedPath)
|
||||
{
|
||||
int prev = polyCorners - 1;
|
||||
|
||||
if (precaclulate[prev].DoIntersect)
|
||||
{
|
||||
lastPoint = FindIntersection(this.points[prev].Point, this.points[0].Point, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < polyCorners && count > 0; i++)
|
||||
{
|
||||
int next = (i + 1) % this.points.Length;
|
||||
|
||||
if (precaclulate[i].RemoveLastIntersectionAndSkip)
|
||||
{
|
||||
position--;
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
if (precaclulate[i].DoIntersect)
|
||||
{
|
||||
Vector2 point = FindIntersection(this.points[i].Point, this.points[next].Point, start, end);
|
||||
if (point != MaxVector)
|
||||
{
|
||||
if (lastPoint.Equivelent(point, Epsilon2))
|
||||
{
|
||||
lastPoint = MaxVector;
|
||||
|
||||
int last = (i - 1 + polyCorners) % polyCorners;
|
||||
|
||||
// hit the same point a second time do we need to remove the old one if just clipping
|
||||
if (this.points[next].Point.Equivelent(point, Epsilon))
|
||||
{
|
||||
next = i;
|
||||
}
|
||||
|
||||
if (this.points[last].Point.Equivelent(point, Epsilon))
|
||||
{
|
||||
last = i;
|
||||
}
|
||||
|
||||
Orientation side = precaclulate[next].RelativeOrientation;
|
||||
Orientation side2 = precaclulate[last].RelativeOrientation;
|
||||
|
||||
//if (side == Orientation.Colinear && side2 == Orientation.Colinear)
|
||||
//{
|
||||
// position--;
|
||||
// count++;
|
||||
// continue;
|
||||
//}
|
||||
|
||||
if (side != side2)
|
||||
{
|
||||
// differnet side we skip adding as we are passing through it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we are not double crossing so just add it once
|
||||
buffer[position + offset] = point;
|
||||
position++;
|
||||
count--;
|
||||
}
|
||||
lastPoint = point;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastPoint = MaxVector;
|
||||
}
|
||||
|
||||
lastCorner = i;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<PassPointData>.Shared.Return(precaclulate);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClampPoints(ref Vector2 start, ref Vector2 end)
|
||||
{
|
||||
// clean up start and end points
|
||||
if (start.X == float.MaxValue)
|
||||
{
|
||||
start.X = this.Bounds.Right + 1;
|
||||
}
|
||||
if (start.X == float.MinValue)
|
||||
{
|
||||
start.X = this.Bounds.Left - 1;
|
||||
}
|
||||
if (end.X == float.MaxValue)
|
||||
{
|
||||
end.X = this.Bounds.Right + 1;
|
||||
}
|
||||
if (end.X == float.MinValue)
|
||||
{
|
||||
end.X = this.Bounds.Left - 1;
|
||||
}
|
||||
|
||||
if (start.Y == float.MaxValue)
|
||||
{
|
||||
start.Y = this.Bounds.Bottom + 1;
|
||||
}
|
||||
if (start.Y == float.MinValue)
|
||||
{
|
||||
start.Y = this.Bounds.Top - 1;
|
||||
}
|
||||
if (end.Y == float.MaxValue)
|
||||
{
|
||||
end.Y = this.Bounds.Bottom + 1;
|
||||
}
|
||||
if (end.Y == float.MinValue)
|
||||
{
|
||||
end.Y = this.Bounds.Top - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(Vector2 point)
|
||||
{
|
||||
// You can only be inside a path if its "closed"
|
||||
if (!this.closedPath)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.Bounds.Contains(point))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it hit any points then class it as inside
|
||||
Vector2[] buffer = ArrayPool<Vector2>.Shared.Rent(this.points.Length);
|
||||
try
|
||||
{
|
||||
int intersection = this.FindIntersections(point, new Vector2(this.Bounds.Left - 1, this.Bounds.Top - 1), buffer, this.points.Length, 0);
|
||||
if (intersection % 2 == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the point is on an intersection is it is then inside
|
||||
for (int i = 0; i < intersection; i++)
|
||||
{
|
||||
if (buffer[i].Equivelent(point, Epsilon))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Vector2>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsOnSegment(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
return (q.X - Epsilon2) <= Math.Max(p.X, r.X) &&
|
||||
(q.X + Epsilon2) >= Math.Min(p.X, r.X) &&
|
||||
(q.Y - Epsilon2) <= Math.Max(p.Y, r.Y) &&
|
||||
(q.Y + Epsilon2) >= Math.Min(p.Y, r.Y);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Orientation CalulateOrientation(Vector2 p, Vector2 q, Vector2 r)
|
||||
{
|
||||
// See http://www.geeksforgeeks.org/orientation-3-ordered-points/
|
||||
// for details of below formula.
|
||||
Vector2 qp = q - p;
|
||||
Vector2 rq = r - q;
|
||||
|
||||
float val = (qp.Y * rq.X) - (qp.X * rq.Y);
|
||||
|
||||
if (val > -Epsilon && val < Epsilon)
|
||||
{
|
||||
return Orientation.Colinear; // colinear
|
||||
}
|
||||
|
||||
return (val > 0) ? Orientation.Clockwise : Orientation.Counterclockwise; // clock or counterclock wise
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// the number of points on the line that it hit
|
||||
/// </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 (!DoIntersect(line1Start, line1End, line2Start, line2End))
|
||||
// {
|
||||
// return MaxVector;
|
||||
// }
|
||||
|
||||
float x1, y1, x2, y2, x3, y3, x4, y4;
|
||||
x1 = line1Start.X;
|
||||
y1 = line1Start.Y;
|
||||
x2 = line1End.X;
|
||||
y2 = line1End.Y;
|
||||
|
||||
x3 = line2Start.X;
|
||||
y3 = line2Start.Y;
|
||||
x4 = line2End.X;
|
||||
y4 = line2End.Y;
|
||||
|
||||
float inter = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4));
|
||||
|
||||
if (inter > -Epsilon && inter < Epsilon)
|
||||
{
|
||||
return MaxVector;
|
||||
}
|
||||
|
||||
float x = (((x2 - x1) * ((x3 * y4) - (x4 * y3))) - ((x4 - x3) * ((x1 * y2) - (x2 * y1)))) / inter;
|
||||
float y = (((y3 - y4) * ((x1 * y2) - (x2 * y1))) - ((y1 - y2) * ((x3 * y4) - (x4 * y3)))) / inter;
|
||||
|
||||
var point = new Vector2(x, y);
|
||||
|
||||
if (IsOnSegment(line1Start, point, line1End) && IsOnSegment(line2Start, point, line2End))
|
||||
{
|
||||
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 PointData[] Simplify(IEnumerable<ILineSegment> segments, bool isClosed)
|
||||
{
|
||||
List<Vector2> simplified = new List<Vector2>();
|
||||
foreach (ILineSegment seg in segments)
|
||||
{
|
||||
simplified.AddRange(seg.Flatten());
|
||||
}
|
||||
|
||||
return Simplify(simplified, isClosed);
|
||||
}
|
||||
|
||||
private static PointData[] Simplify(IEnumerable<Vector2> vectors, bool isClosed)
|
||||
{
|
||||
Vector2[] points = vectors.ToArray();
|
||||
List<PointData> results = new List<PointData>();
|
||||
|
||||
int polyCorners = points.Length;
|
||||
|
||||
Vector2 lastPoint = points[0];
|
||||
|
||||
if (!isClosed)
|
||||
{
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = Orientation.Colinear,
|
||||
Length = 0
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isClosed)
|
||||
{
|
||||
int prev = polyCorners;
|
||||
do
|
||||
{
|
||||
prev--;
|
||||
if (prev == 0)
|
||||
{
|
||||
// all points are common, shouldn't match anything
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = Orientation.Colinear,
|
||||
Length = 0,
|
||||
TotalLength = 0
|
||||
});
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
while (points[0].Equivelent(points[prev], Epsilon2)); // skip points too close together
|
||||
polyCorners = prev + 1;
|
||||
lastPoint = points[prev];
|
||||
}
|
||||
|
||||
results.Add(new PointData
|
||||
{
|
||||
Point = points[0],
|
||||
Orientation = CalulateOrientation(lastPoint, points[0], points[1]),
|
||||
Length = Vector2.Distance(lastPoint, points[0]),
|
||||
TotalLength = 0
|
||||
});
|
||||
|
||||
lastPoint = points[0];
|
||||
}
|
||||
float totalDist = 0;
|
||||
for (int i = 1; i < polyCorners; i++)
|
||||
{
|
||||
int next = (i + 1) % polyCorners;
|
||||
Orientation or = CalulateOrientation(lastPoint, points[i], points[next]);
|
||||
if (or == Orientation.Colinear && next != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist = Vector2.Distance(lastPoint, points[i]);
|
||||
totalDist += dist;
|
||||
results.Add(
|
||||
new PointData
|
||||
{
|
||||
Point = points[i],
|
||||
Orientation = or,
|
||||
Length = dist,
|
||||
TotalLength = totalDist
|
||||
});
|
||||
lastPoint = points[i];
|
||||
}
|
||||
// walk back removing collinear points
|
||||
while (results.Count > 2 && results.Last().Orientation == Orientation.Colinear)
|
||||
{
|
||||
results.RemoveAt(results.Count - 1);
|
||||
}
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
/// <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 += this.points[i].Length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <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 Vector2 PointOnLine;
|
||||
}
|
||||
|
||||
private struct PointData
|
||||
{
|
||||
public Vector2 Point;
|
||||
public Orientation Orientation;
|
||||
|
||||
public float Length;
|
||||
public float TotalLength;
|
||||
}
|
||||
|
||||
private struct PassPointData
|
||||
{
|
||||
public bool RemoveLastIntersectionAndSkip;
|
||||
public Orientation RelativeOrientation;
|
||||
public bool DoIntersect;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace SixLabors.Shapes.Benchmarks
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
//var p = new InteralPath_FindIntersections();
|
||||
//for (var i = 0; i < 10; i++)
|
||||
//{
|
||||
// p.InternalNew();
|
||||
// Console.WriteLine(i);
|
||||
//}
|
||||
|
||||
new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args);
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\SixLabors.Shapes\SixLabors.Shapes.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -79,7 +79,7 @@ namespace SixLabors.Shapes.Tests
|
|||
var intersections = polygon.FindIntersections(new Vector2(polygon.Bounds.Left - 1, y), new Vector2(polygon.Bounds.Right + 1, y));
|
||||
if (intersections.Count() % 2 != 0)
|
||||
{
|
||||
Assert.True(false, $"crosssections at '{y}' produced {intersections.Count()} number of intersections");
|
||||
Assert.True(false, $"crosssection of '{name}' at '{y}' produced {intersections.Count()} number of intersections");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,8 +93,10 @@ namespace SixLabors.Shapes.Tests
|
|||
{ "scaled_300_iris_3", 135 },
|
||||
{ "scaled_300_iris_0", 165 },
|
||||
{ "clippedRect", 20},
|
||||
|
||||
{ "clippedRect", 10},
|
||||
|
||||
{ "hourGlass", 25 },
|
||||
{ "hourGlass", 175 },
|
||||
{ "BigCurve", 115},
|
||||
{ "ChopCorner", 64},
|
||||
};
|
||||
|
@ -107,7 +109,7 @@ namespace SixLabors.Shapes.Tests
|
|||
|
||||
var intersections = polygon.FindIntersections(new Vector2(polygon.Bounds.Left - 1, yScanLine), new Vector2(polygon.Bounds.Right + 1, yScanLine)).Count();
|
||||
|
||||
Assert.True(intersections % 2 == 0, $"crosssections at '{yScanLine}' produced {intersections} intersections");
|
||||
Assert.True(intersections % 2 == 0, $"crosssection of '{name}' at '{yScanLine}' produced {intersections} intersections");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ namespace SixLabors.Shapes.Tests
|
|||
|
||||
var path = new InternalPath(new ILineSegment[] { seg1, seg2 }, true);
|
||||
|
||||
Assert.Equal(new Vector2(0, 0), path.Points[0]);
|
||||
Assert.Equal(new Vector2(2, 2), path.Points[1]);
|
||||
Assert.Equal(new Vector2(4, 4), path.Points[2]);
|
||||
Assert.Equal(new Vector2(5, 5), path.Points[3]);
|
||||
Assert.Contains(new Vector2(0, 0), path.Points());
|
||||
Assert.DoesNotContain(new Vector2(2, 2), path.Points());
|
||||
Assert.DoesNotContain(new Vector2(4, 4), path.Points());
|
||||
Assert.Contains(new Vector2(5, 5), path.Points());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -157,7 +157,7 @@ namespace SixLabors.Shapes.Tests
|
|||
public void Intersections_buffer()
|
||||
{
|
||||
var shape = Create(new Vector2(0, 0), new Size(10, 10));
|
||||
var buffer = new Vector2[shape.Points.Length];
|
||||
var buffer = new Vector2[shape.PointCount];
|
||||
var hits = shape.FindIntersections(new Vector2(5, -10), new Vector2(5, 20), buffer, 4, 0);
|
||||
|
||||
Assert.Equal(2, hits);
|
||||
|
|
|
@ -23,10 +23,10 @@ namespace SixLabors.Shapes
|
|||
/// <returns>The points along the line the intersect with the boundaries of the polygon.</returns>
|
||||
internal static IEnumerable<Vector2> FindIntersections(this InternalPath path, Vector2 start, Vector2 end)
|
||||
{
|
||||
var buffer = ArrayPool<Vector2>.Shared.Rent(path.Points.Length);
|
||||
var buffer = ArrayPool<Vector2>.Shared.Rent(path.PointCount);
|
||||
try
|
||||
{
|
||||
var hits = path.FindIntersections(start, end, buffer, path.Points.Length, 0);
|
||||
var hits = path.FindIntersections(start, end, buffer, path.PointCount, 0);
|
||||
for (var i = 0; i < hits; i++)
|
||||
{
|
||||
yield return buffer[i];
|
||||
|
|
|
@ -57,8 +57,8 @@ namespace SixLabors.Shapes.Tests
|
|||
[Fact]
|
||||
public void GeneratesCorrectPath()
|
||||
{
|
||||
float radius = 10;
|
||||
float radius2 = 20;
|
||||
float radius = 5;
|
||||
float radius2 = 30;
|
||||
int pointsCount = new Random().Next(3, 20);
|
||||
|
||||
var poly = new Star(Vector2.Zero, pointsCount, radius, radius2, 0);
|
||||
|
|
Загрузка…
Ссылка в новой задаче