prefilter and calculate data per point

This commit is contained in:
Scott Williams 2017-03-31 22:32:54 +01:00
Родитель c1810e8d9c
Коммит 172236263d
12 изменённых файлов: 996 добавлений и 74 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -257,3 +257,4 @@ paket-files/
/tests/CodeCoverage/OpenCover.* /tests/CodeCoverage/OpenCover.*
SixLabors.Shapes.Coverage.xml SixLabors.Shapes.Coverage.xml
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/

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

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26228.4 VisualStudioVersion = 15.0.26228.10
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SixLabors.Shapes.Tests", "t
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawShapesWithImageSharp", "samples\DrawShapesWithImageSharp\DrawShapesWithImageSharp.csproj", "{999EDFB3-9FE4-4E09-B669-CB02E597EC20}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawShapesWithImageSharp", "samples\DrawShapesWithImageSharp\DrawShapesWithImageSharp.csproj", "{999EDFB3-9FE4-4E09-B669-CB02E597EC20}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -46,6 +48,10 @@ Global
{999EDFB3-9FE4-4E09-B669-CB02E597EC20}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{999EDFB3-9FE4-4E09-B669-CB02E597EC20}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -55,5 +61,6 @@ Global
{09E744EC-4852-4FC7-BE78-C1B399F17967} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {09E744EC-4852-4FC7-BE78-C1B399F17967} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{999EDFB3-9FE4-4E09-B669-CB02E597EC20} = {9F33164A-9EA9-4CB4-A384-A8A0A6DCA35D} {999EDFB3-9FE4-4E09-B669-CB02E597EC20} = {9F33164A-9EA9-4CB4-A384-A8A0A6DCA35D}
{87E262FA-57FE-4AA7-853C-9DD91E769D4B} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

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

@ -35,3 +35,4 @@ using System.Runtime.CompilerServices;
// Ensure the internals can be tested. // Ensure the internals can be tested.
[assembly: InternalsVisibleTo("SixLabors.Shapes.Tests")] [assembly: InternalsVisibleTo("SixLabors.Shapes.Tests")]
[assembly: InternalsVisibleTo("SixLabors.Shapes.Benchmarks")]

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

@ -79,7 +79,7 @@ namespace SixLabors.Shapes
/// <summary> /// <summary>
/// Gets the points that make up this simple linear path. /// Gets the points that make up this simple linear path.
/// </summary> /// </summary>
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points; ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points();
/// <inheritdoc /> /// <inheritdoc />
public Rectangle Bounds => this.innerPath.Bounds; public Rectangle Bounds => this.innerPath.Bounds;
@ -92,7 +92,7 @@ namespace SixLabors.Shapes
/// <summary> /// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line. /// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary> /// </summary>
int IPath.MaxIntersections => this.innerPath.Points.Length; int IPath.MaxIntersections => this.innerPath.PointCount;
/// <inheritdoc /> /// <inheritdoc />
public PointInfo Distance(Vector2 point) public PointInfo Distance(Vector2 point)

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

