用于在多边形中创建圆角的算法(Algorithm for creating rounded corners in a polygon)

编程入门 行业动态 更新时间:2024-10-10 02:16:22
用于在多边形中创建圆角的算法(Algorithm for creating rounded corners in a polygon)

我正在寻找一种算法,可以让我从多边形创建圆角。 在输入中,我得到一个表示多边形(红线)和输出的点阵列,它表示具有圆角(黑线)的多边形。

我也想有办法控制每个角落的半径。 我已经试过使用Bezier和细分,但它不是我想要的。 Bezier和细分正在平滑所有的多边形。 我想要的,只是使角落变圆。

有人知道这样做的好算法吗? 我正在使用C#,但代码必须独立于任何.NET库。

I'm looking for an algorithm that allows me to create rounded corners from a polygon. In Input, I get an array of points that represents the polygon (red line) and in output, an array of points that represents the polygon with rounded corner (black line).

I would also like to have a way to control the radius of each corner. I already tried to use Bezier and Subdivision but it's not what I'm looking for. Bezier and Subdivision are smoothing all the polygon. What I want, it's only make the corners rounded.

Somebody knows any good algorithm for doing that? I'm working in C# but the code has to be independent from any .NET libraries.

最满意答案

使用Paint的几何几何:

0.你有一个角落: 角 你知道角点的坐标,让它为P 1 ,P 2和P: 角点 2.现在,您可以从矢量之间的点和角度获取矢量: 矢量和角度

angle = atan(PY - P1Y, PX - P1X) - atan(PY - P2Y, PX - P2X)

3.获取角点和与圆的交点之间的线段长度。 分割

segment = PC1 = PC2 = radius / |tan(angle / 2)|

4.在这里您需要检查段1的长度和PP 1和PP 2的最小长度: 最小长度 PP 1的长度:

PP1 = sqrt((PX - P1X)2 + (PY - P1Y)2)

PP 2的长度:

PP2 = sqrt((PX - P2X)2 + (PY - P2Y)2)

如果分段> PP 1或分段> PP 2,则需要减小半径:

min = Min(PP1, PP2) (for polygon is better to divide this value by 2) segment > min ? segment = min radius = segment * |tan(angle / 2)|

5.获取PO的长度:

PO = sqrt(radius2 + segment2)

6.按照矢量坐标,矢量长度和段长度之间的比例获取C 1 X和C 1 YPC1的坐标

比例:

(PX - C1X) / (PX - P1X) = PC1 / PP1

所以:

C1X = PX - (PX - P1X) * PC1 / PP1

C 1 Y相同:

C1Y = PY - (PY - P1Y) * PC1 / PP1

7.以相同的方式获取C 2 X和C 2 Y

C2X = PX - (PX - P2X) * PC2 / PP2 C2Y = PY - (PY - P2Y) * PC2 / PP2

8.现在,您可以使用向量PC 1和PC 2的相加来按相同方式按比例查找圆心: 添加载体

(PX - OX) / (PX - CX) = PO / PC (PY - OY) / (PY - CY) = PO / PC

这里:

CX = C1X + C2X - PX CY = C1Y + C2Y - PY PC = sqrt((PX - CX)2 + (PY - CY)2)

让:

dx = PX - CX = PX * 2 - C1X - C2X dy = PY - CY = PY * 2 - C1Y - C2Y

所以:

PC = sqrt(dx2 + dy2) OX = PX - dx * PO / PC OY = PY - dy * PO / PC

9.在这里你可以绘制一条弧线。 为此,您需要获取弧的起始角和终止角: 弧 在这里找到它:

startAngle = atan((C1Y - OY) / (C1X - OX)) endAngle = atan((C2Y - OY) / (C2X - OX))

10.最后你需要得到一个扫描角度并对它进行一些检查: 扫掠角度

sweepAngle = endAngle - startAngle
 

如果sweepAngle <0,则交换startAngle和endAngle,并反转sweepAngle:

