зеркало из https://github.com/SixLabors/Shapes.git
add regular polygons
This commit is contained in:
Родитель
096cf49d0b
Коммит
37c79b5223
|
@ -1,4 +1,4 @@
|
|||
// <copyright file="ArrayExtensions.cs" company="Scott Williams">
|
||||
// <copyright file="VectorExtensions.cs" company="Scott Williams">
|
||||
// Copyright (c) Scott Williams and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
||||
|
|
|
@ -120,6 +120,27 @@ namespace SixLabors.Shapes
|
|||
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>
|
||||
/// Gets the bounds.
|
||||
/// </summary>
|
||||
|
@ -210,8 +231,6 @@ namespace SixLabors.Shapes
|
|||
}
|
||||
|
||||
int position = 0;
|
||||
bool onCornerFirstPass = true;
|
||||
Side lastEdgeSide = Side.Same;
|
||||
Vector2 lastPoint = MaxVector;
|
||||
int last = -1;
|
||||
for (int i = 0; i < polyCorners && count > 0; i++)
|
||||
|
@ -232,7 +251,7 @@ namespace SixLabors.Shapes
|
|||
if (side != Side.Same && side == SideOfLine(this.points[next], start, end))
|
||||
{
|
||||
// same side we don't bohter adding the crossing
|
||||
position--; //move back one and the next hist will replace it
|
||||
position--; // move back one and the next hist will replace it
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +318,7 @@ namespace SixLabors.Shapes
|
|||
var bottomLeft = new Vector2(this.Bounds.Left - 1, this.Bounds.Bottom + 1);
|
||||
var bottomRight = new Vector2(this.Bounds.Right + 1, this.Bounds.Bottom + 1);
|
||||
|
||||
//get the point that cause the most intersections
|
||||
// get the point that cause the most intersections
|
||||
var buffer = ArrayPool<Vector2>.Shared.Rent(this.points.Length);
|
||||
try
|
||||
{
|
||||
|
@ -334,13 +353,6 @@ namespace SixLabors.Shapes
|
|||
return Side.Right;
|
||||
}
|
||||
|
||||
public enum Side
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Same
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the bounding box for 2 lines
|
||||
/// described by <paramref name="line1Start" /> and <paramref name="line1End" />
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
namespace SixLabors.Shapes
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
|
||||
|
@ -19,6 +20,11 @@ namespace SixLabors.Shapes
|
|||
/// </summary>
|
||||
private readonly InternalPath innerPath;
|
||||
|
||||
/// <summary>
|
||||
/// a place for the shape to be cached.
|
||||
/// </summary>
|
||||
private IShape cachedShape;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Path"/> class.
|
||||
/// </summary>
|
||||
|
@ -91,7 +97,7 @@ namespace SixLabors.Shapes
|
|||
/// </returns>
|
||||
public IShape AsShape()
|
||||
{
|
||||
return new Polygon(this.LineSegments);
|
||||
return this.cachedShape ?? (this.cachedShape = new Polygon(this.LineSegments));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace SixLabors.Shapes
|
|||
/// <summary>
|
||||
/// A shape made up of a single path made up of one of more <see cref="ILineSegment"/>s
|
||||
/// </summary>
|
||||
public sealed class Polygon : IShape
|
||||
public class Polygon : IShape
|
||||
{
|
||||
private readonly InternalPath innerPath;
|
||||
private readonly PolygonPath path;
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
// <copyright file="RegularPolygon.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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// A shape made up of a single path made up of one of more <see cref="ILineSegment"/>s
|
||||
/// </summary>
|
||||
public class RegularPolygon : Polygon
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegularPolygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The location the center of the polygon will be placed.</param>
|
||||
/// <param name="verticies">The number of verticies the <see cref="RegularPolygon"/> should have.</param>
|
||||
/// <param name="radius">The radius of the circle that would touch all verticies.</param>
|
||||
/// <param name="angle">The angle of rotation in Radians</param>
|
||||
public RegularPolygon(Vector2 location, int verticies, float radius, float angle)
|
||||
: base(CreateSegment(location, radius, verticies, angle))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegularPolygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The location the center of the polygon will be placed.</param>
|
||||
/// <param name="radius">The radius of the circle that would touch all verticies.</param>
|
||||
/// <param name="verticies">The number of verticies the <see cref="RegularPolygon"/> should have.</param>
|
||||
public RegularPolygon(Vector2 location, int verticies, float radius)
|
||||
: this(location, verticies, radius, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegularPolygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the center of the polygon.</param>
|
||||
/// <param name="y">The Y coordinate of the center of the polygon.</param>
|
||||
/// <param name="verticies">The number of verticies the <see cref="RegularPolygon" /> should have.</param>
|
||||
/// <param name="radius">The radius of the circle that would touch all verticies.</param>
|
||||
/// <param name="angle">The angle of rotation in Radians</param>
|
||||
public RegularPolygon(float x, float y, int verticies, float radius, float angle)
|
||||
: this(new Vector2(x, y), verticies, radius, angle)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegularPolygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the center of the polygon.</param>
|
||||
/// <param name="y">The Y coordinate of the center of the polygon.</param>
|
||||
/// <param name="radius">The radius of the circle that would touch all verticies.</param>
|
||||
/// <param name="verticies">The number of verticies the <see cref="RegularPolygon"/> should have.</param>
|
||||
public RegularPolygon(float x, float y, int verticies, float radius)
|
||||
: this(new Vector2(x, y), verticies, radius)
|
||||
{
|
||||
}
|
||||
|
||||
private static LinearLineSegment CreateSegment(Vector2 location, float radius, int verticies, float angle)
|
||||
{
|
||||
Guard.MustBeGreaterThan(verticies, 2, nameof(verticies));
|
||||
Guard.MustBeGreaterThan(radius, 0, nameof(radius));
|
||||
|
||||
Vector2 distanceVector = new Vector2(radius, 0);
|
||||
|
||||
float anglePerSegemnts = (float)((2 * Math.PI) / verticies);
|
||||
float current = angle;
|
||||
Vector2[] points = new Vector2[verticies];
|
||||
for (var i = 0; i < verticies; i++)
|
||||
{
|
||||
Vector2 rotated = Vector2.Transform(distanceVector, Matrix3x2.CreateRotation(current));
|
||||
|
||||
points[i] = rotated + location;
|
||||
|
||||
current += anglePerSegemnts;
|
||||
}
|
||||
|
||||
return new LinearLineSegment(points);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
"version": "0.1.0-alpha1-*",
|
||||
"title": "SixLabors.Shapes",
|
||||
"description": "Polygon manipulation/merging and interrogation library. \n\nIts a fully manged netstandard library so should work everywhere.",
|
||||
"summary": "Polygon manipulation/merging and interrogation library.",
|
||||
"authors": [
|
||||
"Scott Williams and contributors"
|
||||
],
|
||||
|
@ -10,6 +9,7 @@
|
|||
"owners": [
|
||||
"Scott Williams and contributors"
|
||||
],
|
||||
"summary": "Polygon manipulation/merging and interrogation library.",
|
||||
"projectUrl": "https://github.com/SixLabors/Shapes",
|
||||
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
|
||||
"iconUrl": "https://raw.githubusercontent.com/SixLabors/Shapes/master/icons/icon.png",
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace SixLabors.Shapes.Tests
|
||||
{
|
||||
using System.Numerics;
|
||||
|
||||
public class RegularPolygonTests
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, true)]
|
||||
[InlineData(1, true)]
|
||||
[InlineData(2, true)]
|
||||
[InlineData(3, false)]
|
||||
[InlineData(4, false)]
|
||||
public void RequiresAtleast3Verticies(int points, bool throws)
|
||||
{
|
||||
if (throws)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new RegularPolygon(Vector2.Zero, points, 10f, 0));
|
||||
|
||||
Assert.Equal("verticies", ex.ParamName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = new RegularPolygon(Vector2.Zero, points, 10f, 0);
|
||||
Assert.NotNull(p);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, true)]
|
||||
[InlineData(0, true)]
|
||||
[InlineData(0.00001, false)]
|
||||
[InlineData(1, false)]
|
||||
public void RadiusMustBeGreateThan0(float radius, bool throws)
|
||||
{
|
||||
if (throws)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new RegularPolygon(Vector2.Zero, 3, radius, 0));
|
||||
|
||||
Assert.Equal("radius", ex.ParamName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = new RegularPolygon(Vector2.Zero, 3, radius, 0);
|
||||
Assert.NotNull(p);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GeneratesCorrectPath()
|
||||
{
|
||||
float radius = 10;
|
||||
int pointsCount = new Random().Next(3, 99);
|
||||
|
||||
var poly = new RegularPolygon(Vector2.Zero, pointsCount, radius, 0);
|
||||
var path = poly.AsPath();
|
||||
|
||||
var points = path.Flatten();
|
||||
|
||||
// calcualte baselineDistance
|
||||
var baseline = Vector2.Distance(points[0], points[1]);
|
||||
|
||||
// all points are extact the same distance away from the center
|
||||
for (var i = 0; i < points.Length; i++)
|
||||
{
|
||||
var j = i - 1;
|
||||
if (i == 0)
|
||||
{
|
||||
j = points.Length - 1;
|
||||
}
|
||||
|
||||
var actual = Vector2.Distance(points[i], points[j]);
|
||||
Assert.Equal(baseline, actual, 4);
|
||||
Assert.Equal(radius, Vector2.Distance(Vector2.Zero, points[i]), 4);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AngleChangesOnePointToStartAtThatPosition()
|
||||
{
|
||||
const double TwoPI = 2 * Math.PI;
|
||||
float radius = 10;
|
||||
double anAngle = new Random().NextDouble() * TwoPI;
|
||||
|
||||
var poly = new RegularPolygon(Vector2.Zero, 3, radius, (float)anAngle);
|
||||
var path = poly.AsPath();
|
||||
var points = path.Flatten();
|
||||
|
||||
var allAngles = points.Select(b => Math.Atan2(b.Y, b.X))
|
||||
.Select(x=> x<0 ? x + TwoPI : x); // normalise it from +/- PI to 0 to TwoPI
|
||||
|
||||
Assert.Contains(allAngles, a=> Math.Abs(a- anAngle) > 0.000001);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче