This commit is contained in:
Scott Williams 2017-02-03 15:31:26 +00:00
Родитель 096cf49d0b
Коммит 37c79b5223
7 изменённых файлов: 224 добавлений и 15 удалений

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

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