How to calculate rounded corners for a polygon?
Asked Answered
D

7

60

I'm looking for an algorithm that allows me to create rounded corners from a polygon.

I have an array of points that represents the polygon (outlined in red) and on output I want an array of points that represents the polygon with rounded corners (outlined in black).

I would also like to have a way to control the radius of each corner.

I tried to use Bézier curves and subdivision but it's not what I'm looking for. Bézier curves and subdivision are smoothing the polygon.

What I want is to only make the corners rounded.

Does somebody know any good algorithm for doing that?

I'm working with C# but the code has to be independent from any .NET libraries.

Example

Deeprooted answered 16/7, 2014 at 3:41 Comment(1)
Given R, find the circle that is tangent to the two neighboring line segments. The center is on the angle bisector, t=R/sin(a/2), where t is the distance from the center to the angle point, a is the angle.Preengage
F
101

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);
}
Foursome answered 16/7, 2014 at 11:52 Comment(14)
Thank you! It works perfectly! Answer of dbc explains the way, and your answer gives the implementation. It's a shame I can't valid your two answers. For those who wants to generate points and not draw an arc using graphics library, here is the code: PointF[] points = new PointF[pointsCount]; for(int i=0; i<pointsCount; ++i) { points[i] = new PointF(circleRadius.x + Math.Cos(startAngle + i * sweepAngle / pointsCount) * radius, circleRadius.y + Math.Sin(startAngle + i * sweepAngle / pointsCount) * radius); }Deeprooted
@Deeprooted I corrected mistake in additional checks of sweepAngle (check the new code), and updated my answer with your code with some changes. My algorithm is differs from dbc's algorithm.Foursome
@Deeprooted I have updated my answer with explanation of my algorithm.Foursome
Thank you, your comment is clear, explains everything and gives an implementation in c#. I marked you as right answer.Deeprooted
Seems there is small issue with the latest function which draws arc: if sign is negative pointsCount should be calculated different way. By the way, for source points (20, 0) and (20, 20) arc will be rendered partially as pointCount will be calculated as 45 but we need 135 (180 - 45)Scriber
@IlyaBuiluk pointsCount variable is only indicates how many points for arc will be used. The greater the value, the more accurate arc will be drawn.Foursome
This is the best explanation I ever see answering a doubt! FANTASTIC @nempoBu4!Bordiuk
@Foursome I've adapted this algorithm to Java, and it works for a lot of cases, but when i try to draw an arc between line1 where startX > endX && startY > endY and line2 where startX < endX && startY > endY it doesn't create an arc to fit in between those lines. any ideas?Saturnalia
@Saturnalia Sorry, I have no ideas because I don't see your code. You can create new question in StackOverflow and ask your problem there.Foursome
In case someone comes back and have the same problem as me. I had to change if (sweepAngle > Math.PI) sweepAngle = Math.PI - sweepAngle; to if (sweepAngle > Math.PI) sweepAngle = -(2 * Math.PI - sweepAngle); to fix some curves missing part of it.X
@X - My curve was overshooting the intersecting line. Your suggestion fixed it.Ratcliff
Hi, I had confused at step 8 logic at Cx = C1x + C2x - Px, which formula does this logic come from?Catheterize
@Mate, this logic come from formula for addition of vectors a + b = {ax + bx; ay + by}. Where ax = C1x - Px, bx = C2x - Px, ax + bx = Cx - Px => Cx - Px = C1x - Px + C2x - Px => Cx = C1x + C2x - PxFoursome
Thanks bro! You are brilliant. So professional, so clear.Unthrone
D
36

