зеркало из https://github.com/SixLabors/Shapes.git
prefilter and calculate data per point
This commit is contained in:
Родитель
c1810e8d9c
Коммит
172236263d
|
@ -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];
|
||||||
|
|
Загрузка…
Ссылка в новой задаче