sweepAngle < 0 ?    
    sweepAngle = - sweepAngle
    startAngle = endAngle
 

检查sweepAngle> 180度:

sweepAngle > 180 ?    
    sweepAngle = 180 - sweepAngle
 

11.现在你可以绘制一个圆角: 结果

一些几何与C#:

private void DrawRoundedCorner(Graphics graphics, PointF angularPoint, 
                                PointF p1, PointF p2, float radius)
{
    //Vector 1
    double dx1 = angularPoint.X - p1.X;
    double dy1 = angularPoint.Y - p1.Y;

    //Vector 2
    double dx2 = angularPoint.X - p2.X;
    double dy2 = angularPoint.Y - p2.Y;

    //Angle between vector 1 and vector 2 divided by 2
    double angle = (Math.Atan2(dy1, dx1) - Math.Atan2(dy2, dx2)) / 2;

    // The length of segment between angular point and the
    // points of intersection with the circle of a given radius
    double tan = Math.Abs(Math.Tan(angle));
    double segment = radius / tan;

    //Check the segment
    double length1 = GetLength(dx1, dy1);
    double length2 = GetLength(dx2, dy2);

    double length = Math.Min(length1, length2);

    if (segment > length)
    {
        segment = length;
        radius = (float)(length * tan);
    }

    // Points of intersection are calculated by the proportion between 
    // the coordinates of the vector, length of vector and the length of the segment.
    var p1Cross = GetProportionPoint(angularPoint, segment, length1, dx1, dy1);
    var p2Cross = GetProportionPoint(angularPoint, segment, length2, dx2, dy2);

    // Calculation of the coordinates of the circle 
    // center by the addition of angular vectors.
    double dx = angularPoint.X * 2 - p1Cross.X - p2Cross.X;
    double dy = angularPoint.Y * 2 - p1Cross.Y - p2Cross.Y;

    double L = GetLength(dx, dy);
    double d = GetLength(segment, radius);

    var circlePoint = GetProportionPoint(angularPoint, d, L, dx, dy);

    //StartAngle and EndAngle of arc
    var startAngle = Math.Atan2(p1Cross.Y - circlePoint.Y, p1Cross.X - circlePoint.X);
    var endAngle = Math.Atan2(p2Cross.Y - circlePoint.Y, p2Cross.X - circlePoint.X);

    //Sweep angle
    var sweepAngle = endAngle - startAngle;

    //Some additional checks
    if (sweepAngle < 0)
    {
        startAngle = endAngle;
        sweepAngle = -sweepAngle;
    }

    if (sweepAngle > Math.PI)
        sweepAngle = Math.PI - sweepAngle;

    //Draw result using graphics
    var pen = new Pen(Color.Black);

    graphics.Clear(Color.White);
    graphics.SmoothingMode = SmoothingMode.AntiAlias;

    graphics.DrawLine(pen, p1, p1Cross);
    graphics.DrawLine(pen, p2, p2Cross);

    var left = circlePoint.X - radius;
    var top = circlePoint.Y - radius;
    var diameter = 2 * radius;
    var degreeFactor = 180 / Math.PI;

    graphics.DrawArc(pen, left, top, diameter, diameter, 
                     (float)(startAngle * degreeFactor), 
                     (float)(sweepAngle * degreeFactor));
}

private double GetLength(double dx, double dy)
{
    return Math.Sqrt(dx * dx + dy * dy);
}

private PointF GetProportionPoint(PointF point, double segment, 
                                  double length, double dx, double dy)
{
    double factor = segment / length;

    return new PointF((float)(point.X - dx * factor), 
                      (float)(point.Y - dy * factor));
}
 

为了得到弧的点,你可以使用这个:

//One point for each degree. But in some cases it will be necessary 
// to use more points. Just change a degreeFactor.
int pointsCount = (int)Math.Abs(sweepAngle * degreeFactor);
int sign = Math.Sign(sweepAngle);

