计算具有角度的多边形顶点会产生错误大小的形状

Calculating polygon vertices with an angle produce the shape wrong size

当我用 startingAngle=0 调用我的函数时,它会生成尺寸正确的良好形状。 示例:

var points = GetPolygonVertices(sides:4, radius:5, center:(5, 5), startingAngle:0), produces:
points[0] = {X = 10 Y = 5}
points[1] = {X = 5 Y = 0}
points[2] = {X = 0 Y = 5}
points[3] = {X = 5 Y = 10}

根据观察,边长是 10px,这是正确的,但是从人眼角度来看,会产生一个 45º 的旋转正方形。

为了解决这个问题,我添加了一个 switch/case 来偏移 startAngle,这样它将通过旋转 45º 将正方形置于人眼正确的角度。旋转有效,但形状不再是 10x10px 的正方形,相反我从边上损失了 1 到 2px:

[0] = {X = 9 Y = 1}
[1] = {X = 1 Y = 1}
[2] = {X = 1 Y = 9}
[3] = {X = 9 Y = 9}

随着半径的增加变得更糟,例如 radius=10:

[0] = {X = 17 Y = 3}
[1] = {X = 3 Y = 3}
[2] = {X = 3 Y = 17}
[3] = {X = 17 Y = 17}

我尝试同时使用 floor 和 ceil 而不是 round,但它总是以丢失 1 或 2px 结束... 有没有办法改进功能,无论边数和旋转角度如何,形状大小都保持相等?

我的函数:

    public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
    {
        if (sides < 3)
            throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));


        // Fix rotation
        switch (sides)
        {
            case 3:
                startingAngle += 90;
                break;
            case 4:
                startingAngle += 45;
                break;
            case 5:
                startingAngle += 22.5;
                break;
        }

        var points = new Point[sides];
        var step = 360.0 / sides;
        int i = 0;
        for (var angle = startingAngle; angle < startingAngle + 360.0; angle += step) //go in a circle
        {
            if (i == sides) break; // Fix floating problem
            double radians = angle * Math.PI / 180.0;
            points[i++] = new(
                (int) Math.Round(Math.Cos(radians) * radius + center.X),
                (int) Math.Round(Math.Sin(-radians) * radius + center.Y)
            );
        }
        return points;
    }

编辑: 我更新了功能,以在未给出角度时摆脱人眼正确方向的开关条件和产品形状。仍然遇到同样的“问题”

public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
{
    if (sides < 3)
        throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));

    var vertices = new Point[sides];

    double deg = 360.0 / sides;//calculate the rotation angle
    var rad = Math.PI / 180.0;

    var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
    var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);

    var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
    var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);

    vertices[0] = new(
        (int) Math.Round(x0),
        (int) Math.Round(y0)
        );

    vertices[1] = new(
        (int) Math.Round(x1),
        (int) Math.Round(y1)
        );

    for (int i = 0; i < sides - 2; i++)
    {
        double dsinrot = Math.Sin((deg * (i + 1)) * rad);
        double dcosrot = Math.Cos((deg * (i + 1)) * rad);

        vertices[i + 2] = new(
                (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
                (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
        );
    }

    if (flipHorizontally)
    {
        var startX = center.X - radius;
        var endX = center.X + radius;
        for (int i = 0; i < sides; i++)
        {
            vertices[i].X = endX - (vertices[i].X - startX);
        }
    }

    if (flipVertically)
    {
        var startY = center.Y - radius;
        var endY = center.Y + radius;
        for (int i = 0; i < sides; i++)
        {
            vertices[i].Y = endY - (vertices[i].Y - startY);
        }
    }

    return vertices;
}

编辑 2: 来自 Tim Roberts anwser here the functions to calculate side length from radius and radius from side length, 这解决了我的问题问题。谢谢!

public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
{
    return 2 * radius * Math.Sin(Math.PI / sides);
}

public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
{
    return radius * Math.Cos(Math.PI / sides);
}

public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
{
    var theta = 360.0 / sides;
    return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
}

你的问题是数学问题。你说“根据观察,边长是 10px”。它绝对不是 10px。从 (10,5) 到 (5,0) 的距离是 sqrt(5*5 + 5*5),即 7.07。这正是我们对内接在半径为 5: 5 x sqrt(2) 的圆中的正方形的期望。

其他方块也是如此。

跟进

作为额外的奖励,这里有一个函数 returns 外接 N 条边长 L 的正多边形的圆的半径:

import math

def rad(length,nsides):
    theta = 360/nsides
    r = length / (2 * math.cos( (90-theta/2) * math.pi / 180))
    return r

for s in range(3,9):
    print(s, rad(10,s))