@ -35,7 +35,7 @@ namespace SixLabors.Shapes
/// <summary> /// <summary>
/// The points. /// The points.
/// </summary> /// </summary>
private readonly ImmutableArray<Vector2> points; private readonly PointData[] points;
/// <summary> /// <summary>
/// The closed path. /// The closed path.
@ -62,18 +62,8 @@ namespace SixLabors.Shapes
/// </summary> /// </summary>
/// <param name="segments">The segments.</param> /// <param name="segments">The segments.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param> /// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
internal InternalPath(ILineSegment[] segments, bool isClosedPath) internal InternalPath(IEnumerable<ILineSegment> segments, bool isClosedPath)
: this(ImmutableArray.Create(segments), isClosedPath) : this(Simplify(segments, isClosedPath), 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)
{ {
} }
@ -83,7 +73,7 @@ namespace SixLabors.Shapes
/// <param name="segment">The segment.</param> /// <param name="segment">The segment.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param> /// <param name="isClosedPath">if set to <c>true</c> [is closed path].</param>
internal InternalPath(ILineSegment segment, bool isClosedPath) internal InternalPath(ILineSegment segment, bool isClosedPath)
: this(segment?.Flatten() ?? ImmutableArray<Vector2>.Empty, isClosedPath) : this(segment?.Flatten() ?? Enumerable.Empty<Vector2>(), isClosedPath)
{ {
} }
@ -92,18 +82,27 @@ namespace SixLabors.Shapes
/// </summary> /// </summary>
/// <param name="points">The points.</param> /// <param name="points">The points.</param>
/// <param name="isClosedPath">if set to <c>true</c> [is closed path].</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.points = points;
this.closedPath = isClosedPath; this.closedPath = isClosedPath;
float minX = this.points.Min(x => x.X); float minX = this.points.Min(x => x.Point.X);
float maxX = this.points.Max(x => x.X); float maxX = this.points.Max(x => x.Point.X);
float minY = this.points.Min(x => x.Y); float minY = this.points.Min(x => x.Point.Y);
float maxY = this.points.Max(x => x.Y); float maxY = this.points.Max(x => x.Point.Y);
this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY); this.Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
this.totalDistance = new Lazy<float>(this.CalculateLength); this.Length = this.points.Sum(x => x.Length);
} }
/// <summary> /// <summary>
@ -165,7 +164,15 @@ namespace SixLabors.Shapes
/// <value> /// <value>
/// The length. /// The length.
/// </value> /// </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> /// <summary>
/// Gets the points. /// Gets the points.
@ -173,7 +180,7 @@ namespace SixLabors.Shapes
/// <value> /// <value>
/// The points. /// The points.
/// </value> /// </value>
internal ImmutableArray<Vector2> Points => this.points; internal ImmutableArray<Vector2> Points() => this.points.Select(X=>X.Point).ToImmutableArray();
/// <summary> /// <summary>
/// Calculates the distance from the path. /// Calculates the distance from the path.
@ -183,7 +190,6 @@ namespace SixLabors.Shapes
public PointInfo DistanceFromPath(Vector2 point) public PointInfo DistanceFromPath(Vector2 point)
{ {
this.CalculateConstants(); this.CalculateConstants();
PointInfoInternal internalInfo = default(PointInfoInternal); PointInfoInternal internalInfo = default(PointInfoInternal);
internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down
@ -203,7 +209,7 @@ namespace SixLabors.Shapes
next = 0; 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; closestPoint = i;
} }
@ -211,7 +217,7 @@ namespace SixLabors.Shapes
return new PointInfo return new PointInfo
{ {
DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], internalInfo.PointOnLine), DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint].Point, internalInfo.PointOnLine),
DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared), DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared),
SearchPoint = point, SearchPoint = point,
ClosestPointOnPath = internalInfo.PointOnLine ClosestPointOnPath = internalInfo.PointOnLine
@ -241,21 +247,9 @@ namespace SixLabors.Shapes
Vector2 lastPoint = MaxVector; Vector2 lastPoint = MaxVector;
if (this.closedPath) if (this.closedPath)
{ {
int prev = polyCorners; int prev = polyCorners-1;
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); lastPoint = FindIntersection(this.points[prev].Point, this.points[0].Point, start, end);
polyCorners = prev + 1;
} }
int inc = 0; int inc = 0;
@ -271,7 +265,7 @@ namespace SixLabors.Shapes
inc = 1; inc = 1;
} }
if (closedPath && AreColliner(this.points[i], this.points[next], start, end)) if (closedPath && AreColliner(this.points[i].Point, this.points[next].Point, start, end))
{ {
// lines are colinear and intersect // lines are colinear and intersect
// if this is the case we need to tell if this is an inflection or not // if this is the case we need to tell if this is an inflection or not
@ -280,7 +274,7 @@ namespace SixLabors.Shapes
while (nextSide == Side.Same) while (nextSide == Side.Same)
{ {
var nextPlus1 = FindNextPoint(polyCorners, next); var nextPlus1 = FindNextPoint(polyCorners, next);
nextSide = SideOfLine(this.points[nextPlus1], this.points[i], this.points[next]); nextSide = SideOfLine(this.points[nextPlus1].Point, this.points[i].Point, this.points[next].Point);
if (nextSide == Side.Same) if (nextSide == Side.Same)
{ {
//skip a point //skip a point
@ -296,7 +290,7 @@ namespace SixLabors.Shapes
} }
} }
var prevSide = SideOfLine(this.points[lastCorner], this.points[i], this.points[next]); var prevSide = SideOfLine(this.points[lastCorner].Point, this.points[i].Point, this.points[next].Point);
if (prevSide != nextSide) if (prevSide != nextSide)
{ {
position--; position--;
@ -305,7 +299,7 @@ namespace SixLabors.Shapes
} }
} }
Vector2 point = FindIntersection(this.points[i], this.points[next], start, end); Vector2 point = FindIntersection(this.points[i].Point, this.points[next].Point, start, end);
if (point != MaxVector) if (point != MaxVector)
{ {
if (lastPoint.Equivelent(point, Epsilon2)) if (lastPoint.Equivelent(point, Epsilon2))
@ -315,18 +309,18 @@ namespace SixLabors.Shapes
int last = (i - 1 + polyCorners) % polyCorners; int last = (i - 1 + polyCorners) % polyCorners;
// hit the same point a second time do we need to remove the old one if just clipping // 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)) if (this.points[next].Point.Equivelent(point, Epsilon))
{ {
next = i; next = i;
} }
if (this.points[last].Equivelent(point, Epsilon)) if (this.points[last].Point.Equivelent(point, Epsilon))
{ {
last = i; last = i;
} }
var side = SideOfLine(this.points[last], start, end); var side = SideOfLine(this.points[last].Point, start, end);
var side2 = SideOfLine(this.points[next], start, end); var side2 = SideOfLine(this.points[next].Point, start, end);
if (side == Side.Same && side2 == Side.Same) if (side == Side.Same && side2 == Side.Same)
{ {
@ -346,8 +340,6 @@ namespace SixLabors.Shapes
buffer[position + offset] = point; buffer[position + offset] = point;
position++; position++;
count--; count--;
} }
lastPoint = point; lastPoint = point;
@ -361,16 +353,13 @@ namespace SixLabors.Shapes
{ {
int inc1 = 0; int inc1 = 0;
int nxt = i; int nxt = i;
do
nxt = i + 1;
if (this.closedPath && nxt == polyCorners)
{ {
inc1++; nxt -= polyCorners;
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; return nxt;
} }
@ -623,7 +612,7 @@ namespace SixLabors.Shapes
/// <returns> /// <returns>
/// The <see cref="T:Vector2[]"/>. /// The <see cref="T:Vector2[]"/>.
/// </returns> /// </returns>
private static ImmutableArray<Vector2> Simplify(ImmutableArray<ILineSegment> segments) private static PointData[] Simplify(IEnumerable<ILineSegment> segments, bool isClosed)
{ {
List<Vector2> simplified = new List<Vector2>(); List<Vector2> simplified = new List<Vector2>();
foreach (ILineSegment seg in segments) foreach (ILineSegment seg in segments)
@ -631,7 +620,81 @@ namespace SixLabors.Shapes
simplified.AddRange(seg.Flatten()); 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,
Length = 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])
});
lastPoint = points[0];
}
for (var i = 1; i < polyCorners; i++)
{
var next = (i + 1) % polyCorners;
var or = CalulateOrientation(lastPoint, points[i], points[next]);
if(or == Orientation.Colinear && next != 0)
{
continue;
}
results.Add(
new PointData
{
Point = points[i],
Orientation = or,
Length = Vector2.Distance(lastPoint, points[i])
});
lastPoint = points[i];
}
return results.ToArray();
} }
/// <summary> /// <summary>
@ -658,7 +721,7 @@ namespace SixLabors.Shapes
next = 0; next = 0;
} }
length += Vector2.Distance(this.points[i], this.points[next]); length += this.points[i].Length;
} }
return length; return length;
@ -682,7 +745,7 @@ namespace SixLabors.Shapes
return; return;
} }
ImmutableArray<Vector2> poly = this.points; PointData[] poly = this.points;
int polyCorners = poly.Length; int polyCorners = poly.Length;
this.distance = new float[polyCorners]; this.distance = new float[polyCorners];
@ -691,7 +754,7 @@ namespace SixLabors.Shapes
for (int i = 1; i < polyCorners; i++) for (int i = 1; i < polyCorners; i++)
{ {
int previousIndex = i - 1; int previousIndex = i - 1;
this.distance[i] = this.distance[previousIndex] + Vector2.Distance(poly[i], poly[previousIndex]); this.distance[i] = this.distance[previousIndex] + Vector2.Distance(poly[i].Point, poly[previousIndex].Point);
} }
this.calculated = true; this.calculated = true;
@ -760,5 +823,13 @@ namespace SixLabors.Shapes
/// </summary> /// </summary>
public Vector2 PointOnLine; public Vector2 PointOnLine;
} }
private struct PointData
{
public Vector2 Point;
public Orientation Orientation;
public float Length;
}
} }
} }

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