You are looking for an arc tangent to two connected line segments, of a given radius, given by some sequential array of points. The algorithm to find this arc is as follows:

  1. For each segment, construct a normal vector.

    1. If you are working in 2d, you can just subtract the two endpoints to get a tangent vector (X, Y). In that case, normal vectors will be plus or minus (-Y, X). Normalize the normal vector to length one. Finally, choose the direction with a positive dot product with the tangent vector of the next segment. (See update below).

    2. If you are working in 3d not 2d, to get the normal, cross the tangent vectors of the two segments at the vertex you wish to round to get a perpendicular vector to the plane of the lines. If the perpendicular has length zero, the segments are parallel and no round can be required. Otherwise, normalize it, then cross the perpendicular with the tangent to get the normal.)

  2. Using the normal vectors, offset each line segment towards the interior of the polygon by your desired radius. To offset a segment, offset its endpoints using the normal vector N you just computed, like so: P' = P + r * N (a linear combination).

  3. Intersect the two offset lines to find the center. (This works because a radius vector of a circle is always perpendicular to its tangent.)

  4. To find the point at which the circle intersects each segment, offset the circle center backwards to each original segment. These will be the endpoints of your arc.

  5. Make sure the arc endpoints are inside each segment, otherwise you will be creating a self-intersecting polygon.

  6. Create an arc through both endpoints with center & radius you determined.

I don't have any proper drafting software at hand, but this diagram sort of shows the idea:

enter image description here

At this point you will need either to introduce classes to represent a figure consisting of line and arc segments, or polygonize the arc to an appropriate accuracy and add all the segments to the polygon.

Update: I have updated the image, labeling the points P1, P2, and P3, and normal vectors Norm12 and Norm23. The normalized normals are unique only up to flipping direction, and you should choose the flips as follows:

  • The dot product of Norm12 with (P3 - P2) must be positive. If it is negative, multiple Norm12 by -1.0. If it is zero, the points are collinear and no rounded corner need be created. This is because you want to offset towards P3.

  • The dot product of Norm23 with (P1 - P2) must also be positive since you are offsetting towards P1.

Dextrorse answered 16/7, 2014 at 4:9 Comment(4)
Thank you, I understand the way you want me to do it. But I have one question for now: how do I offset a line towards the interior of the polygon?Deeprooted
@Deeprooted Basically this line would always intersect with the other two lines. Maybe you could check for that.Andante
dbc, Thank you for your edit. I think it's the best answer and I will try to write the code for doing that.Deeprooted
@JakeStelman - I notice your edit got rejected, but you could add your Matlab code as a separate answer, if you wanted. It looks pretty useful!Dextrorse
L
12

Objective-C adaptation of answer by nempoBu4:

typedef enum {
    path_move_to,
    path_line_to
} Path_command;

static inline CGFloat sqr (CGFloat a) {
    return a * a;
}

static inline CGFloat positive_angle (CGFloat angle) {
    return angle < 0 ? angle + 2 * (CGFloat) M_PI : angle;
}

static void add_corner (UIBezierPath* path, CGPoint p1, CGPoint p, CGPoint p2, CGFloat radius, Path_command first_add) {
    // 2
    CGFloat angle = positive_angle (atan2f (p.y - p1.y, p.x - p1.x) - atan2f (p.y - p2.y, p.x - p2.x));
    // 3
    CGFloat segment = radius / fabsf (tanf (angle / 2));
    CGFloat p_c1 = segment;
    CGFloat p_c2 = segment;
    // 4
    CGFloat p_p1 = sqrtf (sqr (p.x - p1.x) + sqr (p.y - p1.y));
    CGFloat p_p2 = sqrtf (sqr (p.x - p2.x) + sqr (p.y - p2.y));
    CGFloat min = MIN(p_p1, p_p2);
    if (segment > min) {
        segment = min;
        radius = segment * fabsf (tanf (angle / 2));
    }
    // 5
    CGFloat p_o = sqrtf (sqr (radius) + sqr (segment));
    // 6
    CGPoint c1;
    c1.x = (CGFloat) (p.x - (p.x - p1.x) * p_c1 / p_p1);
    c1.y = (CGFloat) (p.y - (p.y - p1.y) * p_c1 / p_p1);
    //  7
    CGPoint c2;
    c2.x = (CGFloat) (p.x - (p.x - p2.x) * p_c2 / p_p2);
    c2.y = (CGFloat) (p.y - (p.y - p2.y) * p_c2 / p_p2);
    // 8
    CGFloat dx = p.x * 2 - c1.x - c2.x;
    CGFloat dy = p.y * 2 - c1.y - c2.y;
    CGFloat p_c = sqrtf (sqr (dx) + sqr (dy));
    CGPoint o;
    o.x = p.x - dx * p_o / p_c;
    o.y = p.y - dy * p_o / p_c;    
    // 9
    CGFloat start_angle = positive_angle (atan2f ((c1.y - o.y), (c1.x - o.x)));
    CGFloat end_angle = positive_angle (atan2f ((c2.y - o.y), (c2.x - o.x)));
    if (first_add == path_move_to) {
        [path moveToPoint: c1];
    }
    else {
        [path addLineToPoint: c1];
    }
    [path addArcWithCenter: o radius: radius startAngle: start_angle endAngle: end_angle clockwise: angle < M_PI];
}

