Algorithme pour créer des coins arrondis dans un polygone

je cherche un algorithme qui me permet de créer des coins arrondis à partir d'un polygone. En Entrée, j'obtiens un tableau de points qui représente le polygone (ligne rouge) et en sortie, un tableau de points qui représente le polygone avec des coins arrondis (ligne noire).

je voudrais aussi avoir un moyen de contrôler le rayon de chaque coin. J'ai déjà essayé D'utiliser Bezier et Subdivision mais ce n'est pas ce que je cherche. Bezier et la Subdivision lissent tout le polygone. Ce Que Je ça ne fait que arrondir les coins.

Quelqu'un connaît un bon algorithme pour le faire? Je travaille en C# mais le code doit être indépendant de toute bibliothèque .NET.

Example

37
demandé sur Michael Vlasov 2014-07-16 07:41:26

5 réponses

Certains géométrie avec de la Peinture:



0. Vous disposez d'un coin:

Corner



1. Vous connaissez les coordonnées des points d'angle, laissez-Le être P1, P2 et P:

Points of corner



2. Maintenant vous pouvez obtenir des vecteurs de points et d'angle entre vecteurs:

Vectors and angle



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



3. Obtenez la longueur du segment entre le point angulaire et les points d'intersection avec le cercle.

Segment

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



4. Ici, vous devez vérifier la longueur de segment et la longueur minimale de PP1 et PP2:

Minimal length

Longueur de PP1:

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

Longueur de PP2:

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

si segment > PP1 ou segment > PP2 alors vous devez diminuer le rayon:

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



5. Obtenir la longueur de la PO:

PO = sqrt(radius2 + segment2)



6. Obtenez le C1 X et C1O par la proportion entre la coordonnées du vecteur, longueur du vecteur et longueur du segment:

Coordinates of PC1

Proportion:

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

Donc:

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

idem pour C1O:

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



7. Obtenez le C2 X et C2O de la même manière:

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



8. Maintenant, vous peut utiliser l'ajout de vecteurs PC1 et PC2 pour trouver le centre du cercle par le même chemin par la proportion:

Addition of vectors

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

Ici:

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

Donc:

PC = sqrt(dx2 + dy2)

OX = PX - dx * PO / PC
OY = PY - dy * PO / PC



9. Ici, vous pouvez dessiner un arc. Pour cela, vous devez obtenir l'angle de début et l'angle d'extrémité de l'arc:

Arc

Trouvé ici:

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



10. Enfin vous devez obtenir un angle de balayage et faire quelques vérifications pour elle:

Sweep angle

sweepAngle = endAngle - startAngle

Si sweepAngle < 0 alors swap startAngle et endAngle, et inverser sweepAngle:

sweepAngle < 0 ?    
    sweepAngle = - sweepAngle
    startAngle = endAngle

Vérifier si sweepAngle > 180 degrés:

sweepAngle > 180 ?    
    sweepAngle = 180 - sweepAngle



11. Et maintenant vous pouvez dessiner un arrondi coin:

The result

Certains de la géométrie avec 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));
}

Pour obtenir des points de l'arc, vous pouvez utiliser ceci:

//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);
}
54
répondu nempoBu4 2017-05-23 12:18:10

vous recherchez un arc tangent à deux segments de ligne connectés, d'un rayon donné, donné par un tableau séquentiel de points. algorithme pour trouver cet arc est comme suit:

  1. pour chaque segment, construire un vecteur normal.

    1. Si vous travaillez en 2d, vous pouvez juste soustraire les deux extrémités pour obtenir un vecteur tangent (X, Y). Dans ce cas, les vecteurs normaux seront plus ou moins (-Y, X). Normaliser le vecteur normal à la longueur. Enfin, choisissez la direction avec un produit à points positifs avec le vecteur tangent du segment suivant. ( voir Mise à jour ci-dessous