@ -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;
}
}
}

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

@ -55,7 +55,7 @@ namespace SixLabors.Shapes
/// <summary> /// <summary>
/// Gets the points that make up this simple linear path. /// Gets the points that make up this simple linear path.
/// </summary> /// </summary>
ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points; ImmutableArray<Vector2> ISimplePath.Points => this.innerPath.Points();
/// <inheritdoc /> /// <inheritdoc />
public Rectangle Bounds => this.innerPath.Bounds; public Rectangle Bounds => this.innerPath.Bounds;
@ -68,7 +68,7 @@ namespace SixLabors.Shapes
/// <summary> /// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line. /// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary> /// </summary>
public int MaxIntersections => this.innerPath.Points.Length; public int MaxIntersections => this.innerPath.PointCount;
/// <summary> /// <summary>
/// Gets the line segments /// Gets the line segments

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

@ -0,0 +1,47 @@
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
{
[Benchmark]
public Vector2[] InternalOld()
{
var vectors = new Ellipse(new System.Numerics.Vector2(0, 0), new Size(20, 10))
.Flatten()
.First().Points.ToArray();
var path = new InternalPath_Old(vectors, true);
Vector2[] buffer = new Vector2[path.PointCount*2];
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[] InternalNew()
{
var vectors = new Ellipse(new System.Numerics.Vector2(0, 0), new Size(20, 10))
.Flatten()
.First().Points.ToArray();
var path = new InternalPath(vectors, true);
Vector2[] buffer = new Vector2[path.PointCount*2];
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,15 @@
using System;
using System.Reflection;
using BenchmarkDotNet.Running;
namespace SixLabors.Shapes.Benchmarks
{
class Program
{
static void Main(string[] args)
{
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>

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

@ -18,10 +18,10 @@ namespace SixLabors.Shapes.Tests
var path = new InternalPath(new ILineSegment[] { seg1, seg2 }, true); var path = new InternalPath(new ILineSegment[] { seg1, seg2 }, true);
Assert.Equal(new Vector2(0, 0), path.Points[0]); Assert.Contains(new Vector2(0, 0), path.Points());
Assert.Equal(new Vector2(2, 2), path.Points[1]); Assert.DoesNotContain(new Vector2(2, 2), path.Points());
Assert.Equal(new Vector2(4, 4), path.Points[2]); Assert.DoesNotContain(new Vector2(4, 4), path.Points());
Assert.Equal(new Vector2(5, 5), path.Points[3]); Assert.Contains(new Vector2(5, 5), path.Points());
} }
[Fact] [Fact]
@ -157,7 +157,7 @@ namespace SixLabors.Shapes.Tests
public void Intersections_buffer() public void Intersections_buffer()
{ {
var shape = Create(new Vector2(0, 0), new Size(10, 10)); 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); var hits = shape.FindIntersections(new Vector2(5, -10), new Vector2(5, 20), buffer, 4, 0);
Assert.Equal(2, hits); 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> /// <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) 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 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++) for (var i = 0; i < hits; i++)
{ {
yield return buffer[i]; yield return buffer[i];