UIBezierPath* path_with_rounded_corners (NSArray<NSValue*>* points, CGFloat corner_radius) {
    UIBezierPath* path = [UIBezierPath bezierPath];
    NSUInteger count = points.count;
    for (NSUInteger i = 0; i < count; ++i) {
        CGPoint prev = points[i > 0 ? i - 1 : count - 1].CGPointValue;
        CGPoint p = points[i].CGPointValue;
        CGPoint next = points[i + 1 < count ? i + 1 : 0].CGPointValue;
        add_corner (path, prev, p, next, corner_radius, i == 0 ? path_move_to : path_line_to);
    }
    [path closePath];
    return path;
}
Lucubration answered 5/2, 2016 at 17:12 Comment(4)
This is a C# question, not an objective C question.Dynamite
@Teepeemm, you are right about C#, but brilliant answer from nempoBu4 helps me in my iOS development. Many iOS and Mac OS developers, like me, visit this page from google search. Our goal is to help them, I think.Lucubration
meta.stackoverflow.com/q/290046/2336725 could be a useful reference. I don't know either language to know how different are Objective C and C#. Does your implementation add anything other than a simple change of programming language? Also, you may want to remove all the extra blank lines.Dynamite
My adaptation introduce minor changes to original algorithm: 1) angles converted to positive values; 2) iOs uses different way to define arcs (start, end angles and clockwise flag) vs .Net (start, sweep angles). 3) My algorithm builds full closed graphics path with rounded corners instead of drawing arcs in corners.Lucubration
J
8

I can offer a simple and very calculable and programmable approach that uses optimally few calculations including "only" 3 square roots and no inverse trigonometric functions whatsoever.

Do not feel daunted by the deliberately elaborate explanation that follows which I wrote as such in the interest of making sure the absolutely trivial (by comparison to all other solutions here at the time of writing this) algorithm can be understood. In fact, I devised it only after acknowledging the algorithmic and computational complexity the alternatives would require, what with multiple calls to inverse trigonometric functions (which hide a lot of computation complexity behind their apparently benign names) and substantially larger amount of operations.

(I have verified the validity of proposed approach by programming it with JavaScript and SVG. I will use the former programming language to aid in illustrating the approach)

Let's assume some corner you want to "round" is made up of known points A, B and C, with B being "the corner".

Points A, B and C with lines from A to B and from B to C; a circle that includes the arc for the rounded corner, at point O as its center, line perpendicular to the vector AB going from O to meet some point F between A and B