PointF[] points = new PointF[pointsCount];

for (int i = 0; i < pointsCount; ++i)
{
    var pointX = 
       (float)(circlePoint.X  
               + Math.Cos(startAngle + sign * (double)i / degreeFactor)  
               * radius);

    var pointY = 
       (float)(circlePoint.Y 
               + Math.Sin(startAngle + sign * (double)i / degreeFactor) 
               * radius);

    points[i] = new PointF(pointX, pointY);
}

Some geometry with Paint:

0. You have a corner: Corner 1. You know the coordinates of corner points, let it be P1, P2 and P: Points of corner 2. Now you can get vectors from points and angle between vectors: Vectors and angle

angle = atan(PY - P1Y, PX - P1X) - atan(PY - P2Y, PX - P2X)

3. Get the length of segment between angular point and the points of intersection with the circle. Segment

segment = PC1 = PC2 = radius / |tan(angle / 2)|

4. Here you need to check the length of segment and the minimal length from PP1 and PP2: Minimal length Length of PP1:

PP1 = sqrt((PX - P1X)2 + (PY - P1Y)2)

Length of PP2:

PP2 = sqrt((PX - P2X)2 + (PY - P2Y)2)

If segment > PP1 or segment > PP2 then you need to decrease the radius:

min = Min(PP1, PP2) (for polygon is better to divide this value by 2) segment > min ? segment = min radius = segment * |tan(angle / 2)|

5. Get the length of PO:

PO = sqrt(radius2 + segment2)

6. Get the C1X and C1Y by the proportion between the coordinates of the vector, length of vector and the length of the segment: Coordinates of PC1

Proportion:

(PX - C1X) / (PX - P1X) = PC1 / PP1

So:

C1X = PX - (PX - P1X) * PC1 / PP1

The same for C1Y:

C1Y = PY - (PY - P1Y) * PC1 / PP1

7. Get the C2X and C2Y by the same way:

C2X = PX - (PX - P2X) * PC2 / PP2 C2Y = PY - (PY - P2Y) * PC2 / PP2

8. Now you can use the addition of vectors PC1 and PC2 to find the centre of circle by the same way by proportion: Addition of vectors

(PX - OX) / (PX - CX) = PO / PC (PY - OY) / (PY - CY) = PO / PC

Here:

CX = C1X + C2X - PX CY = C1Y + C2Y - PY PC = sqrt((PX - CX)2 + (PY - CY)2)

Let:

dx = PX - CX = PX * 2 - C1X - C2X dy = PY - CY = PY * 2 - C1Y - C2Y

So:

PC = sqrt(dx2 + dy2) OX = PX - dx * PO / PC OY = PY - dy * PO / PC

9. Here you can draw an arc. For this you need to get start angle and end angle of arc: Arc Found it here:

startAngle = atan((C1Y - OY) / (C1X - OX)) endAngle = atan((C2Y - OY) / (C2X - OX))

10. At last you need to get a sweep angle and make some checks for it: Sweep angle

sweepAngle = endAngle - startAngle
 

If sweepAngle < 0 then swap startAngle and endAngle, and invert sweepAngle:

sweepAngle < 0 ?    
    sweepAngle = - sweepAngle
    startAngle = endAngle
 

Check if sweepAngle > 180 degrees:

sweepAngle > 180 ?    
    sweepAngle = 180 - sweepAngle
 

11. And now you can draw a rounded corner: The result

Some geometry with c#:

private void DrawRoundedCorner(Graphics graphics, PointF angularPoint, 
                                PointF p1, PointF p2, float radius)
{
    //Vector 1
    double dx1 = angularPoint.X - p1.X;
    double dy1 = angularPoint.Y - p1.Y;

    //Vector 2
    double dx2 = angularPoint.X - p2.X;
    double dy2 = angularPoint.Y - p2.Y;

    //Angle between vector 1 and vector 2 divided by 2
    double angle = (Math.Atan2(dy1, dx1) - Math.Atan2(dy2, dx2)) / 2;

    // The length of segment between angular point and the
    // points of intersection with the circle of a given radius
    double tan = Math.Abs(Math.Tan(angle));
    double segment = radius / tan;

    //Check the segment
    double length1 = GetLength(dx1, dy1);
    double length2 = GetLength(dx2, dy2);

    double length = Math.Min(length1, length2);

    if (segment > length)
    {
        segment = length;
        radius = (float)(length * tan);
    }

    // Points of intersection are calculated by the proportion between 
    // the coordinates of the vector, length of vector and the length of the segment.
    var p1Cross = GetProportionPoint(angularPoint, segment, length1, dx1, dy1);
    var p2Cross = GetProportionPoint(angularPoint, segment, length2, dx2, dy2);

    // Calculation of the coordinates of the circle 
    // center by the addition of angular vectors.
    double dx = angularPoint.X * 2 - p1Cross.X - p2Cross.X;
    double dy = angularPoint.Y * 2 - p1Cross.Y - p2Cross.Y;

    double L = GetLength(dx, dy);
    double d = GetLength(segment, radius);

    var circlePoint = GetProportionPoint(angularPoint, d, L, dx, dy);

    //StartAngle and EndAngle of arc
    var startAngle = Math.Atan2(p1Cross.Y - circlePoint.Y, p1Cross.X - circlePoint.X);
    var endAngle = Math.Atan2(p2Cross.Y - circlePoint.Y, p2Cross.X - circlePoint.X);

    //Sweep angle
    var sweepAngle = endAngle - startAngle;

    //Some additional checks
    if (sweepAngle < 0)
    {
        startAngle = endAngle;
        sweepAngle = -sweepAngle;
    }

    if (sweepAngle > Math.PI)
        sweepAngle = Math.PI - sweepAngle;

    //Draw result using graphics
    var pen = new Pen(Color.Black);

    graphics.Clear(Color.White);
    graphics.SmoothingMode = SmoothingMode.AntiAlias;

    graphics.DrawLine(pen, p1, p1Cross);
    graphics.DrawLine(pen, p2, p2Cross);

    var left = circlePoint.X - radius;
    var top = circlePoint.Y - radius;
    var diameter = 2 * radius;
    var degreeFactor = 180 / Math.PI;

    graphics.DrawArc(pen, left, top, diameter, diameter, 
                     (float)(startAngle * degreeFactor), 
                     (float)(sweepAngle * degreeFactor));
}

private double GetLength(double dx, double dy)
{
    return Math.Sqrt(dx * dx + dy * dy);
}

private PointF GetProportionPoint(PointF point, double segment, 
                                  double length, double dx, double dy)
{
    double factor = segment / length;

    return new PointF((float)(point.X - dx * factor), 
                      (float)(point.Y - dy * factor));
}
 

To get points of arc you can use this:

//One point for each degree. But in some cases it will be necessary 
// to use more points. Just change a degreeFactor.
int pointsCount = (int)Math.Abs(sweepAngle * degreeFactor);
int sign = Math.Sign(sweepAngle);

PointF[] points = new PointF[pointsCount];

for (int i = 0; i < pointsCount; ++i)
{
    var pointX = 
       (float)(circlePoint.X  
               + Math.Cos(startAngle + sign * (double)i / degreeFactor)  
               * radius);

    var pointY = 
       (float)(circlePoint.Y 
               + Math.Sin(startAngle + sign * (double)i / degreeFactor) 
               * radius);

    points[i] = new PointF(pointX, pointY);
}

                    
                     
          

更多推荐

多边形,polygon,I'm,rounded,电脑培训,计算机培训,IT培训"/> <meta name="

本文发布于:2023-08-06 11:42:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1449346.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多边形   算法   圆角   Algorithm   polygon

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!