The solution can be described by the following steps:

  1. Calculate the length of the BF vector:

    The length equals to the radius (FO) of your circle (a known value you choose yourself) divided by the tangent of the angle between vectors BF and BO. This is because the triangle made by points B, O and F is a 'right' triangle (the angle between vectors BF and FO is 90 degrees).

    The angle between vectors BF and BO is half the angle between vectors BA and BC. This may or may not sound obvious, rest assured it's trivially provable but I omit the proof.

    The relationship between the angles is useful because there happens to be a fairly simple equation expressing the relationship between the tangent of an angle and the cosine of twice the angle: Math.tan(a/2) == Math.sqrt((1 - Math.cos(a)) / (1 + Math.cos(a)).

    And it so happens that the cosine of the angle between vectors BA and BC (Math.cos(a)) is the dot product of the two vectors divided by the product of their lengths (see definition of vector dot product on Wikipedia).

    And so, having calculated the cosine of the angle, you can then calculate the tangent of the half angle, and, subsequently, the length of BF:

    (Legend: I model vectors (BA, BC, etc) as objects with properties x and y for their respective coordinates in screen space (X increases to the right, Y downwards); radius is the desired radius of the would-be rounded corner, and BF_length is the length of BF (obviously))

    /// Helper functions
    const length = v => Math.sqrt(v.x * v.x + v.y * v.y);
    const dot_product = (v1, v2) => v1.x * v2.x + v1.y * v2.y;
    const cosine_between = (v1, v2) => dot_product(v1, v2) / (length(v1) * length(v2));
    
    const cos_a = cosine_between(BA, BC);
    const tan_half_a = Math.sqrt((1 - cos_a) / (1 + cos_a));
    const BF_length = radius / tan_half_a;
    
  2. Compute the BF vector. We know its length now (BF_length above) and since BF lies on the same line the vector BA lies on, the former (and, by implication, the coordinate of the point F relative to point B) is computable by doing a scalar multiplication of the length of BF by the unit vector equivalent of BA:

    /// Helper functions
    const unit = v => {
        const l = length(v);
        return { x: v.x / l, y: v.y / l };
    };
    const scalar_multiply = (v, n) => ({ x: v.x * n, y: v.y * n });
    
    const BF = scalar_multiply(unit(BA), BF_length);
    
  3. Now that you have coordinates of F from the prior step, you calculate the FO vector, or the O coordinate. This is done by rotating some vector of length radius that lies on the same line that the vector BA lies on, both vectors pointing in the same direction, by 90 degrees, and moving it so it starts at F.

    Now, whether the rotation is clockwise or counter-clockwise depends on the sign of the angle between the vectors BA and BC, more concretely if the difference between the respective angles (each counted against the same reference, in this case the X axis) of BA and BC is positive then the rotation is counter-clockwise, otherwise it's clockwise.

    We don't want to calculate angles if we can avoid it -- it's the sign of the difference we want, after all. Long story short the sign of the angle (sign) can be calculated with the expression Math.sign(BA.x * BC.y - BA.y * BC.x).

    Here is computation of coordinates of O (O), with F being the coordinate of well, F:

    /// Helper functions
    const add = (v1, v2) => ({ x: v1.x + v2.x, y: v1.y + v2.y });
    const rotate_by_90_degrees = (v, sign) => ({ x: -v.y * sign, y: v.x * sign });
    
    const sign = Math.sign(BA.x * BC.y - BA.y * BC.x);
    const O = add(F, rotate_by_90_degrees(scalar_multiply(unit(BA), radius), sign));
    
    

That's all -- since you've obtained the point O with coordinates in the same space as those of your original points (A, B and C), you can just put a circle of the used radius with O as its centre.

Optional

Computing the corresponding circular arc from point F to some F' (on the BC vector) should be relatively simple but I am not including it unless someone expresses a wish for it.

Terminology

This may be obvious to most making use of this answer, but to be on the safe side: please keep in mind that in this answer I would normally refer to vectors and coordinates as the same kind of measure -- a vector has arity which is amount of components it has; for a 2-dimensional system of coordinates, the arity is obviously 2. A vector object thus does not specifically encode its "start", only "end" -- since there is just two components, the implication is that the vector "starts" at the coordinate system origin. The vector BA, for instance is indeed the vector between points B and A, but since the program stores only two components for the vector (x and y in the snippets), it is as if the vector was moved so that the point B is now at the origin of the coordinate system. A point also consists of two components, so "vector" and "point" are interchangeable. You have to understand this very clearly, otherwise some calculations I have offered may seem strange at times. It may be easier if you just think of vectors in this answer as "one dimensional" arrays with two elements each. In fact this is how I originally programmed these, but I switched to objects with x and y properties for the sake of expressing the solution with code.

Jenniejennifer answered 31/5, 2021 at 21:20 Comment(0)
P
1

Here is my realisation of dbc's idea on c#:

/// <summary>
/// Round polygon corners
/// </summary>
/// <param name="points">Vertices array</param>
/// <param name="radius">Round radius</param>
/// <returns></returns>
static public GraphicsPath RoundCorners(PointF[] points, float radius) {
    GraphicsPath retval = new GraphicsPath();
    if (points.Length < 3) {
        throw new ArgumentException();
    }
    rects = new RectangleF[points.Length];
    PointF pt1, pt2;
    //Vectors for polygon sides and normal vectors
    Vector v1, v2, n1 = new Vector(), n2 = new Vector();
    //Rectangle that bounds arc
    SizeF size = new SizeF(2 * radius, 2 * radius);
    //Arc center
    PointF center = new PointF();

    for (int i = 0; i < points.Length; i++) {
        pt1 = points[i];//First vertex
        pt2 = points[i == points.Length - 1 ? 0 : i + 1];//Second vertex
        v1 = new Vector(pt2.X, pt2.Y) - new Vector(pt1.X, pt1.Y);//One vector
        pt2 = points[i == 0 ? points.Length - 1 : i - 1];//Third vertex
        v2 = new Vector(pt2.X, pt2.Y) - new Vector(pt1.X, pt1.Y);//Second vector
        //Angle between vectors
        float sweepangle = (float)Vector.AngleBetween(v1, v2);
        //Direction for normal vectors
        if (sweepangle < 0) { 
            n1 = new Vector(v1.Y, -v1.X);
            n2 = new Vector(-v2.Y, v2.X);
        }
        else {
            n1 = new Vector(-v1.Y, v1.X);
            n2 = new Vector(v2.Y, -v2.X);
        }

        n1.Normalize(); n2.Normalize();
        n1 *= radius; n2 *= radius;
        /// Points for lines which intersect in the arc center
        PointF pt = points[i];
        pt1 = new PointF((float)(pt.X + n1.X), (float)(pt.Y + n1.Y));
        pt2 = new PointF((float)(pt.X + n2.X), (float)(pt.Y + n2.Y));
        double m1 = v1.Y / v1.X, m2 = v2.Y / v2.X;
        //Arc center
        if (v1.X == 0) {// first line is parallel OY
            center.X = pt1.X;
            center.Y = (float)(m2 * (pt1.X - pt2.X) + pt2.Y);
        }
        else if (v1.Y == 0) {// first line is parallel OX
            center.X = (float)((pt1.Y - pt2.Y) / m2 + pt2.X);
            center.Y = pt1.Y;
        }
        else if (v2.X == 0) {// second line is parallel OY
            center.X = pt2.X;
            center.Y = (float)(m1 * (pt2.X - pt1.X) + pt1.Y);
        }
        else if (v2.Y == 0) {//second line is parallel OX
            center.X = (float)((pt2.Y - pt1.Y) / m1 + pt1.X);
            center.Y = pt2.Y;
        }
        else {
            center.X = (float)((pt2.Y - pt1.Y + m1 * pt1.X - m2 * pt2.X) / (m1 - m2));
            center.Y = (float)(pt1.Y + m1 * (center.X - pt1.X));
        }
        rects[i] = new RectangleF(center.X - 2, center.Y - 2, 4, 4);
        //Tangent points on polygon sides
        n1.Negate(); n2.Negate();
        pt1 = new PointF((float)(center.X + n1.X), (float)(center.Y + n1.Y));
        pt2 = new PointF((float)(center.X + n2.X), (float)(center.Y + n2.Y));
        //Rectangle that bounds tangent arc
        RectangleF rect = new RectangleF(new PointF(center.X - radius, center.Y - radius), size);
        sweepangle = (float)Vector.AngleBetween(n2, n1);
        retval.AddArc(rect, (float)Vector.AngleBetween(new Vector(1, 0), n2), sweepangle);
    }
    retval.CloseAllFigures();
    return retval;
}
Pewter answered 28/9, 2015 at 20:39 Comment(0)
B
0

Here is a way using some geometry:

  1. the two lines are tangent to circle inscribed
  2. The normal to the tangent meet at centre of the circle.
  3. Let angle between lines be X
  4. Angle subtended at centre of the circle will be K = 360-90*2-X = 180-X
  5. Lets decide the two point of tangents as (x1,y) and (x2,y)
  6. The chord joining the points has length l = (x2-x1)
  7. Inside the circle, the chord and two normals of length r (radius) form isosceles triangle
  8. The pendicular divide the traingle in to equal halved right angled triangles.
  9. One of angle is K/2 and side is l/2
  10. using properties of right angle triangle sin(K/2) = (l/2)/r
  11. r = (l/2)/sin(K/2)
  12. but K = 180-X so r = (l/2)/sin(90-X/2) = (l/2)/cos(X/2)
  13. hence r = (x2-x1)/(2*cos(X/2))
  14. Now simply draw a arc from (x1,y) to (x2,y) using radius r

Note

The above is explained only for lines which meet at origin and where the Y axis divides the angle between them in half. But it is equally applicable for all corners that you just need to apply a rotation and translation before applying the above. Moreover you need to select some X values of intersection from where you want to draw the arc. The values should not be too far or close to origin.

Barbrabarbuda answered 16/7, 2014 at 5:45 Comment(4)
Thank you for taking time but I barely understand your way and how to implement it...Deeprooted
try imagine your corner vertex at origin and line towards the positive y axis with Y axis bisecting the angle between them.Barbrabarbuda
Sorry i cannot explain without a image but will try to add one.Barbrabarbuda
The solution is constant time if you understand it , moreover you can rotate and translate other vertices and do the steps and reverse the translation and rotation.Barbrabarbuda
A
0

This is a bit of a deviation from the other answers but it's also a lot simpler.

We'll deal with angles a lot so a lot of arctan2 to calculate the angles with tolerances, so know how to use that. You could also do the determinate since near 0 the lines are near parallel.

Find the angles you want to simplify, say any beyond a given threshold of some angle. Perform the following three operations.

  1. Segmentize: Divide the two segments that connect this angle into arbitrarily 50 small lines. You could also just segmentize the part near the sharp angle.
  2. Smooth: Repeatedly apply smooth to control the amount of smoothing you want of for this sharp angle. Take each series of 3 points. And move the middle-point closer to the midpoint of the first-point and the last-point. Note: closer means towards not to. So amount * (p1 - p2) + p2 where amount is adjustable.
  3. Simplify: Find any points whose angles have not exceeded some threshold let's say 1% and go ahead and delete those points (you can also check the deviation of the midpoint from middle-point). This would typically undo segmentize but we smoothed it and the original sharp angle propagated into a curve.

You can note that the number of times you apply smooth will propagate the angle only that many segments. So If you segmentize to 50. If you only apply smooth 10 times then only the 10 lines closest to the corner could have a modified angle.

So you just pick some values, how many lines do you segment into? How much smoothing do you apply? How many times do you apply the smoothing? And how much deviation is enough to preserve the angle when you're simplifying things back down.

Alidis answered 18/12, 2022 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.